Ribbon组件学习
1. Ribbon简介
1.1 Ribbon是啥
Spring Cloud Ribbon是Netflix开源的一套客户端负载均衡工具。其主要功能包括提供客户端负载均衡算法和服务调用。Ribbon客户端组件提供了一系列完善的特性,如轮询、hash权重等。
有两种使用方式:一种是和RestTemplate相结合,另一种是和OpenFeign相结合。
1.2 负载均衡比较
Ribbon | 传统负载均衡服务(Nginx) | |
---|---|---|
工作模式 | 客户端负载均衡 | 服务端负载均衡 |
集成性 | 与Spring Cloud等微服务框架集成紧密、部署简单。 | 独立部署,需单独配置 |
负载均衡算法 | 支持多种算法(轮询、随机、最少活跃连接等),<br/可自定义 | 支持多种算法(轮询、IP_Hash、最少连接数等) |
灵活性 | 高,可通过配置和编程方式轻松实现负载均衡策略的切换和自定义 | 相对较低,配置和修改负载均衡策略通常需要重启服务或进行复杂配置 |
扩展性 | 易于与微服务架构集成,支持服务的动态发现和注册 | 通过增加硬件或软件实例实现扩展 |
性能 | 取决于客户端配置和负载情况,减少网络延迟和服务器压力 | 性能强大,能处理高并发请求,但可能成为系统瓶颈 |
可靠性 | 取决于客户端稳定性和负载均衡算法的有效性,存在单点故障风险(客户端) | 可靠性较高,具有冗余和故障转移机制,但也可能存在单点故障风险(未部署高可用时) |
2. 项目集成
2.1 RestTemplate集成
maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
</dependencies>
配置负载
给RestTemplate的Bean实例使用@LoadBalanced修饰后,该RestTemplate实例就具备了负载均衡的能力了。
@Configuration
public class RestClientConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
使用负载
RestTemplate使用就和普通的spring实例使用方式一致,只是调用http请求的时候的url地址。不是具体的ip+port的形式,而是使用对应的${serviceName} 调用请求,最终调用哪个服务实例的,是Ribbon从注册中心获取服务基于其负载均衡测试获取到具体的服务实例最终调用
@Service
@Slf4j
public class RestService {
@Resource
private RestTemplate restTemplate;
public String callService() {
// 调用我们的"order-service"的服务 这里不用写具体的ip端口地址
String url = "http://order-server/provider/order/v1/create";
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
log.info("restTemplate集成后服务调用结果:{}",response.getBody());
return response.getBody();
}
}
2.2 OpenFeign集成
maven依赖
因为openFeign默认集成了Ribbon,所以只要引入openFegin的依赖包即可使用Ribbon
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@FeignClient注解
@FeignClient(name = "${com.xiu.order.service}", primary = false,decode404 = false)
public interface OrderClient {
@GetMapping("/provider/order/v1/create")
String create();
}
使用
@Slf4j
@Service("feignTestService")
public class FeignTestServiceImpl implements FeignTestService {
@Resource
OrderClient orderClient;
@Override
public String sayHello() {
log.info(orderClient.create());
return "hello nacos!!!";
}
}
2.3 ribbon的负载策略
算法名称 | 描述 | 特性 |
---|---|---|
轮询(Round Robin) | 默认的负载均衡算法,按照固定顺序依次分配请求到各个服务实例。 | - 简单易用 - 适用于服务实例性能相近的场景 - 均衡分配流量 |
随机(Random) | 从所有可用的服务实例中随机选择一个来处理请求。 | - 适用于服务实例性能差异不大的场景 - 提高系统的随机性和不可预测性 - 可能在访问次数较少时表现不均匀 |
最少活跃连接(Least Active) | 选择当前活跃连接数最少的服务实例来处理请求。 | - 适用于连接数敏感的场景 - 确保负载不会集中在某些实例上 |
响应时间加权重(Weighted Response Time) | 根据服务实例的响应时间动态调整权重,响应时间越短,权重越高,被选中的概率也越大。 | -智能化负载均衡 - 适用于服务实例性能差异较大的场景 - 动态调整以适应实时性能变化 |
区域感知(Zone Aware) | 在选择服务实例时考虑其所在的区域(例如,机房、数据中心等),优先选择与客户端相同区域或网络延迟较低的服务实例。 | - 提高请求的本地化处理 - 减少网络延迟和传输成本 - 适用于跨地域部署的服务 |
2.4 高级用法
负载均衡策略
- yml文件配置
//${service-name} 具体的服务名称
${service-name:order-server}:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
- 代码配置
- 设置负载均衡测试类,注意:该类不能在启用类所在的包以及子包下。
@Configuration
public class MySelfRule {
@Bean
public IRule myRule() {
//默认是轮询,此处变更为随机
//此处也可以是自定义的负载均衡测试方法
return new RoundRobinRule();
}
}
-
@RibbonClient 将服务和策略关联
启动类配置@RibbonClient注解 将具体的服务name需要使用哪种配置
@RibbonClient(name = "order-server", configuration = MySelfRule.class)
public class CustomerApp {
public static void main(String[] args) {
SpringApplication.run(CustomerApp.class, args);
}
}
重试机制
yml文件配置
参数 | 描述 |
---|---|
ribbon.ConnectTimeout : | 连接超时时间,单位为毫秒。 |
ribbon.ReadTimeout : | 读取超时时间,单位为毫秒 |
ribbon.MaxAutoRetries : | 对当前实例的重试次数。如果达到此次数后请求还是失败,就会切换到下一个实例。默认值是0 |
ribbon.MaxAutoRetriesNextServer : | 表示重试的最大实例数,其默认值1 |
ribbon.OkToRetryOnAllOperations : | 是否对所有操作都进行重试。 |
# 对所有的服务该配置都生效
ribbon:
ConnectTimeout: 1000
ReadTimeout: 1000
OkToRetryOnAllOperations: false
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 1
retryableStatusCodes: ''
#只针对 order-service生效
order-service:
ribbon:
ConnectTimeout: 1000
ReadTimeout: 1000
OkToRetryOnAllOperations: false
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 1
retryableStatusCodes: ''
-
openFeign设置重试机制
虽然OpenFeign默认使用Ribbon的重试机制,但OpenFeign本身也提供了自定义重试逻辑的能力。这通常涉及到实现
feign.Retryer
接口,并在创建Feign客户端时注入该重试器。
- Feign 硬编码的重试
feign.Retryer
有个默认实现类 feign.Retryer.Default
,其无参构造方法会构建一个间隔100毫秒、最多重试5次的重试器Retryer。
- spring容器中feign接口重试
使用的Retryer的实现为**Retryer.NEVER_RETRY **该接口continueOrPropagate直接抛出异常。
也就是在spring 环境中feign本身的默认重试逻辑是不重试、直接抛出异常。
- Ribbon和feign整合中的重试
真实环境中,一般是将feign、ribbon集成起来结合使用。在feign引入ribbon负载均衡时,创建一个可重试的DefaultLoadBalancerRetryHandler对象,构造方法从IClientConfig中取出了配置的 MaxAutoRetries
MaxAutoRetriesNextServer
OkToRetryOnAllOperations
这几个属性。作为其重试的相关参数
3 restTemplate整合Ribbon
3.1 @LoadBalanced注解
- 当在Spring Cloud应用中配置RestTemplate时,通过在RestTemplate的@Bean注解上添加@LoadBalanced注解,可以使得RestTemplate在发起请求时具备负载均衡的能力。
- @LoadBalanced注解的加入,实际上是在RestTemplate的Bean初始化过程中,为其添加了一个LoadBalancerInterceptor拦截器。这个拦截器负责在请求发送前,根据负载均衡策略从服务注册中心(如Eureka)获取服务实例列表,并选择一个实例进行请求。
RestTemplate整合Ribbon的核心在于通过@LoadBalanced
注解和LoadBalancerAutoConfiguration
配置类来自动配置拦截器
LoadBalancerAutoConfiguration
- 服务启用过程中:LoadBalancerAutoConfiguration(自动装配类)的LoadBalancerInterceptorConfig会初始化一个LoadBalancerInterceptor拦截器。同时会将该拦截器set进RestTemplate。代码如下:
//初始化LoadBalancerInterceptor
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
//为restTemplate追加加LoadBalancerInterceptor拦截器
restTemplate.setInterceptors(list);
};
}
- 服务调用过程中:http请求的时候会通过LoadBalancerInterceptor拦截进行Ribbon负载均衡。
服务请求的链路如下:
getForEntity (RestTemplate)
└─ execute (RestTemplate)
└─ doExecute (RestTemplate)
└─ execute (AbstractClientHttpRequest)
└─ executeInternal (AbstractBufferingClientHttpRequest)
└─ executeInternal (InterceptingClientHttpRequest)
└─ execute (InterceptingClientHttpRequest$InterceptingRequestExecution)
└─ intercept (LoadBalancerInterceptor) ─ Ribbon负载均衡拦截器()
└─ execute (ClientHttpRequestExecution)
└─ (实际发送HTTP请求)
代码如下:
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
//获取服务url 例子:http://order-server/provider/order/v1/create
final URI originalUri = request.getURI();
//获取到服务名order-server 这里是服务名而非具体的ip+PORT
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
//真正负载均衡 RibbonLoadBalancerClient去执行
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
3.2 Ribbon负载具体实现
RibbonLoadBalancerClient的execute是Ribbon根据服务名从注册中心获取一个实例的负载具体实现,下面我们继续看看
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
//获取负载均衡组件 ZoneAwareLoadBalancer
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
//从负载均衡组件中获取一个服务实例
Server server = this.getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
} else {
//将获取到的服务实例包装成RibbonServer
RibbonServer ribbonServer = new RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
//HTTP请求调用
return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
}
}
- 上述方法先是获取到一个负载均衡组件ZoneAwareLoadBalancer实例:其核心类为DynamicServerListLoadBalancer,用于实现客户端的负载均衡,并具备动态获取服务列表的能力。该对象中有两个属性allServerList(全部服务列表,包含不可用的)、upServerList(全部可用的服务列表)。
- 紧接着从复杂均衡组件的upServerList中基于负载均衡策略选出一个服务实例Server。
- 包装服务实例为RibbonServer,它封装了从服务注册中心(如 Eureka)获取的服务实例信息,并用于后续的请求分发。
- 最后进行HTTP请求调用。
负载均衡策略
//loadBalancer.chooseServer获取服务
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
}
//
public Server chooseServer(Object key) {
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
//BaseLoadBalancer的chooseServer
return super.chooseServer(key);
}
Server server = null;
//省略基于区域获取服务的负载策略省略
//在选择服务实例时考虑区域因素,优化请求路由(中小规模企业服务用不上这个。)
}
//基于负载均衡策略获取服务实例
public Server chooseServer(Object key) {
//计数,用于跟踪调用次数、失败次数或其他统计信息
if (counter == null) {
counter = createCounter();
}
counter.increment();
//判断负责策略是否存在
if (rule == null) {
return null;
} else {
try {
//使用复杂策略获取到一个服务实例,默认是RoundRobinRule(轮询)
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
3.3 ILoadBalancer负载均衡组件
ILoadBalancer介绍
ILoadBalancer是Ribbon中的一个关键接口,它定义了软件负载均衡器的操作过程,主要作用包括维护服务实例列表、更新服务列表、检测服务可用性、根据指定算法选择服务实例等。以下是ILoadBalancer的作用以及相关实现的详细介绍,以表格形式展示:
序号 | 作用描述 | 相关实现 |
---|---|---|
1 | 维护服务实例列表 | ILoadBalancer接口的实现类(如ZoneAwareLoadBalancer、BaseLoadBalancer)会维护两个主要的列表:allServerList (存储所有服务实例)和upServerList (存储正常服务的实例)。这些列表用于后续的服务选择和负载均衡。 |
2 | 更新服务列表 | 通过ServerList接口的实现类(如DiscoveryEnabledNIWSServerList)与Eureka等注册中心交互,获取最新的服务实例列表,并更新到ILoadBalancer的缓存中。此外,ServerListUpdater接口的实现类(如PollingServerListUpdater)会定时从注册中心拉取服务列表,以确保服务列表的实时性。 |
3 | 检测服务可用性 | ILoadBalancer的实现类会维护一个IPing对象,用于检测服务实例的可用性。通过定时任务(默认每隔10秒)执行Ping操作,判断服务实例是否在线。IPing接口的实现类(如NIWSDiscoveryPing)会实际执行Ping操作,并返回服务实例的可用性状态。 |
4 | 根据指定算法选择服务实例 | ILoadBalancer通过调用IRule接口的实现类(如RoundRobinRule、RandomRule等)的choose方法,根据指定的负载均衡算法从upServerList 中选择一个可用的服务实例进行调用。不同的IRule实现类提供了不同的负载均衡策略。 |
5 | 提供服务选择接口 | ILoadBalancer接口定义了如chooseServer(Object key) 等方法,这些方法允许外部调用者根据指定的条件(如zone的id)选择可用的服务实例。这些方法是Ribbon实现客户端负载均衡的核心。 |
ILoadBalancer相关实现
实现类 | 描述 | 相关作用 |
---|---|---|
NoOpLoadBalancer | NoOpLoadBalancer是ILoadBalancer的一个“无操作”实现类,它基本上不执行任何负载均衡操作。 | 1. 占位符:在不需要实际负载均衡功能的场景中。 |
BaseLoadBalancer | BaseLoadBalancer是ILoadBalancer的基础实现类,提供了负载均衡器的基本框架和通用功能。 | 1. 服务实例管理:维护了服务实例的列表,包括所有服务实例(allServerList)和正常服务实例(upServerList)。 2. 服务列表更新:通过ServerList接口和ServerListUpdater接口的实现类,支持从静态或动态源更新服务列表。 3. 服务可用性检测:通过IPing接口的实现类,定期检测服务实例的可用性,并更新upServerList。 4. 服务选择:通过IRule接口的实现类,根据负载均衡算法选择可用的服务实例进行请求分发。 5. 基础功能:为其他更高级的负载均衡器实现类提供了基础框架和通用方法的实现。 |
DynamicServerListLoadBalancer | DynamicServerListLoadBalancer是ILoadBalancer的一个实现类,它支持从动态服务列表(如Eureka)中获取服务实例,并根据服务列表的变化实时更新负载均衡器。 | 1. 动态服务列表:能够动态地从服务注册中心获取最新的服务实例列表,并实时更新到负载均衡器中。 2. 负载均衡:结合IRule接口的实现类,根据配置的负载均衡算法选择服务实例进行请求分发。 3. 服务列表更新:通过ServerListUpdater接口的实现类,定期从服务注册中心拉取最新的服务列表,并更新到本地缓存中。 |
ZoneAwareLoadBalancer | ZoneAwareLoadBalancer是DynamicServerListLoadBalancer的一个扩展,它增加了对AWS(或类似环境)中的区域(Zone)的感知能力。 | 1. 区域感知:能够识别请求的源区域和服务的目标区域,从而在选择服务实例时考虑区域因素,优化请求路由。 2. 跨区域负载均衡:在多个区域部署服务时,能够根据区域负载、网络延迟等因素,智能地选择最优的服务实例进行请求分发。 3. 负载均衡策略:结合区域信息和负载均衡算法,实现更智能的负载均衡决策。 |
- 请求发送流程
- 当使用RestTemplate发送请求时,请求会首先经过LoadBalancerInterceptor拦截器。
- 拦截器会解析请求的URL,识别出服务名(即URL中的主机名部分)。
- 然后,拦截器会利用Ribbon的负载均衡策略(如轮询、随机等),从服务注册中心获取到的服务实例列表中,选择一个实例。
- 接着,拦截器会将请求URL中的服务名替换为选定的服务实例的实际地址(IP+端口)。
- 最后,修改后的请求会继续沿着RestTemplate的执行链传递,最终发送到目标服务实例。
4 openFeign整合Ribbon
基于上篇文档微服务feign组件学习我们分析到了最终是调用**feign.target(target)**生成proxy代理对象,我们继续分析一下该方法。该方法最终会 调用ReflectiveFeign的newInstance方法
4.1 服务启动
public <T> T newInstance(Target<T> target) {
//从一个feign接口中获取其所有的接口方法作为MethodHandler
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
//遍历Feign接口将方法以存放到methodToHandler和defaultMethodHandlers(接口的默认方法 java8新特性)
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//创建代理对象的接口回调处理器InvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
//JDK动态代理
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
//为代理对象绑定默认方法
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
Feign实例中,用于处理接口回调的类是InvocationHandler,其由一个工厂类InvocationHandlerFactory生成。工厂类接口中还有一个MethodHandler,其用于处理接口中的方法。在进行回调时,先调用InvocationHandler的invoke()方法,在invoke()方法中,再根据实际的方法调用使用对应的MethodHandler的invoke()方法进行处理。
也就是说,InvocationHandler总览一个接口所有的调用,其中再根据调用方法的不同,分发到不同的MethodHandler。
这个InvocationHandler的实例是ReflectiveFeign.FeignInvocationHandler。
4.2 服务调用
代理对象生成了,其回调处理器为ReflectiveFeign.FeignInvocationHandler对象,回调的时候会调用其invoke()方法,看看该方法做了什么
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通用方法 则不需要进行处理(equals、hashCode、toString方法)
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
//接口方法则是交由不同的MethodHandler
return dispatch.get(method).invoke(args);
}
上述方法根据Feign接口方法获取对应的MethodHandler调用其Invoke方法。debug调试其实现类为SynchronousMethodHandler。
public Object invoke(Object[] argv) throws Throwable {
//构造http请求参数模板
RequestTemplate template = buildTemplateFromArgs.create(argv);
//获取http的连接配置 比如连接超时时间connectTimeout,读取超时时间readTimeout
Options options = findOptions(argv);
//获取重试器 参考该博客2.4 高级用法-重试机制
Retryer retryer = this.retryer.clone();
while (true) {
try {
//执行请求
return executeAndDecode(template, options);
} catch (RetryableException e) {
//当Feign客户端发起HTTP请求时,如果请求因为某些原因失败了
//如网络超时、目标服务器返回可重试的错误码等)
//Feign会根据配置的重试策略来决定是否进行重试
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
继续分析真正执行请求的方法executeAndDecode()。
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
//根据http请求模板构建http请求
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//LoadBalancerFeignClient去执行http请求
//该client是具有负载能力的
response = client.execute(request, options);
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
//对应请求返回类型进行编解码处理
if (decoder != null)
return decoder.decode(response, metadata.returnType());
//异步地返回HTTP响应的结果
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(),
elapsedTime);
try {
if (!resultFuture.isDone())
throw new IllegalStateException("Response handling not done");
return resultFuture.join();
} catch (CompletionException e) {
Throwable cause = e.getCause();
if (cause != null)
throw cause;
throw e;
}
}
上述方法进行了方法的http请求的调用。其client.execute(request, options)就是进行负载请求的具体方法,其实现类为LoadBalancerFeignClient去所以下面继续分析该类的execute方法。
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
// this.delegate就是一个feign.Client
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 服务配置,如readTimeout、connectTimeout
IClientConfig requestConfig = getClientConfig(options, clientName);
// lbClient()返回一个FeignLoadBalancer对象
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
LoadBalancerFeignClient
在LoadBalancerFeignClient的execute() 方法中,其根据参数和配置组装RibbonRequest、RequestConfig对象。
然后调用lbClient()方法获取一个FeignLoadBalancer对象,执行其executeWithLoadBalancer()方法,执行请求发送。
4.3 FeignLoadBalancer
FeignLoadBalancer.executeWithLoadBalancer()方法继承自其抽象类AbstractLoadBalancerAwareClient,在方法中,ribbon相关的功能被封装在了一个LoadBalancerCommand对象中。
调用command.submit()方法,并提供了一个ServerOperation实现,ServerOperation的作用是使用一个Feign.Client进行请求的发送。
LoadBalancerCommand的submit()方法,包含了ribbon中的核心功能,一些方法和变量作用如下
- selectServer():动态选择一个服务实例,默认轮询
- retryHandler:用于执行请求重试,默认未启用
- serverStats:用于跟踪和记录服务实例的统计信息,例如请求成功次数、请求失败次数、响应时间等。它可以与负载均衡器结合使用,以帮助其做出更加智能的选择
selectServer
private Observable<Server> selectServer() {
//获取到可用服务实例
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
//依据负载均衡策略获取到服务实例
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
//从uri中获取host,port
String host = null;
int port = -1;
if (original != null) {
host = original.getHost();
}
if (original != null) {
Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);
port = schemeAndPort.second();
}
ILoadBalancer lb = getLoadBalancer();
//没有对应的 host+port 则需要从负载均衡中根据服务名获取到对应的服务实例
if (host == null) {
if (lb != null){
//根据负载均衡策略从服务列表中获取到一个服务实例。
//ILoadBalancer实现类为ZoneAwareLoadBalancer
Server svc = lb.chooseServer(loadBalancerKey);
if (svc == null){
throw new ClientException(ClientException.ErrorType.GENERAL,
"Load balancer does not have available server for client: "
+ clientName);
}
host = svc.getHost();
if (host == null){
throw new ClientException(ClientException.ErrorType.GENERAL,
"Invalid Server for :" + svc);
}
logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original});
return svc;
}
//省略部分代码
if (host == null){
throw new ClientException(ClientException.ErrorType.GENERAL,"Request contains no HOST to talk to");
}
return new Server(host, port);
}
终于我们看到** lb.chooseServer(loadBalancerKey);**和RestTemplate整合Ribbon一样,其最终调用ZoneAwareLoadBalancer中的IRule负载均衡策略获取到具体的服务实例并最终完成http请求处理。