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问题。

内存占用分析

Maglev 一致性Hash调研_后端服务

(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;

其他

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

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

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

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

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

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

7)一致性效果:

Maglev 一致性Hash调研_红黑树_02


删除rs1,则给其他rs的原有流量应该不受到影响; 引入了虚拟节点,给这个删除rs的流量,均分到其他的rs中;

增加rs1,则给其他rs的原有流量尽可能少的受到影响.因为添加RS3,原本给RS1的流量可能此时会给RS3.

google maglev 一致性hash原理

简介

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

概念

(1)查询表(lookup table):  

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

(2)优先表 (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;

注:

entry[i](即lookup表) : 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中的空白位置,直至填满。

(1)优先表(permutation表)的生成

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

Maglev 一致性Hash调研_红黑树_03

实际上,上图中的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)如下所示:

Maglev 一致性Hash调研_红黑树_04

(2)查询表的生成

使用以下算法构造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。

分析

1)entry 表的大小

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

2)svc中增删rs

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

3)rs的调度

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

4)均衡性

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

5) rs的权重

看论文中简单提到了一句基于rs的权重生成对应的entry表,但是详细实现在论文中没有涉及;

facebook的katran使用是maglev的一致性hash,其中rs可配置权重; 如下所示:

Maglev 一致性Hash调研_权重_05

PS:内核ipvs中的maglev中有实现RS带有权重的实现。

效果

Maglev 一致性Hash调研_红黑树_06

上图是 Google 测试了有 1000 台 后端nodes 时,随着部分 后端 node 下线(横轴:移除failed backends的个数),前端并发请求受影响的程度(纵 轴)。

测试结果显示越大的 lookup table(即entry表的大小M)可以带来更好的稳定性。但是在谷歌的机器上, 将 lookup table 的长度从 65537 调整到 655373 导致其计算时间从 1.8ms 提高到了 22.9ms。

测试结果

功能测试

entry表的大小M:M必须是一个质数;

测试方法

rs的权重比:目前LB支持maglev实现rs带权重,rs的权重比 = rs的权重/多个rs权重的最小公约数;目前每创建一个rs,其权重默认为1,权重为0的rs不 会出现在entry表中;

测试case中,LB中每次删除、添加一个rs,或者更改某个rs的权重,就会重新得到一个maglev entry表,可以和之前的maglev entry表进行对比;

如果想要测试增加、删除多个rs,对于maglev entry表的影响,就通过操作weight > 1的rs来实现;因为weight = n 的一个rs,其实就是相当于n 个 weight = 1的rs;

entry表整体变化率:整体变化率是比较前后2个entry的每个相同位置的元素值,如果有不同,则认为该处发生了改变;变化的位置的个数/M = 整体 变化率;

其他rs间分布的变化率:比如添加rs4,之前1号位置存储的是rs1,如今给了rs2,则认为其他rs间分布发生了变化; 之前3号存储的是rs3, 如今给了rs4, 则不计算在内;

Maglev 一致性Hash调研_权重_07

Maglev 一致性Hash调研_后端服务_08

Maglev Hash存在的问题:

  • RS3挂掉后,原本给RS1的流量,可能会小概率的给了RS2。概率的大小和M有关,M越大,则概率越小,但是生成长度为M的查询表的时间越长。
  • 另外一方面,RS3挂掉之后,给RS3的流量会分布到多个RS上,比如分配到RS1,以及RS2上。即同一条流之前在RS3,后续在RS1或者RS2。这个问题是无法在LB上解决的,只能是后端感知或者解决。

问题1和问题2,都是同一条流,之前和之后在两个不同的RS上。那么问题2可以忍受,问题1也应该是可以忍受的。或者后端可以感知感觉的。

性能测试

测试方法

3个vs,每个vs下1000个rs,每个rs的权重比都是1, 每个vs下的rs 相同;vs分别使用LB原始的sip一致性hash调度算法, 以及 maglev的sip一致性hash调度算法;

128字节的tcp syn包,查看fps,cps,以及时延数据;

报文大小

报文类型

调度算法

rs个数

rs权重

0丢包吞吐率

fps

平均时延(ms)

最大时延

最小时延

其他

128B

tcp-syn

sip maglev hash

1000

1

26.87%

226.98w

193.04

704.39

39.19

-

128B

tcp-syn

sip conhash 

1000

1

32.5%

274.49w

179.75

652.48

21.23

-

 

如上所示:  和sip一致性 hash相比,使用maglev sip 一致性hash,新建连接的 cps以及吞吐 有大概 23%的提升,平均时延也更加好一些;

 

内核ipvs中maglev一致性hash实现

原理

同上

特点

(1)dest 带有权重信息

根据权重比决定 dest 在 lookup表中 在 出现的比例;

如果 rs 的 权重为 0, 则该 rs 不会出现在 lookup 表中,或者 说 从 lookup 表中查询到的 rs 其 weight 都是非0的。

如果不考虑 DPVS_DEST_F_OVERLOAD 标记,则从 lookup 表中查询到的 rs 都是 有效的 (keepalived 健康检查 决定了 rs 的有效);

如果设置了 rs 的 max_conn,  则 rs 可能存在 DPVS_DEST_F_OVERLOAD 标记,那么需要开启 mh-fallback;

注:权重最大的和权重最小的rs的权重比相差比较大(差距32以上),则进一步将权重比进行缩放;

(2) hash因子有限

目前默认hash 因子 为sip, 也可设置为 sip+ sport; 但是不可以指定hash 因子为 cid (quic connection id)等;

keepalived 中设置maglev hash

virtual_server group xxx {
...
lb_algo mh
mh-port
mh-fallback
...
}

static inline bool is_unavailable(struct dp_vs_dest *dest)
{
return rte_atomic16_read(&dest->weight) <= 0 ||
dest->flags & DPVS_DEST_F_OVERLOAD;
}

说明:
mh: maglev hash scheduling algorithm
sip: source ip as hash factor
l4s: source ip and source port as hash factor
fallback: another rehashed valid rs may be selected while the rs hashed at the first time is invalid

mh: maglev hash
mh-port: 设置了 mh-port 则 以 sip+sport 作为 hash因子; 如果不配置 mh-port, 则默认以 sip + 0(port=0)作为hash因子;
mh-fallback: 设置了 mh-fallback 则 如果基于 sip 或 sip+sport 为 因子得到的 dest 不可用(unavailable),则继续在 lookup 表中查询,直到找到一个 可用的 dest 或者 遍历 一圈还是没有找到,返回 NULL;
未设置 mh-fallback 则 如果基于 sip 或 sip+sport 为 因子得到的 dest 不可用,则返回 null;

dest 不可用(unavailable): dest 的 weight 为0 ,dest 过载(dest的连接数超过max_conn);

参考

​https://github.com/kkdai/maglev​

​http://static.googleusercontent.com/media/research.google.com/zh-TW//pubs/archive/44824.pdf​

​https://github.com/facebookincubator/katran​

​https://engineering.fb.com/open-source/open-sourcing-katran-a-scalable-network-load-balancer/​

​https://cloud.tencent.com/developer/news/220111​

​https://gitlab.freedesktop.org/drawat/linux/commit/039f32e8cdea29b4d0680df7a83817b5ec4166e1​​  (netfilter: ipvs: Add Maglev hashing scheduler)