SOFA 源码分析 — 预热权重
前言
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 源码分析 — 预热权重的更多相关文章
- SOFA 源码分析 —— 服务引用过程
前言 在前面的 SOFA 源码分析 -- 服务发布过程 文章中,我们分析了 SOFA 的服务发布过程,一个完整的 RPC 除了发布服务,当然还需要引用服务. So,今天就一起来看看 SOFA 是如何引 ...
- SOFA 源码分析 — 自动故障剔除
前言 集群中通常一个服务有多个服务提供者.其中部分服务提供者可能由于网络,配置,长时间 fullgc ,线程池满,硬件故障等导致长连接还存活但是程序已经无法正常响应.单机故障剔除功能会将这部分异常的服 ...
- SOFA 源码分析 — 负载均衡和一致性 Hash
前言 SOFA 内置负载均衡,支持 5 种负载均衡算法,随机(默认算法),本地优先,轮询算法,一致性 hash,按权重负载轮询(不推荐,已被标注废弃). 一起看看他们的实现(重点还是一致性 hash) ...
- SOFA 源码分析 —— 服务发布过程
前言 SOFA 包含了 RPC 框架,底层通信框架是 bolt ,基于 Netty 4,今天将通过 SOFA-RPC 源码中的例子,看看他是如何发布一个服务的. 示例代码 下面的代码在 com.ali ...
- SOFA 源码分析 — 调用方式
前言 SOFARPC 提供了多种调用方式满足不同的场景. 例如,同步阻塞调用:异步 future 调用,Callback 回调调用,Oneway 调用. 每种调用模式都有对应的场景.类似于单进程中的调 ...
- SOFA 源码分析— 事件总线
前言 大部分框架都是事件订阅功能,即观察者模式,或者叫事件机制.通过订阅某个事件,当触发事件时,回调某个方法.该功能非常的好用,而 SOFA 内部也设计了这个功能,并且内部大量使用了该功能.来看看是如 ...
- SOFA 源码分析 — 自定义线程池原理
前言 在 SOFA-RPC 的官方介绍里,介绍了自定义线程池,可以为指定服务设置一个独立的业务线程池,和 SOFARPC 自身的业务线程池是隔离的.多个服务可以共用一个独立的线程池. API使用方式如 ...
- SOFA 源码分析 — 链路数据透传
前言 SOFA-RPC 支持数据链路透传功能,官方解释: 链路数据透传功能支持应用向调用上下文中存放数据,达到整个链路上的应用都可以操作该数据. 使用方式如下,可分别向链路的 request 和 re ...
- SOFA 源码分析 —— 过滤器设计
前言 通常 Web 服务器在处理请求时,都会使用过滤器模式,无论是 Tomcat ,还是 Netty,过滤器的好处是能够将处理的流程进行分离和解耦,比如一个 Http 请求进入服务器,可能需要解析 h ...
随机推荐
- Leetcode_112_Path Sum
本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/41910495 Given a binary tree an ...
- iOS APP设计规范大全
目前最为齐全的iOS APP设计规范大全,Mark一个- 欢迎参考本文,未经许可,严禁转载!
- [Django] 单元测试小记
从前很少写单元测试了,特别是web应用.最近不知不觉喜欢起来这个事情了,发现单元测试对于软件的模块,正交性有很大促进作用,因为函数,模块写的不合理,单元测试写起来就麻烦的多呀.公司的项目一直都是用Dj ...
- Android高效率编码-第三方SDK详解系列(三)——JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送
Android高效率编码-第三方SDK详解系列(三)--JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送 很久没有更新第三方SDK这个系列了,所以更新一下这几天工作中使用到的推送, ...
- 修改DrawerLayout 和toolbar 配合navigation的颜色
大家都知道DrawerLayout 和toolbar 结合能出来高大上的效果. 使用到一个ActionBarDrawerToggle类. 那么怎么修改DrawerToggle的颜色呢,搜索了很多中文网 ...
- mysql进阶(十八)完全卸载mysql数据库图文教程
完全卸载mysql数据库图文教程 有时候MySQL不能完全卸载,这时候必须通过一些途径删除掉注册表和一些残余的文件,然后才能重新安装才可以成功! 方法/步骤 1.控制面板-->所有控制面板项-- ...
- 史上最全Android Studio快捷键 -2016-02-28
- Netmask, 子网与 CIDR (Classless Interdomain Routing)
Netmask, 子网与 CIDR (Classless Interdomain Routing) 我们前面谈到 IP 是有等级的,而设定在一般计算机系统上面的则是 Class A, B, C.现在我 ...
- IDE
IDE(Integrated Development Environment,集成开发环境).DE集成开发环境(简称IDE)软件是用于程序开发环境的应用程序,一般包括代码编辑器.编译器.调试器和图形用 ...
- unix下的ACL
acl可以针对user,组,目录默认属性(mask)来控制. acl需要文件系统支持,ext2/3,jfs,xfs等都支持. getfacl setfacl 对于mac os X系统的acl 可以使用 ...