Ribbon底层RibbonLoadBalancerClient源码解析,如何实现负载均衡执行流程分析(二)

LoadBalancerAutoConfiguration自动配置类

ribbon自动配置类
自动配置类基本是通过springboot启动时扫描的META-INF/spring.factories 和@ConditionalOnXXX 就能实现自动装配
在这里插入图片描述

当前自动配置类中有一个负载均衡窃听器配置静态类LoadBalancerInterceptorConfig


	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {
    
    

		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
    
    
				/*
				LoadBalancerInterceptor 拦截器是Ribbon核心组件极其重要
				restTemplate发出的请求进行拦截处理
				*/
				//将拦截器加载到ioc 
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
    
    
			return restTemplate -> {
    
    
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}

LoadBalancerInterceptor 负载均衡拦截器

/*
	实现了ClientHttpRequestInterceptor  重新了非常重要的拦截方法intercept
*/
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    
    
	//是完成负载均衡工作 执行指定请求 创建新的url
	private LoadBalancerClient loadBalancer;

	private LoadBalancerRequestFactory requestFactory;

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
			LoadBalancerRequestFactory requestFactory) {
    
    
		this.loadBalancer = loadBalancer;
		this.requestFactory = requestFactory;
	}

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
    
    
		// for backwards compatibility
		this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
	}
	
	/*
		拦截给定的请求,并返回响应。
		给定的ClientHttpRequestExecution允许拦截器将请求和响应传递给链中的下一个实体。
		此方法的典型实现将遵循以下模式:
		检查请求和正文
		可选地包装请求以筛选HTTP属性。
		可以选择修改请求的正文。
		restTemplate的方法都会经过这个方法
*/
	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
    
    
		//获取当前请求的URL
		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,
				this.requestFactory.createRequest(request, body, execution));
	}

}

LoadBalancerClient客户端负载均衡器

在这里插入图片描述

表示客户端负载均衡器

public interface LoadBalancerClient extends ServiceInstanceChooser {
    
    

	/**
	 * 使用来自指定服务的LoadBalancer的ServiceInstance执行请求。
	 * @param serviceId 用于查找LoadBalancer的服务ID。
	 * @param request 负载均衡请求域
	 * @param <T> 响应的类型
	 * @throws IOException in case of IO issues.
	 * @return 对所选对象执行LoadBalancerRequest回调的结果 ServiceInstance.
	 */
	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

	/**
	 * 使用来自指定服务的LoadBalancer的ServiceInstance执行请求。
	 * @param serviceId 用于查找LoadBalancer的服务ID。
	 * @param serviceInstance 要对其执行请求的服务。 指定好具体某个客户端的实例 
	 * @param request 负载均衡请求域
	 * @param <T> 响应的类型
	 * @throws IOException in case of IO issues.
	 * @return 对所选对象执行LoadBalancerRequest回调的结果 ServiceInstance.
	 */
	<T> T execute(String serviceId, ServiceInstance serviceInstance,
			LoadBalancerRequest<T> request) throws IOException;

	/**
	 * 创建一个带有真实主机和端口的正确URI,供系统使用。有些系统使用逻辑服务名作为主机的URI,
	 * 例如http://myservice/path/to/service。这将用主机:端口自服务实例。
	 * @param instance 需要被包装操作的服务实例
	 * @param 以主机作为逻辑服务名创建一个URI。
	 * @return 重建的URI。被分配服务的实例构建的URI
	 */
	URI reconstructURI(ServiceInstance instance, URI original);

}

ServiceInstanceChooser

由使用负载均衡器选择要向其发送请求的服务器的类实现。

/**
	 * 从LoadBalancer中为指定的服务选择ServiceInstance。并返回该实例
	 * (如三台服务实例,该方法通过serverid 选择一台实例并返回)
	 * ServiceInstance :包含了改服务实例的全部信息 如主机名端口号等等
	 */
	ServiceInstance choose(String serviceId);

上述负载均衡器的的所有接口(执行请求,查找实例,返回实例新建的url)都是通过RibbonLoadBalancerClient类来实现

RibbonLoadBalancerClient

在这里插入图片描述

	@Override
	public URI reconstructURI(ServiceInstance instance, URI original) {
    
    
		Assert.notNull(instance, "instance can not be null");
		//获取服务实例的id
		String serviceId = instance.getServiceId();
		//获取服务的上下文
		RibbonLoadBalancerContext context = this.clientFactory
				.getLoadBalancerContext(serviceId);

		URI uri;
		Server server;
		//如果当前服务实例已经是ribbon服务实例
		if (instance instanceof RibbonServer) {
    
    
			//获取ribbon服务实例
			RibbonServer ribbonServer = (RibbonServer) instance;
			//得到服务
			server = ribbonServer.getServer();
			//通过服务实例和old url(原始url当前请求的url)重新编译成new的url
			uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
		}
		else {
    
    
			//如果第一次请求 构建一个新的服务 主机名 端口号 名称
			server = new Server(instance.getScheme(), instance.getHost(),
					instance.getPort());
			//获取serviceId对应配置信息
			IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
			ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
			//构建新的url
			uri = updateToSecureConnectionIfNeeded(original, clientConfig,
					serverIntrospector, server);
		}
		//返回指定实例的新的url
		return context.reconstructURIWithServer(server, uri);
	}
	/**
	执行请求
	*/
	@Override
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
			throws IOException {
    
    
		return execute(serviceId, request, null);
	}

	/**
	 * New: Execute a request by selecting server using a 'key'. The hint will have to be
	 * the last parameter to not mess with the `execute(serviceId, ServiceInstance,
	 * request)` method. This somewhat breaks the fluent coding style when using a lambda
	 * to define the LoadBalancerRequest.
	 * @param <T> returned request execution result type
	 * @param serviceId id of the service to execute the request to
	 * @param request to be executed
	 * @param hint used to choose appropriate {@link Server} instance
	 * @return request execution result
	 * @throws IOException executing the request may result in an {@link IOException}
	 */
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
    
    
		//通过id得到ILoadBalancer  该方法定义了 新增 删除 查找指定服务等方法
		//具体看具体实现类
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		//通过serviceId解析出来指定的server (server 形式为 ip:端口号)
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
    
    
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		//通过得到的服务信息 创建了一个RibbonServer 特定于rebbon本身的server实现
		//该server中有对应的主机名 解析的server 源数据 等具体信息
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
		//重载 得到特定的ribbonserver 就开始真正执行操作了
		return execute(serviceId, ribbonServer, request);
	}

	@Override
	public <T> T execute(String serviceId, ServiceInstance serviceInstance,
			LoadBalancerRequest<T> request) throws IOException {
    
    
		Server server = null;
		if (serviceInstance instanceof RibbonServer) {
    
    
			//如果当前的实例属于RibbonServer 得到当前的server
			server = ((RibbonServer) serviceInstance).getServer();
		}
		if (server == null) {
    
    
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		//获取当前服务工厂的负载均衡上下文
		RibbonLoadBalancerContext context = this.clientFactory
				.getLoadBalancerContext(serviceId);
		RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

		try {
    
    
			//开始发送请求
			T returnVal = request.apply(serviceInstance);
			statsRecorder.recordStats(returnVal);
			//返回响应结果
			return returnVal;
		}
		// catch IOException and rethrow so RestTemplate behaves correctly
		catch (IOException ex) {
    
    
			statsRecorder.recordStats(ex);
			throw ex;
		}
		catch (Exception ex) {
    
    
			statsRecorder.recordStats(ex);
			ReflectionUtils.rethrowRuntimeException(ex);
		}
		return null;
	}

特定于ribbon的实例
在这里插入图片描述

总结

当我们在ioc中注入restTemplate时加了@LoadBalanced注解就是启用了负载均衡LoadBalancerAutoConfiguration就开始拦截rustTemplate调用的服务。此时我们在使用rustTemplate调用服务时就会被intercept()方法拦截此时LoadBalancerClient.execute方法会去解析得到RibbonServer调用getServer(主机名:端口号 )得到 特定的server 通过request.apply(serviceInstance);按照设置的规则执行请求得到 响应(信息)并返回

猜你喜欢

转载自blog.csdn.net/qq_42261668/article/details/108034859