Spring Cloud Ribbon 源代码学习笔记

 图片来自网络

     Spring cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它是基于Netflix的Ribbon实现的。Ribbon是客户端负载均衡器,这有别语例如Nginx服务端负载均衡器。Ribbon本身提供了不通负载均衡策略使用不通的应用场景。

Ribbon  源代码分析

批注:测试用例可以参考前面的文章  Spring Cloud Eureka 概念与示例, 可以将断点设置到本文中提到的类的方法中

1.RestTemplate

1.1 @LoadBalanced注解

/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
 * 为RestTemplate 声明一个标记,用于使用负载均衡的客户端LoadBalancerClient 
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

从@LoadBalanced 代码的注解可以知道,这个注解用来给RestTemplate 做一个标记,用于使用负载均衡的客户端LoadBalancerClient ,查看LoadBalancerClient源码,LoadBalancerClient接口定义继承接口ServiceInstanceChooser共有4个方法

  • ServiceInstance choose(String serviceId);根据传入的服务名serviceId,从负载均衡器中挑选一个对应服务的实例
  • <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;根据serviceId使用从负载均衡器中挑选出的服务实例来执行请求内容
  • <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;根据serviceId使用从负载均衡器中挑选出的服务实例来执行请求内容
  • URI reconstructURI(ServiceInstance instance, URI original);构建一个合适的host:port形式的URI,在分布式系统中,使用逻辑上的服务u名称作为host来构建URI进行请求

1.2 LoadBalancerAutoConfiguration对象

LoadBalancerAutoConfiguration 是为了实现客户端负载均衡器的自动化配置类,查看源码会知道其创建了一个LoadBalanceInterceptor的Bea,用于实现对客户端发起请求时拦截,拦截时实现客户端负载均衡

	    @Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return new RestTemplateCustomizer() {
				@Override
				public void customize(RestTemplate restTemplate) {
                    //1.获取已有拦截器  
					List<ClientHttpRequestInterceptor> list = new ArrayList<>(
							restTemplate.getInterceptors());
                    //2.向拦截器结合中增加负载均衡拦截器
					list.add(loadBalancerInterceptor);
					restTemplate.setInterceptors(list);
				}
			};
		}

1.3 RestTemplate调用流程图

RestTemplate调用LoadBalanceInterceptor 实现负载的调用流程程如下图所示 

2. LoadBalanceIntercepter

2.1 LoadBalanceIntercepter对象

下面,再看LoadBalanceIntercepter对象,对象中LoadBalanceClient是一个抽象的负载均衡接口,此接口只有一个实现类RibbonLoadBalancerClient,可见是调用RibbonLoadBalancerClient#execute实现的负载均衡

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;
	private LoadBalancerRequestFactory requestFactory;
    //略

	@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);
        //RibbonLoadBalancerClient 实现了loadBalancer接口
		return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
	}
}

2.2 RibbonLoadBalancerClient

public class RibbonLoadBalancerClient implements LoadBalancerClient {

	private SpringClientFactory clientFactory;
    //略

    	@Override
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        //通过serviceId 例如 MS-CUSTOMER  获取ZoneAwareLoadBalancer
        //ZoneAwareLoadBalancer是接口ILoadBalancer 的实现类
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        
        //再通过ZoneAwareLoadBalancer获取负载均衡的服务
		Server server = getServer(loadBalancer);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
				serviceId), serverIntrospector(serviceId).getMetadata(server));
        
        // 最后调用同名重载方法实现request.apply
		return execute(serviceId, ribbonServer, request);
	}

	protected Server getServer(ILoadBalancer loadBalancer) {
		if (loadBalancer == null) {
			return null;
		}
		return loadBalancer.chooseServer("default"); // TODO: better handling of key
	}

    @Override
	public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
		Server server = null;
		if(serviceInstance instanceof RibbonServer) {
			server = ((RibbonServer)serviceInstance).getServer();
		}
		//...
		RibbonLoadBalancerContext context = this.clientFactory
				.getLoadBalancerContext(serviceId);
		RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

		try {
			T returnVal = request.apply(serviceInstance);
			statsRecorder.recordStats(returnVal);
			return returnVal;
		}
		//。。。
	}

}

RibbonLoadBalancerClient处理的流程如下

第一步:先通过通过serviceId 获取负载均衡器,ILoadBalancer接口定义了负载均衡器操作, 通过RibbonClientConfiguration配置类我们知道,整合Ribbon的时候默认使用的ILoadBalancer接口的实现是ZoneAwareLoadBalancer

第二步:再通过ZoneAwareLoadBalancer获取负载均衡后的服务

第三步: 将服务等信息封装成RibbonServer对象,RibbonServer对象是serviceInstance接口的实现,serviceInstance表示发现的系统中的某个服务实例其定义了服务智力系统中serviceId,host,port等信息。然后调用重载方法excute方法执行request.apply,apply方法传入的参数serviceInstance即上面封装的的RibbonServer对象

2.3 ILoadBalancer接口

该接口定义了负载均衡器的抽象操作,当前有六中操作,详细信息如下。通过

// ILoadBalancer 接口定义
public interface ILoadBalancer {

    //向负载均衡器维护的实例表中增加一组服务
	public void addServers(List<Server> newServers);
	//根据传入的策略,从负载均衡器中挑选出一个具体的服务实例
	public Server chooseServer(Object key);
	//通知指定的服务已经停止
	public void markServerDown(Server server);
	//获取有效的服务列表,
    //当参数=true时候,获取所有有效服务等同getReachableServers
    //当参数=false时候,获取所有服务等同getAllServers
    @deprecated 2016-01-20此方法已被弃用
	public List<Server> getServerList(boolean availableOnly);
    //获取当前正常的服务列表
    public List<Server> getReachableServers();
    //获取已知道的全部服务包括正常状态的服务和停止状态的服务
	public List<Server> getAllServers();
}

2.4 ZoneAwareLoadBalancer

下面看看ZoneAwareLoadBalancer是如何实现的负载均衡

  1.      当只有1个有效Zones的时候通过ZoneAvoidanceRule规则该规则根据区域和可用性规则来获取服务
  2.      当有多个Zone的时候,通过RoundRobinRule规则获取
 public Server chooseServer(Object key) {
        //1. 当只有1个有效Zones的时候通过ZoneAvoidanceRule规则
        //该规则根据区域和可用性规则来获取服务
        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            logger.debug("Zone aware logic disabled or there is only one zone");
            return super.chooseServer(key);
        }
        Server server = null;
        try {
            LoadBalancerStats lbStats = getLoadBalancerStats();
            Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
            logger.debug("Zone snapshots: {}", zoneSnapshot);
            if (triggeringLoad == null) {
                triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
            }

            if (triggeringBlackoutPercentage == null) {
                triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
            }
            Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
            logger.debug("Available zones: {}", availableZones);
            if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                logger.debug("Zone chosen: {}", zone);
                if (zone != null) {
                    //2. 通过RoundRobinRule规则获取
                    BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                    server = zoneLoadBalancer.chooseServer(key);
                }
            }
        } catch (Exception e) {
            logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
        }
        if (server != null) {
            return server;
        } else {
            logger.debug("Zone avoidance logic is not invoked.");
            return super.chooseServer(key);
        }
    }

2.5 LoadBalancerRequest.apply回调方法

LoadBalancerRequest 对象是LoadBalanceIntercepter#intercept处理时候,通过requestFactory构建并返回的对象, apply函数处理的时候,将httpRequest对象封装成了ServiceRequestWapper对象,ServiceRequestWapper 对象重写了getURI方法

public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,
			final byte[] body, final ClientHttpRequestExecution execution) {
		return new LoadBalancerRequest<ClientHttpResponse>() {

			@Override
			public ClientHttpResponse apply(final ServiceInstance instance)
					throws Exception {
                //将HttpRequest和ServiceInstance封装成新对象
                //ServiceRequestWrapper重写getURI方法,通过LoadBalanceClient获取推荐的URI
				HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
                //略部分代码
				return execution.execute(serviceRequest, body);
			}

		};
	}
public class ServiceRequestWrapper extends HttpRequestWrapper {
    //略部分代码
	@Override
	public URI getURI() {
		URI uri = this.loadBalancer.reconstructURI(
				this.instance, getRequest().getURI());
		return uri;
	}
}

2.6 负载处理流程图

拦截器处理流程图如下

文章总结

       当一个被@LoadBalanced注解修饰的RestTemplate对象向外发起HTTP请求的时候,会被LoadBalanceerIntercepor类拦截。由于我们在使用RestTemplate时采用了服务名为host,所以直接从HttpRequest的URI对象中getHost拿到服务名称,然后通过RibbonLoadBalancerClient的 execute函数实现负载调用客户端

       负载均衡器RibbonLoadBalancerClient处理的时候, 先根据服务名和规则获取到满足条件的客户端服务信息ServiceInstance,再通过LoadBalancerRequest.apply(ServiceInstance instance)回调方法将HttpRequest请求封装为ServiceRequestWrapper对象, ServiceRequestWrapper重写getURI方法得到负载均衡器LoadBalancerClient推荐ServiceInstance指定的客户端地址

  

  

  

上一篇:Spring Cloud Eureka Client源代码学习笔记

猜你喜欢

转载自blog.csdn.net/Beijing_L/article/details/121040368
今日推荐