新 SpringCloud (二) 之 Ribbon 负载均衡

一、前言

0. 之前写过两篇Spring Cloud,但是感觉不够具体,所以重新写了一份。

新 Spring Cloud (一) 之 Eureka 服务注册中心
新 SpringCloud (二) 之 Ribbon 负载均衡
新 Spring Cloud (三) 之 Hystrix熔断保护
新 Spring Cloud (四) 之 Fegin远程调用
新 Spring Cloud (五) 之 Zuul 网关

1. 正文

继续我们上个项目搭建,不过为了方便识别,进行了一些改动。

  1. 为了方便更改配置,而不是一直在一个文件配置,所以在Eurake注册中心配置了三个yml文件如下:在这里插入图片描述
    其中application-10086.yml 自然是10086端口的配置,application-10087.yml 自然是10087端口的配置
    application.yml 用来切换环境
    如下:

    application.yml

    spring:
      profiles:
        active: 10087		#  通过active的值来切换不同的配置文件 原则是加载 application-{active值}.yml
    
    

    application-10086.yml

    server:
      port: 10086 # Eureka服务的端口
    spring:
      application:
        name: eureka-registety-server # 应用名称,会在Eureka中显示
    eureka:
      client:
        service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
          defaultZone: http://127.0.0.1:10087/eureka
        register-with-eureka: true  # 是否把自己注册到eureka服务列表,默认true,会将自己本身注册到Eureka中
        fetch-registry: false # 拉取eureka服务信息  指示此客户端是否应从eureka服务器获取eureka注册表信息,注意开启这个时候,在启动Eureka服务的时候会报错,但不影响使用。
      instance:   # 使用自定义ip ,否则在获取服务的时候ip地址会错误
        preferIpAddress: true
        instance-id: ${spring.application.name}:${server.port}
      server:
        eviction-interval-timer-in-ms: 1000  # 每隔1000ms 清除一次失效列表
        enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
    

    application-10087.yml

    server:
      port: 10087 # Eureka服务的端口
    spring:
      application:
        name: eureka-registety-server # 应用名称,会在Eureka中显示
    eureka:
      client:
        service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
          defaultZone: http://127.0.0.1:10086/eureka
        register-with-eureka: true  # 是否把自己注册到eureka服务列表,默认true,会将自己本身注册到Eureka中
        fetch-registry: false # 拉取eureka服务信息  指示此客户端是否应从eureka服务器获取eureka注册表信息,注意开启这个时候,在启动Eureka服务的时候会报错,但不影响使用。
      instance:   # 使用自定义ip ,否则在获取服务的时候ip地址会错误
        preferIpAddress: true
        instance-id: ${spring.application.name}:${server.port}
      server:
        eviction-interval-timer-in-ms: 1000  # 每隔1000ms 清除一次失效列表
        enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
    
  2. 启动器名称改了一下,方便看懂
    在这里插入图片描述

二、Ribbon 介绍

Ribbon是Netlix发布的负载均衡器,它有助于控制HTTP和TCP客户端的行为。为Ribbon配置服务提供者地址列表后,Ribbon 就可基于某种负载均衡算法,自动地帮助服务消费者去请求。Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机等。当然,我们也可为Ribbon实现自定义的负载均衡算法。

三、Eureka 整合 Ribbon

Eureka中已经帮我们集成了负载均衡组件:Ribbon,简单修改代码即可使用。
对于我们这个例子,我们假设,现在有两个司机服务提供者,一个占用端口10000,一个占用端口10010。那么我们在实际打车时,不可能一直让10000端口的服务接单,也不可能一直让10010端口的司机接单。我们需要一个策略来将订单分派给不同的司机。Ribbon组件就可以完成这项工作。

  1. 在司机服务端(EurekaServerProvider)添加一个10001端口配置文件和启动器。和上面一样,创建三个yml文件如下。
    在这里插入图片描述
    application.yml

    spring:
      profiles:
        active: 10000 # 通过更改 active 的值来引入不同的配置文件
    

    application-10000.yml

    server:
      port: 10000 # Eureka服务的端口
    spring:
      application:
        name: eureka-server-provider # 应用名称,会在Eureka中显示
    eureka:
      client:
        service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
          defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
        register-with-eureka: true  # 是否把自己注册到eureka服务列表,默认true,会将自己本身注册到Eureka中
        fetch-registry: true # 拉取eureka服务信息  指示此客户端是否应从eureka服务器获取eureka注册表信息,注意开启这个时候,在启动Eureka服务的时候会报错,但不影响使用。
      instance:   # 使用自定义ip ,否则在获取服务的时候ip地址会错误
        preferIpAddress: true
        instance-id: ${spring.application.name}:${server.port}    # 自定义实例id,后面方便获取
        lease-expiration-duration-in-seconds:  10  # 服务失效时间
        lease-renewal-interval-in-seconds: 5      # 服务续约(心跳)时间
    

    application-10001.yml

    server:
      port: 10001 # Eureka服务的端口
    spring:
      application:
        name: eureka-server-provider # 应用名称,会在Eureka中显示
    eureka:
      client:
        service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
          defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
        register-with-eureka: true  # 是否把自己注册到eureka服务列表,默认true,会将自己本身注册到Eureka中
        fetch-registry: true # 拉取eureka服务信息  指示此客户端是否应从eureka服务器获取eureka注册表信息,注意开启这个时候,在启动Eureka服务的时候会报错,但不影响使用。
      instance:   # 使用自定义ip ,否则在获取服务的时候ip地址会错误
        preferIpAddress: true
        instance-id: ${spring.application.name}:${server.port}    # 自定义实例id,后面方便获取
        lease-expiration-duration-in-seconds:  10  # 服务失效时间
        lease-renewal-interval-in-seconds: 5      # 服务续约(心跳)时间
    
  2. 乘客端(EurekaServerConsumer)进行修改:
    1. RestTemplate注入时 添加注解 @LoadBalanced ,标志启用负载均衡

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

    2.修改 乘客调用司机服务接口部分,如下:

      @RequestMapping("callTaxi")
        public String callTaxi(String msg) {
            String url = "http://eureka-server-provider/driver/takeOrder";  // 通过 Eureka 服务注册中心的服务名称来调用
            // post方式嗲用传递参数
            MultiValueMap<String,String> multiValueMap =  new LinkedMultiValueMap<String,String>();
            multiValueMap.add("msg", msg);
            // 访问服务
            String result = restTemplate.postForObject(url, multiValueMap, String.class);
            System.out.println("乘客收到信息: " + result);
            return result;
        }
    
  3. 至此,搭建完毕。启动10000,10001,10086,10087,10010端口的服务,我们开始访问http://localhost:10010/passenger/callTaxi。多次访问,我们发现接单的端口并不一样,也就是说接单的服务司机并不是同一个人,即有了默认的分派功能。
    在这里插入图片描述

四、负载均衡配置

1. Ribbon的几种负载均衡策略

Ribbon 默认的负载均衡策略是轮询策略。
在这里插入图片描述

2. 配置负载均衡策略

1. yml 方式 :

yml配置原则是 : ${服务名}.ribbon.NFLoadBalancerRuleClassName: com.netflix.loadbalancer.IRule的实现类

eureka-server-provider:  # 服务提供者的服务名称,而不是服务消费者。
    ribbon:
      NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

2. Bean注入方式:

直接注入IRule的实现类

    @Bean
    public IRule ribbonRule() {
        return new RandomRule();//这里配置策略,和配置文件对应
    }

3 . 源码解析:

  1. 进入 RibbonClientConfiguration 中,如下的 name 字段,其上面有个注解 @RibbonClientName, 有道翻译如下:该注释注入在运行时分配的Ribbon客户端名称。提供了一个方便的选择。即可以看到name的值是eureka-server-provider,是我们“司机服务的服务名”。即可知道@RibbonClientName 将**“司机服务的名称**”注入到name中。
    在这里插入图片描述
  2. 继续往下看,可以看到 ribbonRule 方法,注入了一个IRule对象。通过 @ConditionalOnMissingBean 保证用户定制的自由(用户若自己注入,则不注入默认)。其中可以看到如果我们自己配置了IRule实例,则Ribbon会使用我们配置的IRule作为负载均衡实例。同时,因为name的值是服务提供者(eureka-server-provider),这也就解释了yml文件中我们配置负载均衡策略时为什么要使用服务提供者的服务名。
    在这里插入图片描述
  3. 看到 ribbonLoadBalancer 方法,将IRule实例注入进来,并将ILoadBalancer 的实例注入到Spring容器中。这个实例在后面负载均衡的时候会使用上。
    在这里插入图片描述
  4. 这也就解释了上面的yml配置和Bean注入IRule实例为什么可以配置Ribbon 负载均衡策略。
  5. 当我们使用RestTemplate.postForObject 方法时,流程如下: RestTemplate.postForObject --> RestTemplate.execute --> RestTemplate.doExecute -->AbstractClientHttpRequest.execute --> AbstractBufferingClientHttpRequest.executeInternal --> InterceptingClientHttpRequest.executeInternal --> InterceptingClientHttpRequest.execute。发下 迭代器中的类是 : LoadBalancerInterceptor
    在这里插入图片描述
  6. 继续跟进 LoadBalancerInterceptor.intercept 。
    在这里插入图片描述
  7. 再跟进 RibbonLoadBalancerClient.execute,已经可以看到服务的地址,并且通过getServer方法已经筛选出这次请求的地址了,所以再进入 RibbonLoadBalancerClient.getServer方法
    在这里插入图片描述
    8.RibbonLoadBalancerClient.getServer --> ZoneAwareLoadBalancer.chooseServer -> BaseLoadBalancer.chooseServer。看到rule的值为我们指定的随机策略。ZoneAwareLoadBalancer 是我们在第三步的时候注入的实例,所以IRule也被初始化为对应的负载均衡规则。
    在这里插入图片描述
  8. 进入 RandomRule.choose方法。可以看到其内部逻辑就是随机使用一个服务作为结果服务返回。从而呼应了第七步的内容。
    在这里插入图片描述

5. 自定义负载均衡策略

我们可以按照RandomRule、RoundRobinRule来照葫芦画瓢。
首先创建一个MyRule 继承 AbstractLoadBalancerRule 类,按照RoundRobinRule内容,自定义一个MyRule。规则是每个服务访问四次后切换下一个服务。

package com.lkingfish.springcloud;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Data: 2019/10/24
 * @Des: 自定义负载均衡规则。
 */
public class MyRule extends AbstractLoadBalancerRule {
    private AtomicInteger nextServerCyclicCounter;
    private AtomicInteger atomerCounter;
    private AtomicInteger currentServerCounter;

    public MyRule() {
        nextServerCyclicCounter = new AtomicInteger(0);
        atomerCounter = new AtomicInteger(3);
        currentServerCounter = new AtomicInteger(3);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }

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

    /**
     * 具体策略实现
     *
     * @param lb
     * @param key
     * @return
     */
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }

        Server server = null;
        int count = 0;
        while (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)) {
                return null;
            }
            int currCount = atomerCounter.get();
            if (currCount < 3) {
                atomerCounter.compareAndSet(currCount, currCount+1);
            } else {
                int nextServerIndex = incrementAndGetModulo(serverCount);
                atomerCounter.set(0);
                currentServerCounter.set(nextServerIndex);
            }
            server = allServers.get(currentServerCounter.get());

            if (server == null) {
                Thread.yield();
                continue;
            }
            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }
            // Next.
            server = null;
        }

        if (count >= 10) {
            System.out.println("尝试10次失败");
        }
        return server;
    }

    private int incrementAndGetModulo(int modulo) {
        for (; ; ) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }

}

随后将MyRule注解即可。

    @Bean
    public IRule ribbonRule() {
        return new MyRule();//这里配置策略,和配置文件对应
    }

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫吻鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值