✨【微服务】SpringCloud中Ribbon的轮询(RoundRobinRule)与重试(RetryRule)策略
✨【微服务】SpringCloud中Ribbon集成Eureka实现负载均衡
✨【微服务】SpringCloud轮询拉取注册表及服务发现源码解析
✨【微服务】SpringCloud微服务续约源码解析
✨【微服务】SpringCloud微服务注册源码解析
✨【微服务】Nacos2.x服务发现?RPC调用?重试机制?
✨【微服务】Nacos通知客户端服务变更以及重试机制
✨【微服务】Nacos服务发现源码分析
✨【微服务】SpringBoot监听器机制以及在Nacos中的应用
✨【微服务】Nacos服务端完成微服务注册以及健康检查流程
✨【微服务】Nacos客户端微服务注册原理流程
✨【微服务】SpringCloud中使用Ribbon实现负载均衡的原理
✨【微服务】SpringBoot启动流程注册FeignClient
✨【微服务】SpringBoot启动流程初始化OpenFeign的入口
✨Spring Bean的生命周期
✨Spring事务原理
✨SpringBoot自动装配原理机制及过程
✨SpringBoot获取处理器流程
✨SpringBoot中处理器映射关系注册流程
✨Spring5.x中Bean初始化流程
✨Spring中Bean定义的注册流程
✨Spring的处理器映射器与适配器的架构设计
✨SpringMVC执行流程图解及源码
目录
一、前言
二、源码剖析
1、WeightedResponseTimeRule策略
1.1、初始化定时更新权重任务
1.2、计算权重任务
2、权重数据来源
2.1、维护响应数据的入口
3、如何根据权重选取服务
前面的文章也分析了Ribbon的负载均衡轮询算法的实现、重试、CAS、死循环。重试策略怎么实现的、多线程编程、延迟任务、中断。本篇文章探讨如何根据权重选取服务、如何计算权重的、定时任务、权重数据来源维护?
WeightedResponseTimeRule继承了RoundRobinRule,即继承了RoundRobinRule的功能并且进行了功能上面的扩展。
public class WeightedResponseTimeRule extends RoundRobinRule {
// 按照毫秒单位,则30秒
public static final int DEFAULT_TIMER_INTERVAL = 30 * 1000;
private int serverWeightTaskTimerInterval = DEFAULT_TIMER_INTERVAL;
// 包含从索引0到当前索引的累计权重,例如,索引2的元素包含从0到2的服务器权重之和
private volatile List accumulatedWeights = new ArrayList<>();
// 随机类,选择服务时用到
private final Random random = new Random();
// 用于定时任务
protected Timer serverWeightTimer = null;
// 用于CAS
protected AtomicBoolean serverWeightAssignmentInProgress = new AtomicBoolean(false);
String name = "unknown";
public WeightedResponseTimeRule() {
// 初始化父类
super();
}
public WeightedResponseTimeRule(ILoadBalancer lb) {
super(lb);
}
@Override
public void setLoadBalancer(ILoadBalancer lb) {
// 调用RoundRobinRule的setLoadBalancer(ILoadBalancer lb)方法
super.setLoadBalancer(lb);
if (lb instanceof BaseLoadBalancer) {
name = ((BaseLoadBalancer) lb).getName();
}
initialize(lb);
}
void initialize(ILoadBalancer lb) {
if (serverWeightTimer != null) {
serverWeightTimer.cancel();
}
serverWeightTimer = new Timer("NFLoadBalancer-serverWeightTimer-"
+ name, true);
// 默认30妙
serverWeightTimer.schedule(new DynamicServerWeightTask(), 0,
serverWeightTaskTimerInterval);
// do a initial run
ServerWeight sw = new ServerWeight();
sw.maintainWeights();
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
logger
.info("Stopping NFLoadBalancer-serverWeightTimer-"
+ name);
serverWeightTimer.cancel();
}
}));
}
}
主要工作:
- 初始化时会初始化父类
- 通过super调用父类方法做一些初始化工作,initialize(lb)初始化定时任务
- 默认30秒更新权重
class DynamicServerWeightTask extends TimerTask {
public void run() {
ServerWeight serverWeight = new ServerWeight();
try {
serverWeight.maintainWeights();
} catch (Exception e) {
logger.error("Error running DynamicServerWeightTask for {}", name, e);
}
}
}
class ServerWeight {
public void maintainWeights() {
// 获取负载均衡器
ILoadBalancer lb = getLoadBalancer();
// 预检验,防止空指针异常
if (lb == null) {
return;
}
// CAS避免更新丢失,同一时刻只允许一个线程进入
if (!serverWeightAssignmentInProgress.compareAndSet(false, true)) {
return;
}
try {
// 权重调整工作开始
logger.info("Weight adjusting job started");
// 强制类型转换,以便获取负载均衡器统计器及所有服务
AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;
// 从负载均衡器获取负载均衡统计器,该统计是在负载均衡请求响应后或异常后进行的
LoadBalancerStats stats = nlb.getLoadBalancerStats();
if (stats == null) {
// no statistics, nothing to do没有统计数据,无所事事
return;
}
double totalResponseTime = 0;
// 找到最大95% 的响应时间
for (Server server : nlb.getAllServers()) {
// this will automatically load the stats if not in cache
// 如果不在缓存中,这将自动加载统计信息
ServerStats ss = stats.getSingleServerStat(server);
totalResponseTime += ss.getResponseTimeAvg();
}
// 每个服务器的权重是(所有服务器的 responseTime 之和-responseTime) ,
// 因此响应时间越长,权重越小,选择的可能性越小
Double weightSoFar = 0.0;
// 创建新的列表和热交换引用
List finalWeights = new ArrayList<>();
// 从负载均衡器获取所有服务,然后遍历
for (Server server : nlb.getAllServers()) {
// 根据服务获取服务(在负载均衡器中捕获每个服务器(节点)的)统计信息
ServerStats ss = stats.getSingleServerStat(server);
double weight = totalResponseTime - ss.getResponseTimeAvg();
weightSoFar += weight;
finalWeights.add(weightSoFar);
}
// 更新权重
setWeights(finalWeights);
} catch (Exception e) {
logger.error("Error calculating server weights", e);
} finally {
// 类似于释放锁,以便下次执行更新权重
serverWeightAssignmentInProgress.set(false);
}
}
}
主要逻辑:
- 获取负载均衡器,预检验,防止空指针异常,如果负载均衡器不存在结束方法体
- CAS避免更新丢失,同一时刻只允许一个线程进入,如果CAS失败结束方法体
- 强制类型转换为AbstractLoadBalancer,从负载均衡器获取负载均衡统计器,该统计是在负载均衡请求响应后或异常后进行的,如果统计信息没有则结束方法体
- 从负载均衡器获取所有服务并遍历,根据服务获取服务(在负载均衡器中捕获每个服务器(节点)的)统计信息,累加服务实例的平均响应时间和为totalResponseTime
- 从负载均衡器获取所有服务,然后遍历;根据服务获取服务(在负载均衡器中捕获每个服务器(节点)的)统计信息;每个服务器的权重是(所有服务器的 responseTime 之和-responseTime) ,因此响应时间越长,权重越小,选择的可能性越小;计算的权重累加weightSoFar 得到一个区间边界值
// ServerOperation匿名内部类调用call
return operation.call(server).doOnEach(new Observer() {
private T entity;
@Override
public void onCompleted() {
recordStats(tracer, stats, entity, null);
// TODO: What to do if onNext or onError are never called?
}
@Override
public void onError(Throwable e) {
recordStats(tracer, stats, null, e);
logger.debug("Got error {} when executed on server {}", e, server);
if (listenerInvoker != null) {
listenerInvoker.onExceptionWithServer(e, context.toExecutionInfo());
}
}
@Override
public void onNext(T entity) {
this.entity = entity;
if (listenerInvoker != null) {
listenerInvoker.onExecutionSuccess(entity, context.toExecutionInfo());
}
}
private void recordStats(Stopwatch tracer, ServerStats stats, Object entity, Throwable exception) {
tracer.stop();
// 记录请求完成的信息,用于统计,如:权重等
loadBalancerContext.noteRequestCompletion(stats, entity, exception,
tracer.getDuration(TimeUnit.MILLISECONDS), retryHandler);
}
});
ServerOperation匿名内部类调用call,无论是响应成功还是错误都会记录响应时间以便计算权重
@Override
public Server choose(ILoadBalancer lb, Object key) {
// 预校验,以防空指针异常
if (lb == null) {
// 负载均衡器空则返回null
return null;
}
Server server = null;
// 循环的边界server是否null
while (server == null) {
// 获取当前引用,以防它从另一个线程更改
List currentWeights = accumulatedWeights;
// 当前线程被中断,返回null
if (Thread.interrupted()) {
return null;
}
// 从负载均衡器获取所有服务
List allList = lb.getAllServers();
// 服务总数
int serverCount = allList.size();
// 服务总数为0,返回null
if (serverCount == 0) {
return null;
}
// 初始化服务索引由0开始
int serverIndex = 0;
// 列表中的最后一个是所有权重的总和
double maxTotalWeight = currentWeights.size() == 0 ? 0 : currentWeights.get(currentWeights.size() - 1);
// 尚未命中任何服务器,总重量未初始化为回退以使用循环
if (maxTotalWeight < 0.001d || serverCount != currentWeights.size()) {
// 当没有为服务器收集足够的统计信息时,该规则将回退到使用 RoundRobinRule。
server = super.choose(getLoadBalancer(), key);
if(server == null) {
return server;
}
} else {
// 生成一个从0(包含)到 maxTotalWeight (独占)之间的随机权重
double randomWeight = random.nextDouble() * maxTotalWeight;
// 根据随机索引选择服务器索引
int n = 0;
// 遍历权重列表
for (Double d : currentWeights) {
// 如果当前权重d大于等于随机权重,则取该索引n,break跳出循环体
if (d >= randomWeight) {
serverIndex = n;
break;
} else {
// 否则继续选取
n++;
}
}
// 根据计算的索引从服务列表获取一个服务
server = allList.get(serverIndex);
}
// 服务为空继续选取
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
// 服务可用返回结束方法体
if (server.isAlive()) {
return (server);
}
// Next.服务不可用继续选取
server = null;
}
return server;
}
主要逻辑:
- 预校验,以防空指针异常。负载均衡器空则返回null
- 当前线程被中断,返回null
- 从负载均衡器获取所有服务,服务总数为0,返回null
- 列表中的最后一个是所有权重的总和maxTotalWeight,尚未命中任何服务器,总重量未初始化为回退以使用RoundRobinRule循环
- 生成一个从0(包含)到 maxTotalWeight (独占)之间的随机权重;遍历权重列表,如果当前权重d大于等于随机权重,则取该索引n(整形),break跳出循环体;否则继续;根据计算得出的索引从服务列表获取一个服务
- 如果服务为空继续选取
- 如果选取的服务可用返回结束方法体
- 如果选取到的服务不可用,则置空null再次选取