美团大规模KV存储挑战与架构实践--图文分析

美团大规模KV存储挑战与架构实践–图文分析

原作者:美团技术团队

原文链接:https://tech.meituan.com/2024/03/15/kv-squirrel-cellar.html

1 美团 KV 存储发展历程

第一代:使用Memcached

img

什么是一致性哈希?

哈希:hash,可以通过Key存储Value,也可以通过Key取得Value. 简单说就是Value存储的位置是通过Hash算法计算出来的

(实现,如HashMap)。

随着客户端发起的请求数量越来越高,为了加快响应速度,在客户端与数据库之间增加了缓存层,把数据库中的热点数据存入缓存,这样客户端可以直接从缓存取得热点数据,无需每次都访问数据库,即加快了响应时间,又降低了数据库层的压力。

在这里插入图片描述

hash取模运算

随着业务规模不断扩大,数据量也跟着增大,缓存数量飙增,这样就需要考据搭建缓存集群,把大量的缓存分散到多台缓存服务器中进行存储

取模算法hash(key)%N,即:对缓存数据的key进行hash运算后取模,N是机器的数量;运算后的结果映射对应集群中的节点。具体如下图所示:

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=F%3A%2FBaiduNetdiskDownload%2FDevOps%E7%AC%94%E8%AE%B0-MarkDown%E5%8E%9F%E7%89%88%2FDevOps%2FPictures%2Fimage-20240607182832191.png&pos_id=img-VgEmEbVn-1717929022871在这里插入图片描述

问题,如上图,三个node,所以N为3,此时key通过公式hash(key)%3存入指定节点,之后扩容成4个节点,N为4,公式也变成了hash(key)%4,之前n为3的值取不出来了。

一致性哈希

一致性哈希算法将整个哈希值空间映射成一个虚拟的圆环。整个哈希空间的取值范围为02^32-1,按顺时针方向开始从0232-1排列,最后的节点232-1在0开始位置重合,形成一个虚拟的圆环。

对服务器IP地址进行哈希计算,哈希计算后的结果对232取模,结果一定是一个0到232-1之间的整数。最后将这个整数映射在哈希环上,整数的值就代表了一个服务器节点的在哈希环上的位置。即:hash(服务器ip)% 2^32。

当服务器接收到数据请求时,首先需要计算请求Key的哈希值;然后将计算的哈希值映射到哈希环上的具体位置;接下来,从这个位置沿着哈希环顺时针查找,遇到的第一个节点就是key对应的节点

在这里插入图片描述

服务器扩容

D数据本来应该会落在node1中,由于扩容节点node4,D数据落在了node4中

在这里插入图片描述

服务器缩容

node4节点宕机或者移除后,数据D继续顺时针找到node1节点存储。

在这里插入图片描述

数据倾斜与虚拟节点

数据abcd都落在node1中,node2,node3没事干,node1压力山大。

在这里插入图片描述

解决方案,使用虚拟节点

hash(服务器ip)% 2^32 变更为 hash(服务器ip+随机数)% 2^32

例如:Hash(“114.211.11.141”);

变更为:

Hash(“114.211.11.141#1”);

Hash(“114.211.11.141#2”);

注意,虚拟节点只是帮助真实节点扩大获取数据的范围,并不会保存数据,所获取的数据最终还是要存储在真实节点中。

在这里插入图片描述

memcached的问题

传统的memcached是不支持内存数据的持久化操作,因为它将数据存储在内存当中的,当服务器宕机重启,数据会丢失。

不适合存储1M以上的数据。

适用场景:缓存一些很小但是被频繁访问的,即便你丢失也不会影响系统以及系统业务正常执行的数据。如:新闻热点缓存,即便日后数据丢失,热点已经降温,数据不重要。

第二代:使用Redis

img

优点:支持持久化,服务宕机后数据不会丢失,可以通过哨兵机制进行故障恢复(failover)

宕机数据不丢失策略

RDB每5分钟一次生成快照,AOF每秒通过一个后台的线程持久化操作,最多丢一秒的数据。单独用RDB会丢失很多数据,单独用AOF数据恢复没RDB快,所以解决方案是第一时间用RDB恢复,然后AOF做数据补全

缺点:扩缩容丢失数据。

一致性hash只是用来减少扩缩容时数据迁移量,无法对整个集群进行统一管理(缺乏类似提供心跳检测等服务的中心管理层,数据迁徙的时候,节点无法感知整个集群的状态,数据从A节点中迁徙出去了,但接收的B节点处于网络卡顿状态导致无法接受全部迁徙数据等问题)

第三代:阿里巴巴的 Tair

Tair 开源版本的架构主要是三部分:最下边的是存储节点,存储节点会上报心跳到它的中心节点,中心节点内部设有两个配置管理节点,会监控所有的存储节点。如果有任何存储节点宕机或者扩容之类的行为,它会做集群拓扑的重新构建,拥有数据迁移机制来保证数据的完整性。

img

缺点:

1.中心节点是高可用的,但是会发生脑裂。

脑裂:选举bug,多个leader,多个从节点被选举成了主节点,不干从节点的活了导致数据丢失。

2.和redis的数据类型不兼容

第四代:

基于Tair的Cellar ,基于Redis Cluster的Squirrel

img

这两个存储其实都是 KV 存储领域的解决方案。实际应用上,如果业务的数据量小,对延迟敏感,建议用 Squirrel ;如果数据量大,对延迟不是特别敏感,我们建议用成本更低的 Cellar 。

2 大规模 KV 存储的挑战

1.集群的规模越来越大,节点增加困难(比如带宽饱和)

2.部分业务场景带来的木桶效应以及长尾延迟(例如:mget

3.如何保证集群可用性不会随着规模的变大而有所降低

名词解释:

mget:

可以一次性读取redis中多个值:

redis 127.0.0.1:6379> SET key1 "hello"
OK
redis 127.0.0.1:6379> SET key2 "world"
OK
redis 127.0.0.1:6379> MGET key1 key2 someOtherKey
1) "Hello"
2) "World"
3) (nil)

长尾效应:

红色区域量大响应时间短,黄色区域量小耗时长。比如使用了上面的mget批量读取就有大概率造成此类问题。

在这里插入图片描述

3 内存 KV Squirrel 挑战和架构实践

img

上图是美团的 Squirrel 架构。中间部分跟 Redis 社区集群是一致的。它有主从的结构,Redis 实例之间通过 Gossip 协议去通信。右边添加了一个集群调度平台管理整个集群,把管理结果作为元数据更新到 ZooKeeper。我们的客户端会订阅 ZooKeeper 上的元数据变更,实时获取到集群的拓扑状态,直接对 Redis 集群节点进行读写操作。

难点补充:

Gossip 原理:

节点1更新数据,告知了相邻节点2,4,7;相邻节点再去告诉自己的相邻节点。

在这里插入图片描述

优点:去中心化(多个缓存中间件采用,区块链采用)

缺点:当节点特别多的时候消耗性能,节点更新后相邻节点可能会重复更新,非常损耗性能。

3.2 Gossip优化

为了解决上述的扩展性问题,我们对社区的 Gossip 方案进行了优化。首先针对 Gossip 传输的消息,我们通过 Merkle Tree 对其做了一个摘要,把集群 Gossip 通信的数据量减少了90%以上。服务端节点仅需要对比 Hash 值即可判断元数据是否有更新对于存在更新的情况也能快速判断出更新的部分并仅对此部分元数据进行获取、更新,大幅降低了 Gossip 消息处理的资源消耗

原文中的默克尔树(Merkle Tree)

img

补全图:

在这里插入图片描述

类似平衡二叉树,通过对比上层节点的hash是否一致即可得知是否已经更新,可双向判断:判断更新以及找到更新位置。难度:之前为O(N)现在为O Log(N)

3.3 Squirrel 垂直扩展的挑战

节点过大,Fork生成RDB影响性能。(执行RDB的主要三种场景:save,bgsave,主从复制)

Redis备份数据做持久化操作有AOF和RDB两种,为了不影响主线程,会通过内置的Fork系统启动一个子线程执行,但Fork子进程过程中如果节点过大,依然会占用大量资源导致阻塞。可以简单理解为,Redis会在数据增长到一定程度或者执行主从复制的时候,备份自己的数据,生成一个RDB文件(快照),保存当时Redis中的所有数据用于以后的数据恢复,但这个RDB文件尽管通过子线程生成,但每次生成一个大文件在保存还是耗费资源。

3.4 forkless RDB

解决方案就是采用复制队列的方式,由原来的一次拷贝一个大的(越大越慢,会造成阻塞),改成一个key一个key的往队列拷贝生成的方式。

拷贝期间数据变化怎么办:把变化过程也拷贝到队列里。

拷贝期间发生rehash导致里面的key顺序重排怎么办:暂停rehash.

img

这里的rehash 又是什么意思呢?

redis中有一个全局Hash表(HashTable),表里有一个一个的哈希桶(Bucket),桶里装着Entry(存放key,value的指针),数据量特别大的时候会发生Hash冲突行成链表,如下图的Bucket2.

在这里插入图片描述

解决方案:rehash,扩容。

创建一个比原来还大的HashTable,向其中传递原来HashTable的数据,并对存在Hash冲突的地方进行哈希桶的重新分配。

在这里插入图片描述

这就是为什么美团的forkless RDB执行的时候需要停止rehash了,因为要一个一个的拷贝key,并且有游标指向key的位置,但rehash发生后,key的位置就会发生变化。

3.5 工作多线程

原生Redis:IO多线程,工作线程为单线程。

修改为:IO多线程,工作线程也是多线程,通过加锁解决线程安全问题。

img

3.6 Squirrel可用性的挑战

只有两个机房,A选B,B选A,选不出主或者造成脑裂,必须部署3机房,但通常两个副本足够用了,没必要部署3个副本。

3.7 两机房容灾

img

参考 Google Spanner(可伸缩分布式数据库)的见证者投票方式。

简单说就是:

还是三个机房,但保存两个副本就行了,另外一个只投票,不保存副本。

3.8 跨地域容灾

img

3.9 双向同步冲突自动解决

img

简单说就是谁的时间新就保存谁的。

服务器发生了时间回退怎么办:保存数据时使用时间戳,发生回退了依然使用时间戳来防止数据跟着回退。

两个值的更新时间一样怎么办:比较集群ID,保存集群ID大的那一个。(个人理解类似足球比赛积分净胜球等都相同,靠抽签决定谁进入下一轮)

由复制操作改为复制变更后的数据:只考虑操作后的数据,而不是复制操作。

链图片转存中…(img-qMRzytbP-1717929022879)]

3.9 双向同步冲突自动解决

[外链图片转存中…(img-CDljEhDy-1717929022880)]

简单说就是谁的时间新就保存谁的。

服务器发生了时间回退怎么办:保存数据时使用时间戳,发生回退了依然使用时间戳来防止数据跟着回退。

两个值的更新时间一样怎么办:比较集群ID,保存集群ID大的那一个。(个人理解类似足球比赛积分净胜球等都相同,靠抽签决定谁进入下一轮)

由复制操作改为复制变更后的数据:只考虑操作后的数据,而不是复制操作。

保存最近删除的 Key:简单说就是B集群同步复制A集群数据的时候,对于复制过来一个已经不存在的数据,先不要创建,而是到删除库中看一看这个数据是不是已经被删除了。

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值