0


详解Ribbon

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移除的高版本,本文使用的依赖版本:

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

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

2.2.启用

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

2.3.切换负载均衡算法

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

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

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

3.负载均衡源码分析

3.1.接口

IRule接口:

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

ILoadBalancer:

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

3.2.抽象类

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

3.3.选择服务器

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

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

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

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

public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        } else {
            Server server = null;
            int count = 0;

            while(true) {
                if (server == null && count++ < 10) {
                    List<Server> reachableServers = lb.getReachableServers();
                    List<Server> allServers = lb.getAllServers();
                    int upCount = reachableServers.size();
                    int serverCount = allServers.size();
                    if (upCount != 0 && serverCount != 0) {
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);
                        server = (Server)allServers.get(nextServerIndex);
                        if (server == null) {
                            Thread.yield();
                        } else {
                            if (server.isAlive() && server.isReadyToServe()) {
                                return server;
                            }

                            server = null;
                        }
                        continue;
                    }

                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }

                if (count >= 10) {
                    log.warn("No available alive servers after 10 tries from load balancer: " + lb);
                }

                return server;
            }
        }
    }

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

3.4.原子性

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

compareAndSet(期望值,修改值)

期望值,即版本号。

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

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

返回true,否则返回false。

4.自定义负载均衡算法

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

public class CustomWeightedRandomRule extends AbstractLoadBalancerRule {
    private AtomicInteger totalWeight = new AtomicInteger(0);

    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        if (lb == null) {
            return null;
        }

        List<Server> allServers = lb.getAllServers();
        int serverCount = allServers.size();
        if (serverCount == 0) {
            return null;
        }

        int randomWeight = ThreadLocalRandom.current().nextInt(totalWeight.get());
        int currentWeight = 0;

        for (Server server : allServers) {
            currentWeight += getWeight(server);
            if (randomWeight < currentWeight) {
                return server;
            }
        }

        // Fallback to the default server if no server is selected
        return super.choose(key);
    }

    private int getWeight(Server server) {
        // Return the weight of the server (custom logic)
        // Example: return server.getMetadata().getWeight();
        return 1; // Default weight is 1
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        super.initWithNiwsConfig(clientConfig);
        // Calculate the total weight of all servers
        List<Server> allServers = getLoadBalancer().getAllServers();
        totalWeight.set(allServers.stream().mapToInt(this::getWeight).sum());
    }
}
标签: ribbon java spring

本文转载自: https://blog.csdn.net/Joker_ZJN/article/details/131150360
版权归原作者 _BugMan 所有, 如有侵权,请联系我们删除。

“详解Ribbon”的评论:

还没有评论