Spring Cloud Ribbon 是一套基于 Netflix Ribbon 实现的客户端负载均衡和服务调用工具

负载均衡

在任何一个系统中,负载均衡都是一个十分重要且不得不去实施的内容,它是系统处理高并发、缓解网络压力和服务端扩容的重要手段之一。

负载均衡(Load Balance) ,简单点说就是将用户的请求平摊分配到多个服务器上运行,以达到扩展服务器带宽、增强数据处理能力、增加吞吐量、提高网络的可用性和灵活性的目的。

常见的负载均衡方式有两种:

  • 服务端负载均衡
  • 客户端负载均衡

服务端负载均衡

服务端负载均衡是在客户端和服务端之间建立一个独立的负载均衡服务器,该服务器既可以是硬件设备(例如 F5),也可以是软件(例如 Nginx)。这个负载均衡服务器维护了一份可用服务端清单,然后通过心跳机制来删除故障的服务端节点,以保证清单中的所有服务节点都是可以正常访问的。

当客户端发送请求时,该请求不会直接发送到服务端进行处理,而是全部交给负载均衡服务器,由负载均衡服务器按照某种算法(例如轮询、随机等),从其维护的可用服务清单中选择一个服务端,然后进行转发。

服务端负载均衡具有以下特点:

  • 需要建立一个独立的负载均衡服务器。
  • 负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务。
  • 可用服务端清单存储在负载均衡服务器上。

客户端负载均衡

客户端负载均衡是将负载均衡逻辑以代码的形式封装到客户端上,即负载均衡器位于客户端。客户端通过服务注册中心(例如 Eureka Server)获取到一份服务端提供的可用服务清单。有了服务清单后,负载均衡器会在客户端发送请求前通过负载均衡算法选择一个服务端实例再进行访问,以达到负载均衡的目的;

客户端负载均衡也需要心跳机制去维护服务端清单的有效性,这个过程需要配合服务注册中心一起完成。

客户端负载均衡具有以下特点:

  • 负载均衡器位于客户端,不需要单独搭建一个负载均衡服务器。
  • 负载均衡是在客户端发送请求前进行的,因此客户端清楚地知道是哪个服务端提供的服务。
  • 客户端都维护了一份可用服务清单,而这份清单都是从服务注册中心获取的。

Ribbon 就是一个基于 HTTP 和 TCP 的客户端负载均衡器,当我们将 Ribbon 和 Eureka 一起使用时,Ribbon 会从 Eureka Server(服务注册中心)中获取服务端列表,然后通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。

服务端负载均衡 VS 客户端负载均衡

不同点 服务端负载均衡 客户端负载均衡
是否需要建立负载均衡服务器 需要在客户端和服务端之间建立一个独立的负载均衡服务器。 将负载均衡的逻辑以代码的形式封装到客户端上,因此不需要单独建立负载均衡服务器。
是否需要服务注册中心 不需要服务注册中心。 需要服务注册中心。 在客户端负载均衡中,所有的客户端和服务端都需要将其提供的服务注册到服务注册中心上。
可用服务清单存储的位置 可用服务清单存储在位于客户端与服务器之间的负载均衡服务器上。 所有的客户端都维护了一份可用服务清单,这些清单都是从服务注册中心获取的。
负载均衡的时机 先将请求发送到负载均衡服务器,然后由负载均衡服务器通过负载均衡算法,在多个服务端之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。 简单点说就是,先发送请求,再进行负载均衡。 在发送请求前,由位于客户端的服务负载均衡器(例如 Ribbon)通过负载均衡算法选择一个服务器,然后进行访问。 简单点说就是,先进行负载均衡,再发送请求。
客户端是否了解服务提供方信息 由于负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务。 负载均衡是在客户端发送请求前进行的,因此客户端清楚的知道是哪个服务端提供的服务。

服务调用

  1. 引入依赖(eureka已经引入了ribbon,不用引入)

    <!--Spring Cloud Ribbon 依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
    
  2. RestTemplate上加@LoadBalanced注解

  3. 访问,通过微服务的名称来获取调用地址

负载均衡

负责均衡策略7个默认实现类(默认使用ZoneAvoidanceRule)

序号 实现类 负载均衡策略
1 RoundRobinRule 按照线性轮询策略,即按照一定的顺序依次选取服务实例
2 RandomRule 随机选取一个服务实例
3 RetryRule 按照 RoundRobinRule(轮询)的策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试(重试时获取服务的策略还是 RoundRobinRule 中定义的策略),如果超过指定时间依然没获取到服务实例则返回 null 。
4 WeightedResponseTimeRule WeightedResponseTimeRule 是 RoundRobinRule 的一个子类,它对 RoundRobinRule 的功能进行了扩展。 根据平均响应时间,来计算所有服务实例的权重,响应时间越短的服务实例权重越高,被选中的概率越大。刚启动时,如果统计信息不足,则使用线性轮询策略,等信息足够时,再切换到 WeightedResponseTimeRule。
5 BestAvailableRule 继承自 ClientConfigEnabledRoundRobinRule。先过滤点故障或失效的服务实例,然后再选择并发量最小的服务实例。
6 AvailabilityFilteringRule 先过滤掉故障或失效的服务实例,然后再选择并发量较小的服务实例。
7 ZoneAvoidanceRule 默认的负载均衡策略,综合判断服务所在区域(zone)的性能和服务(server)的可用性,来选择服务实例。在没有区域的环境下,该策略与轮询(RandomRule)策略类似。

使用

@Bean
public IRule myRule() {
    // RandomRule 为随机策略
    return  new RandomRule();
}

定制负载均衡策略

public class MyRandomRule extends AbstractLoadBalancerRule {

    /**
     * 总共被调用的次数,目前要求每台被调用5次
     */
    private int total = 0;
    /**
     * 当前提供服务的机器号
     */
    private int currentIndex = 0;

    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;

        while (true) {
            if (Thread.interrupted()) {
                return null;
            }
            //获取所有有效的服务实例列表
            List<Server> upList = lb.getReachableServers();
            //获取所有的服务实例的列表
            List<Server> allList = lb.getAllServers();

            //如果没有任何的服务实例则返回 null
            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }
            //与随机策略相似,但每个服务实例只有在调用 3 次之后,才会调用其他的服务实例
            if (total < 3) {
                server = upList.get(currentIndex);
                total++;
            } else {
                total = 0;
                currentIndex++;
                if (currentIndex >= upList.size()) {
                    currentIndex = 0;
                }
            }
            if (server == null) {
                Thread.yield();
                continue;
            }
            if (server.isAlive()) {
                return (server);
            }
            server = null;
            Thread.yield();
        }
    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        // TODO Auto-generated method stub
    }
}