负载均衡Ribbon

负载均衡概述

  • 实际环境中,我们往往会开启很多个 goods-service 服务的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢
  • 如何从多台服务器当中, 均衡的调用

SpringCloud-Ribbon

  • Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具
  • Ribbon是Netflix发布的开源项目,主要功能是提供客户端的负载均衡算法,将Netflix的中间层服务连接在一起
  • Ribbon是Netflix发布的负载均衡器,Ribbon默认为我们提供了很多负载均衡算法
  • 例如轮询、随机等。当然,我们也可为Ribbon实现自定义的负载均衡算法
  • 在SpringCloud中,当Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表
  • 并基于负载均衡算法,请求其中一个服务提供者实例。展示了Ribbon与Eureka配合使用时的架构

Ribbon负载均衡配置

  1. 在Eureka当中已经包含了Ribbon, 所以我们不需要单独添加依赖

  1. 在restTemplate的Bean上再添加一个注解@LoadBalanced

  1. 在控制器当中定义要调用服务的实例名称

默认是轮训

🐤注意点

  • 就是配置服务端口号的问题,不要使用Java的配置方式进行配置端口,要使用 yaml 文件 或者 properties 文件配置的方式进行配置否则远程调用服务会调用不了

负载均衡算法

  • 随机
  • 轮询
  • hash(哈希)
  • 最少访问
  • 这些算法, 都不需要要我们去写, 直接使用一个Robbin进行操作

核心组件IRule

  • IRule是Ribbon对于负载均衡策略实现的接口
  • 默认实现IRule接口的类
    • RoundRobinRule: 轮询
    • RandomRule: 随机
    • AvailabilityFilteringRule: 会先过滤由于多次访问故障处于断路器跳闸状态的服务, 还有并发的连接数据超过阈值的服务, 然后对剩余的服务列表按照轮询策略进行选取
    • WeightedResponseTimeRule: 根据平均响应时间计算所有服务的权重, 响应时间越快权重越大, 刚启动时如果统计信息不足, 则使用RoundRobinRule策略, 等统计信息足够时后, 会切换到 WeightedResponseTieRule
    • RetryRule: 先按照RoundRobinRule的策略获取服务, 如果获取服务失败, 则在制定时间内会重试, 再获取不到, 则放弃
    • BestAvailableRule: 会先过滤掉由于多次访问故障, 而处于断路器跳闸的服务, 然后选择一个并发量最小的服务
    • ZoneAvoidanceRule: 默认规则, 符合判断 Server 所在区域的性能和 server 的可用性选择服务器

🐤IRule配置

@Bean
public IRule iRule() {
    return new RandomRule();
}

🐸自定义IRule

  • 伪随机
    • 当一个下标(伪服务)连接被调用两次
    • 第三次如果还是它, 就让再随机一次

核心代码

int index = chooseRandomInt(serverCount);
//如果取出来的下标 是要跳过的下标
if (index == skipIndex) {
    System.out.println("跳过重新随机");
    index = chooseRandomInt(serverCount);
}
// 跳过后, 清空要跳过的下标
skipIndex = -1;
// 记录这次下标
nowIndex = index;
// 如果这次下标等于上次下标
if (lastIndex == nowIndex) {
    // 就把这次下标赋值给跳过的下标
    skipIndex = nowIndex;
    System.out.println("这次和上次的下标一样");
}
// 随机完之后,这一次的下标就是上一次的下标
lastIndex = nowIndex;

递归写法

public int bnTangRandom(int index, int serverCount) {
    // 如果取出来的下标 是要跳过的下标
    if (index == skipIndex) {
        System.out.println("跳过重新随机");
        index = chooseRandomInt(serverCount);
        if (index == skipIndex) {
            index = bnTangRandom(index, serverCount);
        }
    }
    // 跳过后,清空要跳过的下标
    skipIndex = -1;
    // 记录这次下标
    nowIndex = index;
    // 如果这次下标等于上次下标
    if (lastIndex == nowIndex) {
        // 就把这次下标赋值给跳过的下标
        skipIndex = nowIndex;
        System.out.println("这次和上次的下标一样");
    }
    // 随机完之后,这一次的下标就是上一次的下标
    lastIndex = nowIndex;
    return index;
}

MyIRule.java 全部代码

package com.bntang666.config.irule;

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.Random;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @author BNTang
 * @version V1.1.1
 * @program springcloud-netflix
 * @date Created in 2020/9/7 9:33
 * @description 自定义IRule
 **/
public class MyIRule extends AbstractLoadBalancerRule {

    Random random;
    // 这次下标
    private int nowIndex = -1;
    // 上一次下标
    private int lastIndex = -1;
    // 要跳过的下标(上一次下标等于这次下标 就把这次下标赋值给跳过的下标)
    private int skipIndex = -1; 

    public MyIRule() {
        random = new Random();
    }

    /**
     * Randomly choose from all living servers
     */
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;

        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            // 可用的服务列表
            List<Server> upList = lb.getReachableServers();
            // 所有的服务列表
            List<Server> allList = lb.getAllServers();
            // 获取所有服务列表的数量
            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }
            int index = chooseRandomInt(serverCount);
            index = bnTangRandom(index, serverCount);
            server = upList.get(index);
            if (server == null) {
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }
            // Shouldn't actually happen.. but must be transient or a bug.
            server = null;
            Thread.yield();
        }
        return server;
    }

    public int bnTangRandom(int index, int serverCount) {
        // 如果取出来的下标 是要跳过的下标
        if (index == skipIndex) {
            System.out.println("跳过重新随机");
            index = chooseRandomInt(serverCount);
            if (index == skipIndex) {
                index = bnTangRandom(index, serverCount);
            }
        }
        // 跳过后,清空要跳过的下标
        skipIndex = -1;
        // 记录这次下标
        nowIndex = index;
        // 如果这次下标等于上次下标
        if (lastIndex == nowIndex) {
            // 就把这次下标赋值给跳过的下标
            skipIndex = nowIndex;
            System.out.println("这次和上次的下标一样");
        }
        // 随机完之后,这一次的下标就是上一次的下标
        lastIndex = nowIndex;
        return index;
    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

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

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

配置 MyIRule.java

不同服务设置策略

  • 创建两个 order 子工程

order

添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

添加启动类

@SpringBootApplication
@EnableEurekaClient
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

添加 OrderController 控制器

@RestController
public class OrderController {
    @RequestMapping("/getOrder.do")
    public Object getOrder() {
        Map<Object, Object> map = new HashMap<>();
        map.put("name", "orderValue");
        return map;
    }
}

添加 yml 配置文件,并把服务注册到注册中心中,我直接上配置文件截图和代码了

eureka:
  client:
    serviceUrl:
      # eureka服务端提供的注册地址 参考服务端配置的这个路径
      defaultZone: http://eureka:3000/eureka,http://eureka1:3001/eureka,http://eureka2:3002/eureka
  instance:
    # 此实例注册到eureka服务端的唯一的实例ID
    instance-id: order-1
    # 是否显示IP地址
    prefer-ip-address: true
    # eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)
    leaseRenewalIntervalInSeconds: 10
    # Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
    leaseExpirationDurationInSeconds: 30

spring:
  application:
    # 此实例注册到eureka服务端的name
    name: server-order
server:
  port: 8001

order1 模块照葫芦画瓢即可, order1 我配置的端口号为 8002,我还是粘贴在下方吧

添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

添加启动类

@SpringBootApplication
@EnableEurekaClient
public class Order1Application {
    public static void main(String[] args) {
        SpringApplication.run(Order1Application.class, args);
    }
}

添加 Order1Controller 控制器

@RestController
public class Order1Controller {
    @RequestMapping("/getOrder.do")
    public Object getOrder() {
        Map<Object, Object> map = new HashMap<>();
        map.put("name", "order1Value");
        return map;
    }
}

添加 yml 配置文件,并把服务注册到注册中心中

eureka:
  client:
    serviceUrl:
      # eureka服务端提供的注册地址 参考服务端配置的这个路径
      defaultZone: http://eureka:3000/eureka,http://eureka1:3001/eureka,http://eureka2:3002/eureka
  instance:
    # 此实例注册到eureka服务端的唯一的实例ID
    instance-id: order-2
    # 是否显示IP地址
    prefer-ip-address: true
    # eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)
    leaseRenewalIntervalInSeconds: 10
    # Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
    leaseExpirationDurationInSeconds: 30

spring:
  application:
    # 此实例注册到eureka服务端的name
    name: server-order
server:
  port: 8002

配置负载均衡

  • 如果把负载均衡策略配置放到了 com 包中, 那么它配置的是所有的服务, 它和 UserApplication 在同一个目录
  • 如果想要不同服务设置不同的策略, 就要单独创建一个目录, 不要和UserApplication同一个目录

@Bean
public IRule iRule() {
    return new MyIRule();
}
@Bean
public IRule iRule() {
    return new RoundRobinRule();
}

在启动类当中为指定的服务, 配置不同的负载均衡调用策略, 配置完成之后自行测试即可

@EnableEurekaClient
@SpringBootApplication
@RibbonClients({
        @RibbonClient(name = "CLIENT-GOODS", configuration = GoodsConfig.class),
        @RibbonClient(name = "SERVER-ORDER", configuration = OrderConfig.class)
})
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}
posted @ 2020-09-03 20:31  BNTang  阅读(97)  评论(0编辑  收藏  举报