SpringCloud基础Ribbon:负载均衡

Posted 烟锁迷城

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud基础Ribbon:负载均衡相关的知识,希望对你有一定的参考价值。

目录

1、基础使用

2、自带策略

2.1、RandomRule 随机

2.2、RoundRobinRule:轮询

2.3、RetryRule:重试

2.4、ZoneAvoidanceRule:默认的线性轮询

3、自定义策略

3.1、自定义选择

3.2、自定义策略


1、基础使用

Ribbon主要负责解决在集群部署时客户端选择连接到哪一个服务的问题,选择唯一一个连接地址的功能就叫负载均衡。

负载均衡有一些算法,比如:

  1. 随机/随机加权
  2. 轮询/轮询加权
  3. hash算法
  4. 最小连接数

在上一篇文章的基础上,先构建一个服务注册中心Eureka Server,之后建立三个服务提供方Provider Service和一个服务调用方Provider Consumer。

服务提供方Provider Service的服务名称需要相同,端口号不同。

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String sayHello() {
        System.out.println("service 03");
        return "hello lily";
    }

}

服务调用方Provider Consumer

@RestController
public class TestController {

    @Autowired
    private RestTemplate restTemplate;

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

    @GetMapping("test")
    public String sayHello() {
        return restTemplate.getForObject("http://spring-cloud-provider-service/hello",String.class);
    }
}

在其中,起到负载均衡作用的注解就是@LoadBalanced。

这样,在调用http://localhost:8086/test,就会自动负载均衡,决定调用哪一个服务。

2、自带策略

在Ribbon组件中,IRule的接口主要负责对负载均衡策略的生成,它具有一系列的实现类:

2.1、RandomRule 随机

核心代码是根据服务个数servercount获取到一个数值,启动时获取的服务列表会根据这个数值去拿出里面的一个服务,这个逻辑就是非常简单的。

int index = this.chooseRandomInt(serverCount);
server = (Server)upList.get(index);
if (server == null) {
    Thread.yield();
} else {
    if (server.isAlive()) {
        return server;
    }
    server = null;
    Thread.yield();
}

chooseRandomInt方法也是非常的简单,用的就是ThreadLocalRandom直接获取到的一个随机数,虽然不知道ThreadLocalRandom是什么,但是直到ThreadLocal是什么,可以推断出来,就是在线程内安全的一个随机数,根据服务个数来取值。

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

2.2、RoundRobinRule:轮询

这个类有两个实现子类:ResponseTimeWeightedRule和WeightedResponseTimeRule,但是ResponseTimeWeightedRule现在已经过时。

WeightedResponseTimeRule主要作用是根据服务器响应时间来增加权重。

轮询的核心代码,在服务为空的情况下,将循环10次,超过10次将抛出错误。十次以内,在有服务的情况下,根据函数incrementAndGetModulo获取下一个服务在全部服务allserver的列表中的位置,取出这个服务。

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;
}

incrementAndGetModulo方法就是根据当前位置获取下一个服务位置。

private int incrementAndGetModulo(int modulo) {
    int current;
    int next;
    do {
        current = this.nextServerCyclicCounter.get();
        next = (current + 1) % modulo;
    } while(!this.nextServerCyclicCounter.compareAndSet(current, next));
        return next;
    }
}

2.3、RetryRule:重试

在重试中,有一个subRule,这个属性是:

IRule subRule = new RoundRobinRule();

可以看出来,这是一个轮询,也就是说,在尝试用轮询获取到一个服务,如果成功就返回,如果失败,就会进入重试,重试的机制也是在线程没有中断的情况下,继续从轮询中获取到服务,如果失败超过了deadline,就取消任务,返回null

    public Server choose(ILoadBalancer lb, Object key) {
        long requestTime = System.currentTimeMillis();
        long deadline = requestTime + this.maxRetryMillis;
        Server answer = null;
        answer = this.subRule.choose(key);
        if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline) {
            InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());

            while(!Thread.interrupted()) {
                answer = this.subRule.choose(key);
                if (answer != null && answer.isAlive() || System.currentTimeMillis() >= deadline) {
                    break;
                }

                Thread.yield();
            }

            task.cancel();
        }

        return answer != null && answer.isAlive() ? answer : null;
    }

2.4、ZoneAvoidanceRule:默认的线性轮询

ZoneAvoidanceRule是默认的负载均衡策略,使用的是两个过滤器来达到线性轮询的效果。

    public ZoneAvoidanceRule() {
        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
        this.compositePredicate = this.createCompositePredicate(zonePredicate, availabilityPredicate);
    }

3、自定义策略

3.1、自定义选择

在配置文件中,可以指定某个服务所使用的负载均衡策略:

  • clientName.ribbon.NfLoadBalancerClass:配置ILoadBalancer的实现类
  • clientName.ribbon.NfLoadBalancerRuleClassName:配置IRule的实现类
  • clientName.ribbon.NfLoadBalancerPingClassName:配置IPing的实现类
  • clientName.ribbon.NFWSServerListClassName:配置ServerList的实现类
  • clientName.ribbon.NFWSServerListFilterClassName:配置ServerListFilter的实现类

举例:

spring-cloud-provider-service.ribbon.NfLoadBalancerClass=com.netflix.loadbalancer.RandomRule

这样spring-cloud-provider-service服务的负载均衡策略就会变成随机。

3.2、自定义策略

除了指定服务的负载均衡,也可以自己实现一个,就是通过继承AbstractLoadBalancerRule

重写choose方法,先是判断负载均衡是否启用,然后获取可用服务和全部服务,

public class MyDefineIpHashRule extends AbstractLoadBalancerRule {

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

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

    public Server choose(ILoadBalancer loadBalancer, Object o) {
        if (loadBalancer == null) {
            return null;
        }

        //获取当前可用服务
        List<Server> upServers = loadBalancer.getReachableServers();
        //获取全部注册服务
        List<Server> allServers = loadBalancer.getAllServers();
        int serverCount = allServers.size();

        if (serverCount == 0) {
            return null;
        }

        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //获取到请求地址
        String remoteAddr = servletRequestAttributes.getRequest().getRemoteAddr();
        //对请求地址获取hash
        int hashCode = Math.abs(remoteAddr.hashCode());
        //对服务长度取模
        int code = hashCode%serverCount;
        //返回服务
        return upServers.get(code);
    }
}

以上是关于SpringCloud基础Ribbon:负载均衡的主要内容,如果未能解决你的问题,请参考以下文章

SpringCloud微服务负载均衡与网关

Springcloud + nacos + gateway 负载均衡(ribbon)

SpringCloud微服务负载均衡器Ribbon

SpringCloud-Ribbon

SpringCloud微服务负载均衡器Ribbon

springcloud之Ribbon负载均衡