基于开源Tars的动态负载均衡实践
一、背景
vivo 互联网领域的部分业务在微服务的实践过程当中基于很多综合因素的考虑选择了TARS微服务框架。
官方的描述是:TARS是一个支持多语言、内嵌服务治理功能,与Devops能很好协同的微服务框架。我们在开源的基础上做了很多适配内部系统的事情,比如与CICD构建发布系统、单点登录系统的打通,但不是这次我们要介绍的重点。这里想着重介绍一下我们在现有的负载均衡算法之外实现的动态负载均衡算法。
二、什么是负载均衡
维基百科的定义如下:负载平衡(Load balancing)是一种电子计算机技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。使用带有负载平衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。负载平衡服务通常是由专用软件和硬件来完成。主要作用是将大量作业合理地分摊到多个操作单元上进行执行,用于解决互联网架构中的高并发和高可用的问题。
这段话很好理解,本质上是一种解决分布式服务应对大量并发请求时流量分配问题的方法。
三、TARS 支持哪些负载均衡算法
TARS支持三种负载均衡算法,基于轮询的负载均衡算法、基于权重分配的轮询负载均衡算法、一致性hash负载均衡算法。函数入口是selectAdapterProxy,代码在 TarsCpp 文件里,感兴趣的可以从这个函数开始深入了解。
3.1 基于轮询的负载均衡算法
基于轮询的负载均衡算法实现很简单,原理就是将所有提供服务的可用 ip 形成一个调用列表。当有请求到来时将请求按时间顺序逐个分配给请求列表中的每个机器,如果分配到了最后列表中的最后一个节点则再从列表第一个节点重新开始循环。这样就达到了流量分散的目的,尽可能的平衡每一台机器的负载,提高机器的使用效率。这个算法基本上能满足大量的分布式场景了,这也是TARS默认的负载均衡算法。
但是如果每个节点的处理能力不一样呢?虽然流量是均分的,但是由于中间有处理能力较弱的节点,这些节点仍然存在过载的可能性。于是我们就有了下面这种负载均衡算法。
3.2 基于权重分配的轮询负载均衡算法
权重分配顾名思义就是给每个节点赋值一个固定的权重,这个权重表示每个节点可以分配到流量的概率。举个例子,有5个节点,配置的权重分别是4,1,1,1,3,如果有100个请求过来,则对应分配到的流量也分别是40,10,10,10,30。这样就实现了按配置的权重来分配客户端的请求了。这里有个细节需要注意一下,在实现加权轮询的时候一定要是平滑的。也就是说假如有10个请求,不能前4次都落在第1个节点上。
业界已经有了很多平滑加权轮询的算法,感兴趣的读者可以自行搜索了解。
3.3 一致性Hash
很多时候在一些存在缓存的业务场景中,我们除了对流量平均分配有需求,同时也对同一个客户端请求应该尽可能落在同一个节点上有需求。
假设有这样一种场景,某业务有1000万用户,每个用户有一个标识id和一组用户信息。用户标识id和用户信息是一一对应的关系,这个映射关系存在于DB中,并且其它所有模块需要去查询这个映射关系并从中获取一些必要的用户字段信息。在大并发的场景下,直接请求DB系统肯定是抗不住的,于是我们自然就想到用缓存的方案去解决。是每个节点都需要去存储全量的用户信息么?虽然可以,但不是最佳方案,万一用户规模从1000万上升到1亿呢?很显然这种解决方案随着用户规模的上升,变得捉襟见肘,很快就会出现瓶颈甚至无法满足需求。于是就需要一致性hash算法来解决这个问题。一致性hash算法提供了相同输入下请求尽可能落在同一个节点的保证。
为什么说是尽可能?因为节点会出现故障下线,也有可能因为扩容而新增,一致性hash算法是能够在这种变化的情况下做到尽量减少缓存重建的。TARS使用的hash算法有两种,一种是对key求md5值后,取地址偏移做异或操作,另一种是ketama hash。
四、为什么需要动态负载均衡?
我们目前的服务大部分还是跑在以虚拟机为主的机器上,因此混合部署(一个节点部署多个服务)是常见现象。在混合部署的情况下,如果一个服务代码有bug了占用大量的CPU或内存,那么必然跟他一起部署的服务都会受到影响。
那么如果仍然采用上述三种负载均衡算法的情况下,就有问题了,被影响的机器仍然会按指定的规则分配到流量。也许有人会想,基于权重的轮询负载均衡算法不是可以配置有问题的节点为低权重然后分配到很少的流量么?确实可以,但是这种方法往往处理不及时,如果是发生在半夜呢?并且在故障解除后需要再手动配置回去,增加了运维成本。因此我们需要一种动态调整的负载均衡算法来自动调整流量的分配,尽可能的保证这种异常情况下的服务质量。
从这里我们也不难看出,要实现动态负载均衡功能的核心其实只需要根据服务的负载动态的调整不同节点的权重就可以了。这其实也是业界常用的一些做法,都是通过周期性地获取服务器状态信息,动态地计算出当前每台服务器应具有的权值。
五、动态负载均衡策略
在这里我们采用的也是基于各种负载因子的方式对可用节点动态计算权重,将权重返回后复用TARS静态权重节点选择算法。我们选择的负载因子有:接口5分钟平均耗时/接口5分钟超时率/接口5分钟异常率/CPU负载/内存使用率/网卡负载。负载因子支持动态扩展。
整体功能图如下:
5.1 整体交互时序图
rpc调用时,EndpointManager定期获得可用节点集合。节点附带权重信息。业务在发起调用时根据业务方指定的负载均衡算法选择对应的节点;
RegistrServer定期从db/监控中习获取超时率和平均耗时等信息。从其它平台(比如CMDB)获得机器负载类信息,比如cpu/内存等。所有计算过程线程异步执行缓存在本地;
EndpointManager根据获得的权重执行选择策略。下图为节点权重变化对请求流量分配的影响:
5.2 节点更新和负载均衡策略
节点所有性能数据每60秒更新一次,使用线程定时更新;
计算所有节点权重值和取值范围,存入内存缓存;
主调获取到节点权重信息后执行当前静态权重负载均衡算法选择节点;
兜底策略:如果所有节点要重都一样或者异常则默认采用轮询的方式选择节点;
5.3 负载的计算方式
负载计算方式:每个负载因子设定权重值和对应的重要程度(按百分比表示),根据具体的重要程度调整设置,最后会根据所有负载因子算出的权重值乘对应的百分比后算出总值。比如:耗时权重为10,超时率权重为20,对应的重要程度分别为40%和60%,则总和为10 * 0.4 + 20 * 0.6 = 16。对应每个负载因子计算的方式如下(当前我们只使用了平均耗时和超时率这两个负载因子,这也是最容易在TARS当前系统中能获取到的数据):
1、按每台机器在总耗时的占比反比例分配权重:权重 = 初始权重 *(耗时总和 - 单台机器平均耗时)/ 耗时总和(不足之处在于并不完全是按耗时比分配流量);
2、超时率权重:超时率权重 = 初始权重 - 超时率 * 初始权重 * 90%,折算90%是因为100%超时时也可能是因为流量过大导致的,保留小流量试探请求;
对应代码实现如下:
void LoadBalanceThread::calculateWeight(LoadCache &loadCache)
{
for (auto &loadPair : loadCache)
{
ostringstream log;
const auto ITEM_SIZE(static_cast<int>(loadPair.second.vtBalanceItem.size()));
int aveTime(loadPair.second.aveTimeSum / ITEM_SIZE);
log << "aveTime: " << aveTime << "|"
<< "vtBalanceItem size: " << ITEM_SIZE << "|";
for (auto &loadInfo : loadPair.second.vtBalanceItem)
{
// 按每台机器在总耗时的占比反比例分配权重:权重 = 初始权重 *(耗时总和 - 单台机器平均耗时)/ 耗时总和
TLOGDEBUG("loadPair.second.aveTimeSum: " << loadPair.second.aveTimeSum << endl);
int aveTimeWeight(loadPair.second.aveTimeSum ? (DEFAULT_WEIGHT * ITEM_SIZE * (loadPair.second.aveTimeSum - loadInfo.aveTime) / loadPair.second.aveTimeSum) : 0);
aveTimeWeight = aveTimeWeight <= 0 ? MIN_WEIGHT : aveTimeWeight;
// 超时率权重:超时率权重 = 初始权重 - 超时率 * 初始权重 * 90%,折算90%是因为100%超时时也可能是因为流量过大导致的,保留小流量试探请求
int timeoutRateWeight(loadInfo.succCount ? (DEFAULT_WEIGHT - static_cast<int>(loadInfo.timeoutCount * TIMEOUT_WEIGHT_FACTOR / (loadInfo.succCount
+ loadInfo.timeoutCount))) : (loadInfo.timeoutCount ? MIN_WEIGHT : DEFAULT_WEIGHT));
// 各类权重乘对应比例后相加求和
loadInfo.weight = aveTimeWeight * getProportion(TIME_CONSUMING_WEIGHT_PROPORTION) / WEIGHT_PERCENT_UNIT
+ timeoutRateWeight * getProportion(TIMEOUT_WEIGHT_PROPORTION) / WEIGHT_PERCENT_UNIT ;
log << "aveTimeWeight: " << aveTimeWeight << ", "
<< "timeoutRateWeight: " << timeoutRateWeight << ", "
<< "loadInfo.weight: " << loadInfo.weight << "; ";
}
TLOGDEBUG(log.str() << "|" << endl);
}
}
相关代码实现在RegistryServer,代码文件如下图:
核心实现是LoadBalanceThread类,欢迎大家指正。
5.4 使用方式
- 在Servant管理处配置-w -v 参数即可支持动态负载均衡,不配置则不启用。
如下图:
- 注意:需要全部节点启用才生效,否则rpc框架处发现不同节点采用不同的负载均衡算法则强制将所有节点调整为轮询方式。
六、动态负载均衡适用的场景
如果你的服务是跑在Docker容器上的,那可能不太需要动态负载均衡这个特性。直接使用Docker的调度能力进行服务的自动伸缩,或者在部署上直接将Docker分配的粒度拆小,让服务独占docker就不存在相互影响的问题了。如果服务是混合部署的,并且服务大概率会受到其它服务的影响,比如某个服务直接把cpu占满,那建议开启这个功能。
七、下一步计划
目前的实现中只考虑了平均耗时和超时率两个因子,这能在一定程度上反映服务能力提供情况,但不够完全。因此,未来我们还会考虑加入cpu使用情况这些能更好反映节点负载的指标。以及,在主调方根据返回码来调整权重的一些策略。
最后也欢迎大家与我们讨论交流,一起为TARS开源做贡献。
作者:vivo互联网服务器团队-Yang Minshan
基于开源Tars的动态负载均衡实践的更多相关文章
- 基于Consul+Upsync+Nginx实现动态负载均衡
基于Consul+Upsync+Nginx实现动态负载均衡 1.Consul环境搭建 下载consul_0.7.5_linux_amd64.zip到/usr/local/src目录 cd /usr/l ...
- 【Nginx】基于Consul+Upsync+Nginx实现动态负载均衡
一.Http动态负载均衡 什么是动态负载均衡 动态负载均衡实现方案 常用服务器注册与发现框架 二.Consul快速入门 Consul环境搭建 三.nginx-upsync-module nginx-u ...
- Consul+upsync+Nginx实现动态负载均衡 摘自https://blog.csdn.net/qq_29247945/article/details/80787014
传统感念:每次修改完nginx配置文件,要重启nginx 动态感念:每次修改完nginx配置信息,不需要重启,nginx实时读取配置信息. Nginx: 反向代理和负载均衡 Consul:是用go编写 ...
- 《nginx 三》实现nginx的动态负载均衡——实战
Http动态负载均衡 什么是动态负载均衡 传统的负载均衡,如果Upstream参数发生变化,每次都需要重新加载nginx.conf文件, 因此扩展性不是很高,所以我们可以采用动态负载均衡,实现Upst ...
- Nginx(四) nginx+consul+upasync 在ubnutu18带桌面系统 实现动态负载均衡
1.1 什么是动态负载均衡 传统的负载均衡,如果Upstream参数发生变化,每次都需要重新加载nginx.conf文件,因此扩展性不是很高,所以我们可以采用动态负载均衡,实现Upstream可配置化 ...
- Consul+upsync+Nginx 动态负载均衡
1,动态负载均衡 传统的负载均衡,如果修改了nginx.conf 的配置,必须需要重启nginx 服务,效率不高.动态负载均衡,就是可配置化,动态化的去配置负载均衡. 2,实现方案 1. Consul ...
- 通过Nginx、Consul、Upsync实现动态负载均衡和服务平滑发布
前提 前段时间顺利地把整个服务集群和中间件全部从UCloud迁移到阿里云,笔者担任了架构和半个运维的角色.这里详细记录一下通过Nginx.Consul.Upsync实现动态负载均衡和服务平滑发布的核心 ...
- 基于Nginx反向代理及负载均衡
基于Nginx反向代理及负载均衡 参考:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass 只要没有被启用,默认就是 ...
- Nginx 实现动态负载均衡(Nginx-1.10.1 + Consul v0.6.4)
一直也没有找到合适的类似Socat + Haproxy 的组合能用在Nginx,后来发现了Nginx的几个模块,但是也存在各种不足. 而且Nginx 在大流量的情况下nginx -s reload 是 ...
随机推荐
- Linux下Matlab的安装
1 概述 笔者环境Manjaro,本来想直接通过yay安装的,但无奈失败了,于是直接从官网下载进行安装. 2 下载安装包 Matlab官网可以戳这里,点击右上角的Get MATLAB就可以下载了: 没 ...
- linux 更新python3.8
1 下载源码 地址 选版本下载即可,目前最新为3.8.2版本. 2 解压 tar -zxvf Python-3.8.2.tgz cd Python-3.8.2 3 新建安装目录 安装目录在/usr/l ...
- 4. selectKey语句属性配置细节
selectKey语句属性配置细节:
- NumPy之:结构化数组详解
目录 简介 结构化数组中的字段field 结构化数据类型 创建结构化数据类型 从元组创建 从逗号分割的dtype创建 从字典创建 操作结构化数据类型 Offsets 和Alignment Field ...
- LA3029最大子矩阵
题意: 给你一个n*m的矩阵<每个格子不是'F'就是'R'>,让你找一个最大的'F'矩阵,输出他的面积*3. 思路: 比较经典的题目了,现在想起来比较好想,以前的话 ...
- CVE-2014-3153分析和利用
本文是结合参考资料对CVE-2014-3153的分析,当然各位看官可以看最后的资料,他们写的比我好. 在看CVE-2014-3153之前我们用参考资料4中例子来熟悉下这类漏洞是如何产生的: /** * ...
- CVE-2014-7911学习笔记
工作日分析的差不多了,写个标题周末搞
- K8s Scheduler 在调度 pod 过程中遗漏部分节点的问题排查
问题现象 在TKE控制台上新建版本为v1.18.4(详细版本号 < v1.18.4-tke.5)的独立集群,其中,集群的节点信息如下: 有3个master node和1个worker node, ...
- 测试的V模型和W模型
V模型 :后测试 优点: 1.每一阶段都清晰明了,便于把控开发的每一个过程. 2.既包含了单元测试又包含了系统测试 缺点: 1.测试介入的比较晚,所以开发前期的缺陷无从修改. 2.开发和测试串行. W ...
- C++ primer plus读书笔记——第2章 开始学习C++
第2章 开始学习C++ 1. endl确保程序继续运行前刷新输出(将其立即显示在屏幕上),而使用"\n"不提供这样的保证,这意味着在有些系统中,有时可能在您输入信息后才会出现提示. ...