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
例如我们现在有两台机子去负载均衡
请求次数 | 计算公式 | 取得下标 |
---|---|---|
1 | 1%2=1 | 对应127.0.0.1:8001 |
2 | 2%2=0 | 对应127.0.0.1:8002 |
3 | 3%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);
}