这篇文章是CMU Intel lab在2011年发表的,它高屋建瓴地提出:空间复杂度下界为O(nlogn)的cache(n为后端节点数)即可保证集群服务的负载均衡可靠性。建模和证明的过程非常巧妙,对cache design会很有启发。
在大规模的云计算服务中,为了避免后端节点过早暴露性能瓶颈、保证服务的SLO,以及更好地水平扩展,通常会在应用请求到达时经由一个load balancer处理,将请求平滑均匀地分发给后端节点。优秀的负载均衡能力是系统高吞吐,低延迟的前提。
但在生产环境中,没有cache加持的load balancer只能是阿克琉斯之踵:随著集群规模的动态变更,load balancer的partition规则不能保证负载被均匀地划分给各个后端节点;此外,请求负载的类型和分布无法预测,不排除DoS攻击的可能。系统设计者必须很小心地选择/组合load balancing策略,否则不同规格后端节点的处理能力将很容易成为系统瓶颈,被skewed load洪流击垮。
《Small Cache, Big Effect》提出:在load balancer中集成一层空间复杂度下界为O(nlogn)的cache(n为后端节点数!)来缓存热点数据,即可在显著提升load balancer吞吐量的同时,保证到达后端节点的请求负载是均匀分布的。
文中还给出了漂亮的证明过程和模拟结果,非常反直觉对吧?我们往下看。
简单讲一下处理负载均衡的两种方式:
为了模拟skewed load,文中假设了一个对抗型的请求模式(adversarial workload),请求要尽可能旁路cache,直接命中后端节点,与load balancer呈一个攻防态势。后文直接简称对抗模式。
首先对模型做几个假设吧:
再定义一些证明过程需要用到的符号,请别右上角:
请求的范围是所有key或者key的子集,那么对抗模式的请求策略可以用以下分布表示:
p1,p2 ... pm即各个key的请求概率,和为1。为了方便证明,给它们排序一下,默认p1到pm降序排列。因为前c个key一定会被缓存,所以S实际上呈如下分布:
对抗模式需要研究的是如何让分布在 p(c+1)~p(m) 这个范围的key,best effort地向后端节点发起请求,设这个临界的概率为h。因为同一个key一定会被hash到同一个节点(假设此过程节点数不变),所以对抗模式通过构造uncached keys对某个或某几个节点发起skewed queries,企图让这些后端节点达到最大负载,直到不可用。
这里再引入一个定理:
某两个未缓存的keyi和keyi, ,对抗模式可以构造一对新的概率:
实质就是负载可以在节点间转移,证明比较简单,感兴趣的同学可以看参考资料中的原文附录。对抗模式通过lantency等特征来判断key的热度,不断尝试转移负载,并调整keys的概率分布,尽可能多地构造请求概率同样为h的uncached keys,一波操作之后,设最后只剩下x个key。
此时能对系统产生威胁的keys的范围为(c+1)~(x),共x-c个,如下图所示。
前面的假设中有一条,对每个key的请求都会随机分配给某个后端节点,这是经典的Balls into Bins模型:
M个ball依次随机地投入N个bin中(queries-for-keys依次随机地被分配给某个后端节点)。
当balls数量远大于bins时,负载最大的一个bin中balls数目有极大的概率遵循以下Balls into Bins模型的分布(硬核推导见参考资料中的原文):
,是一个常系数。在我们的场景中,N = n,M = x-c,那么负载最大的一个节点被分配的keys数遵循以下分布:
更近一步!假设对抗模式发起速率为R的请求,前x-1个key(那些请求概率为h的key)被平均分配到的速率为 ,那么单节点的负载上界为:
若请求被完全均匀地分发给后端节点,那么每个后端节点处理的请求速率为 ,为了得到这样的运算元,不等式的右侧乘上 ,得到下一行的变换式。
在系统设计阶段,我们不知道哪个节点会承受最大的负载,所以需要为每个节点都预留最大负载所需的资源,严重拉低了整体的资源利用率,显然无法被接受。
对抗模式为了达成目的,要继续优化这个不等式,确定x值使得可能的负载最大化,R和n都可视作常量的话,即求 这部分的最大值,换元求解一下可得函数极大值对应的x,这个x值也是对抗模式应该选取的最优值:
把 代入原来的负载上界不等式,可以得到一个更简洁清晰的式子:
那么单节点的最大吞吐量(最大负载)可以表示为:
这个式子已经代表了cache size(c)和单节点最大负载的关系。为了让最大负载与节点数n无关,把根号下的分式因子消掉,只需要取 ,k是一个常量系数。
O(nlogn)的空间复杂度是这么得出的!最终独立于节点数n的单节点max-load为:
可以通过调整系数k缩放max-load。对于异构的后端节点,可以参考负载静态处理方式中提到的:把一个physical node拆分成多个virtual nodes。
为每个节点预留max-load所需的资源,即可保证对抗模式发起的请求永远不会饱和。
以上模型还是有很多理想条件约束的,需要丢到模拟环境里摩擦一下,paper的作者利用1个高性能的前端节点和85个普通后端节点搭建了一个FAWN-KV集群,每个后端节点承担100k的kv键值对存储(key 20bytes / value 128bytes),所有节点与同一个交换机相连,网路不会成为瓶颈。
实验结果非常惊艳:
重点关注adversarial workload和uniform workload,可以明显看到加了cache之后,adversarial workload的吞吐和uniform workload几乎重合,完美符合推演。并且在cache加持下,这三种workload的吞吐和节点数n都呈线性关系。
建模和模拟共同验证了这么一层薄薄的small-fast-cache对负载均衡效果的重大影响,我感觉它在load balancer中像是扮演了一个「filter」的角色,skewed load经由cache过滤,以足够uniform的分布被后端节点处理,很神奇但又很合理。
建模的过程遵循的是朴素的Balls into Bins模型,如果采用The Power of Two Random Choices的方法,max-load还可以进一步降低:
在balls into bins的基础上,一次选择d个bins(d≥2),把ball投入负载最低的bin中
The Power of Two Random Choices模型也很有意思,并且在负载均衡中已经成熟应用,大家可以参见参考资料自行了解。
当然了,建模过程中的很多假设是不能直接搬到真实环境中的,比如「cache只缓存最热的keys」,假设了一个perfect caching policy,但在现实中几乎做不到,诸如LRU、LRU的变种,这些缓存策略也只能在特定场景下做到逼近完美,并且维护key的状态,缓存的置换,保证cache consistency,都会有额外的开销;再比如「后端节点处理单次请求负载的时间都一致」,显然这在真实环境中无法保证。
除此之外,网路,多级缓存设计,读写分离等等都会成为影响最终性能的因素。
也许对于部署在纯应用层的load balancer, 的指标意义没有特别大,毕竟内存可以放心糟蹋,但对cache的容量规划,命中率与内存利用率的兼顾,还是有一定指导意义的。重点在于,这样经过验证的轻量级缓存,完全可以集成进专有硬体中,比如CPU的L3 cache,甚至是L2 cache,做到真正的 small and fast。
以上。更详细的内容请参见原文,如有不当之处欢迎指出
暗淡了乌云:The Power of Two Random Choices?zhuanlan.zhihu.com「Balls into Bins」 — A Simple and Tight Analysis?www.dblab.ntua.grSmall Cache, Big Effect: Provable Load Balancing for. Randomly Partitioned Cluster Services?www.cs.cmu.eduThe Power of Two Random Choices: A Survey of Techniques and Results?www.eecs.harvard.edu古文:[Paper Review]FAST19 DistCache?zhuanlan.zhihu.com 推荐阅读: