转载于:https://www.modb.pro/db/380587
2020年以后随着Netflix的相关SpringCloud组件进入停更状态,Cloud吸收前人经验,自己创造了一套相关组件,今天就跟大家揭秘一下客户端负载均衡组件Ribbon和LoadBalancer。
现状大PK
Ribbon:
目前处于停更维护阶段,由于Ribbon比较优秀,生命力顽强,在生产环境还处在大规模使用中, 暂时还没有完全被替换,但是在2020年以后的cloud版本中已经删除了Ribbon的依赖。
LoadBalancer:

Ribbon简介&实战
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项,如连接超时,重试等。简单说,就是在配置文件中列出LoadBalancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如轮询,随机等)去连接这些机器,很容易使用Ribbon实现自定义的负载均衡算法。
LB负载均衡是什么
我给大家讲一下什么是客户端负载均衡,服务消费者从注册中心拿到服务提供者集群,自己决定使用何种算法找到目标服务,这个过程就是客户端负载均衡,即主动权掌握在自己手里
相对客户端负载均衡,服务端负载均衡,就是消费者把请求交给服务端,由服务端来负责找到目标服务提供者,即主动权掌握在被人手里。
我看大家还有一种分类方式:集中式LB和进程内LB,都是一个意思。
Ribbon工作流程
从注册中心获取服务列表
根据用户指定的策略,找到目标服务提供者
通过Feign或者OpenFeign,Web客户端调用工具完成用户请求
负载均衡策略
RoundRobinRule:轮询
RandomRule:随机
RetryRule:先按照轮询策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务
WeightedResponseTimeRule:对轮询的扩展,响应速度越快的实例选择权重越大,越容易被选择
BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
AvailabiliyFilteringRule:先过滤掉故障实例,再选择并发较小的实例
ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器

自定义负载均衡策略
Ribbon官方明确规定,自动以的配置类,不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们定义的配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的。
在某些特殊场景下,我们可能需要targetId相同,所有请求应该落到同一服务上。这个时候随机和轮询都不满足,所以就需要自定义一致性hash算法。
在某一个服务中,如果只调用一个服务提供方,可以写在@ComponentScan所扫描的包及子包下。如果是调用多个服务提供方,需要不同的策略,且互不干扰。就需要遵守第一条原则。
一般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的优势
引入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
| @Configuration public class ApplicationConfig {
@Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); }
}
@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> <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
| @Configuration public class ApplicationConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); } @Bean @LoadBalanced public WebClient.Builder builder() { return WebClient.builder(); } }
@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 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 clientBuilder.baseUrl(PAYMENTURL).build().get().uri("/payment/getPayment/" + id + "?userName=123").retrieve().bodyToMono(MsgResponseBody.class); } }
|