客户端负载均衡
负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。
- 硬件负载均衡,eg:F5
- 软件负载均衡,eg:Nginx
通过下挂一个可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按照某种算法(线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端的地址,然后进行转发。
Ribbon通过使用@LoadBalanced注释修饰过的RestTemplate来实现接口调用。
RestTemplate
GET请求
getForObject()是对getForEntity()的进一步封装,它通过HttpMessageConverterExtractor对HTTP的请求响应体body内容进行对象转换,返回包装好的对象内容。
- getForObject(String url, Class responseType, Object... uriVariables)
public String getUserByName(String name) {
return restTemplate.getForObject("http://USER-SERVICE/user?name={1}", String.class, "Huffam");
}
- getForObject(String url, Class responseType, Map<String, ?> uriVariables)
public String getUserByName(String name) {
Map<String, String> params = new HashMap<>();
params.put("name", name);
return restTemplate.getForObject("http://USER-SERVICE/user?name={1}", String.class, params);
}
- getForObject(URI url, Class responseType)
public String getUserByName(String name) {
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"http://USER-SERVICE/user?name={1}")
.build()
.expand("Huffam")
.encode();
URI uri = uriComponents.toUri();
return restTemplate.getForObject(uri, String.class);
}
POST请求
- postForObject(String url, Object request, Class responseType, Object... uriVariables)
- postForObject(String url, Object request, Class responseType, Map<String, ?> uriVariables)
- postForObject(URI url, Object request, Class responseType)
PUT请求
- put(String url, Object request, Object... uriVariables)
- put(String url, Object request, Map<String, ?> uriVariables)
- put(URI url, Object request)
DELETE请求
- delete(String url, Object... uriVariables)
- delete(String url, Map<String, ?> uriVariables)
- delete(URI url)
源码分析
从@LoadBalanced注解源码的注释中,我们可以知道该注解用来给RestTemplate标记,以使用负载均衡的客户端(LoadBalancerClient)来配置它。
源码分析有篇文章总结的比较全,这里是传送门
负载均衡器
Ribbon的负载均衡,主要通过LoadBalancerClient来实现的,而LoadBalancerClient具体交给了ILoadBalancer来处理,ILoadBalancer通过配置IRule、IPing等信息,并向EurekaClient获取注册列表的信息,默认10秒一次向EurekaClient发送“ping”,进而检查是否更新服务列表,最后,得到注册列表后,ILoadBalancer根据IRule的策略进行负载均衡。
负载均衡策略
RandomRule
通过rand.nextInde(serverCount)函数获取一个随机数,并将该随机数作为upList的索引值来返回具体实例。
RoundRobinRule
该策略实现了按照线性轮询的方式依次选择每个服务实例的功能。
RetryRule
该策略实现了一个具备重试机制的实例选择功能。其内部还定义了一个IRule对象,默认使用了RoundRobinRule实例。
WeightedResponseTimeRule
该策略是对RoundRobinRule的扩展,增加了根据实例的运行情况来计算权重,并根据权重来挑选实例,以达到更优的分配效果。
eg: 耗时情况
A: 10ms
B: 20ms
C: 30ms
weight = totalTime - time[i]
[0, 50], (51, 90], (91, 120]
ClientConfigEnabledRoundRobinRule
默认采用RoundRobinRule,我们一般不会直接使用这个策略,下面的高级策略都是基于这个策略实现的。
BestAvailableRule
选出当前并发最低的实例策略
PredicateBasedRule
这是一个抽象策略,这是一个基于Predicate实现的策略,Predicate是Google Guava Collection工具对集合进行过滤的条件接口。策略是先过滤清单,再轮询选择。而如何过滤需要在子类中实现apply方法来确定具体的过滤策略。以下两个策略是本策略的两种实现。
AvailabilityFilteringRule
该策略通过线性抽样的方式直接尝试寻找可用且较空闲的实例来使用,优化了父类每次都要遍历所有实例的开销。
ZoneAvoidanceRule
这是一个组合过滤条件,以ZoneAvoidancePredicate为主过滤条件,AvailabilityPredicate为次过滤条件。
重试策略
由于Spring Cloud Eureka实现的服务治理机制强调CAP原理中的AP,即可用性和可靠性,与Zookeeper这类强调CP(一致性、可靠性)的区别在于,Eureka为了实现更高的服务可用性,牺牲了一定的一致性。在极端情况下它宁愿接受故障实例也不丢掉“健康”实例(保护机制)。
从Camden SR2版本开始,Spring Cloud整合了Spring Retry来增强RestTemplate的重试能力。
spring.cloud.loadbalancer.retry.enabled=true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
hello-service.ribbon.ConnectTimeout=250
hello-service.ribbon.ReadTimeout=1000
hello-service.ribbon.OkToRetryOnAllOperations=true
hello-service.ribbon.MaxAutoRetriesNextServer=2
hello-service.ribbon.MaxAutoRetries=1
Reference
Spring Cloud源码分析(二)Ribbon
Ribbon源码解析