扒一扒Nacos、OpenFeign、Ribbon、loadbalancer组件协调工作的原理

前几天有个大兄弟问了我一个问题,注册中心要集成SpringCloud,想实现SpringCloud的负载均衡,需要实现哪些接口和规范。

既然这个兄弟问到我了,而我又刚好知道,这不得好好写一篇文章来回答这个问题,虽然在后面的聊天中我已经回答过了。

接下来本文就以探究一下Nacos、OpenFeign、Ribbon、loadbalancer等组件协调工作的原理的方式,来讲一讲应该需要是实现哪些接口了。

再多说一句,本文并没有详细地深入剖析各个组件的源码,如果有感兴趣的兄弟可以从微信公众号三友的java日记后台菜单栏中的文章分类中查看我之前写的关于Nacos、OpenFeign、Ribbon源码剖析的文章。

Nacos

先从Nacos讲起。

Nacos是什么,官网中有这么一段话

这一段话说的直白点就是Nacos是一个注册中心和配置中心!

在Nacos中有客户端和服务端的这个概念

  • 服务端需要单独部署,用来保存服务实例数据的
  • 客户端就是用来跟服务端通信的SDK,支持不同语言

当需要向Nacos服务端注册或者获取服务实例数据的时候,只需要通过Nacos提供的客户端SDK就可以了,就像下面这样:

引入依赖

<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.4.4</version>
</dependency>
复制代码

示例代码

Properties properties = new Properties();
properties.setProperty("serverAddr", "localhost");
properties.setProperty("namespace", "8848");

NamingService naming = NamingFactory.createNamingService(properties);

//服务注册,注册一个order服务,order服务的ip是192.168.2.100,端口8080
naming.registerInstance("order", "192.168.2.100", 8080);

//服务发现,获取所有的order服务实例
List<Instance> instanceList = naming.selectInstances("order", true);
复制代码

当服务注册到Nacos服务端的时候,在服务端内部会有一个集合去存储服务的信息

这个集合在注册中心界中有个响亮的名字,服务注册表

如何进行服务自动注册?

用过SpringCloud的小伙伴肯定知道,在项目启动的时候服务能够自动注册到服务注册中心,并不需要手动写上面那段代码,那么服务自动注册是如何实现的呢?

服务自动注册三板斧

SpringCloud本身提供了一套服务自动注册的机制,或者说是约束,其实就是三个接口,只要注册中心实现这些接口,就能够在服务启动时自动注册到注册中心,而这三个接口我称为服务自动注册三板斧。

服务实例数据封装--Registration

Registration是SpringCloud提供的一个接口,继承了ServiceInstance接口

Registration

ServiceInstance

从ServiceInstance的接口定义可以看出,这是一个服务实例数据的封装,比如这个服务的ip是多少,端口号是多少。

所以Registration就是当前服务实例数据封装,封装了当前服务的所在的机器ip和端口号等信息。

Nacos既然要整合SpringCloud,自然而然也实现了这个接口

NacosRegistration

这样当前服务需要被注册到注册中心的信息就封装好了。

服务注册--ServiceRegistry

ServiceRegistry也是个接口,泛型就是上面提到的服务实例数据封装的接口

ServiceRegistry

这个接口的作用就是把上面封装的当前服务的数据Registration注册通过register方法注册到注册中心中。

Nacos也实现了这个接口。

NacosServiceRegistry

并且核心的注册方法的实现代码跟前面的demo几乎一样

服务自动注册--AutoServiceRegistration

AutoServiceRegistration

AutoServiceRegistration是一个标记接口,所以本身没有实际的意义,仅仅代表了自动注册的意思。

AutoServiceRegistration有个抽象实现AbstractAutoServiceRegistration

AbstractAutoServiceRegistration是个抽象类

AbstractAutoServiceRegistration实现了ApplicationListener,监听了WebServerInitializedEvent事件。

WebServerInitializedEvent这个事件是SpringBoot在项目启动时,当诸如tomcat这类Web服务启动之后就会发布,注意,只有在Web环境才会发布这个事件。

ServletWebServerInitializedEvent继承自WebServerInitializedEvent。

所以一旦当SpringBoot项目启动,tomcat等web服务器启动成功之后,就会触发AbstractAutoServiceRegistration监听器的执行。

最终就会调用ServiceRegistry注册Registration,实现服务自动注册

Nacos自然而然也继承了AbstractAutoServiceRegistration

NacosAutoServiceRegistration

对于Nacos而言,就将当前的服务注册的ip和端口等信息,就注册到了Nacos服务注册中心。

所以整个注册流程就可以用这么一张图概括

当然,不仅仅是Nacos是这么实现的,常见的比如Eureka,Zookeeper等注册中心在整合SpringCloud都是实现上面的三板斧。

Ribbon

讲完了SpringCloud环境底下是如何自动注册服务到注册中心的,下面来讲一讲Ribbon。

我们都知道,Ribbon是负载均衡组件,他的作用就是从众多的服务实例中根据一定的算法选择一个服务实例。

但是有个疑问,服务实例的数据都在注册中心,Ribbon是怎么知道的呢???

答案其实很简单,那就是需要注册中心去主动适配Ribbon,只要注册中心去适配了Ribbon,那么Ribbon自然而然就知道服务实例的数据了。

Ribbon提供了一个获取服务实例的接口,叫ServerList

ServerList

接口中提供了两个方法,这两个方法在众多的实现中实际是一样的,并没有区别。

当Ribbon通过ServerList获取到服务实例数据之后,会基于这些数据来做负载均衡的。

Nacos自然而然也实现了ServerList接口,为Ribbon提供Nacos注册中心中的服务数据。

NacosServerList

这样,Ribbon就能获取到了Nacos服务注册中心的数据。

同样地,除了Nacos之外,Eureka、Zookeeper等注册中心也都实现了这个接口。

到这,其实就明白了Ribbon是如何知道注册中心的数据了,需要注册中心来适配。

在这里插个个人的看法,其实我觉得Ribbon在适配SpringCloud时对获取服务实例这块支持封装的不太好。

因为SpringCloud本身就是一套约束、规范,只要遵守这套规范,那么就可以实现各个组件的替换,这就是为什么换个注册中心只需要换个依赖,改个配置文件就行。

而Ribbon本身是一个具体的负载均衡组件,注册中心要想整合SpringCloud,还得需要单独去适配Ribbon,有点违背了SpringCloud约束的意义。

就类似mybatis一样,mybatis依靠jdbc,但是mybatis根本不关心哪个数据库实现的jdbc。

真正好的做法是Ribbon去适配SpringCloud时,用SpringCloud提供的api去获取服务实例,这样不同的注册中心只需要适配这个api,无需单独适配Ribbon了。

而SpringCloud实际上是提供了这么一个获取服务实例的api,DiscoveryClient

DiscoveryClient

通过DiscoveryClient就能够获取到服务实例,当然也是需要不同注册中心的适配。

随着Ribbon等组件停止维护之后,SpringCloud官方自己也搞了一个负载均衡组件loadbalancer,用来平替Ribbon。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>
复制代码

这个组件底层在获取服务实例的时候,就是使用的DiscoveryClient。

所以对于loadbalancer这个负载均衡组价来说,注册中心只需要实现DiscoveryClient之后就自然而然适配了loadbalancer

OpenFeign

OpenFeign是一个rpc框架,当我们需要调用远程服务的时候,只需要声明个接口就可以远程调用了,就像下面这样

听上去很神奇,其实本质上就是后面会为接口创建一个动态代理对象,解析类上,方法上的注解。

当调用方法的时候,会根据方法上面的参数拼接一个http请求地址,这个地址的格式是这样的http://服务名/接口路径

比如,上面的例子,当调用saveOrder方法的时候,按照这种规律拼出的地址就是这样的 http://order/order,第一个order是服务名,第二个order是PostMapping注解上面的。

但是由于只知道需要调用服务的服务名,不知道服务的ip和端口,还是无法调用远程服务,这咋办呢?

这时就轮到Ribbon登场了,因为Ribbon这个大兄弟知道服务实例的数据。

于是乎,OpenFeign就对Ribbon说,兄弟,你不是可以从注册中心获取到order服务所有服务实例数据么,帮我从这些服务实例数据中找一个给我。

于是Ribbon就会从注册中心获取到的服务实例中根据负载均衡策略选择一个服务实例返回给OpenFeign。

OpenFeign拿到了服务实例,此时就获取到了服务所在的ip和端口,接下来就会重新构建请求路径,将路径中的服务名替换成ip和端口,代码如下

reconstructURIWithServer

  • Server就是服务实例信息的封装
  • orignal就是原始的url,就是上面提到的,http://order/order

假设获取到的orde服务所在的ip和端口分别是192.168.2.1008080,最终重构后的路径就是http://192.168.2.100:8080/order,之后OpenFeign就可以发送http请求了。

至于前面提到的loadbalancer,其实也是一样的,他也会根据负载均衡算法,从DiscoveryClient获取到的服务实例中选择一个服务实例给OpenFeign,后面也会根据服务实例重构url,再发送http请求。

loadbalancer组件重构url代码

总结

到这,就把Nacos、OpenFeign、Ribbon、loadbalancer等组件协调工作的原理讲完了,其实就是各个组件会预留一些扩展接口,这也是很多开源框架都会干的事,当第三方框架去适配的,只要实现这些接口就可以了。

作者:zzyang90
链接:https://juejin.cn/post/7220622297362759736

<think>我们正在使用Spring Cloud OpenFeign,并且同时引入了feign-httpclient和feign-okhttp依赖。现在需要理解当接口返回类型为`org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient`时的原因和意义。 首先,我们需要明确几个关键点: 1. Spring Cloud OpenFeign在微服务架构中用于声明式的HTTP客户端调用,它集成了服务发现(如Eureka)和负载均衡(如RibbonSpring Cloud LoadBalancer)[^2]。 2. 在Spring Cloud OpenFeign中,默认的HTTP客户端可以通过自动配置选择,但同时也支持负载均衡客户端。 3. 当我们看到`FeignBlockingLoadBalancerClient`时,这表明Spring Cloud OpenFeign正在使用Spring Cloud LoadBalancer作为负载均衡器,并且这个客户端是个阻塞式的负载均衡客户端。 ### 原因分析 在Spring Cloud OpenFeign中,如果我们的应用引入了`spring-cloud-starter-loadbalancer`(这是Spring Cloud OpenFeign的默认负载均衡器,取代了Ribbon),那么OpenFeign会自动配置使用`FeignBlockingLoadBalancerClient`作为客户端。具体原因如下: - **负载均衡集成**:Spring Cloud OpenFeign需要与负载均衡器集成,以便在调用服务时能够从服务发现中心(如Nacos、Eureka等)获取服务实例列表,并进行负载均衡选择。`FeignBlockingLoadBalancerClient`就是负责这个过程的客户端实现。 - **自动配置**:当项目中存在`spring-cloud-starter-loadbalancer`依赖时,Spring Cloud OpenFeign的自动配置类(如`FeignLoadBalancerAutoConfiguration`)会配置`FeignBlockingLoadBalancerClient`作为Feign的客户端[^3]。 ### 意义 `FeignBlockingLoadBalancerClient`的意义主要体现在: 1. **负载均衡能力**:它封装了负载均衡的功能,使得Feign客户端在发起请求前,可以通过Spring Cloud LoadBalancer选择个目标服务实例,然后向该实例发起请求。 2. **阻塞式调用**:它是个阻塞式的客户端,适用于传统的同步调用场景。如果我们需要异步调用,则需要考虑其他方式(如使用Reactive的WebClient)。 3. **与底层HTTP客户端的协作**:`FeignBlockingLoadBalancerClient`本身并不是直接执行HTTP请求的客户端,而是将实际的HTTP请求委托给底层的客户端(如OkHttpClient或ApacheHttpClient)。也就是说,无论底层使用的是OkHttp还是Apache HttpClient,负载均衡的功能都由`FeignBlockingLoadBalancerClient`提供。 ### 执行流程 当使用Feign客户端发起请求时,流程大致如下: 1. Feign代理对象被调用。 2. 调用被`FeignBlockingLoadBalancerClient`拦截,该客户端通过Spring Cloud LoadBalancer获取目标服务的实例列表,并根据负载均衡策略选择个实例。 3. 将请求的目标URL替换为选中的实例的地址(IP和端口)。 4. 然后,使用底层配置的HTTP客户端(可能是OkHttp或Apache HttpClient)实际发送HTTP请求到该实例。 5. 返回响应结果。 ### 验证 在同时引入`feign-httpclient`和`feign-okhttp`的情况下,如果我们在日志中看到使用的是`FeignBlockingLoadBalancerClient`,那么底层实际使用的HTTP客户端是什么呢? 我们可以通过以下方式验证: 1. 注入`feign.Client`类型的Bean,并查看其类型: - 如果返回的是`FeignBlockingLoadBalancerClient`,说明负载均衡客户端已启用。 - 然后,我们可以进步查看`FeignBlockingLoadBalancerClient`内部委托的`delegate`属性,这个属性就是实际的HTTP客户端(可能是OkHttpClient或ApacheHttpClient)。 2. 代码示例: ```java @Autowired private Client feignClient; @GetMapping("/feign-client-type") public String getFeignClientType() { if (feignClient instanceof FeignBlockingLoadBalancerClient) { Client delegate = ((FeignBlockingLoadBalancerClient) feignClient).getDelegate(); return "负载均衡客户端: FeignBlockingLoadBalancerClient, 底层客户端: " + delegate.getClass().getName(); } return feignClient.getClass().getName(); } ``` ### 配置影响 - 如果我们不想使用负载均衡客户端(例如,直接调用指定URL的服务),可以设置: ```properties spring.cloud.loadbalancer.enabled=false ``` 这样,Feign将使用默认的客户端(可能是OkHttp或Apache HttpClient),而不会使用`FeignBlockingLoadBalancerClient`。 - 但是,在微服务架构中,我们通常需要服务发现和负载均衡,所以般会保持启用。 ### 总结 1. **原因**:项目中引入了Spring Cloud LoadBalancer(通过`spring-cloud-starter-loadbalancer`),Spring Cloud OpenFeign自动配置了`FeignBlockingLoadBalancerClient`作为客户端,以实现负载均衡功能。 2. **意义**:`FeignBlockingLoadBalancerClient`为Feign提供了负载均衡能力,它负责从服务发现中心获取服务实例并选择其中个实例进行调用,底层实际HTTP请求则委托给配置的HTTP客户端(OkHttp或Apache HttpClient)执行[^2][^3]。 ### 最佳实践 - 在微服务项目中,使用负载均衡客户端是标准实践。 - 如果需要调整负载均衡策略,可以通过自定义`LoadBalancerClient`的配置来实现。 - 底层HTTP客户端的选择(OkHttp或Apache HttpClient)可以通过配置或依赖管理来控制,建议根据项目需求选择其中个,并移除另个依赖以避免冲突。 §§相关问题§§ 1. 如何自定义 Spring Cloud LoadBalancer 的负载均衡策略? 2. 在 Spring Cloud OpenFeign 中如何禁用负载均衡? 3. 如何配置 Feign 使用异步非阻塞的 HTTP 客户端?[^2][^3] </think>在项目中同时使用 `spring-cloud-starter-openfeign`、`feign-httpclient` 和 `feign-okhttp` 时,接口返回 `org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient` 是由 Spring Cloud OpenFeign 的负载均衡机制决定的,具有明确的架构意义: --- ### 、原因分析 1. **负载均衡集成** `FeignBlockingLoadBalancerClient` 是 Spring Cloud OpenFeignSpring Cloud LoadBalancer 的集成实现。当您的项目满足以下条件时自动激活: - 启用了服务发现(如 Eureka/Nacos) - 添加了 `spring-cloud-starter-loadbalancer` 依赖(OpenFeign 2020+ 版本默认集成 [^2]) ```java // 自动配置类片段 @ConditionalOnBean(LoadBalancerClientFactory.class) @ConditionalOnProperty(value = "spring.cloud.loadbalancer.enabled", matchIfMissing = true) public class FeignLoadBalancerAutoConfiguration { @Bean public Client feignClient(LoadBalancerClientFactory loadBalancerClientFactory) { return new FeignBlockingLoadBalancerClient(/* 委托给实际HTTP客户端 */); } } ``` 2. **HTTP 客户端的委托关系** 无论底层使用 Apache HttpClient 还是 OkHttp,都会被包装在此负载均衡客户端中: ```mermaid graph LR A[Feign接口调用] --> B(FeignBlockingLoadBalancerClient) B --> C{负载均衡决策} C -->|选择服务实例| D[ApacheHttpClient/OkHttp] D -->|实际HTTP请求| E[目标服务] ``` --- ### 二、核心作用和意义 1. **服务发现与路由** 自动从注册中心(如 Eureka)获取服务实例列表,并根据负载均衡策略(轮询/随机等)选择具体实例 [^2]。 2. **底层 HTTP 客户端封装** 在内部委托给真实的 HTTP 客户端执行请求: - 若启用 Apache HttpClient:`delegate = new ApacheHttpClient()` - 若启用 OkHttp:`delegate = new OkHttpClient()` ```java // 伪代码实现 public class FeignBlockingLoadBalancerClient implements Client { private final Client delegate; // 实际HTTP客户端(HttpClient/OkHttp) public Response execute(Request request, Options options) { // 1. 通过LoadBalancer选择服务实例 ServiceInstance instance = loadBalancer.choose("service-name"); // 2. 重构请求URL(http://service-name/api → http://192.168.1.10:8080/api) Request newRequest = rebuildRequest(request, instance); // 3. 委托给底层HTTP客户端执行 return delegate.execute(newRequest, options); } } ``` 3. **故障隔离与重试** 整合了 Spring Cloud CircuitBreaker 的熔断能力,当服务不可用时自动触发降级逻辑 [^2]。 4. **统入口点** 为开发者屏蔽底层 HTTP 客户端差异(Apache/OkHttp/JDK)和负载均衡逻辑,提供致的编程接口。 --- ### 三、验证实验 1. **查看客户端类型** 添加测试接口打印客户端类型: ```java @RestController public class ClientInspectorController { @Autowired private Client feignClient; // 自动注入的Feign客户端 @GetMapping("/client-type") public String showClientType() { if (feignClient instanceof FeignBlockingLoadBalancerClient) { Client delegate = ((FeignBlockingLoadBalancerClient)feignClient).getDelegate(); return "负载均衡客户端: FeignBlockingLoadBalancerClient, 底层实现: " + delegate.getClass().getSimpleName(); } return feignClient.getClass().getName(); } } ``` 2. **预期输出示例** - 使用 Apache HttpClient: `负载均衡客户端: FeignBlockingLoadBalancerClient, 底层实现: ApacheHttpClient` - 使用 OkHttp: `负载均衡客户端: FeignBlockingLoadBalancerClient, 底层实现: OkHttpClient` --- ### 四、典型应用场景 | 场景 | `FeignBlockingLoadBalancerClient` 的作用 | |------|------------------------------------------| | 服务间调用 | 自动将 `@FeignClient("user-service")` 解析为具体实例地址 | | 灰度发布 | 通过自定义 LoadBalancer 实现基于标头的路由 | | 跨可用区容灾 | 优先选择同区域服务实例 | | 客户端限流 | 整合 Resilience4j 实现请求速率控制 | > **关键结论**:该类的存在表明您的 Feign 客户端已正确集成 Spring Cloud 负载均衡体系,是微服务通信的核心枢纽 [^2][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值