Ribbon使用
在平时使用Ribbon时,更多的是将Ribbon与RestTemplate相结合:
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
复制代码
首先定义一个RestTemplate,通过注解注入,同时注解也完成了负载均衡。
同时去使用restTemplate进行Rest调用
@Override
public String hiService(String name){
return restTemplate.getForObject("http://SERVER-HI/hi?name="+name,String.class);
}
复制代码
那么在我们在输入http://localhost:8770/hi?name=lixin后,到底做了什么
一、封装
首先,在进行getForObject方法后,会将带入的url进行封装,封装成http请求request,然后被拦截器LoadBalancerInterceptor进行拦截,代码分别是:
@Override
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
复制代码
二、拦截
以及拦截部分:将request拦截下,截取其中URL及服务名,用于之后调用负载均衡方法时,选择合适的服务实例
@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, requestFactory.createRequest(request, body, execution));
}
复制代码
三、根据负载均衡器调用服务实例
至此,调用RibbonLoadBalancerClient中的execute方法,先查看一下RibbonLoadBalancerClient类:
其中LoadBalancerClient接口,有如下三个方法,其中excute()为执行请求,reconstructURI()用来重构url。
ServiceInstanceChooser接口,主要有一个方法,用来根据serviceId来获取ServiceInstance。
它继承了ServiceInstanceChooser及LoadBalancerClient类,最终的负载均衡的请求处理,由它来执行
首先,去获取需要加载的负载均衡策略,通过getLoadBalancer方法执行,默认是轮询RoundRobbinRule方式:
在获取到负载均衡策略之后,通过getServer()方法去获取实例,点击进入getServer方法,发现是ILoadBalancer类去选择服务实例。
在ILoadBalancer接口中,addServers()方法是添加一个Server集合;chooseServer()方法是根据key去获取Server;markServerDown()方法用来标记某个服务下线;getReachableServers()获取可用的Server集合;getAllServers()获取所有的Server集合。
chooseServer则是由BaseLoadBalancer类进行实现:
根据代码可以看出,具体choose方法是根据不同的负载均衡策略,会有不同的选择方法,返回具体根据策略得到的服务实例。
最后根据服务实例,进行请求的调用。
负载均衡策略
IRule用于复杂均衡的策略,它有三个方法,其中choose()是根据key 来获取server,setLoadBalancer()和getLoadBalancer()是用来设置和获取ILoadBalancer的
IRule有很多默认的实现类,这些实现类根据不同的算法和逻辑来处理负载均衡。Ribbon实现的IRule有以下几个。在大多数情况下,这些默认的实现类是可以满足需求的,如果有特性的需求,可以自己实现。
- BestAvailableRule 选择最小请求数
- ClientConfigEnabledRoundRobinRule 轮询
- RandomRule 随机选择一个server
- RoundRobinRule 轮询选择server
- RetryRule 根据轮询的方式重试
- WeightedResponseTimeRule 根据响应时间去分配一个weight ,weight越低,被选择的可能性就越低
- ZoneAvoidanceRule 根据server的zone区域和可用性来轮询选择
RoundRobbinRule
那我们就先看看RoundRobbinRule类中的轮询策略:
①首先获取所有存活的服务列表reachableServers及所有服务列表allServers,判断两个list是否为空。 ②incrementAndGetModulo中则是对一个原子性变量进行+1操作,并同时进行一个CAS操作,去修改nextServerIndex值,保证轮询的可靠性。③最后判断服务是否可用,如果不可用,则重新进入循环。
④如果在10次循环后,仍然没有可用的服务,则退出循环并进行警告。最后返回服务实例
RetryRule
可重试的轮询策略如下:
可见此处多了两行代码:
long requestTime = System.currentTimeMillis();
long deadline = requestTime + maxRetryMillis;
复制代码
定义了500ms的总重试时间,如果服务实例获取不到,则进入循环,在循环中每次需要判断一下是否超过总时间
while循环中,每次都会去获取一下服务实例,然后进行判断,如果实例仍然没有获取到,则对当前线程进行Thread.yield()操作,此操作的意义是:让出当前线程时间分片,重新争夺时间片,让定时任务去执行,看是否达到规定时间,如到时间,则执行interrupt()操作,退出循环
这即是可重试的策略
总体流程
Ribbon + RestTemplate 的负载平衡,流程是: 通过注解后,对url进行封装request,拦截器对request进行拦截,然后根据负载均衡器去调用服务实例,完成负载平衡和服务调用