TheTailAtScale&HotRing论文

简介

在分布式系统中会存在耗时过长的请求,或者服务本身也需要进行相关的耗时优化,本期给大家带来的是两篇耗时优化相关的论文,《The Tail At Scale》是Google发布的关于分布式系统中关于在不可靠的系统中打造一个可靠的整体耗时模型的论文,《HotRing》是阿里发布的关于服务本地缓存优化的论文。

The Tail At Scale

论文简介

在下游众多的系统中,由于我们无法预测下游正在发生或者将要发生的事情,有的时候会因为下游服务的一些异样操作而导致耗时上涨,即便这个比例是很低的,但是在大型的分布式系统上,一个请求可能会经过成百上千个模块,这样就会导致耗时增长的可能性大大增加。下游有可能出现的情况有:

  • CPU过载降频
  • LSM-TREE compation操作
  • 网络缓存过载
  • GC

由于下游的情况是不可预测的,所以我们不能期待于下游提供100%稳定的服务,在这个前提之下,我们能不能够建立稳定的模型呢。

对冲请求

论文中提到的解决方法是,评估出下游服务p95的耗时,在一次请求已经超过这个时间的时候就再发送一次相同的请求,取最新返回的一次来做结果返回,耗时长的结果就丢弃掉。这样做为什么能降低P99耗时呢,下面我们建立一个数学模型来解释这个问题。

在这里插入图片描述
在这里插入图片描述

图中横轴表示的请求中分位的耗时,纵轴表示的是对应P(X)分位的耗时。此图中基于一些假设:

  • 下游一定会出现一些问题导致P9x耗时过高
  • 当前监控时刻与下一个监控时刻的请求量与耗时情况大致相同
  • 新增的对冲请求的qps不会对下游造成影响
  • 大多数对下游的请求都是良好的,能正常返回,耗时远远低于异常的时候。
  • 各个阶段的耗时模型拟合成一次函数。

模型解读:

  • 任意x1 > x2, f(x1) >= f(x2),其实这个很好理解,99分位耗时是不会比95分位耗时低的。
  • 由于会下游会出现一些异样的操作,那么存在x1,x2, x2 = x1 + 1,使得P(x1) << P(x2)。 在这个时刻两个点之间的图像并没有什么意义,所以图中并没有绘制

模型推论:

  • 令x2 = x1 + 1, P(x1) = t1, P(x2) = t2, 且t1 << t2, 0 - x1分位的耗时模型为 f(x) = k1x + b1, x2 - 100 分位耗时模型为f(x) = k2x + b2
  • 假定我们选择在t1时刻发送对冲请求,由于我们假定流量是均匀的,那么这个对冲的请求有x1/100 的概率落在0 - x1的耗时区间,有1-x1/100的概率落在x2 - 100的耗时区间。那么我们只需要比较0 - x1和原请求在x2-100的时间长短就可以了,因为对冲请求落在后半区间的耗时肯定是比原请求要大的。
  • 那么我们能得到的x2-100区间段的耗时模型就变成了min(t1 + x1/100 * (k1x1 + b1), k2x + b2)

实操的考量

经过上面的推论之后,我们可以看下如果要落地这个东西需要考量的东西有哪些:

  • 这个模型适合那些场景?
  • t1时刻怎么选取和评估?
  • 放大的流量怎么评估?

1.实际上这个模型适合在后半段分位耗时远远比前半段大的情况,如果整个下游耗时都是平缓稳定的,在发一次请求只会增大调用量和下游压力,不会对耗时有任何优化。

2.关于t0时刻可以建立一个反馈机制,选取一个单位时间内的流量来做流量耗时的评估,因为流量段时间内是均匀的,取上一个时刻的流量对当前的评估不会有太大的问题,并且可以设置兜底的配置,如果不大于兜底的值,就不会重新发送请求。

3.关于放大的流量实际上是耗时大于t0的流量,实际上比1 - x1/100 占比的流量少很多。

4.对冲请求只适用于读取操作,写操作不适合重新发送,很难消除两个写请求的影响。

HotRing

简介

关于缓存大家一定不陌生,如codis这样的缓存组件,还有应用内的缓存。缓存从某种意义上来说是符合28定律的,也就是说80%的流量访问20%的缓存数据。这就涉及到一个热点数据的问题,当一些数据被集中访问的时候,我们怎么做优化的处理,市面上常见的优化策略都是针对codis样的缓存组件做的优化,比如一致性hash策略,但是对应用内缓存的优化确实极少的。这里介绍的HotRing就是对应用内缓存做的优化。

论文又一些前置的知识在里面有大量的使用,分别是:

  • RCU机制
  • map原理
  • CAS

在服务本地缓存中绕不开用map来作为缓存的数据结构,我们这里举go的map为例,java的HashMap原理也差不多:

初始化的map

面对热点现象这样的数据结构会有一些问题,

1.无法感知到热点,获取缓存的时候是线性访问链表,会存在多余的内存操作。

2.缓存miss,我们在获取缓存的时候key的hash值命中某个bucket,会遍历bucket上的每一个元素,没有匹配的key才会返回miss。这里其实可以通过在最外层加一个布隆过滤器解决。

那么如何解决这个问题呢, 第一个问题最直观的解法就是把最热的数据放在链表的前面,这样就可以做到尽量少的操作就可以拿出热点数据。第二个问题可以构造一个有序的链表,这样可以根据链表节点的key的大小来判断key是否存在,从而做到减少内存访问的效果。

img

如上图所示,就是HotRing的数据结构。Head节点可以指向有序冲突环中的任意一个节点,并且有相关热点切换事件的时候Head会改变指向。

这样的模型我们需要考虑的是

  • 热点转移怎么操作?
  • 怎么做到无锁操作?

1.论文中关于热点转移提供了两种方案:

  • 随机切换,如果我们的流量中存在热点事件,那么在流量中随机取一个流量的缓存访问,大概率会取到热点,Head切换到这个节点就好了。但是如果流量中存在多个热点或者不存在热点,那么这种策略就会带来频繁的Head切换的问题。
  • 经过统计计算的热点切换,统计整个环和环上每一个节点的访问次数,然后通过下面这个公式计算Head如果移动到每个节点带来的收益。n代表每个节点的访问次数,N表示环的总访问次数。后面代表的的(i-t)mod k代表如果head移动到t节点,从t访问环上每个节点需要做的内存访问次数。由此我们得出将Head移动到T节点上带来的收益。

img
2.通过RCU和CAS机制做到无锁操作。详情可以看参考资料中的文章。

实操考量:

这个模型要是投入使用,要考虑什么东西呢?

  • 系统本地缓存中存在热点问题吗?
  • 投入产出比?

注释

由于在参考资料中HotRing的文章已经把这些都介绍的很详细了,所以就不再展开篇幅对一些细节进行讲解了。感兴趣的读者可以去看参考资料中关于HotRing的文章。

参考资料