SpringCloud Ribbon源码探索学习

746 阅读4分钟

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的总重试时间,如果服务实例获取不到,则进入循环,在循环中每次需要判断一下是否超过总时间

if判断中,嵌套了一个定时任务,去判断是否达到预定时间,如果达到了,则对当前线程进行interrupt()操作

while循环中,每次都会去获取一下服务实例,然后进行判断,如果实例仍然没有获取到,则对当前线程进行Thread.yield()操作,此操作的意义是:让出当前线程时间分片,重新争夺时间片,让定时任务去执行,看是否达到规定时间,如到时间,则执行interrupt()操作,退出循环

这即是可重试的策略

总体流程

Ribbon + RestTemplate 的负载平衡,流程是: 通过注解后,对url进行封装request,拦截器对request进行拦截,然后根据负载均衡器去调用服务实例,完成负载平衡和服务调用