Ribbon常用负载均衡算法:

IRule接口,Riboon使用该接口,根据特定算法从所有服务中,选择一个服务,

Rule接口有7个实现类,每个实现类代表一个负载均衡算法,默认使用轮询

如何替换掉轮询

我们需要新建一个规则类,然后在启动类中添加注解即可。

但是:

官方文档给出了警告:

这个自定义配置类不能放在 @CommpomentScan 所扫描的当前包下以及子包下,

(即不能放在SpringBoot启动类包下及其子包)

否则我们自定义的这个规则类会被所有的 Ribbon 客户端共享,达不到特殊定制化的目的。

下面我们来操作:

在已有的order80服务中新建一个package,(即服务提供者)

目录结构如下

package com.xn2001.myrule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created by 乐心湖 on 2020/5/2 22:14
 */
@Configuration
public class MyselfRule {
    @Bean
    public IRule myRule(){
        // 定义为随机
        return new RandomRule();
    }
}

在主启动类添加注解

@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyselfRule.class)

注意:name填写的是application.yml中配置的spring.application.name的大写形式。

测试,访问查看调用消费者是否已经随机而不是轮询。

在之前轮询的情况下端口是8001与8002交替出现,而负载均衡规则变为随机后,端口是随机出现的

负载均衡算法

负载均衡算法:rest 接口第几次请求数 % 服务器集群总数量 = 时机调用服务器位置下标,每次服务重启后rest 接口技术求从1开始。

List<ServiceInstance> instances = discoverClient.getInstances("CLOUD-PROVIDER-SERVICE");

如: List[0] instances = 127.0.0.1:8002

      List[1] instances = 127.0.0.1:8001

8001 + 8002 组合为集群,他们共计2台服务器,集群总数为2 , 按照轮询算法原理:

当请求总数为1 时:1%2 = 1, 对应下标位置为1, 则获得服务地址为 127.0.0.1:8001
当请求总数为2 时:2%2 = 0, 对应下标位置为1, 则获得服务地址为 127.0.0.1:8002
当请求总数为3 时:2%2 = 1, 对应下标位置为1, 则获得服务地址为 127.0.0.1:8001

例如我们现在有两台机子去负载均衡

请求次数计算公式取得下标
11%2=1对应127.0.0.1:8001
22%2=0对应127.0.0.1:8002
33%2=1对应127.0.0.1:8001

手写负载均衡算法

首先你需要对CAS和自旋锁有一定的了解

推荐阅读


下文仅仅是对过程的大致描述,不是实际的演示过程

在服务提供者中的的controller添加一个方法,这里我们直接返回是端口号serverPort

@GetMapping("/payment/lb")
public String getPaymentLb(){
    return serverPort;
}

将restTemplate配置类中的@LoadBalanced注解删除

开始撸自己的算法

先建一个lib包,这里需要放到SpringBoot启动类下可以让Spring扫描到的包下,然后写一个接口

package com.xn2001.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

/**
 * Created by 乐心湖 on 2020/5/7 14:55
 */
public interface LoadBalancer {
    ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
package com.xn2001.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;

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

/**
 * Created by 乐心湖 on 2020/5/7 14:54
 */
@Component
public class MyLB implements LoadBalancer{

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public final int getAndIncrement(){
        int current;
        int next;
        do {
            current = this.atomicInteger.get();
            next = current >= 2147483647 ? 0 : current+1;
        }while (!this.atomicInteger.compareAndSet(current,next));

        System.out.println("第几次访问"+next);
        return next;
    }

    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        int index = getAndIncrement() % serviceInstances.size();
        return serviceInstances.get(index);
    }
}

current是初始值,next是访问次数。每次访问都会在自旋锁中把初始值+1,然后使用compareAndSet方法比较并交换。成功就跳出循环,失败则继续进入循环重新获取初始值+1…

这里保证了不用synchronized方法也能在高并发下实现线程安全的增加次数

instances()实现了访问次数%集群数量,使这个值永远不超过集群数量,然后得到这个值作为获取单个实例的下标,根据实例返回实例

在服务消费者中的controller层加入方法

先获取集群中的实例,然后判断是否为空,把获取到的list传给获取负载均衡算法中去,获取到实例地址(也就是分配了哪那一台服务器),restTemplate去调用服务。

@GetMapping("/consumer/payment/lb")
public String GetPaymentLB(){
    List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
    if (instances == null || instances.size() <= 0){
        return null;
    }
    ServiceInstance serviceInstance = loadBalancer.instances(instances);
    URI uri = serviceInstance.getUri();
    return restTemplate.getForObject(uri+"/payment/lb",String.class);
}

Last modification:August 18, 2020
如果觉得我的文章对你有用,请随意赞赏