查看源码
负载均衡轮询算法 : rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。
先来看看它的规则实现类接口,即我们要看的轮询规则 --> RoundRobinRule类
private AtomicInteger nextServerCyclicCounter;
public RoundRobinRule() {
nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
setLoadBalancer(lb);
}
通过构造方法初始化规则
① 无参的初始化了一个原子操作类,为保证线程安全;
② 有参构造类设置了加载的服务器集群LoadBalancer。
然后就是通过其中实现的choose方法来选择要指定的服务器。
其中还有一个incrementAndGetModulo方法通过自旋锁来获取服务器对应下标。
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
// 首先查看服务器集群是否为空
log.warn("no load balancer");
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(); // up 状态的服务器数
int serverCount = allServers.size(); // 服务器集群总数量
if ((upCount == 0) || (serverCount == 0)) {
// 如果没有up状态的服务器或者没有服务器,直接退出选择
log.warn("No up servers available from load balancer: " + lb);
return null;
}
int nextServerIndex = incrementAndGetModulo(serverCount); // 增加并获得,返回要选择的服务器下标
server = allServers.get(nextServerIndex); // 通过下标选择服务器
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server; // 返回选择的服务器
}
private int incrementAndGetModulo(int modulo) {
for (;;) {
// 自旋锁 得到下标
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
手写轮询规则
根据源码,写一个轮序算法,首先写一个LoadBalancer接口,根据接口写需求。
接口中有一个instances方法,传入的参数就是我们的服务器集群,返回的就是我们要选择的服务器。
public interface LoadBalancer {
ServiceInstance instances(List<ServiceInstance> instances);
}
写一个实现类,实现LoadBalancer接口。
首先设置一个全局变量,原子操作类atomicInteger,保证线程安全,减少锁的使用,根据源码的incrementAndGetModulo方法,写出我们的getAndIncrement方法,其中的current就是我们当前的atomicInteger的值,在自旋锁中,直到获取我们的next值并返回,即我们的访问次数,通过访问次数来获取服务器下标。
在instances方法就是我们要接收传入的服务器集群,通过文章第一行就说过的,
rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标
通过 --> next % 服务器数量 获取 对应选择的服务器下标,通过get方法获得对应的服务器并返回。
@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 = " + next);
return next;
}
@Override
public ServiceInstance instances(List<ServiceInstance> instances) {
int index = getAndIncrement() % instances.size();
return instances.get(index);
}
}
这里测试一下我们的轮询算法,在我们的controller中进行测试。
通过discoveryClient类通过名称发现我们的服务器集群,然后进行判断,是否为空等条件,如果都通过,就通过负载均衡算法获取对应的服务器,获取服务器的uri,调用服务器的对应方法。
@RestController
@Slf4j
public class OrderController {
@Resource
private RestTemplate restTemplate;
@Resource
private LoadBalancer loadBalancer;
@Resource
private DiscoveryClient discoveryClient;
...
@GetMapping("/consumer/payment/lb")
public String getPaymentLB() {
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if (instances == null || instances.size() <= 0) {
return null;
}
ServiceInstance instance = loadBalancer.instances(instances);
URI uri = instance.getUri();
return restTemplate.getForObject(uri + "/payment/lb", String.class);
}
}
测试结果为成功,调用的服务器轮换调用。
太久没记笔记了0.0…更新一下目前学习cloud的内容,继续努力!