Ribbon 是一个客户端负载均衡器,它赋予了应用一些支配HTTP 与 TCP 行为的能力,这里的负载均衡是客户端的负载均衡,也有人称为后端负载均衡是进程内负载均衡的一种。
前面分析了 Eureka 的使用,作为服务注册中心,Eureka 分为 Server 端和 Client 端,Client 端作为服务的提供者,将自己注册到 Server 端,Client 端高可用的方式是使用多机部署然后注册到 Server,Server 端为了保证服务的高可用,也可以使用多机部署的方式。前面简单搭建了Eureka Client 和 Server,然后将Client成功注册到 Server,本节我们将来看看如何调用Eureka 服务,在多机部署情况下如何保证负载均衡。Spring Cloud 提供了一个组件:Ribbon。Ribbon是客户端负载均衡器,可以对 HTTP 和 TCP 客户端的行为进行大量控制。Ribbon中的中心概念是指定客户端的概念。 Ribbon API
服务端负载均衡
服务端负载均衡。将多个服务注册到一个公共的注册中心,服务调用者访问注册中心,由注册中心提供服务的负载均衡。
客户端负载均衡:
服务端负载均衡的代表性例子就是nginx,LVS。那么客户端的负载均衡就是我们要说的Ribbon。Ribbon主要提供客户端负载平衡算法,除此之外,Ribbon还提供:
服务发现集成 : 功能区负载平衡器在动态环境(如云)中提供服务发现。功能区库中包含与Eureka和Netflix服务发现组件的集成;容错 : Ribbon API 可以动态确定服务器是否已在实时环境中启动并运行,并且可以 检测到那些已关闭的服务器;可配置的负载平衡规则 : Ribbon支持开箱即用的 RoundRobinRule,AvailabilityFilteringRule,WeightedResponseTimeRule,还支持定义自定义规则。
Ribbon API
Rule :定义负载均衡策略;Ping : 定义如何 ping 目标服务实例来判断是否存活, ribbon 使用单独的线程每隔一段时间(默认10s)对本地缓存的 ServerList 做一次检查;ServerList :定义如何获取服务实例列表. 两种实现基于配置的 ConfigurationbasedServerList 和基于Eureka 服务发现的 DiscoveryEnabledNIWSServerList;ServerListFilter: 用来使用期望的特征过滤静态配置,动态获得的候选服务实例列表. 若未提供, 默认使用ZoneAffinityServerListFilter;ILoadBalancer: 定义了软负载均衡器的操作的接口。一个典型的负载均衡器至少需要一组用来做负载均衡的服务实例;ServerListUpdater: DynamicServerListLoadBalancer用来更新实例列表的策略(推EurekaNotificationServerListUpdater/拉PollingServerListUpdater, 默认是拉) Ribbon 的搭建
由于客户端负载均衡需要从注册中心获取服务列表,所以需要集成注册中心。
创建注册中心
创建父级工程 cloud-ribbon-practice
org.springframework.boot spring-boot-starter-parent 2.2.3.RELEASE UTF-8 UTF-8 1.8 Hoxton.SR3 org.springframework.cloud spring-cloud-dependencies ${spring.cloud-version} pom import org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-maven-plugin
创建注册中心 cloud-eureka-server
org.springframework.cloud spring-cloud-starter-netflix-eureka-server
配置文件 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);
}
}
创建源服务的提供者 cloud-ribbon-hello-b1、cloud-ribbon-hello-b2、cloud-ribbon-hello-b3
每个工程都引入 Eureka
org.springframework.cloud spring-cloud-starter-netflix-eureka-client
配置文件 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);
}
}
创建服务的调用者 cloud-ribbon-hello-a
spring-cloud-starter-netflix-eureka-client 已经集成了 ribbon。不需要额外引入,直接使用即可。
org.springframework.cloud spring-cloud-starter-netflix-eureka-client
配置文件 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;
}
}
测试
测试,使用postman访问几次:http://localhost:7776/ribbon/...
Ribbon 的服务策略配置
策略配置参数
从上一节可以看到,开启负载均衡只需通过@LoadBalanced注解即可。
定义一个负载均衡的 RestTemplate,使用了 @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
Ribbon 超时与重试
使用 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 客户端开启立即加载(饥饿加载),在应用启动的时候就立即加载所有配置项的应用程序上下文。
ribbon:
eager-load:
clients: ribbon-service-b, ribbon-service-order
enabled: true
Eureka 在 Ribbon 的作用
下面看一下整合 Eureka 和 Ribbon 如何实现服务调用 和 负载均衡。有了 Eureka 之后,服务调用就无需关注服务提供者的IP。



