SpringCloud之负载均衡 --(Ribbon)源码分析

20 篇文章 0 订阅
Ribbon介绍

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。

源码分析(RestTemplate)
  1. 首先我的RestTemplate是一个http调用的模版(spring-web中)

    1.RestTemplate类
    public class RestTemplate extends InterceptingHttpAccessor implements RestOperations
     2. 父类支持的拦截器
     List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
     
     3. 创建Request对象(选择工厂方法)
     public ClientHttpRequestFactory getRequestFactory() {
     	List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
     	if (!CollectionUtils.isEmpty(interceptors)) {
     		ClientHttpRequestFactory factory = this.interceptingRequestFactory;
     		if (factory == null) {
     			factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
     			this.interceptingRequestFactory = factory;
     		}
     		return factory;
     	}
     	else {
     		return super.getRequestFactory();
     	}
     }
     4.带有拦截器的会走的execute方法
     @Override
     	public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
     		if (this.iterator.hasNext()) {
     			ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
     			return nextInterceptor.intercept(request, body, this);
     		}
     		else {
     			HttpMethod method = request.getMethod();
     			Assert.state(method != null, "No standard HTTP method");
     			ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
     			request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
     			if (body.length > 0) {
     				if (delegate instanceof StreamingHttpOutputMessage) {
     					StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
     					streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
     				}
     				else {
     					StreamUtils.copy(body, delegate.getBody());
     				}
     			}
     			return delegate.execute();
     		}
     	}
     5.拦截器走的方法
     public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
     									final ClientHttpRequestExecution execution) throws IOException {
     	final URI originalUri = request.getURI();
     	final String serviceName = originalUri.getHost();
     	Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
     	final LoadBalancedRetryPolicy retryPolicy = lbRetryPolicyFactory.create(serviceName,
     			loadBalancer);
     	RetryTemplate template = this.retryTemplate == null ? new RetryTemplate() : this.retryTemplate;
     	template.setThrowLastExceptionOnExhausted(true);
     	template.setRetryPolicy(
     			!lbProperties.isEnabled() || retryPolicy == null ? new NeverRetryPolicy()
     					: new InterceptorRetryPolicy(request, retryPolicy, loadBalancer,
     					serviceName));
     	return template
     			.execute(new RetryCallback<ClientHttpResponse, IOException>() {
     				@Override
     				public ClientHttpResponse doWithRetry(RetryContext context)
     						throws IOException {
     					ServiceInstance serviceInstance = null;
     					if (context instanceof LoadBalancedRetryContext) {
     						LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
     						serviceInstance = lbContext.getServiceInstance();
     					}
     					if (serviceInstance == null) {
     						serviceInstance = loadBalancer.choose(serviceName);
     					}
     					ClientHttpResponse response = RetryLoadBalancerInterceptor.this.loadBalancer.execute(
     							serviceName, serviceInstance,
     							requestFactory.createRequest(request, body, execution));
     					int statusCode = response.getRawStatusCode();
     					if(retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
     						response.close();
     						throw new RetryableStatusCodeException(serviceName, statusCode);
     					}
     					return response;
     				}
     			});
     }
    

    加上@LoadBalanced (spring-cloud-common)注解,意味着是要对我们的http请求做一个balance
    RestTemplate 父类有一个拦截器的集合,加上这个注解必然注入了一个拦截器,拦截器怎么注入的注入了什么拦截器
    流程图
    在这里插入图片描述
    放进去了什么类型的拦截器,怎么放进去了

  2. 放入拦截器的注解 LoadBalancerAutoConfiguration
    怎么知道是这个注解的? 我们使用RestTemplate 上面加了一个注解 @LoadBalanced 这个在我们的spring-cloud-commons包里面 我就看这个包的一个自动注入的文件(spring.factories)
    里面有一个LoadBalancerAutoConfiguration 我们进去看源码:

LoadBalancerAutoConfiguration 类上的注解
@ConditionalOnClass(RestTemplate.class) 
// 依赖RestTemplate 所以我们的ribbon是依赖我们的RestTemplate 也是为这个服务的
@ConditionalOnBean(LoadBalancerClient.class) 
//同时也要依赖LoadBalancerClient 这个类 ,这个类实在common包里面的 他的注释表示支持负载均衡的client端

//公共的区域有一个LoadBalancerRequestFactory bean (和上面的ClientHttpRequestFactory 没关系的)用在哪里 从名字来就是负载均衡时候用的,拦截器里面用的
public LoadBalancerRequestFactory loadBalancerRequestFactory

LoadBalancerInterceptorConfig 类 上的注解 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
当没有下面那个RetryTemplate的时候那就使用这个 可以说是一个默认的
这个生成的拦截器就是 RetryLoadBalancerInterceptor 同时也是放入上面的LoadBalancerRequestFactory

RetryAutoConfiguration 类上的注解@ConditionalOnClass(RetryTemplate.class)
RetryTemplate 这个是例外一个包里面的spring的 如果你引入了这个包 那就是使用这个类初始化我们的拦截器
这个生成的拦截器就是 RetryLoadBalancerInterceptor 同时也是放入LoadBalancedRetryFactory(这个和上面是不一样的)

上面解释是什么类型的,但是是怎么注入到RestTemplate里面去的

@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
	}

	@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
		}
		@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();
		可能觉得奇怪 ,首先spring 会把 所有的RestTemplate实例 放到这个集合里面,上面的方法就是遍历这个实例然后放进去,是通过RestTemplateCustomizer 辅助的

在这里插入图片描述
RestTemplateCustomizer 类的作用是 : 可以自己定义这个类的实现,然后判断我的拦截器要不要放进去

到上面为止都没用到 ribbon里面的类

  1. 负载均衡的调用
    在初始化拦截器会注入一个LoadBalancerClient loadBalancerClient 这个实现类是哪里的bean 我们可以看到就RibbonLoadBalancerClient 这个类是ribbon的,这里面就用到了ribbon的初始化了
RibbonAutoConfiguration 类
上面的注解
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
//EurekaClientAutoConfiguration 在这个之后呢 肯定是依赖这里面的东西 eureka就是一个注册中心也是 ribbon的数据来源

@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
//要在 LoadBalancerAutoConfiguration 之前肯定要先生成一些bean给他用
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})


	@Bean
	@ConditionalOnMissingBean(LoadBalancerClient.class)
	public LoadBalancerClient loadBalancerClient() {
		return new RibbonLoadBalancerClient(springClientFactory());
	}

首先我们分析一个不带重试策略的(带了重试策略的比较复杂)

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
   	ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
   	Server server = getServer(loadBalancer, hint);
   	if (server == null) {
   		throw new IllegalStateException("No instances available for " + serviceId);
   	}
   	RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
   			serviceId), serverIntrospector(serviceId).getMetadata(server));

   	return execute(serviceId, ribbonServer, request);
   }

ILoadBalancer 类(服务的选择和操作)的在哪边初始化的
重点是在 getLoadBalancer(serviceId); 里面 进入里面的内

protected AnnotationConfigApplicationContext getContext(String name) {
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					this.contexts.put(name, createContext(name)); // 注意 createContext(name)
				}
			}
		}
		return this.contexts.get(name);
	}

protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		// 创建一个注解的容器
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
				context.register(configuration);
			}
		}
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);//defaultConfigType 的 是RibbonClientConfiguration 类型的在SpringClientFactory传进来的
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
				this.propertySourceName,
				Collections.<String, Object> singletonMap(this.propertyName, name)));
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh(); //加载容器
		return context;
	}

**springcloud 本身不会加载(RibbonClientConfiguration )只会默认加载spring.factories(ribbon里面只有一个RibbonAutoConfiguration) 所以RibbonClientConfiguration 不会加载 在首次调用的时候会自定义一个注解容器加载 会有点慢 当然会缓存容器的 (这个容器会对每个serviceId) 都会创建一个 **

RibbonClientConfiguration 类 加载了ILoadBalancer 对应的bean 默认是ZoneAwareLoadBalancer
Server server = getServer(loadBalancer, hint); 获取真正的服务信息 这里面会调用rule默认是RoundRobinRule 去获取server 所以需要重写的话就去修改这个
在这里插入图片描述

自定义配置(配置参考类CommonClientConfigKey)

支持的属性如下所示,应以.ribbon.下列属性。(属性文件中定义的优先于默认的代码配置的
NFLoadBalancerClassName:应实施 ILoadBalancer
NFLoadBalancerRuleClassName:应实施 IRule
NFLoadBalancerPingClassName:应实施 IPing
NIWSServerListClassName:应实施 ServerList
NIWSServerListFilterClassName:应实施 ServerListFilter
对于每个服务类型都会设置一种配置

怎么实现一个简单服务的高可用的方式

高可用的实现点

  1. 把你们需要高可用的节点地址注册到注册中心(eureka zk redis 数据库)
  2. 实现高可用的节点断开的一个失效方式(redis采用的key的失效时间 数据库可以采用时间戳间隔设置)
  3. 实现客户端的负载均衡算法(ribbon 来做)
扩展

ribbon 怎么做一个平滑升级 版本管理

  1. 怎么做到平滑升级(服务的平滑替换)
    我们需要对服务进行多状态管理 (停用,维护 ,上线等)
    对于我们要平滑升级的服务我们管理端需要提供一个维护窗口(需要等流量全部没有才能下线)
    重写一个Irule的实现就可以了
    当然了我们客户端可以采用重试机制
  2. 版本管理
    目前支持的默认的注册中心是没有服务管理的,而spring cloud是面向资源的,那就可以通过资源uri
    去进行版本的管理标识
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值