1. Ribbon负载均衡
1.1 负载均衡原理
SpringCloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的。
1.2 负载均衡源码
原理 : LoadBalancerInterceptor 类会对RestTemplate的请求进行拦截. 然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。
浏览器发出请求后, 先被 LoadBalancerInterceptor
的 intercept
所拦截
// LoadBalancerInterceptor 类
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
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));
}
然后通过 getHost()
和 getURI();
获取服务名和请求链接
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
接下来执行 execute()
方法, 先从 Eureka 服务端根据服务名拉取微服务实例
RibbonLoadBalancerClient
的 execute()
方法如下
// RibbonLoadBalancerClient 类
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);
}
具体步骤是 : 先通过 getLoadBalancer(serviceId);
方法根据微服务名称 order-service
获取所有的微服务实例 :[localhost:8081, localhost:8082]
然后再根据一定的负载均衡策略选取微服务
// RibbonLoadBalancerClient 类
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");
}
通过 loadBalancer.chooseServer
选取具体的微服务策略
// ZoneAwareLoadBalancer 类
@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");
return super.chooseServer(key);
}
// .... 省略
}
第一次执行先调用父类的 super.chooseServer(key)
方法
// BaseLoadBalancer 类
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;
}
}
}
然后根据相应的负载均衡策略 rule.choose(key)
去选择服务 , 规则接口是 IRule
protected IRule rule = DEFAULT_RULE;
public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
我们使用 ctrl + h
快捷键即可查看 IRule 接口的所有的实现类, 或者选中接口, 单击右键找到Diagrams 功能展示该接口所有的实现类
如上图, 该接口有多重实现策略, 比如 RoundRobinRule
就是轮寻策略
最后来个总结, 负载均衡的具体流程 :
1.3 负载均衡策略
1.3.1 基本策略
负载均衡的规则都定义在IRule接口中,而IRule接口有很多不同的实现类:
具体的策略如下
默认的实现就是ZoneAvoidanceRule,是一种轮询方案 , 轮询方案就是挨个调用所有的服务, 比如第一次请求调用微服务01, 第二次调用微服务02, 依次类推…
1.3.2 自定义负载均衡策略
我们通过定义IRule实现可以修改负载均衡规则,有两种方式:
-
方式一 : 在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean public IRule randomRule(){ return new RandomRule(); }
-
方式二 : 在order-service的application.yml文件中,添加新的配置也可以修改规则:
user-service: # 微服务名称 ribbon: # 负载均衡规则 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
注意 : 一般用默认的负载均衡规则,不做修改
1.4 饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
下面是在 order-service
微服务上的配置
user-service: # 微服务名称
ribbon: # 负载均衡规则
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
# 开启饥饿加载
ribbon:
eager-load:
clients: user-service # 指定对 user-service 这个服务端进行饥饿加载
enabled: false # 开启饥饿加载
开启配置后我们可以通过观察user-service
日志
从初始化到拉取微服务实例这一段是需要消耗一定的时间的, 如下图所示第一次访问因为要初始化负载均衡器
所以第一次访问请求响应时间是 816ms , 而再次访问时由于服务实例已经拉取完毕, 所以时间缩短很多
而当我们开启饥饿加载则会在项目启动时创建,降低第一次访问的耗时
如上图可以看到, 项目在启动的时候就会自动创建 DynamicServerListLoadBalancer
实例, 然后从注册中心拉取微服务实例