Ribbon简介和基本原理请参考我的文章SpringCloud 负载均衡 - Ribbon
负载均衡轮询算法原理
其中在服务的调用者可以通过 DiscoveryClient 实例获取到注册中心上所有的服务,也可以获取到具体某个服务所有的实例,即上图的List集合
负载均衡轮询算法源码分析
Ribbon 定义了一个 负载均衡规则 接口 IRule ,所有的负载均衡规则都会实现IRule接口:
public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
其中 choose 方法是核心`,表示具体选中哪一个服务实例提供服务
IRule继承结构如下:
轮询算法的实现是 RoundRobinRule
该类的主要源码如下:
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule() {
nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
setLoadBalancer(lb);
}
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();
int serverCount = allServers.size();
if ((upCount == 0) || (serverCount == 0)) {
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;
}
/**
* Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
*
* @param modulo The modulo to bound the value of the counter.
* @return The next value.
*/
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
}
其中 choose 方法返回一个选中的服务实例,
源码并不难主要运用了 自旋锁
的原理 还有 AtomicInteger原子类
的应用,及CAS
(compareAndSet)的运用,来避免并发数据出错,
源码难点:自旋锁
请参考上面 轮询算法原理
自旋锁代码:
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
modulo表示 服务实例总数,
int current = nextServerCyclicCounter.get(); 获取请求总数
int next = (current + 1) % modulo;:获取实际调用服务器的下标
nextServerCyclicCounter.compareAndSet(current, next)
,返回一个boolen类型,如果CAS成功,就把下标返回
手写负载均衡算法
定义自己的规则接口:
public interface LoadBalance {
/**
* 从instanceList集合中根据算法返回一个ServiceInstance实例
* @param instanceList ServiceInstance集合
* @return 返回一个ServiceInstance实例
*/
ServiceInstance choseServiceInstance(List<ServiceInstance> instanceList);
}
写一个负载均衡算法类,实现 自己定义的规则接口,代码含有看注释:
/**
* 轮询算法
*/
@Component
public class MyRoundRobinLoadBalance implements LoadBalance{
private AtomicInteger totalCountRequest = new AtomicInteger(0);
// 新加一次访问,并返回最后调用的次数(自旋锁,并发安全的CAS)
public final int getAndIncrement() {
// 当前调用的次数
int current;
int next;
do {
current = totalCountRequest.get();
next = current >= Integer.MAX_VALUE ? 0 : current + 1;
} while (!totalCountRequest.compareAndSet(current, next));
return next;
}
/**
* 服务实例的集合
* @param instanceList ServiceInstance集合
* @return
*/
@Override
public ServiceInstance choseServiceInstance(List<ServiceInstance> instanceList) {
if (instanceList == null || instanceList.size() == 0) {
return null;
}
int serverSize = instanceList.size();
int totalCount = getAndIncrement();
// 算法公式:获取调用的服务的下标 = 第几次请求 % 服务器实例总数
int index = totalCount % serverSize;
return instanceList.get(index);
}
}
调用测试:
测试环境说明:
- 必须有一个注册中心,
- 必须有2个服务的提供者,并且他们的服务名一样,并且都注册到注册中心
- 存在一个服务的调用者,也注册了注册中心
以上环境可以参考我的文章SpringCloud 服务注册与发现 - Eureka详解
DiscoveryClient 该类的注入必须在启动类上加上 @EnableDiscoveryClient ,该类可以获取注册中心 上的所有服务实例
@RestController
public class OrderController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private LoadBalance loadBalance;
// SPRINGCLOUD-PAYMENT-PROVIDER:为Consul Server微服务实例名称
private static final String PAYMENT_PREFIX = "http://SPRINGCLOUD-PAYMENT-PROVIDER";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order/payment/{id}")
public CommonResult<Payment> payment(@PathVariable("id") Long id) {
// // 获取注册中心上其中一个服务,该实例下可以有多个实例
List<ServiceInstance> instances = discoveryClient.getInstances("SPRINGCLOUD-PAYMENT-PROVIDER");
// 调用负载均衡算法,获取一个访问实例
ServiceInstance serviceInstance = loadBalance.choseServiceInstance(instances);
URI uri = serviceInstance.getUri();
System.out.println("==========" + uri.toString());
return null;
}
}
浏览器调用,看控制台输出:负载均衡轮询效果已经达到