Ribbon和Spring Cloud Loadbalancer

转载于:https://www.modb.pro/db/380587

2020年以后随着Netflix的相关SpringCloud组件进入停更状态,Cloud吸收前人经验,自己创造了一套相关组件,今天就跟大家揭秘一下客户端负载均衡组件Ribbon和LoadBalancer。

现状大PK

Ribbon:

目前处于停更维护阶段,由于Ribbon比较优秀,生命力顽强,在生产环境还处在大规模使用中, 暂时还没有完全被替换,但是在2020年以后的cloud版本中已经删除了Ribbon的依赖。

LoadBalancer:

  • cloud正在大规模引入LoadBalancer,还未全部占领高地,随着时间的推移,再加上cloud的大力扶持,未来迟早有一天LoadBalancer会一统江湖。

  • 还支持响应式客户端的负载均衡,Spring Cloud LoadBalancer结合Spring Web Flux实现客户端负载均衡调用。即:WebClient。

Ribbon简介&实战

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项,如连接超时,重试等。简单说,就是在配置文件中列出LoadBalancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如轮询,随机等)去连接这些机器,很容易使用Ribbon实现自定义的负载均衡算法。

LB负载均衡是什么

  • 我给大家讲一下什么是客户端负载均衡,服务消费者从注册中心拿到服务提供者集群,自己决定使用何种算法找到目标服务,这个过程就是客户端负载均衡,即主动权掌握在自己手里

  • 相对客户端负载均衡,服务端负载均衡,就是消费者把请求交给服务端,由服务端来负责找到目标服务提供者,即主动权掌握在被人手里。

  • 我看大家还有一种分类方式:集中式LB和进程内LB,都是一个意思。

Ribbon工作流程

  1. 从注册中心获取服务列表

  2. 根据用户指定的策略,找到目标服务提供者

  3. 通过Feign或者OpenFeign,Web客户端调用工具完成用户请求

 

负载均衡策略

  • RoundRobinRule:轮询

  • RandomRule:随机

  • RetryRule:先按照轮询策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务

  • WeightedResponseTimeRule:对轮询的扩展,响应速度越快的实例选择权重越大,越容易被选择

  • BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

  • AvailabiliyFilteringRule:先过滤掉故障实例,再选择并发较小的实例

  • ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器

自定义负载均衡策略

  1. Ribbon官方明确规定,自动以的配置类,不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们定义的配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的。

  2. 在某些特殊场景下,我们可能需要targetId相同,所有请求应该落到同一服务上。这个时候随机和轮询都不满足,所以就需要自定义一致性hash算法。

  3. 在某一个服务中,如果只调用一个服务提供方,可以写在@ComponentScan所扫描的包及子包下。如果是调用多个服务提供方,需要不同的策略,且互不干扰。就需要遵守第一条原则。

  4. 一般boot工程默认配置了@ComponentScan所扫描的包是启动类Application所在包及子包,所以在有时候我们不配置自动扫描也能正常运行的原因。

  • 自定义配置类,比如使用随机替换默认的轮询
1
2
3
4
5
6
7
8
9
@Configuration
public class MyRule{

@Bean
public IRule myRule(){
return new RandomRule();
}

}
  • 在启动类上使用@RibbonClient注解,设置自定义负载均衡策略使其生效,name是服务名,即调用哪个服务集群的时候使用自定义规则。
1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "cloud-payment-service",configuration=MyRule.class)
public class Application8080 {
public static void main(String[] args) {
SpringApplication.run(Application8080.class, args);
}
}

LoadBalancer简介&实战

LoadBalancer的优势

  • 跟Ribbon一样也支持RestTemplate,使用方法一样,由于cloud的其他组件还在使用Ribbon,所以在使用LoadBalancer的时候要屏蔽Ribbon。

  • LoadBalancer还支持响应式编程方式的异步访问客户端的负载均衡,依赖Spring Web Flux实现客户端负载均衡调用,底层依赖Netty的AIO

引入LoadBalancer依赖jar包

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

配置文件application.yml屏蔽Ribbon负载均衡

在Ribbon和LoadBalancer依赖都引入的情况,cloud默认优先启用Ribbon,当我们想使用LoadBalancer的时候就要先屏蔽到Ribbon

1
2
3
4
5
spring:
cloud:
loadbalancer:
ribbon:
enabled: false

业务类和启动类都没有区别,还是使用RestTemplate作为客户端来完成服务调用,此方式跟Ribbon在编码方面没什么区别。是Ribbon还是LoadBalancer,主要体现在jar包引入和yml文件关闭默认Ribbon。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//配置RestTemplate
@Configuration
public class ApplicationConfig {

@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}

}

//controller部分
@RestController
@RequestMapping("/consumer")
public class ConsumerCtrol {
private static String PAYMENTURL = "http://CLOUD-PAYMENT-SERVICE";
@Resource
private RestTemplate restTemplate;

@GetMapping("/getPayment/{id}")
public MsgResponseBody getPaymet(@PathVariable("id") Long id){
System.out.println("adada");
return restTemplate.getForObject(PAYMENTURL+"/payment/getPayment/"+id+"?userName=123", MsgResponseBody.class);
}
}

WebClient方式发起服务调用

引入webflux的相关依赖

1
2
3
4
5
<dependency>
<!-- Spring Webflux响应式web编程-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

配置类中声明要注入的WebClient.Builder,是WebClient的内部类,也是编程的入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//配置RestTemplate
@Configuration
public class ApplicationConfig {

@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}

@Bean
@LoadBalanced
public WebClient.Builder builder() {
return WebClient.builder();
}

}

//controller部分
@RestController
@RequestMapping("/consumer")
public class ConsumerCtrol {
private static String PAYMENTURL = "http://CLOUD-PAYMENT-SERVICE";
@Resource
private RestTemplate restTemplate;

@GetMapping("/getPayment/{id}")
public MsgResponseBody getPaymet(@PathVariable("id") Long id){
System.out.println("adada");
return restTemplate.getForObject(PAYMENTURL+"/payment/getPayment/"+id+"?userName=123", MsgResponseBody.class);
}

@GetMapping("/getPayment/webClient/{id}")
public Mono<MsgResponseBody> getPaymetByWebClient(@PathVariable("id") Long id) {
System.out.println("bbbbbbb");
//return restTemplate.getForObject(PAYMENTURL + "/payment/getPayment/" + id + "?userName=123", MsgResponseBody.class);
return clientBuilder.baseUrl(PAYMENTURL).build().get().uri("/payment/getPayment/" + id + "?userName=123").retrieve().bodyToMono(MsgResponseBody.class);
}

}

默认跟Ribbon是一样的,都是轮询策略,自定义配置使用的是@LoadBalancerClient,用法跟Ribbon的@RibbonClient一样。

在Springcloud alibaba的2.2.6版本对应的Spring-cloud版本是Hoxton.SR9,而spring-cloud-loadbalancer2.2.6版本负载均衡策略仅支持轮询策略。

Hoxton.SR10版本以后增加了随机负载均衡策略

切换随机负载均衡策略,新建两个Module分别绑定8004和8005端口,服务名是CLOUD-PAYMENT-SERVICE1,使用轮询策略,上面说了,MyLoadBalancerRandomConfig要放在@ComponentScan不能扫描到的package中。

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class MyLoadBalancerRandomConfig {

@Bean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}

启动类配置@LoadBalancerClient,配置原来的8001和8002,CLOUD-PAYMENT-SERVICE服务使用随机策略

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
@LoadBalancerClient(value = "CLOUD-PAYMENT-SERVICE", configuration = MyLoadBalancerRandomConfig.class)
public class Application6060 {
public static void main(String[] args) {
SpringApplication.run(Application6060.class,args);
}
}

controller类的修改,getPayment对应轮询,getPaymentWebClient对应随机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
@RequestMapping("/consumer")
public class ConsumerCtrol {
private static final String PAYMENTURL = "https://CLOUD-PAYMENT-SERVICE";//随机
private static final String PAYMENTURL2 = "https://CLOUD-PAYMENT-SERVICE1";//轮询
@Autowired
private RestTemplate restTemplate;
@Autowired
private WebClient.Builder clientBuilder;

@GetMapping("/getPayment/{id}")
public MsgResponseBody getPaymet(@PathVariable("id") Long id) {
System.out.println("adada");
return restTemplate.getForObject(PAYMENTURL2 + "/payment/getPayment/" + id + "?userName=123", MsgResponseBody.class);
}

@GetMapping("/getPayment/webClient/{id}")
public Mono<MsgResponseBody> getPaymetByWebClient(@PathVariable("id") Long id) {
System.out.println("bbbbbbb");
//return restTemplate.getForObject(PAYMENTURL + "/payment/getPayment/" + id + "?userName=123", MsgResponseBody.class);
return clientBuilder.baseUrl(PAYMENTURL).build().get().uri("/payment/getPayment/" + id + "?userName=123").retrieve().bodyToMono(MsgResponseBody.class);
}
}