前言

SOFA-RPC 支持根据权重对服务进行预热功能,具体地址:预热权重.

引用官方文档:

预热权重功能让客户端机器能够根据服务端的相应权重进行流量的分发。该功能也常被用于集群内少数机器的启动场景。利用流量权重功能在短时间内对服务端机器进行预热,然后再接收正常的流量比重。 运行机制如下:

1.服务端服务在启动时会将自身的预热时间,预热期内权重,预热完成后的正常权重推送给服务注册中心。如上图 ServiceB 指向 Service Registry 。

2.客户端在引用服务的时候会获得每个服务实例的预热权重信息。如上图 Service Registry 指向 client 。

3.客户端在进行调用的时候会根据服务所在地址的预热时期所对应的权重进行流量分发。如上图 client 指向 ServiceA 和 ServiceB 。 ServiceA 预热完毕,权重默认 100 , ServiceB 处于预热期,权重为 10,因此所承受流量分别为 100%110 和 10%110 。

如何使用

该功能使用方式如下。

ProviderConfig<HelloWordService> providerConfig = new ProviderConfig<HelloWordService>()
.setWeight(100)
.setParameter(ProviderInfoAttrs.ATTR_WARMUP_WEIGHT,"10")
.setParameter(ProviderInfoAttrs.ATTR_WARM_UP_END_TIME,"12000");

如上,该服务的预热期为12s,在预热期内权重为10,预热期结束后的正常权重为100。如果该服务一共发布在两个机器A,B上,A机器正处于预热期内,并使用上述配置,B已经完成预热,正常权重为200。那么客户端在调用的时候,此时流量分发的比重为10:200,A机器预热结束后,流量分发比重为100:200。 在SOFABoot中,如下配置预热时间,预热期间权重和预热完后的权重即可。

<sofa:reference id="sampleRestFacadeReferenceBolt" interface="com.alipay.sofa.endpoint.facade.SampleFacade">
<sofa:binding.bolt>
<sofa:global-attrs weight="100" warm-up-time="10000" warm-up-weight="1000"/>
</sofa:binding.bolt>
</sofa:reference>

再来看看源码实现。

源码分析

从 demo 中看,SOFA 需要在 ProviderConfig 中配置属性,而这些属性都是保存在一个 Map 中。

代码:

 public S setParameter(String key, String value) {
if (parameters == null) {
parameters = new ConcurrentHashMap<String, String>();
}
if (value == null) {
parameters.remove(key);
} else {
parameters.put(key, value);
}
return castThis();
}

当发布服务的时候,这个 Map 会被发布到注册中心。具体代码如下:

    protected void doRegister(String appName, String serviceName, ProviderInfo providerInfo) {
if (LOGGER.isInfoEnabled(appName)) {
LOGGER.infoWithApp(appName, LogCodes.getLog(LogCodes.INFO_ROUTE_REGISTRY_PUB, serviceName));
}
//{service : [provider...]}
ProviderGroup oldGroup = memoryCache.get(serviceName);
if (oldGroup != null) { // 存在老的key
oldGroup.add(providerInfo);
} else { // 没有老的key,第一次加入
List<ProviderInfo> news = new ArrayList<ProviderInfo>();
news.add(providerInfo);
memoryCache.put(serviceName, new ProviderGroup(news));
}
// 备份到文件 改为定时写
needBackup = true;
doWriteFile(); if (subscribe) {
notifyConsumerListeners(serviceName, memoryCache.get(serviceName));
}
}

上面的代码中,提供者会将 providerInfo 的信息写到本地文件(注册中心)中。

而消费者则会从注册中心订阅服务列表的信息。具体代码如下:

    @Override
public List<ProviderGroup> subscribe(ConsumerConfig config) {
String key = LocalRegistryHelper.buildListDataId(config, config.getProtocol());
List<ConsumerConfig> listeners = notifyListeners.get(key);
if (listeners == null) {
listeners = new ArrayList<ConsumerConfig>();
notifyListeners.put(key, listeners);
}
listeners.add(config);
// 返回已经加载到内存的列表(可能不是最新的)
ProviderGroup group = memoryCache.get(key);
if (group == null) {
group = new ProviderGroup();
memoryCache.put(key, group);
}
return Collections.singletonList(group);
}

上面这段代码会被 DefaultConsumerBootstrap 调用,根据消费者的配置信息,生成一个 key,然后将消费者添加到通知列表中(当数据变化时,通知消费者,由定时任务执行)。

然后,从内存中取出key 对应的服务分组,并返回集合(就是提供者注册的信息)。

这段代码会在 AbstractCluster 的 init 方法中调用—— List<ProviderGroup> all = consumerBootstrap.subscribe();

服务分组的数据结构是 ProviderInfo,是一个抽象的服务提供列表,其中包含服务的信息,比如地址,协议类型,主机地址,端口,路径,版本,动态参数,静态参数,服务状态等等,其中就包括权重

获取权重的方法如下:

public int getWeight() {
ProviderStatus status = getStatus();
if (status == ProviderStatus.WARMING_UP) {
try {
// 还处于预热时间中
Integer warmUpWeight = (Integer) getDynamicAttr(ProviderInfoAttrs.ATTR_WARMUP_WEIGHT);
if (warmUpWeight != null) {
return warmUpWeight;
}
} catch (Exception e) {
return weight;
}
}
return weight;
}

注意 getStatus 方法:

public ProviderStatus getStatus() {
if (status == ProviderStatus.WARMING_UP) {
if (System.currentTimeMillis() > (Long) getDynamicAttr(ProviderInfoAttrs.ATTR_WARM_UP_END_TIME)) {
// 如果已经过了预热时间,恢复为正常
status = ProviderStatus.AVAILABLE;
setDynamicAttr(ProviderInfoAttrs.ATTR_WARM_UP_END_TIME, null);
}
}
return status;
}

逻辑如下:

获取服务状态,如果是预热状态,则获取预热状态的权重值,反之,如果不是,反之正常值(默认 100)。

获取状态的方法则是判断时间,如果当前时间大于预热时间,则修改状态为可用。并删除动态参数列表中的“预热时间”。

那么,什么时候会获取权重呢?

如果看过之前文章的同学肯定知道,在负载均衡的时候,会调用。

我们看看默认的随机均衡算法。还记得当时,楼主有个地方不是很明白,我们要根据权重随机,当时看来,并没有什么用处,今天明白了。再上一遍代码吧:

@ AbstractLoadBalancer.java
protected int getWeight(ProviderInfo providerInfo) {
// 从provider中或得到相关权重,默认值100
return providerInfo.getWeight() < 0 ? 0 : providerInfo.getWeight();
}

获取权重,默认 100.

再看随机算法的 doSelect 方法。

@ RandomLoadBalancer.java
@Override
public ProviderInfo doSelect(SofaRequest invocation, List<ProviderInfo> providerInfos) {
ProviderInfo providerInfo = null;
int size = providerInfos.size(); // 总个数
int totalWeight = 0; // 总权重
boolean isWeightSame = true; // 权重是否都一样
for (int i = 0; i < size; i++) {
int weight = getWeight(providerInfos.get(i));
totalWeight += weight; // 累计总权重
if (isWeightSame && i > 0 && weight != getWeight(providerInfos.get(i - 1))) {
isWeightSame = false; // 计算所有权重是否一样
}
}
if (totalWeight > 0 && !isWeightSame) {
// 如果权重不相同且权重大于0则按总权重数随机
int offset = random.nextInt(totalWeight);
// 并确定随机值落在哪个片断上
for (int i = 0; i < size; i++) {
offset -= getWeight(providerInfos.get(i));
if (offset < 0) {
providerInfo = providerInfos.get(i);
break;
}
}
} else {
// 如果权重相同或权重为0则均等随机
providerInfo = providerInfos.get(random.nextInt(size));
}
return providerInfo;
}

首先判断各个服务的权重是否相同,如果不同,进入第二个 if。

关键点来了,如果权重不同,那么从总的权重中,随机一个数,一次从服务列表的权重递减。知道该值小于0,那么就使用该服务。

这样就能大致保证权重小的被击中的几率较小。具体取决于 Java 的随机算法,但是我们还是比较相信 Java 的。

我们来推倒一下这个算法。

假设有 A, B, C, 3 个服务,每个服务默认权重 100,其中 C 现在处于预热阶段,则 C 的权重等于 10.

那么总权重 210。

随机一个数,

假设是 199,那么当这个算法运行结束,C,永远不会被选择到;

假设这个随机数是 201,那么当这个算法运行结束,极有可能会击中 C(C 在最后一位,即集合的排序是倒序)。但我好像没有从代码中看到倒序排序。

假设是倒叙的,那么,C 被击中的概率为 10/210。符合 SOFA 文档的介绍。

总结

现在看来,预热权重还是挺简单的,但最好要保证最小的权重放到最后,这样能够更加完美,保证公平。我已经在 SOFA 上提了这个 issue。

今天就到这里,bye!!!

重要更新

先说结论:SOFA 的权重算法没有漏洞。

关于之前说的:应该把最小权重放到最后的说法,是错误的。

重新推导:

假设有 A, B, C, 3 个服务,每个服务默认权重 100,其中 C 现在处于预热阶段,则 C 的权重等于 10.

那么总权重 210。

如果C落在第一位,那么一定会选中C的情况是权重落在0-9之间;

如果C落在第二位,那么一定会选中C的情况是权重落在100-109之间;

如果C是在第三位,那么一定会选中C的情况是权重落在200-209;

无论如何,都能保证权重是 10/210。

SOFA 源码分析 — 预热权重的更多相关文章

  1. SOFA 源码分析 —— 服务引用过程

    前言 在前面的 SOFA 源码分析 -- 服务发布过程 文章中,我们分析了 SOFA 的服务发布过程,一个完整的 RPC 除了发布服务,当然还需要引用服务. So,今天就一起来看看 SOFA 是如何引 ...

  2. SOFA 源码分析 — 自动故障剔除

    前言 集群中通常一个服务有多个服务提供者.其中部分服务提供者可能由于网络,配置,长时间 fullgc ,线程池满,硬件故障等导致长连接还存活但是程序已经无法正常响应.单机故障剔除功能会将这部分异常的服 ...

  3. SOFA 源码分析 — 负载均衡和一致性 Hash

    前言 SOFA 内置负载均衡,支持 5 种负载均衡算法,随机(默认算法),本地优先,轮询算法,一致性 hash,按权重负载轮询(不推荐,已被标注废弃). 一起看看他们的实现(重点还是一致性 hash) ...

  4. SOFA 源码分析 —— 服务发布过程

    前言 SOFA 包含了 RPC 框架,底层通信框架是 bolt ,基于 Netty 4,今天将通过 SOFA-RPC 源码中的例子,看看他是如何发布一个服务的. 示例代码 下面的代码在 com.ali ...

  5. SOFA 源码分析 — 调用方式

    前言 SOFARPC 提供了多种调用方式满足不同的场景. 例如,同步阻塞调用:异步 future 调用,Callback 回调调用,Oneway 调用. 每种调用模式都有对应的场景.类似于单进程中的调 ...

  6. SOFA 源码分析— 事件总线

    前言 大部分框架都是事件订阅功能,即观察者模式,或者叫事件机制.通过订阅某个事件,当触发事件时,回调某个方法.该功能非常的好用,而 SOFA 内部也设计了这个功能,并且内部大量使用了该功能.来看看是如 ...

  7. SOFA 源码分析 — 自定义线程池原理

    前言 在 SOFA-RPC 的官方介绍里,介绍了自定义线程池,可以为指定服务设置一个独立的业务线程池,和 SOFARPC 自身的业务线程池是隔离的.多个服务可以共用一个独立的线程池. API使用方式如 ...

  8. SOFA 源码分析 — 链路数据透传

    前言 SOFA-RPC 支持数据链路透传功能,官方解释: 链路数据透传功能支持应用向调用上下文中存放数据,达到整个链路上的应用都可以操作该数据. 使用方式如下,可分别向链路的 request 和 re ...

  9. SOFA 源码分析 —— 过滤器设计

    前言 通常 Web 服务器在处理请求时,都会使用过滤器模式,无论是 Tomcat ,还是 Netty,过滤器的好处是能够将处理的流程进行分离和解耦,比如一个 Http 请求进入服务器,可能需要解析 h ...

随机推荐

  1. Java中Set的contains()方法

    Java中Set的contains()方法 -- hashCode与equals方法的约定及重写原则 翻译人员: 铁锚 翻译时间: 2013年11月5日 原文链接: Java hashCode() a ...

  2. (NO.00001)iOS游戏SpeedBoy Lite成形记(二十五)

    每次压赌要打开弹出菜单还是让人略觉不爽,下面我们再添加一个随机押注的按钮:自动随机选择选手和下赌金额. 打开spriteBuilder,修改GameInterface.ccb的界面,在Run按钮旁边添 ...

  3. Spring获取bean的步骤

    1 处理&问题 2 去chache里找 3 处理对象A依赖对象B的问题 4 生成bean   4.1 处理方法注入 ------lookup-method   4.2 如果类实现了Instan ...

  4. Html5的学习之旅-Html5的web Storage概述(16)

    在html5中除了canvas,另外一个非常重要的功能是客户端本地存储web storage,之前可以在用户端Cookies存贮用户名等信息,后来发现Cookies存储存在以下问题: 大小:Cooki ...

  5. Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局

    Android实训案例(八)--单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局 阿法狗让围棋突然就被热议了,鸿洋大神也顺势出了篇五子棋单机游戏的视频,我看到了就像膜拜膜拜,就 ...

  6. Cocos2D的平台检查宏

    为了避免在非iOS平台包含UIKit.h文件,需要在Prefix.pch文件中添加一个条件判断: #if __CC_PLATFORM_IOS #import <UIKit/UIKit.h> ...

  7. ARC时代的内存管理

    什么是ARC Automatic Reference Counting (ARC) is a compiler feature that provides automatic memory manag ...

  8. node.js 抓取

    http://blog.csdn.net/youyudehexie/article/details/11910465 http://www.tuicool.com/articles/z2YbAr ht ...

  9. LM**项目开发感悟

    LM**项目开发感悟 经过一个多月的项目开发,自己主要负责服务端业务逻辑的实现.服务端采用纯servlet完成,自己是在已有的项目架构上进行编程,对于所使用的架构,自己还没有认真的研究过,但明白其用到 ...

  10. 【一天一道LeetCode】#4 Median of Two Sorted Arrays

    一天一道LeetCode (一)题目 There are two sorted arrays nums1 and nums2 of size m and n respectively. Find th ...