详解Ribbon

淩亂°似流年 2023-10-12 13:53 119阅读 0赞

目录

1.概述

2.使用

2.1.引入

2.2.启用

2.3.切换负载均衡算法

3.负载均衡源码分析

3.1.接口

3.2.抽象类

3.3.选择服务器

3.4.原子性

4.自定义负载均衡算法


1.概述

Ribbon是Netflix开源的一个客户端负载均衡库,也是Spring Cloud Netflix项目的核心组件之一。它主要用于在微服务架构中对服务进行负载均衡,以提高系统的可用性和性能。ribbon不是通信组件,而是服务调用者和通信组件之间的中间层,主要就是用来做负载均衡,选择出适合处理本次请求的服务节点。

现在整个spring cloud体系中的通信组件其实就是封装了负载均衡组件+HTTP通信组件,HTTP通信组件没什么好说的,就是用来发起HTTP请求的,市面上的HTTP通信组件也很多,诸如spring boot自带的RestTemplate,apache的Apache HttpClient等等。值得去探究一下的是负载均衡组件,其作为负载均很组件决定了请求的分发,可以说是整个通信组件的核心。

从Spring Cloud 2020.0版本开始,Spring Cloud官方已经将Ribbon标记为过时(deprecated),推荐使用Spring Cloud LoadBalancer作为替代方案。这样做是基于标准化的考虑,整个spring生态历来都是希望“包容万物”,所以社区自然不希望在负载均衡组件上直接就采用一个固定的实现,而是希望能让三方的解决方案可以平滑的接入、切换。

虽然ribbon以后不再是负载均衡组件的首选,但是作为最经典的负载均衡组件,其底层的一些思想仍然被后面的方案沿用,其实看明白ribbon的源码基本上也就明白负载均衡的原理了,万变不离其宗。

2.使用

2.1.引入

博主的上篇文章详细介绍了怎么使用eureka+通信组件来完成服务的注册、调用,其中也详细的介绍了ribbon的使用,可以移步:

详解Eureka服务注册和调用__BugMan的博客-CSDN博客

总的来说就是:

eureka集成了ribbon,导入eureka后不用单独导入ribbon,但是要注意的是一定要选对版本号,不要选到一个已经把ribbon移除的高版本,本文使用的依赖版本:

423488827561484e8ae5a7869dcb5a23.png

当然你也可以直接降级整个spring cloud的版本号,关于spring cloud版本号的问题,可以移步博主的另一篇文章,会有详细讲解:

详解Spring Cloud版本问题__BugMan的博客-CSDN博客

2.2.启用

引入依赖后在承载HTTP通信组件上开启负载均衡即可,此处以spring boot自带的RestTemplate为例,这样在每次客户端(消费者)发起http请求的时候都会在本端进行负载均衡运算后再进行服务访问。

4d3e5fbdbcd84d79a992d70a23922283.png

2.3.切换负载均衡算法

负载均衡的核心接口Irule有多个实现类,每个实现类实现不同的负载均衡算法,

常用的有,轮询、随机、可获得、重试等几种:

  • RoundRobinRule,轮询
  • RandomRule,随机
  • AvailabilityFilteringRule,会过滤掉,跳闸,访问故障的服务,对剩下的进行轮询
  • RetryRule,会按照轮询获取服务,如果服务获取失败,则会在指定的时间内进行重试
  • 需要切换的时候直接在@Configuration中注入@Bean即可:
  • cac83a27564c46ed92693531245665fa.png

3.负载均衡源码分析

3.1.接口

IRule接口:

  • choose:选举出一个服务器
  • get/setLoadBalancer:获取、修改均衡器

ILoadBalancer:

与服务器打交道,负责寻找、登记服务器

2fc2cc0b923c48d0af86d9ca2cd9be20.png

3.2.抽象类

AbstractLoadBalancer实现了Irule接口,重写了均衡器的get/set方法,只留下一个抽象方法——choose,待子类重写。

315d23a6ec9541e3b9e8c803d8b2c379.png

3.3.选择服务器

所有实现类都继承抽象类AbstractLoadBalancer。各自去重写choose方法,即各自实现不同的负载均衡规则(此处以ribbon默认的负载均衡规则,RoundRobinRule为例):

0ded51b79d3140aebb2e066ec5be8699.png

choose方法即负载均衡策略,是各负载均衡类的核心方法(此处以ribbon默认的负载均衡规则,RoundRobinRule为例):

均衡器会和注册中心交互,然后记录下当前整个系统中所有服务器的相关信息,包含服务器总数,可用总数等。

向均衡器所要服务器总数、服务器可用总数,然后根据这两个值进行运算,挑选出承载该此流量的服务器。

  1. public Server choose(ILoadBalancer lb, Object key) {
  2. if (lb == null) {
  3. log.warn("no load balancer");
  4. return null;
  5. } else {
  6. Server server = null;
  7. int count = 0;
  8. while(true) {
  9. if (server == null && count++ < 10) {
  10. List<Server> reachableServers = lb.getReachableServers();
  11. List<Server> allServers = lb.getAllServers();
  12. int upCount = reachableServers.size();
  13. int serverCount = allServers.size();
  14. if (upCount != 0 && serverCount != 0) {
  15. int nextServerIndex = this.incrementAndGetModulo(serverCount);
  16. server = (Server)allServers.get(nextServerIndex);
  17. if (server == null) {
  18. Thread.yield();
  19. } else {
  20. if (server.isAlive() && server.isReadyToServe()) {
  21. return server;
  22. }
  23. server = null;
  24. }
  25. continue;
  26. }
  27. log.warn("No up servers available from load balancer: " + lb);
  28. return null;
  29. }
  30. if (count >= 10) {
  31. log.warn("No available alive servers after 10 tries from load balancer: " + lb);
  32. }
  33. return server;
  34. }
  35. }
  36. }

整个过程中封装服务器相关信息的Server类,其中有服务器的详细参数:

f4b7354f53744ccda32088939907160c.png

3.4.原子性

挑选过程中,为了保证原子性,使用了自旋锁(CAS),保证每次处理的只有一个访问线程,其余线程处于自选等待状态:

compareAndSet(期望值,修改值)

期望值,即版本号。

修改值,即要将版本号更新为的值。

判断期望值是否改变(前后是否相同),如果期望值未改变,则将期望值更新为修改值。

返回true,否则返回false。

6df4966344a447609c018081e1df66c4.png

4.自定义负载均衡算法

存在顶级接口并且可以切换负载均衡算法,那自然可以自定义负载均衡算法,以下是一个根据服务器权重进行负载均衡的一个负载均衡算法:

  1. public class CustomWeightedRandomRule extends AbstractLoadBalancerRule {
  2. private AtomicInteger totalWeight = new AtomicInteger(0);
  3. @Override
  4. public Server choose(Object key) {
  5. ILoadBalancer lb = getLoadBalancer();
  6. if (lb == null) {
  7. return null;
  8. }
  9. List<Server> allServers = lb.getAllServers();
  10. int serverCount = allServers.size();
  11. if (serverCount == 0) {
  12. return null;
  13. }
  14. int randomWeight = ThreadLocalRandom.current().nextInt(totalWeight.get());
  15. int currentWeight = 0;
  16. for (Server server : allServers) {
  17. currentWeight += getWeight(server);
  18. if (randomWeight < currentWeight) {
  19. return server;
  20. }
  21. }
  22. // Fallback to the default server if no server is selected
  23. return super.choose(key);
  24. }
  25. private int getWeight(Server server) {
  26. // Return the weight of the server (custom logic)
  27. // Example: return server.getMetadata().getWeight();
  28. return 1; // Default weight is 1
  29. }
  30. @Override
  31. public void initWithNiwsConfig(IClientConfig clientConfig) {
  32. super.initWithNiwsConfig(clientConfig);
  33. // Calculate the total weight of all servers
  34. List<Server> allServers = getLoadBalancer().getAllServers();
  35. totalWeight.set(allServers.stream().mapToInt(this::getWeight).sum());
  36. }
  37. }

发表评论

表情:
评论列表 (有 0 条评论,119人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Ribbon详解与实例

            Ribbon是一个为客户端提供负载均衡功能的服务,它内部提供了一个叫做ILoadBalance的接口代表负载均衡器的操作,比如有添加服务器操作、选择服务器操作、