Spring Cloud Ribbon 原理
一、Spring Cloud Ribbon 客户端负载均衡
Ribbon的负载算法是由IRule 接口定义的,提供了七种负载均衡算法
- RandomRule,随机
- RoundRobinRule,轮询
- AvailabilityFilteringRule,先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问
- WeightedResponseTimeRule 根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用RoundRobinRule策略,待统计信息足够会切换到该WeightedResponseTimeRule策略
- RetryRule 先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,然后分发其他可用的服务
- BestAvailableRule 先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务
- ZoneAvoidanceRule (默认) 综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务
也可以自定义实现负载均衡算法,实现IRule 。
二、RESTful HTTP协议通信
restful规范了HTTP通信协议的标准
- HTTP METHOD 约束资源操作类型 GET POST PUT DELETE
- REST是面向资源的
- /order (GET ) /order/${id}
- /order (POST)
- /order (PUT)
- /order/${id} DELELE
- HTTP 返回码 2xxx 3xx 4xx 5xx
三、Ribbon对于请求的拦截
Ribbon底层的网络通信,采用的是HttpClient中的PoolingHttpClientConnectionManager连接池,连接池的好处是避免频繁建立连接(针对单个目标地址)带来的性能开销,但是维护过多的链接会对客户端造成内存以及维护上的成本。
所以,可以通过MaxTotalConnections限制总的连接数量,或者通过MaxConnectionsPerHost限制针对每个host的最大连接数。
# 最大连接数
ribbon.MaxTotalConnections=500 (默认值:200)
# 每个host最大连接数
ribbon.MaxConnectionsPerHost=500 (默认值:50)
请求的过程中,LoadBalancerInterceptor是如何发挥作用的?
通过一些列的方法调用到了LoadBalancerInterceptor.intercept(),它主要实现了对于请求的拦截。
其中最核心的是this.loadBalancer.r.execute方法,实际this.loadBalancer是LoadBalancerClient。LoadBalancerClient其实是一个接口,其具体实例应该是RibbonLoadBalancerClient。
RibbonLoadBalancerClient核心方法execute如下:
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object
hint) throws IOException {
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
Server server = this.getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " +
serviceId);
} else {
RibbonLoadBalancerClient.RibbonServer ribbonServer = new
RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server,
serviceId), this.serverIntrospector(serviceId).getMetadata(server));
return this.execute(serviceId, (ServiceInstance)ribbonServer,
(LoadBalancerRequest)request);
}
}
- 根据serviceId获得一个ILoadBalancer
- 调用getServer方法去获取一个服务实例
- 判断Server的值是否为空。这里的Server实际上就是传统的一个服务节点,这个对象存储了服务节点的一些元数据,比如host、port等
getServer是用来获得一个具体的服务节点,它的实现如下:
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null
? hint : "default");
}
我们发现getServer实际调用了IloadBalancer.chooseServer这个方法,ILoadBalancer这个是一个负载均衡器接口。
public interface ILoadBalancer {
public void addServers(List<Server> newServers);
public Server chooseServer(Object key);
public void markServerDown(Server server);
public List<Server> getReachableServers();
public List<Server> getAllServers();
}
1.addServers表示向负载均衡器中维护的实例列表增加服务实例
2.chooseServer表示通过某种策略,从负载均衡服务器中挑选出一个具体的服务实例
3.markServerDown表示用来通知和标识负载均衡器中某个具体实例已经停止服务,否则负载均衡器在下一次获取服务实例清单前都会认为这个服务实例是正常工作的
4.getReachableServers表示获取当前正常工作的服务实例列表
5.getAllServers表示获取所有的服务实例列表,包括正常的服务和停止工作的服务
从整个类的关系图来看,BaseLoadBalancer类实现了基础的负载均衡,而DynamicServerListLoadBalancer和ZoneAwareLoadBalancer则是在负载均衡策略的基础上做了一些功能扩展。
- AbstractLoadBalancer实现了ILoadBalancer接口,它定义了服务分组的枚类/chooseServer(用来选取一个服务实例)/getServerList(获取某一个分组中的所有服务实例)/getLoadBalancerStats用来获得一个LoadBalancerStats对象,这个对象保存了每一个服务 的状态信息。
- BaseLoadBalancer,它实现了作为负载均衡器的基本功能,比如服务列表维护、服务存活状态监测、负载均衡算法选择Server等。但是它只是完成基本功能,在有些复杂场景中还无法实现,比如动态服务列表、Server过滤、Zone区域意识(服务之间的调用希望尽可能是在同一个区域内进行,减少延迟)
- DynamicServerListLoadBalancer是BaseLoadbalancer的一个子类,它对基础负载均衡提供了扩展,从名字上可以看出,它提供了动态服务列表的特性
- ZoneAwareLoadBalancer 它是在DynamicServerListLoadBalancer的基础上,增加了以Zone的形式来配置多个LoadBalancer的功能。
loadBalancer.chooseServer 具体的实现类是哪个?可以在RibbonClientConfiguration这个配置类中确认,默认情况下采用的是ZoneAwareLoadBalancer。
ZoneAwareLoadBalancer
Zone表示区域的意思,区域指的就是地理区域的概念,一般较大规模的互联网公司,都会做跨区域部署,这样做有几个好处,第一个是为不同地域的用户提供最近的访问节点减少访问延迟,其次是为了保证高可用,做容灾处理。
其核心功能:
- 若开启了区域意识,且zone的个数 > 1,就继续区域选择逻辑
- 根据ZoneAvoidanceRule.getAvailableZones()方法拿到可用区们(会T除掉完全不可用的区域
们,以及可用但是负载最高的一个区域) - 从可用区zone们中,通过ZoneAvoidanceRule.randomChooseZone随机选一个zone出来 (该随机遵从权重规则:谁的zone里面Server数量最多,被选中的概率越大)
- 在选中的zone里面的所有Server中,采用该zone对对应的Rule,进行choose
- BaseLoadBalancer.chooseServer 根据默认的负载均衡算法来获得指定的服务节点。默认的算法是RoundBin。
四、Ribbon对于服务列表的加载过程
上面提到RibbonClientConfiguration
public ServerList<Server> ribbonServerList(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerList.class, name)) {
return this.propertiesFactory.get(ServerList.class, config, name);
}
ConfigurationBasedServerList serverList = new
ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
return serverList;
}
在 RibbonClientConfiguration 配置类中进行初始化了ConfigurationBasedServerList 这个类,ConfigurationBasedServerList 中的getUpdatedListOfServers 方法,这个方法是获取更新的服务列表
public List<Server> getUpdatedListOfServers() {
String listOfServers =
clientConfig.get(CommonClientConfigKey.ListOfServers);
return derive(listOfServers);
}
Iping
public class MyPing implements IPing{
public boolean isAlive(Serverserver){
System.out.println("isAlive"+server.getHostPort());
return true;
}
}
修改配置:
#配置服务器列表
MyRibbonClient.ribbon.listOfServers=localhost:8080,localhost:8002
#配置负载均衡规则IRule的实现类
MyRibbonClient.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.W
eightedResponseTimeRule
#配置负载均衡实现类
MyRibbonClient.ribbon.NFLoadBalancerClassName=com.netflix.loadbalancer.ZoneA
wareLoadBalancer
#配置IPing的实现类
MyRibbonClient.ribbon.NFLoadBalancerPingClassName=org.lixue.ribbon.client.My
Ping
#配置Ping操作的间隔
MyRibbonClient.ribbon.NFLoadBalancerPingInterval=2
在ribbon负载均衡器中,提供了ping机制,每隔一段时间,就会去ping服务器,由com.netflix.loadbalancer.IPing 接口去实现。单独使用ribbon,不会激活ping机制,默认采用DummyPing(在RibbonClientConfiguration中实例化),isAlive()方法直接返回true。
ribbon和eureka集成,默认采用NIWSDiscoveryPing(在EurekaRibbonClientConfiguration中实例化的),只有服务器列表的实例状态为up的时候才会为Alive。
根据的配置的时间间隔(默认30s),执行Ping,检查服务isAlive,然后更新的服务列表。