Spring Cloud OpenFeign 中 hm-api、消费者、提供者、LoadBalancer、OkHttp 应该怎么配置

最近学习 Spring Cloud OpenFeign 的时候,我发现有几个点特别容易混:

  • hm-api 公共模块到底放什么?
  • 服务消费者和服务提供者分别要加什么依赖?
  • @EnableFeignClients@FeignClient@RestController 分别加在哪里?
  • spring-cloud-starter-loadbalancer 写在 hm-api 还是消费者?
  • OkHttp 连接池要在哪个服务里开启?
  • Controller 方法名和 FeignClient 方法名不一样能不能调用?

这篇文章按一个真实调用关系整理一下。

一、项目结构

项目大概是这样的:

hm-api
  └── com.hmall.api.client
      ├── ItemClient
      └── CartClient

cart-service
  └── CartController

trade-service
  └── OrderServiceImpl

其中:

  • hm-api:公共 API 模块,放 FeignClient 接口和 DTO;
  • cart-service:服务提供者,提供购物车接口;
  • trade-service:服务消费者,下单时调用购物车服务;
  • 注册中心:Nacos;
  • 远程调用:OpenFeign;
  • 负载均衡:Spring Cloud LoadBalancer;
  • HTTP 连接池:OkHttp。

二、一句话结论

可以先记住这句话:

谁发起远程调用,谁运行时就必须有 OpenFeign、LoadBalancer,如果要使用连接池,还要有 OkHttp 并开启配置。

所以:

  • hm-api 负责定义调用接口;
  • 消费者负责真正发起调用;
  • 提供者负责暴露 Controller 接口;
  • LoadBalancer 和 OkHttp 真正生效的位置都是消费者运行时。

三、hm-api 公共模块怎么配置

hm-api 一般放:

  • FeignClient 接口;
  • DTO;
  • Feign 相关公共配置。

hm-api/pom.xml 可以这样写:

<!-- OpenFeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- 负载均衡 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- OkHttp 连接池支持 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

如果把这些依赖放在 hm-api 中,消费者只要引入 hm-api,就可以通过 Maven 依赖传递拿到 OpenFeign、LoadBalancer、OkHttp。

但是注意:

依赖可以从 hm-api 传递过去,配置仍然要在消费者服务中开启。

比如 OkHttp 的开关要写在真正发起调用的服务里。

四、FeignClient 怎么写

比如 trade-service 要调用 cart-service 删除购物车中的商品,可以在 hm-api 中定义:

@FeignClient("cart-service")
public interface CartClient {

    @DeleteMapping("/carts")
    void removeByItemIds(@RequestParam("ids") Collection<Long> ids);
}

这里要注意四个对应关系:

服务名:cart-service
请求方式:DELETE
请求路径:/carts
请求参数:ids

这些必须和服务提供者的 Controller 对上。

五、Controller 方法名和 Client 方法名必须一样吗

不需要一样。

比如 cart-service 中的 Controller 是:

@RestController
@RequestMapping("/carts")
public class CartController {

    @DeleteMapping
    public void deleteCartItemByIds(@RequestParam("ids") List<Long> ids) {
        cartService.removeByItemIds(ids);
    }
}

这里 Controller 方法名叫:

deleteCartItemByIds

而 FeignClient 方法名叫:

removeByItemIds

这完全可以。

因为 OpenFeign 调用时看的不是 Java 方法名,而是 HTTP 接口信息:

DELETE /carts?ids=1&ids=2&ids=3

所以只要请求方式、路径、参数名对得上,方法名可以按各自业务语义来命名。

六、服务提供者 cart-service 怎么配置

服务提供者的职责是:

  • 注册到 Nacos;
  • 暴露 Controller 接口;
  • 提供业务实现。

cart-service/pom.xml 至少需要:

<!-- Web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Nacos 服务注册发现 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

如果 cart-service 只是被别人调用,不主动调用别的服务,那么它本身不一定需要 OpenFeign、LoadBalancer、OkHttp。

但是如果 cart-service 自己也要调用 item-serviceuser-service 等其他服务,那它在那个调用关系中也变成了消费者,就也需要配置 OpenFeign。

application.yml 示例:

spring:
  application:
    name: cart-service
  cloud:
    nacos:
      server-addr: 192.168.18.99:8848

Controller 示例:

@RestController
@RequestMapping("/carts")
public class CartController {

    @DeleteMapping
    public void deleteCartItemByIds(@RequestParam("ids") List<Long> ids) {
        cartService.removeByItemIds(ids);
    }
}

七、服务消费者 trade-service 怎么配置

服务消费者的职责是:

  • 引入 hm-api
  • 注册到 Nacos;
  • 开启 FeignClient 扫描;
  • 如果使用 OkHttp,开启 OkHttp 配置。

trade-service/pom.xml

<!-- 引入公共 API 模块 -->
<dependency>
    <groupId>com.heima</groupId>
    <artifactId>hm-api</artifactId>
    <version>1.0.0</version>
</dependency>

<!-- Nacos 服务注册发现 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

如果 hm-api 没有传递 OkHttp,也可以在消费者中直接写:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

消费者启动类建议这样写:

@EnableFeignClients(basePackages = "com.hmall.api.client")
@SpringBootApplication
public class TradeApplication {

    public static void main(String[] args) {
        SpringApplication.run(TradeApplication.class, args);
    }
}

为什么建议写 basePackages

因为 TradeApplication 在:

com.hmall.trade

而 FeignClient 在:

com.hmall.api.client

它们不是父子包关系。如果只写:

@EnableFeignClients

可能扫描不到 hm-api 中的 FeignClient。

所以最好明确指定:

@EnableFeignClients(basePackages = "com.hmall.api.client")

消费者的 application.yml

spring:
  application:
    name: trade-service
  cloud:
    nacos:
      server-addr: 192.168.18.99:8848

feign:
  okhttp:
    enabled: true

我的项目使用的是 Spring Boot 2.7.12、Spring Cloud 2021.0.3,所以 OkHttp 开关写:

feign:
  okhttp:
    enabled: true

如果是较新的 Spring Cloud OpenFeign 版本,可能要写成:

spring:
  cloud:
    openfeign:
      okhttp:
        enabled: true

具体以项目版本为准。

八、调用代码怎么写

消费者中直接注入 CartClient

@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements IOrderService {

    private final CartClient cartClient;

    public void createOrder(List<Long> itemIds) {
        cartClient.removeByItemIds(itemIds);
    }
}

这时调用过程大概是:

trade-service
  -> CartClient.removeByItemIds(itemIds)
  -> DELETE http://cart-service/carts?ids=...
  -> Nacos 找到 cart-service 实例
  -> LoadBalancer 选择一个实例
  -> OkHttp 发送 HTTP 请求
  -> CartController.deleteCartItemByIds(...)
  -> cartService.removeByItemIds(...)

九、LoadBalancer 到底写在哪里

LoadBalancer 真正生效的位置是消费者运行时。

但是依赖可以有两种写法。

第一种,写在 hm-api 中:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

消费者引入 hm-api 后,通过依赖传递拿到 LoadBalancer。

第二种,直接写在消费者中:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

两种都可以。

关键不是依赖写在哪个 pom.xml,而是:

消费者服务启动时的 classpath 中必须有 LoadBalancer。

服务提供者如果只是被调用,不调用别人,就不需要单独配置 LoadBalancer。

十、OkHttp 连接池怎么理解

OpenFeign 是对 HTTP 调用的封装,但底层真正发送 HTTP 请求,还要依赖具体客户端。

常见有三种:

HttpURLConnection:默认实现,不支持连接池
Apache HttpClient:支持连接池
OkHttp:支持连接池

如果使用默认的 HttpURLConnection,每次请求的性能不如连接池方案。

集成 OkHttp 后,Feign 底层会使用 OkHttp 发请求,OkHttp 会复用 HTTP 连接,减少频繁创建连接的开销。

最基本的配置就是两步:

第一步,引入依赖:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

第二步,在消费者中开启:

feign:
  okhttp:
    enabled: true

一般学习阶段做到这里就够了。

十一、是否需要自定义 OkHttp 连接池参数

一般不需要。

如果后期请求量比较大,想自己控制连接池参数,可以写一个配置类:

@Configuration
public class FeignOkHttpConfig {

    @Bean
    public okhttp3.OkHttpClient okHttpClient() {
        return new okhttp3.OkHttpClient.Builder()
                .connectionPool(new ConnectionPool(200, 5, TimeUnit.MINUTES))
                .connectTimeout(2, TimeUnit.SECONDS)
                .readTimeout(5, TimeUnit.SECONDS)
                .writeTimeout(5, TimeUnit.SECONDS)
                .build();
    }
}

这段配置表示:

最多保留 200 个空闲连接
空闲连接最多保留 5 分钟
连接超时时间 2 秒
读取超时时间 5 秒
写入超时时间 5 秒

不过刚开始学习时,先用默认连接池配置即可,没必要一上来就调这些参数。

十二、完整配置总结

最后可以用这张表总结:

模块 依赖 注解或配置
hm-api spring-cloud-starter-openfeignspring-cloud-starter-loadbalancerfeign-okhttp Feign 接口加 @FeignClient
消费者,比如 trade-service 引入 hm-api、Nacos;如果 hm-api 没传递 OkHttp,就自己引入 feign-okhttp 启动类加 @EnableFeignClients(basePackages = "com.hmall.api.client"),配置 feign.okhttp.enabled=true
提供者,比如 cart-service Web、Nacos Controller 加 @RestController@RequestMapping,配置 spring.application.name=cart-service

十三、最终记忆方式

我现在会这样记:

hm-api:定义调用契约,放 FeignClient 和 DTO。

消费者:真正发起远程调用,负责开启 @EnableFeignClients,运行时要有 OpenFeign、LoadBalancer、OkHttp。

提供者:注册到 Nacos,暴露 Controller 接口。

LoadBalancer:负责根据服务名选择实例,生效在消费者运行时。

OkHttp:负责底层 HTTP 请求和连接池,也生效在消费者运行时。

最重要的一句话:

FeignClient 方法名不要求和 Controller 方法名一致;真正要一致的是服务名、请求方式、请求路径和参数名。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐