Ribbon 是一个客户端负载均衡器,它赋予了应用一些支配HTTP 与 TCP 行为的能力,这里的负载均衡是客户端的负载均衡,也有人称为后端负载均衡是进程内负载均衡的一种。
服务端负载均衡
服务端负载均衡的代表性例子就是nginx,LVS。那么客户端的负载均衡就是我们要说的Ribbon。Ribbon主要提供客户端负载平衡算法,除此之外,Ribbon还提供:
Ribbon API
Rule
:定义负载均衡策略;Ping
: 定义如何 ping 目标服务实例来判断是否存活, ribbon 使用单独的线程每隔一段时间(默认10s)对本地缓存的 ServerList
做一次检查;ServerList
:定义如何获取服务实例列表. 两种实现基于配置的 ConfigurationBasedServerList
和基于Eureka 服务发现的 DiscoveryEnabledNIWSServerList
;ServerListFilter
: 用来使用期望的特征过滤静态配置,动态获得的候选服务实例列表. 若未提供, 默认使用ZoneAffinityServerListFilter;ILoadBalancer
: 定义了软负载均衡器的操作的接口。一个典型的负载均衡器至少需要一组用来做负载均衡的服务实例;ServerListUpdater
: DynamicServerListLoadBalancer用来更新实例列表的策略(推EurekaNotificationServerListUpdater/拉PollingServerListUpdater, 默认是拉)由于客户端负载均衡需要从注册中心获取服务列表,所以需要集成注册中心。
创建父级工程 cloud-ribbon-practice
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.3.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<spring.cloud-version>Hoxton.SR3spring.cloud-version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring.cloud-version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
创建注册中心 cloud-eureka-server
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
配置文件 application.yaml
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
每个工程都引入 Eureka
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
配置文件 application.xml
b1
spring:
application:
name: ribbon-service-b
server:
port: 7777
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
b2
spring:
application:
name: ribbon-service-b
server:
port: 7778
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
b3
spring:
application:
name: ribbon-service-b
server:
port: 7779
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
b1、b2、b3的controller
@RestController
public class OrderController {
@Value("${server.port}")
private Integer port;
@Value("${spring.application.name}")
private String name;
@GetMapping("/test")
public String add() {
return "this service name is " + name + " and port is " + port;
}
}
b1、b2、b3的启动类
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceB1Application {
public static void main(String[] args) {
SpringApplication.run(ServiceB1Application.class, args);
}
}
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceB2Application {
public static void main(String[] args) {
SpringApplication.run(ServiceB2Application.class, args);
}
}
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceB3Application {
public static void main(String[] args) {
SpringApplication.run(ServiceB3Application.class, args);
}
}
spring-cloud-starter-netflix-eureka-client
已经集成了 ribbon。不需要额外引入,直接使用即可。
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
配置文件 application.yml
spring:
application:
name: ribbon-hello-a
server:
port: 7776
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
启动类,RestTemplate 使用了@LoadBalanced
,这样 RestTemplate 就开启了 ribbon 的负载均衡了。
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceAApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceAApplication.class, args);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
controller
@RestController
@RequestMapping("ribbon")
public class TestController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/test")
public String test(){
String body = restTemplate.getForEntity("http://RIBBON-SERVICE-B/test", String.class).getBody();
return body;
}
}
从上一节可以看到,开启负载均衡只需通过@LoadBalanced
注解即可。
@LoadBalanced
注解,该注解配合覆盖均衡策略一起使用 RestTemplate
发出的请求才能生效。可以根据实际的业务场景选择最合适的策略:
策略类 | 命名 | 描述 |
---|---|---|
RandomRule | 随机策略 | 随机选择Server |
RoundRobinRule | 轮询策略 | 按顺序选择Server |
RetryRule | 重试策略 | 在一个配置时间段内当选择 Server 不成功,则一直尝试选择一个可用的Server |
BestAvailableRule | 最低并发策略 | 先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。 |
AvailabilityFilteringRule | 可用过滤策略 | 过滤一直连接失败并标记为circuit tripped的Server,过滤掉那些高并发连接的Server(active connections超过配置的阈值) |
ResponseTimeWeightedRule | 响应时间加权策略 | 已经被弃用,作用同WeightedResponseTimeRule |
WeightedResponseTimeRule | 响应时间加权策略 | 根据Server的响应时间分配权重,响应时间越长,权重越低,被选中的概率就越低。响应时间越短,权重越高,被选择到的概率越高 |
ZoneAvoidanceRule | 区域权衡策略 | 综合判断 Server 所在区域的性能和Server 的可用性轮询选择 Server,并且判断一个 AWS Zone 的运行性能是否可用,剔除不可用的 Zone 中 的所有 Server |
全局配置
使用 Ribbon 时配置全局的负载均衡策略,需要加一个配置类。该配置类需要被 @ComponentScan
扫描到才能全局生效。
@Configuration
public class GlobalRuleConfig {
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
}
上面配置了随机的策略,多次访问http://localhost:7776/ribbon/...
基于 @RibbonClient
或@RibbonClients
注解的配置
注意:编写自定义配置类,需要特别注意的是官方文档明确给出了警告:这个自定义配置类不能放在
@ComponentScan
所扫描的包以及其子包下(即不能放在主启动类所在的包及其子包下,因此我们需要新建一个包来放该配置类),否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就达不到特殊化定制的目的了
策略配置
@Configuration
public class AnnoRuleConfig {
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
}
启动类的配置
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "ribbon-service-b", configuration = AnnoRuleConfig.class)
public class ServiceAApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceAApplication.class, args);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@RibbonClient
指定某个服务的负载均衡策略,其他没有被指定的,就是用默认的负载均衡策略。@RibbonClient
注解可以把其他的配置类作为另外一个IOC容器导入到应用中,相当于加载了两个完全不相干的 Spring 的 beans 配置文件,此时应用中会有两个IOC容器。
@RibbonClient(name = "RIBBON-SERVICE-B", configuration = AnnoRuleConfig.class)
也可以使用 RibbonClients
指定多个服务的负载均衡策略
@RibbonClients(value = {
@RibbonClient(name = "RIBBON-SERVICE-B", configuration = AnnoRuleConfig.class),
@RibbonClient(name = "RIBBON-SERVICE-C", configuration = AnnoRuleConfig.class)
})
基于配置文件
下面对服务 ribbon-service-b
的负载均衡策略使用
RIBBON-SERVICE-B:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
使用 HTTP 发起请求难免会发生问题,在F版开始 Ribbon 的重试机制默认是开启的,需要添加对超时时间与重试策略的配置。
列如下面 ribbon-service-b
服务的配置
RIBBON-SERVICE-B:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ConnectTimeout: 3000
ReadTimeout: 60000
MaxAutoRetries: 3 #对第一次请求的服务的重试次数
MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
OkToRetryOnAllOperations: true
也可以全局配置
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
MaxAutoRetries: 3 #对第一次请求的服务的重试次数
MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
OkToRetryOnAllOperations: true
一般 Ribbon 都是搭配 OpenFeign 这类 Http 客户端或者其他RPC 使用。因为这样去调用远程服务会更加优雅和方便。而OpenFeign默认是继承了Ribbon,对于Ribbon的超时时间配置也是很简单。
对于网络抖动这些可以使用spring-retry
,spring-retry是spring提供的一个基于spring的重试框架,非常好用。
Ribbon 在进行客户端负载均衡的时候,并不是启动时就加载上下文,而是在实际请求的时候采取创建。
因为要加载上下文的原因,在第一次调用时可能会很慢,甚至导致超时。
所以我们可以指定 Ribbon 客户端开启立即加载(饥饿加载),在应用启动的时候就立即加载所有配置项的应用程序上下文。
ribbon:
eager-load:
clients: ribbon-service-b, ribbon-service-order
enabled: true
下面看一下整合 Eureka 和 Ribbon 如何实现服务调用 和 负载均衡。有了 Eureka 之后,服务调用就无需关注服务提供者的IP。