SpringCloud4️⃣负载均衡 Ribbon
👉回顾 Eureka
- 部署 eureka-server,将 eureke-client 注册到 Eureka 中。
- 以服务名代替主机和 IP(服务发现)。
@LoadBalanced
实现负载均衡,其原理是本文的 Ribbon。
1、Ribbon
Ribbon 是 Netflix 开发的,提供客户端负载均衡能力。
远程调用流程:
-
服务消费者通过 RestTemplate 发起远程调用,HTTP 请求被 Ribbon 拦截。
-
Ribbon 从 eureka-server 拉取服务实例列表,基于负载均衡算法选择服务实例。
-
远程调用。
Ribbon 底层如何通过 service 服务名,实现服务拉取和远程调用呢?
👇 分析源码,关键类如下:
- LoadBalancerInterceptor:拦截 RestTemplate 的请求,实现服务拉取和负载均衡,用实际地址信息替换服务名称。
- LoadBalcnerClient:
- IRule:负载均衡策略。
2、源码分析
示例:order-service 向 user-service 发起远程调用。
2.1、拦截器
LoadBalancerInterceptor
类拦截客户端 HTTP 请求
-
获取 URI
-
获取主机名,即服务名
-
调用负载均衡器
@Override public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { // 获取URI final URI originalUri = request.getURI(); // 获取服务名 String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); // 负载均衡器 return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); }
2.2、负载均衡器
2.2.1、接口
LoadBalancerClient 接口
客户端负载均衡器。
Hint:此处的 serviceId 即服务名。
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
2.2.2、实现类
RibbonLoadBalancerClient
-
实现方法:调用重载方法。
@Override public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { return execute(serviceId, request, null); }
-
重载方法:
-
基于服务名获取负载均衡器
-
基于 IRule 进行负载均衡
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException { // 获取负载均衡器 ILoadBalancer loadBalancer = getLoadBalancer(serviceId); // 负载均衡 Server server = getServer(loadBalancer, hint); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); return execute(serviceId, ribbonServer, request); }
-
负载均衡器
loadBalancer 类
负载均衡
getServer()
Hint:方法参数 hint
表示要使用的负载均衡策略。
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
ZoneAwareLoadBalancer 类
Hint:方法参数 key
表示要使用的负载均衡策略,即上个方法的 hint
。
@Override
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
return super.chooseServer(key);
}
// ...
}
BaseLoadBalancer 类
Hint:
-
方法参数
key
表示要使用的负载均衡策略。 -
IRule 类 定义了负载均衡策略。
public Server chooseServer(Object key) { if (counter == null) { counter = createCounter(); } counter.increment(); if (rule == null) { return null; } else { try { // 选择实例 return rule.choose(key); } catch (Exception e) { logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e); return null; } } }
最终获取到一个实例:
2.3、图示流程
-
服务消费者
发起 HTTP 请求。 -
RibbonLoadBalancerClient
拦截请求,先后获取 URI 和服务名称。http://userservice/user/1 user-service
-
DynamicServerListLoadBalancer
根据服务名称(user-service)向 Eureka 拉取服务列表。 -
IRule
基于内置负载均衡规则,从列表中选择一个实例。localhost:8081
-
RibbonLoadBalancerClient
以真实地址(localhost:8081)替代服务名(userservice),发起真实请求。http://localhost:8081/user/1
3、负载均衡策略
3.1、IRule
IRule 接口定义了负载均衡规则
-
接口实现关系
-
说明
含义 选择规则 RoundRobinRule 轮询 轮询服务列表(默认) WeightedResponseTimeRule 权重响应 基于响应时间为每个服务器分配动态权重,加权轮询 ZoneAvoidanceRule 区域避免 基于区域(Zone)和可用性,轮询 AvailabilityFilteringRule 可用过滤 忽略 2 类服务器:
连续读取或连接失败而处于断路状态;超过可配置限制的活动连接(高并发)BestAvailableRule 最佳可用 忽略断路服务器,选择并发请求低的服务器 RandomRule 随机 随机选择一个可用服务器 RetryRule 重试 向现有规则添加重试逻辑
3.2、设置策略
默认采用 RoundRobinRule
可通过代码或配置方式选择策略。
-
代码方式:在 order-service 的任意 @Configuration 配置类中,将 IRule 实现类注册到 Spring 中。
(所有微服务都会应用此规则,粒度大)
// 如:在启动类中 @Bean public IRule randomRule(){ return new RandomRule(); }
-
配置方式:在 order-service 的 application.yml 中
(可指定微服务,细粒度)
userservice: # 给指定微服务配置负载均衡规则 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
Hint:通常使用默认的负载均衡规则 RoundRobinRule,不做修改。
4、饥饿加载
Ribbon 默认是懒加载机制
即首次发起远程调用时,才创建 LoadBalanceClient,请求时间长。
饥饿加载:在服务启动时创建 LoadBalanceClient
ribbon:
eager-load:
enabled: true
clients: userservice