Spring Cloud Ribbon源码中如何实现服务调用负载均衡?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1838个文字,预计阅读时间需要8分钟。
负载均衡器是一种网络设备,用于将网络流量分配到多个服务器上。假设有一个分布式系统,该系统由在不同计算机上运行的多个服务组成。但是,当用户数量很大时,通常会为服务创建集群。集群中每个服务实例都在单独的机器上运行。
什么是负载均衡器?
假设有一个分布式系统,该系统由在不同计算机上运行的许多服务组成。但是,当用户数量很大时,通常会为服务创建搭建集群。集群中每个服务实例都在单独一台计算机上运行。此时,出现 “Load Balancer(负载均衡器)”。它有助于在服务器之间平均分配传入流量。
服务器端负载均衡器
传统Load Balancers(例如Nginx、F5)是放置在服务器端的组件。当请求来自 客户端 时,它们将转到负载均衡器,负载均衡器将为请求指定服务器。负载均衡器使用的最简单的算法是随机指定。在这种情况下,大多数负载平衡器是用于控制负载平衡的硬件集成软件。
特点:
- 对客户端不透明,客户端不知道服务器端的服务列表,甚至不知道自己发送请求的目标地址存在负载均衡器。
- 服务器端维护负载均衡服务器,控制负载均衡策略和算法。
客户端负载均衡器
当负载均衡器位于客户端时,客户端得到可用的服务器列表然后按照特定的负载均衡策略,分发请求到不同的服务器 。
特点:
- 对客户端透明,客户端需要知道服务器端的服务列表,需要自行决定请求要发送的目标地址。
- 客户端维护负载均衡服务器,控制负载均衡策略和算法。
- 目前单独提供的客户端实现比较少(Ribbon是其中之一),大部分都是在框架内部自行实现。
Ribbon
简介
Ribbon是Netflix公司开源的一个客户端负载均衡的项目,可以自动与 Eureka 进行交互。它提供下列特性:
- 负载均衡
- 容错
- 以异步和反应式模型执行多协议 (HTTP、TCP、UDP)
- 缓存和批量
Ribbon中的关键组件
- ServerList:可以响应客户端的特定服务的服务器列表。
- ServerListFilter:可以动态获得的具有所需特征的候选服务器列表的过滤器。
- ServerListUpdater:用于执行动态服务器列表更新。
- Rule:负载均衡策略,用于确定从服务器列表返回哪个服务器。
- Ping:客户端用于快速检查服务器当时是否处于活动状态。
- LoadBalancer:负载均衡器,负责负载均衡调度的管理。
源码分析
LoadBalancerClient
实际应用中,通常将 RestTemplate 和 Ribbon 结合使用,例如:
@Configuration public class RibbonConfig { @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } }消费者调用服务接口:
@Service public class RibbonService { @Autowired private RestTemplate restTemplate; public String hi(String name) { return restTemplate.getForObject("service-hi/hi?name="+name,String.class); } }@LoadBalanced,通过源码可以发现这是一个标记注解:
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface LoadBalanced { }通过注释可以知道@LoadBalanced注解是用来给RestTemplate做标记,方便我们对RestTemplate添加一个LoadBalancerClient,以实现客户端负载均衡。
自动装载核心配置类
SpringCloud对EurekaServer的封装使得发布一个EurekaServer无比简单,根据自动装载原则可以在spring-cloud-netflix-ribbon-2.2.5.RELEASE.jar下的META-INF目录下找到 spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.netflix.ribbon.RibbonAutoConfigurationRibbonAutoConfiguration
@Configuration @Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class) @RibbonClients @AutoConfigureAfter( name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration") @AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class }) @EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class }) public class RibbonAutoConfiguration { }先决条件
- @ConditionalOnClass:当前环境必须存在这几个类:IClient、RestTemplate、AsyncRestTemplate、Ribbon
- @RibbonClients:这个注解上面已经讲过了。
- @AutoConfigureAfter:负载均衡肯定是要基于注册中心来做的,所以自动装配是在Eureka初始化完毕之后初始化的。
- @AutoConfigureBefore:这里的两个类先不说,保持神秘。
- @EnableConfigurationProperties,两个配置类,其中:
- RibbonEagerLoadProperties类中是关于Ribbon的饥饿加载模式的属性
- ServerIntrospectorProperties类中是关于安全端口的属性
@RibbonClients
@RibbonClients注解使用@Import注解引入了配置类RibbonClientConfigurationRegistrar
@Configuration(proxyBeanMethods = false) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) @Documented @Import(RibbonClientConfigurationRegistrar.class) public @interface RibbonClients { RibbonClient[] value() default {}; Class<?>[] defaultConfiguration() default {}; }RibbonClientConfigurationRegistrar
RibbonClientConfigurationRegistrar是一个 ImportBeanDefinitionRegistrar,为配置了注册了对应 RibbonClientSpecification 的 BeanDefinition。
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //RibbonClients 注解解析, 遍历注册所有 RibbonClient 配置类的 Map<String, Object> attrs = metadata .getAnnotationAttributes(RibbonClients.class.getName(), true); if (attrs != null && attrs.containsKey("value")) { AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value"); for (AnnotationAttributes client : clients) { registerClientConfiguration(registry, getClientName(client), client.get("configuration")); } } // 全局默认配置 if (attrs != null && attrs.containsKey("defaultConfiguration")) { String name; if (metadata.hasEnclosingClass()) { name = "default." + metadata.getEnclosingClassName(); } else { name = "default." + metadata.getClassName(); } registerClientConfiguration(registry, name, attrs.get("defaultConfiguration")); } // 单个 RibbonClient 的解析,注册对应配置类的 BD Map<String, Object> client = metadata .getAnnotationAttributes(RibbonClient.class.getName(), true); String name = getClientName(client); if (name != null) { registerClientConfiguration(registry, name, client.get("configuration")); } } // 注册类型为 RibbonClientSpecification private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(RibbonClientSpecification.class); builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); registry.registerBeanDefinition(name + ".RibbonClientSpecification", builder.getBeanDefinition()); } }-
首先会判断是否存在注解@RibbonClients,注意,这里可是多了一个s的 然后判断@RibbonClients注解上是否存在属性value和defaultConfiguration,如果存在的话分别注册他们。
-
接着最后才是处理@RibbonClient注解 这里我们就可以猜测RibbonClientConfigurationRegistrar这个类应该是可以同时处理这两个注解的,观察一下@RibbonClients注解的源码发现它确实是引入的也是这个类 这两个注解的区别应该也可以猜测出来,单数和双数
-
观察最后注册的代码,可以看到最后注册bean的类型都是RibbonClientSpecification。
该类由 自动装配 加载,对应于 OpenFeign 的 FeignContext,所有 RibbonContext 的 上下文 由 SpringClientFactory 创建和管理
// 扫描的所有 RibbonClientSpecification @Autowired(required = false) private List<RibbonClientSpecification> configurations = new ArrayList<>(); /** * 将所有的 RibbonClientSpecification 交给 SpringClientFactory * 由 SpringClientFactory 创建和管理对应的 RibbonClient 上下文 */ @Bean @ConditionalOnMissingBean public SpringClientFactory springClientFactory() { SpringClientFactory factory = new SpringClientFactory(); factory.setConfigurations(this.configurations); return factory; }这跟 FeignContext 的原理一模一样,因此不再过多解读,RibbonClient 对应的 上下文 创建与管理由 SpringClientFactory 实现。
SpringClientFactory,每一个微服务在都会调用多个微服务,而调用各个微服务的配置可能是不一样的,所以就需要这个创建客户端负载均衡器的工厂类,它可以为每一个ribbon客户端生成不同的Spring上下文,而观察这个类的configurations属性也验证了这一点。
同时,RibbonAutoConfiguration 还创建了 LoadBalancerClient 的实例,为 RibbonLoadBalancerClient
@Bean @ConditionalOnMissingBean(LoadBalancerClient.class) public LoadBalancerClient loadBalancerClient() { return new RibbonLoadBalancerClient(springClientFactory()); }RestTemplateCustomizer RestTemplate定制器
//Ribbon的www.cnblogs.com/huanchupkblog/p/10923229.htmlblog.csdn.net/u014494148/article/details/108915053
my.oschina.net/mengyuankan/blog/3104184
本文共计1838个文字,预计阅读时间需要8分钟。
负载均衡器是一种网络设备,用于将网络流量分配到多个服务器上。假设有一个分布式系统,该系统由在不同计算机上运行的多个服务组成。但是,当用户数量很大时,通常会为服务创建集群。集群中每个服务实例都在单独的机器上运行。
什么是负载均衡器?
假设有一个分布式系统,该系统由在不同计算机上运行的许多服务组成。但是,当用户数量很大时,通常会为服务创建搭建集群。集群中每个服务实例都在单独一台计算机上运行。此时,出现 “Load Balancer(负载均衡器)”。它有助于在服务器之间平均分配传入流量。
服务器端负载均衡器
传统Load Balancers(例如Nginx、F5)是放置在服务器端的组件。当请求来自 客户端 时,它们将转到负载均衡器,负载均衡器将为请求指定服务器。负载均衡器使用的最简单的算法是随机指定。在这种情况下,大多数负载平衡器是用于控制负载平衡的硬件集成软件。
特点:
- 对客户端不透明,客户端不知道服务器端的服务列表,甚至不知道自己发送请求的目标地址存在负载均衡器。
- 服务器端维护负载均衡服务器,控制负载均衡策略和算法。
客户端负载均衡器
当负载均衡器位于客户端时,客户端得到可用的服务器列表然后按照特定的负载均衡策略,分发请求到不同的服务器 。
特点:
- 对客户端透明,客户端需要知道服务器端的服务列表,需要自行决定请求要发送的目标地址。
- 客户端维护负载均衡服务器,控制负载均衡策略和算法。
- 目前单独提供的客户端实现比较少(Ribbon是其中之一),大部分都是在框架内部自行实现。
Ribbon
简介
Ribbon是Netflix公司开源的一个客户端负载均衡的项目,可以自动与 Eureka 进行交互。它提供下列特性:
- 负载均衡
- 容错
- 以异步和反应式模型执行多协议 (HTTP、TCP、UDP)
- 缓存和批量
Ribbon中的关键组件
- ServerList:可以响应客户端的特定服务的服务器列表。
- ServerListFilter:可以动态获得的具有所需特征的候选服务器列表的过滤器。
- ServerListUpdater:用于执行动态服务器列表更新。
- Rule:负载均衡策略,用于确定从服务器列表返回哪个服务器。
- Ping:客户端用于快速检查服务器当时是否处于活动状态。
- LoadBalancer:负载均衡器,负责负载均衡调度的管理。
源码分析
LoadBalancerClient
实际应用中,通常将 RestTemplate 和 Ribbon 结合使用,例如:
@Configuration public class RibbonConfig { @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } }消费者调用服务接口:
@Service public class RibbonService { @Autowired private RestTemplate restTemplate; public String hi(String name) { return restTemplate.getForObject("service-hi/hi?name="+name,String.class); } }@LoadBalanced,通过源码可以发现这是一个标记注解:
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface LoadBalanced { }通过注释可以知道@LoadBalanced注解是用来给RestTemplate做标记,方便我们对RestTemplate添加一个LoadBalancerClient,以实现客户端负载均衡。
自动装载核心配置类
SpringCloud对EurekaServer的封装使得发布一个EurekaServer无比简单,根据自动装载原则可以在spring-cloud-netflix-ribbon-2.2.5.RELEASE.jar下的META-INF目录下找到 spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.netflix.ribbon.RibbonAutoConfigurationRibbonAutoConfiguration
@Configuration @Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class) @RibbonClients @AutoConfigureAfter( name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration") @AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class }) @EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class }) public class RibbonAutoConfiguration { }先决条件
- @ConditionalOnClass:当前环境必须存在这几个类:IClient、RestTemplate、AsyncRestTemplate、Ribbon
- @RibbonClients:这个注解上面已经讲过了。
- @AutoConfigureAfter:负载均衡肯定是要基于注册中心来做的,所以自动装配是在Eureka初始化完毕之后初始化的。
- @AutoConfigureBefore:这里的两个类先不说,保持神秘。
- @EnableConfigurationProperties,两个配置类,其中:
- RibbonEagerLoadProperties类中是关于Ribbon的饥饿加载模式的属性
- ServerIntrospectorProperties类中是关于安全端口的属性
@RibbonClients
@RibbonClients注解使用@Import注解引入了配置类RibbonClientConfigurationRegistrar
@Configuration(proxyBeanMethods = false) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) @Documented @Import(RibbonClientConfigurationRegistrar.class) public @interface RibbonClients { RibbonClient[] value() default {}; Class<?>[] defaultConfiguration() default {}; }RibbonClientConfigurationRegistrar
RibbonClientConfigurationRegistrar是一个 ImportBeanDefinitionRegistrar,为配置了注册了对应 RibbonClientSpecification 的 BeanDefinition。
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //RibbonClients 注解解析, 遍历注册所有 RibbonClient 配置类的 Map<String, Object> attrs = metadata .getAnnotationAttributes(RibbonClients.class.getName(), true); if (attrs != null && attrs.containsKey("value")) { AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value"); for (AnnotationAttributes client : clients) { registerClientConfiguration(registry, getClientName(client), client.get("configuration")); } } // 全局默认配置 if (attrs != null && attrs.containsKey("defaultConfiguration")) { String name; if (metadata.hasEnclosingClass()) { name = "default." + metadata.getEnclosingClassName(); } else { name = "default." + metadata.getClassName(); } registerClientConfiguration(registry, name, attrs.get("defaultConfiguration")); } // 单个 RibbonClient 的解析,注册对应配置类的 BD Map<String, Object> client = metadata .getAnnotationAttributes(RibbonClient.class.getName(), true); String name = getClientName(client); if (name != null) { registerClientConfiguration(registry, name, client.get("configuration")); } } // 注册类型为 RibbonClientSpecification private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(RibbonClientSpecification.class); builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); registry.registerBeanDefinition(name + ".RibbonClientSpecification", builder.getBeanDefinition()); } }-
首先会判断是否存在注解@RibbonClients,注意,这里可是多了一个s的 然后判断@RibbonClients注解上是否存在属性value和defaultConfiguration,如果存在的话分别注册他们。
-
接着最后才是处理@RibbonClient注解 这里我们就可以猜测RibbonClientConfigurationRegistrar这个类应该是可以同时处理这两个注解的,观察一下@RibbonClients注解的源码发现它确实是引入的也是这个类 这两个注解的区别应该也可以猜测出来,单数和双数
-
观察最后注册的代码,可以看到最后注册bean的类型都是RibbonClientSpecification。
该类由 自动装配 加载,对应于 OpenFeign 的 FeignContext,所有 RibbonContext 的 上下文 由 SpringClientFactory 创建和管理
// 扫描的所有 RibbonClientSpecification @Autowired(required = false) private List<RibbonClientSpecification> configurations = new ArrayList<>(); /** * 将所有的 RibbonClientSpecification 交给 SpringClientFactory * 由 SpringClientFactory 创建和管理对应的 RibbonClient 上下文 */ @Bean @ConditionalOnMissingBean public SpringClientFactory springClientFactory() { SpringClientFactory factory = new SpringClientFactory(); factory.setConfigurations(this.configurations); return factory; }这跟 FeignContext 的原理一模一样,因此不再过多解读,RibbonClient 对应的 上下文 创建与管理由 SpringClientFactory 实现。
SpringClientFactory,每一个微服务在都会调用多个微服务,而调用各个微服务的配置可能是不一样的,所以就需要这个创建客户端负载均衡器的工厂类,它可以为每一个ribbon客户端生成不同的Spring上下文,而观察这个类的configurations属性也验证了这一点。
同时,RibbonAutoConfiguration 还创建了 LoadBalancerClient 的实例,为 RibbonLoadBalancerClient
@Bean @ConditionalOnMissingBean(LoadBalancerClient.class) public LoadBalancerClient loadBalancerClient() { return new RibbonLoadBalancerClient(springClientFactory()); }RestTemplateCustomizer RestTemplate定制器
//Ribbon的www.cnblogs.com/huanchupkblog/p/10923229.htmlblog.csdn.net/u014494148/article/details/108915053
my.oschina.net/mengyuankan/blog/3104184

