Ribbon 是 Netflix 公司开发的一个用于负载均衡的组件.
负载均衡就是利用特定的方式,将流量(简单理解为客户端请求)分摊到多个操作单元上的一种手段.负载均衡对于系统吞吐量与系统处理能力有着质的提升.比如 Nginx 就是负载均衡组件.负载均衡可以简单的理解为有以下一些
集中式负载均衡负载均衡在组件位于客户端与服务端之间,通过一些手段,把收到的客户端的网络请求转到各个服务端之间,比如 Nginx
进程内负载均衡是指从实例库选取一个实例进行流量导入,我个人理解就是从很多的服务提供实例当中选取一个进行调用
1:创建Eureka Server 注册中心 (无特殊要求,按照正常配置即可)
2:创建Eureka Client 服务提供者 (无特殊要求,按照正常配置即可,提供若干接口给消费者进行调用,实例需要注册到 Eureka Server 注册中心)
@RestController
public class DemoController {
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String add(Integer a, Integer b, HttpServletRequest request) {
return " From Port: " + request.getServerPort() + ", Result: " + (a + b);
}
}
3:创建 Eureka Client Ribbon 工程,作为服务消费者,对外提供服务 (实例需要注册到 Eureka Server 注册中心)
添加依赖
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
在程序主入口处实例化一个 RestTemplate, 并且添加注解 @loadBalanced 声明该 RestTemplate 用于负载均衡
@SpringBootApplication
@EnableDiscoveryClient
public class Chapter5RibbonLoadBalancerApplication {
public static void main(String[] args) {
SpringApplication.run(Chapter5RibbonLoadBalancerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
编写对外提供的接口并注入 RestTemplate
@RestController
public class DemoController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/add")
public String add(Integer a, Integer b) {
String result = restTemplate.getForObject("http://eureka-client-A/add?a=" + a + "&b=" + b, String.class);
System.out.println(result);
return result;
}
}
然后依次将 3 个工程打成 jar 包,并执行jar, 在执行 服务提供者的 jar 的时候, 应用不同的端口,多启动几个实例,然后通过服务消费者提供的接口进行调用. 如:http://localhost:7777/add?a=10&b=20 可以看到 效果如下
可以看出,Ribbon 默认使用轮巡的方式访问服务源,这是一种负载均衡策略.
Ribbon总共有7中负载均衡策略
使用 Ribbon 的时候想要全局更改负载均衡策略,只需要加一个配置类,返回一个 负载均衡策略 bean 便可以 如:修改上例为 随机策略
/**
* 修改Ribbon 负载均衡策略的配置类.
*/
@Configuration
public class RibbonRuleConfig {
//返回一个 随机策略.
@Bean
public RandomRule randomRule() {
return new RandomRule();
}
}
然后在启动上例项目的时候, 负载均衡策略就已经变更成了随机策略了. 只要加上这个, 后续的所有 Ribbon 请求都将使用指定的策略.
编写一个空注解
public @interface AvoidScan {
}
编写应用于特定服务实例的的策略配置类
@Configuration
@AvoidScan
public class RoundRobinRuleConfig {
@Resource
private IClientConfig config;
@Bean
public IRule roundRobinRule(IClientConfig clientConfig) {
return new RoundRobinRule();
}
}
在主启动类上加上注解
/**
* @RibbonClient(name = "eureka-client-A", configuration = RoundRobinRuleConfig.class) 表示:
* 对 eureka-client-A 服务使用的策略是经过 RoundRobinRuleConfig 类配置的.
* @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {AvoidScan.class})}) 表示:
* 让spring 不去扫描被 @AvoidScan 注解注释的类,因为不能应用于全局,所以只对 eureka-client-A 服务实例生效 .
*
* 也可以通过 @RibbonClients 注解来对多个服务员进行策略指定 如:
* @RibbonClients(value = {
* @RibbonClient(name = "client-a, configuration = TestA.class),
* @RibbonClient(name = "client-b, configuration = TestB.class)
* })
*/
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "eureka-client-A", configuration = RoundRobinRuleConfig.class)
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {AvoidScan.class})})
public class Chapter5RibbonLoadBalancerApplication {
public static void main(String[] args) {
SpringApplication.run(Chapter5RibbonLoadBalancerApplication.class, args);
}
//使用负载均衡,(使用默认的策略 轮巡策略)
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
个人感觉这种方式比较繁琐,要编写很多只针对一个服务实例的代码
语法是:
#针对 eureka-client-A 服务实例 使用 指定的随机策略./
eureka-client-A:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
然后启动项目 进行访问 http://localhost:7777/add?a=10&b=20 可以看到控制台输出
可以看出使用的是 随机策略. 个人感觉这种方式很方便,因为一般来说我们的配置文件都是远程放在 git 上面的, 如果要更改策略的话, 直接修改配置文件的内容就好了, 不需要像使用注解的方式那样还要修改源代码.然后重新编译等等一系列的事情. 还是文件配置比较方便
使用 HTTP 发起请求,请求超时好像是比较常见的问题,此时对调用进行时限控制和超时之后的重试还是比较重要的. Ribbon 的重试机制默认是开启的.有需要的话可以添加对超时时间与重试机制策略的配置. 如:
#针对 eureka-client-A 服务实例 使用 指定的随机策略./
eureka-client-A:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ConnectTimeout: 30000 #连接超时时间
ReadTimeout: 30000
MaxAutoRetries: 1 #对第一次请求的服务的重试次数 比如第一次请求 A 服务, 失败了, 然后会在重试 1 次
MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量 (不包括第一个服务)
OkToRetryOnAllOperations: true
Ribbon 在进行客户端负载均衡的时候并不是在启动的时候就加载上下文,而是在实际请求到达的时候才回去创建,因此这个特性往往会让第一次调用用时比较久,有可能会超时. 因此我们可以通过指定 Ribbon 具体的客户端来开启饥饿加载.意思就是,除了指定的 Ribbon 客户端是饥饿加载之外,其他的所有配置项的应用上下文都是在启动的时候就加载.
在配置文件当中添加:
#配置指定使用饥饿加载的上下文,所有不在配置项的上下文都会在启动的时候加载.
ribbon:
eager-load:
enabled: true
clients: eureka-client-A, eureka-client-B, eureka-client-C
在默认情况下 Ribbon 客户端会从 Eureka 注册中心读取服务注册信息列表,来达到动态负载均衡的功能.当然也可以脱离 Eureka 使用,只需要在配置文件当中配置禁止使用 Eureka 功能,然后手动指定源服务地址列表就可以.
ribbon:
eureka:
enable: false #禁用 Eureka 功能
client:
ribbon:
listOfServers: http://localhost:7070 #指定源服务地址列表
介绍几个 Ribbon 中的核心接口
IClientConfig:定义 Ribbon 中管理配置的接口, 默认实现 DefaultClientConfigImpl
IRule: 定义 Ribbon 中负载均衡策略的接口, 默认实现 ZoneAvoidanceRule
IPing: 定义定期 ping 服务检查可用性的接口, 默认实现 DummyPing
ServerList
ServerListFilter
ILoadBalancer: 定义负载均衡选择服务器的核心方法的接口, 默认实现 ZoneAwareLoadBalancer
ServerListUpdater: 为DynamicServerListLoadBalancer 定义动态更新服务器列表的接口. 默认实现 PollingServerListUpdater
如下所示
//使用负载均衡,(使用默认的策略 轮巡策略)
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
使用 @LoadBalanced 注解就可以使用负载均衡了,. 查看该注解可以看到,
注释中写的很清楚, , 然后接着看 LoadBalancerClient 类,这个来表示一个 客户端负载均衡器,该类里面有四个很重要的方法,具体可以查看方法上面的注释.
不过根据书上说的,我倒是没有追踪到自动配置类(也就是为该类的实现做初始化自动配置) LoadBalancerAutoConfiguration 类中去(能力有限,正在学习当中). 所以直接看 LoadBalancerAutoConfiguration 的代码吧.
类上的注释表名, 只有在类中包含 RestTemplate 类的实例,并且包含LOadBalancerClient 实例,才会初始化这个 LoadBalancerAutoConfiguration, 我个人理解的是,正因为这个条件, 所以我们在主配置程序当中要手动初始化一个 RestTemplate 实例, 扫描到 Spring 当中.
在这个类中还有一个方法 loadBalancerRequestFactory, 这个方法用于创建 LoadBalancerRequest 实例, 供 LoadBalancerInterceptor 使用.
而在 LosdBalancerInterceptorConfig 静态类中则是维护了 2 个实例, LoadBalancerInterceptor 与 RestTemplateCustomizer 实例.
RestTemplateCustomizer: 为每个 RestTemplate 绑定 LoadBalancerInterceptor 拦截器.可以看到在方法的最后面为 restTemplate 设置了一个拦截器进去.
LoadBalancerInterceptor: 拦截每一次 HTTP 请求, 将请求绑定到 Ribbon 负载均衡的声明周期.然后继续查看该实例所在的类的实现,
发现这个类是实现了 Spring 维护的请求拦截器 ClientHttpRequestInterceptor, 而实现该 ClientHttpRequestInterceptor 拦截器, 并重写其中的 interceptor 方法, 这个时候收到的所有请求就会进入 interceptor 方法体. 而在 interceptor 方法体当中,确实用传入的LoadBalancerClient 的实例 loadBalancer 执行 execute 方法的. 而 LoadBalancerClient 只有一个实现类 RibbonLoadBalancerClient .然后追进去, 查看 execute 方法.
首先获取一个 ILoadBalancer 实例.,然后获取 server.前面说过 ILoadBalancer 接口是定义负载均衡选择服务器的核心方法接口.所以 getServer() 方法应该就是负载均衡选择具体服务实例的方法了.接着往里面看
方法中调用了 chooseServer 方法, 该方法的作用是 从负载均衡器当中选择一个服务器, 使用了 key 值来获取具体的服务实例.如果不指定 key, 则返回的是 null. 每个负载均衡器选择接口(ILoadBalancer )的实现类的方法实现都不一样, 具体可以查看源码.这样就将拦截到的 HTTP 请求 与负载均衡策略关联起来了.
现在回想一下, 好像这里的这个负载均衡就是利用 RestTemplate 发起请求, 然后在用一个静态内部类去实现 Spring 维护的 ClientHttpRequestInterceptor 请求拦截器, 利用该拦截器拦截每一次的请求, 然后强行将 请求 与负载均衡关联起来. 然后发起请求调用,拿到结果. 比起直接发起请求来说, 这里多了一个负载均衡,相当于是 多了一个选择服务器的操作.
IRule 接口是定义 Ribbon 负载均衡策略的父接口,所有策略都是基于它实现的. 主要逻辑是 choose() 方法. Ribbon 是通过 ILoadBalancer 来关联 IRule 的. ILoadBanancer 的chooseServer 会转换成 IRule 的 choose() 方法.