Maglev 一致性Hash调研

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

dpvs中conhash实现

原理

  • 一个RS对应的虚拟节点(副本)的个数

replicas = Weight * REPLICA

  • 虚拟节点的 hash_key

hash_key= rs的地址信息(af,addr,port) + vnode_index (vnode_index 为 虚拟节点的索引,范围为 [0, replicas- 1] ) hash完后,基于hash_value,将虚拟节点插入到红黑树中。

  • 调度选择RS

从数据包中提取hash因子(sip/cid/ktp等),hash后得到 hash_value,在svc的红黑树中查找,返回键值大于等于hash_value 的最接近的节点。

  • 增删改VS
  • 添加VS

在每个线程(master/slave)中,创建一个红黑树,将VS下的每个RS对应的虚拟节点添加到红黑树中。

  • 删除VS

将红黑树删除,删除VS下的RS以及VS自身。

  • 更改VS调度算法

基于旧的调度算法,清理对应的数据结构;基于新的调度算法,初始化对应数据结构。

  • 增删改RS
  • VS下添加RS

一个RS对应多个虚拟节点,添加到VS的红黑树中;

  • VS下删除RS

将RS对应的多个虚拟节点,从红黑树中摘除;

  • 更改RS权重

将RS对应的旧的虚拟节点从红黑树中摘除,再基于新的虚拟节点个数,重新将新的虚拟节点插入到红黑树中。 如果RS健康检查不通过,Keepalived 会将RS的权重设置为0,所以不会将不健康的RS的虚拟节点插入到红黑树中。

优缺点

  • 优点
  • 增删改RS生效速度快
  • 在原有红黑树的基础上,增删虚拟节点,而不需要重新构建红黑树;
  • 缺点
  • 占用内存较大
  • 同一个SVC在每个线程都有一个调度结构(红黑树)
  • 每个虚拟节点需要申请一次内存,占用较多内存空间
  • Slave线程中生成/更新红黑树占用时间较多
  • Slave线程中生成/更新红黑树会进行多个虚拟节点的申请/释放(rte_malloc/rte_free调用),如果消耗时间过长可能会导致网卡出现Imiss问题。
  • 内存占用分析

image.png

(gdb) p sizeof(struct util_rbtree_node_s)
$1 = 48
(gdb) p sizeof(struct conhash_node)
$2 = 96
(gdb) p sizeof(struct virtual_node_s)
$3 = 16
(gdb) p sizeof(struct dp_vs_dest)
$4 = 256
(gdb) p sizeof(struct dp_vs_service)
$5 = 384

由于cache line对齐,rte_malloc 申请 struct util_rbtree_node_s 结构时, size 部分为 64B,每个申请的结构会额外申请 elem_header, 其也是cache_line对齐,占用64B, 所以一共消耗 128B。
同理,rte_malloc 申请 struct virtual_node_s,共占用 128B复制代码

其他

  • svc中增加、删除 rs

红黑树的插入,删除虚拟节点都是 O(lgn) 的时间复杂度; n为 svc下 所有真实rs 的虚拟rs的个数;

  • 调整rs的权重

调整 rs weight 则会将 之前的 rs 的虚拟节点都给删除了,然后基于新的 rs的 虚拟节点的个数,重新加入到 红黑树中; 如果 rs 的 健康检查失败,weight 设置为0,则红黑树中不会存在 rs 的 虚拟节点; 所以:红黑树中存在的节点都是 rs 的 weight 不为0 的 rs 的 虚拟节点;

  • RS调度

红黑树的查找的时间复杂度为: O(lgn); 从红黑树中查找到某个节点,对应rs的虚拟节点,进而得到rs; 找到 rs 后,判断 rs 是否有效,有效则直接返回 rs,不可用则返回 null, 没有进而 fallback (即找到的 rs 不可用,继续往下找)

  • 一致性hash算法通用性

配置下发生成的红黑树是通用的;调度选择RS时,根据svc的hash因子,从报文中提取对应字段,实现一套通用的一致性hash调度算法;

  • rs的权重

rs的权重影响 其虚拟节点的个数;权重可调节;

  • 均衡性

均衡: 目前每个rs的虚拟节点的个数为weight * REPLICA (REPLICA宏默认160); 每个虚拟节点基于 ip+port+index 生成 hash因子字符串, 使用md5算法生成u64的hash值,两个虚拟节点的hash值相同的概率很小; 虚拟节点插入到红黑树时,不存在key相同的两个节点(插入前查找,如果存在key相同,则本次不插入);

  • 一致性效果

image.png

删除rs1,则给其他rs的原有流量应该不受到影响; 引入了虚拟节点,给这个删除rs的流量,均分到其他的rs中;
增加rs1,则给其他rs的原有流量尽可能少的受到影响.因为添加RS3,原本给RS1的流量可能此时会给RS3.

google maglev 一致性hash原理

简介

Maglev是Google开发的基于kernal bypass技术实现的4层负载均衡,它具有非常强大的负载性能。Maglev在负载均衡算法上采用自行开发的一致性哈希 算法被称为Maglev Hashing.

概念

  • 查询表(lookup table):

查询表又称之为 entry表,实际为一个后端服务器的序列,假设数组长度为M,那么对应流F的后端服务器为:Entry[Hash (F) % M];

  • 优先表 (permutation[N][M])

permutation表实际上是用来生成查询表(N 为后端服务器的个数,M为查询表的大小) Maglev的一致性哈希算法本质上设计算法让每个后端按照一定的规则去填满数组lookup table中的empty slot(空槽),确保所构造出来的数组lookup table的元素中,所有后端服务器出现的次数尽可能成比例。 为了设计填充规则,Maglev首先给每个后端服务器i设计了一个permutation表。permutation表存储一个0到M-1的随机排列(0~M-1中的每个数都出现一次).

比如当Lookup_table数组长度M=7时,permutation表内容可能为:3 0 4 1 5 2 6;

注:

entryi : i 为索引, entry[i] 的值对应一个后端服务器; permutation[i][j] : i 标识 服务器 i,permutation[i] 为后端服务器i 的优先表; permutation[i][j] 的值 为 entry表的索引,用来在生成entry表时 判断该索引 处是否 被填充了具体的某个后端服务器; 所以,permutation表就是为了生成entry表,流量过来时查询的是entry表;

流程

Maglev 一致性哈希的基本思想就是:

  • 有一个共享的Entry表,数据包过来之后,可以通过Entry[Hash % M]选择对应后端,M为Entry表大小。
  • 每个后端对Entry表的位置有自己的优先级排序(优先表),存在permutation表里。
  • 所有的后端通过优先级顺序轮流填充Entry中的空白位置,直至填满。
  • 优先表(permutation表)的生成

permutation表实际上是用来填充entry表的。 对于每个后端服务器i,Maglev通过以下方式生成permutation[i] ;

image.png 实际上,上图中的skip,offset还有哈希后端服务器名字都只是一种随机化的方法,用于构造permutation表而已。 该算法保证:后端服务器i生成的大小为M的 permutation[i] 优先表中的元素的值 范围为 0~(M-1)且 无重复元素。 注: M 必须是一个质数,这样才能尽可能保证skip与M互斥; 比如: M =7, offset = 3, skip = 2, 则 后端服务器 i 生成的permutation[I] 优先表 (3,5,0,2,4,6,1)如下所示:

image.png

  • 查询表的生成

使用以下算法构造Entry表: 对于每个服务器后端i,通过其permuation[i]表所设立的规则,在entry数组中寻找empty slot,当发现 slot被填时,不停使用next[i]+1的方式跳到entry [permuation[i][next[i]]中probe是否有empty slot, 一旦发现有empty slot则把entry的empty slot分配给自己,即算法中entry[c] = i。 通过这样的方式,每个服务器都有机会去填一个空位,当最终填满 entry[]表时,所构造出entry[]数组一定是均衡的。

注: next[i] : 大小为N(N为节点的个数),为了方便记录下次从 服务器i 对应的permuation[i]表的哪个位置开始,判断对应的entry位置是否被填充了。 i 的范围是 0~(N-1); next[i] 的值的范围是 0~(M-1); permuation[i][next[i]] 的值 作为entry 数组的索引,将节点 i 填充到entry表的该索引处。 另外,生成entry表过程中,计算的时间复杂度最坏可能达到O(M^2) (N=M时);论文中建议令 M 远大于 N,可以实现平均O(M logM)的时间复杂度生成entry表。 论文中,设置M的经验值为 >= 100*N的质数;看facebook的katran 中的默认M = 65537。

分析

  • entry 表的大小

M 必须是一个prime number (如果M不是 prime number ,生成的 permutation 就会有重复值);看facebook的katran 中的默认M = 65537;

  • svc中增删rs

增加、删除rs,则需要重新生成entry表,平均时间复杂度为O(M*lgM)

  • rs的调度

基于流量查找对应的RS,为Entry[Hash(F) % M]: 时间复杂度为O(1);

  • 均衡性

均衡:每个后端服务器在Entry中出现的次数,整体成比例;

  • rs的权重

看论文中简单提到了一句基于rs的权重生成对应的entry表,但是详细实现在论文中没有涉及; facebook的katran使用是maglev的一致性hash,其中rs可配置权重; 如下所示:

image.png

效果

image.png 上图是 Google 测试了有 1000 台 后端nodes 时,随着部分 后端 node 下线(横轴:移除failed backends的个数),前端并发请求受影响的程度(纵 轴)。 测试结果显示越大的 lookup table(即entry表的大小M)可以带来更好的稳定性。但是在谷歌的机器上, 将 lookup table 的长度从 65537 调整到 655373 导致其计算时间从 1.8ms 提高到了 22.9ms。

猜你喜欢

转载自juejin.im/post/7127546665459777544