美团万亿级 KV 存储架构与实践

2020-07-22 14:34:06 浏览数 (2)

美团点评 KV 存储发展历程

美团第一代的分布式 KV 存储如下图左侧的架构所示,相信很多公司都经历过这个阶段。在客户端内做一致性哈希,在后端部署很多的 Memcached 实例,这样就实现了最基本的 KV 存储分布式设计。但这样的设计存在很明显的问题:比如在宕机摘除节点时,会丢数据,缓存空间不够需要扩容,一致性哈希也会丢失一些数据等等,这样会给业务开发带来的很多困扰。

随着 Redis 项目的成熟,美团也引入了 Redis 来解决我们上面提到的问题,进而演进出来如上图右侧这样一个架构。大家可以看到,客户端还是一样,采用了一致性哈希算法,服务器端变成了 Redis 组成的主从结构。当任何一个节点宕机,我们可以通过 Redis 哨兵完成 Failover,实现高可用。但有一个问题还是没有解决,如果扩缩容的话,一致性哈希仍然会丢数据,那么这个问题该如何解决呢?

内存 KV Squirrel 架构和实践

在开始之前,本文先介绍两个存储系统共通的地方。比如分布式存储的经典问题:数据是如何分布的?这个问题在 KV 存储领域,就是 Key 是怎么分布到存储节点上的。这里 Squirrel 跟 Cellar 是一样的。当我们拿到一个 Key 后,用固定的哈希算法拿到一个哈希值,然后将哈希值对 Slot 数目取模得到一个Slot id,我们两个 KV 现在都是预分片16384个 Slot 。得到 Slot id 之后,再根据路由表就能查到这个 Slot 存储在哪个存储节点上。这个路由表简单来说就是一个 Slot 到存储节点的对照表。

KV 数据分布介绍

接下来讲一下对高可用架构的认知,个人认为高可用可以从宏观和微观两个角度来看。从宏观的角度来看,高可用就是指容灾怎么做。比如说挂掉了一个节点,你该怎么做?一个机房或者说某个地域的一批机房宕机了,你该怎么做?而从微观的角度看,高可用就是怎么能保证端到端的高成功率。我们在做一些运维升级或者扩缩容数据迁移的时候,能否做到业务请求的高可用?本文也会从宏观和微观两个角度来分享美团做的一些高可用工作。

Squirrel 架构

Squirrel—节点容灾

如果过了一段时间,HA 判断它属于一个永久性的宕机,HA 节点会直接从 Kubernetes 集群申请一个新的 Redis 4 容器实例,把它加到集群里。此时,拓扑结构又变成了一主两从的标准结构,HA 节点更新完集群拓扑之后,就会去写 ZooKeeper 通知客户端去更新路由,客户端就能到 Redis 4 这个新从库上进行读操作。

通过上述方案,我们把从库的摘除时间从 30 秒降低到了 5 秒。另外,我们通过 HA 自动申请容器实例加入集群的方式,把宕机补副本变成了一个分钟级的自动操作,不需要任何人工的介入。

Squirrel 跨地域容灾

我们解决了单节点宕机的问题,那么跨地域问题如何解决呢?我们首先来看下跨地域有什么不同。第一,相对于同地域机房间的网络而言,跨地域专线很不稳定;第二,跨地域专线的带宽是非常有限且昂贵的。而集群内的复制没有考虑极端的网络环境。假如我们把主库部署到北京,两个从库部署在上海,同样一份数据要在北上专线传输两次,这样会造成巨大的专线带宽浪费。另外,随着业务的发展和演进,我们也在做单元化部署和异地多活架构。用官方的主从同步,满足不了我们的这些需求。基于此,我们又做了集群间的复制方案。

Squirrel 智能迁移

对于数据迁移,我们主要遇到三个问题:

  • Redis Cluster 虽然提供了数据迁移能力,但是对于要迁哪些 Slot,Slot 从哪迁到哪,它并不管。
  • 做数据迁移的时候,大家都想越快越好,但是迁移速度过快又可能影响业务正常请求。
  • Redis 的 Migrate 命令会阻塞工作线程,尤其在迁移大 Value 的时候会阻塞特别久。

为了解决这些问题,我们做了全新的迁移服务。

Squirrel 持久化重构

Redis 主从同步时会生成 RDB。生成 RDB 的过程会调用 Fork 产生一个子进程去写数据到硬盘,Fork 虽然有操作系统的 COW 机制,但是当内存用量达到 10 G 或 20 G 时,依然会造成整个进程接近秒级的阻塞。这对在线业务来说几乎是无法接受的。我们也会为数据可靠性要求高的业务去开启 AOF,而开 AOF 就可能因 IO 抖动造成进程阻塞,这也会影响请求成功率。对官方持久化机制的这两个问题,我们的解决方案是重构持久化机制。

Squirrel 热点 Key

下面看一下 Squirrel 的热点 Key 解决方案。如下图所示,普通主、从是一个正常集群中的节点,热点主、从是游离于正常集群之外的节点。我们看一下它们之间怎么发生联系。

当有请求进来读写普通节点时,节点内会同时做请求 Key 的统计。如果某个 Key 达到了一定的访问量或者带宽的占用量,会自动触发流控以限制热点 Key 访问,防止节点被热点请求打满。同时,监控服务会周期性的去所有 Redis 实例上查询统计到的热点 Key。如果有热点,监控服务会把热点 Key 所在 Slot 上报到我们的迁移服务。迁移服务这时会把热点主从节点加入到这个集群中,然后把热点 Slot 迁移到这个热点主从上。因为热点主从上只有热点 Slot 的请求,所以热点 Key的处理能力得到了大幅提升。通过这样的设计,我们可以做到实时的热点监控,并及时通过流控去止损;通过热点迁移,我们能做到自动的热点隔离和快速的容量扩充。

持久化 KV Cellar 架构和实践

下面看一下持久化 KV Cellar 的架构和实践。下图是我们最新的 Cellar 架构图。

如上图所示 ,如果 A 节点宕机了,会触发 Handoff 机制,这时候中心节点会通知客户端 A节点发生了故障,让客户端把分片 1 的请求也打到 B 上。B 节点正常处理完客户端的读写请求之后,还会把本应该写入 A 节点的分片 1&2 数据写入到本地的 Log 中。

如果 A 节点宕机后 3~5 分钟,或者网络抖动 30~50 秒之后恢复了,A 节点就会上报心跳到中心节点,中心节点就会通知 B 节点:“ A 节点恢复了,你去把它不在期间的数据传给它。”这时候,B 节点就会把本地存储的 Log 回写到 A 节点上。等到 A 节点拥有了故障期间的全量数据之后,中心节点就会告诉客户端,A 节点已经彻底恢复了,客户端就可以重新把分片 1 的请求打回 A 节点。

接下来,我们看一下 Cellar 如何保证它的端到端高成功率。这里也讲三个影响成功率的问题。Cellar 遇到的数据迁移和热点 Key 问题与 Squirrel 是一样的,但解决方案不一样。这是因为 Cellar 走的是自研路径,不用考虑与官方版本的兼容性,对架构改动更大些。另一个问题是慢请求阻塞服务队列导致大面积超时,这是 Cellar 网络、工作多线程模型设计下会遇到的不同问题。

Cellar 智能迁移

Cellar 热点 Key

上图是 Cellar 热点 Key 解决方案的架构图。我们可以看到中心节点加了一个职责,多了热点区域管理,它现在不只负责正常的数据副本分布,还要管理热点数据的分布,图示这个集群在节点 C、D 放了热点区域。我们通过读写流程看一下这个方案是怎么运转的。如果客户端有一个写操作到了 A 节点,A 节点处理完成后,会根据实时的热点统计结果判断写入的 Key 是否为热点。

如果这个 Key 是一个热点,那么它会在做集群内复制的同时,还会把这个数据复制有热点区域的节点,也就是图中的 C、D 节点。同时,存储节点在返回结果给客户端时,会告诉客户端,这个 Key 是热点,这时客户端内会缓存这个热点 Key。当客户端有这个 Key 的读请求时,它就会直接去热点区域做数据的读取。通过这样的方式,我们可以做到只对热点数据做扩容,不像 Squirrel ,要把整个 Slot 迁出来做扩容。有必要的话,中心节点也可以把热点区域放到集群的所有节点上,所有的热点读请求就能均衡的分到所有节点上。另外,通过这种实时的热点数据复制,我们很好地解决了类似客户端缓存热点 KV 方案造成的一致性问题。

发展规划和业界趋势

最后,一起来看看我们项目的规划和业界的技术趋势。这部分内容会按照服务、系统、硬件三层来进行阐述。首先在服务层,主要有三点:

  • Redis Gossip 协议优化。大家都知道 Gossip 协议在集群的规模变大之后,消息量会剧增,它的 Failover 时间也会变得越来越长。所以当集群规模达到 TB 级后,集群的可用性会受到很大的影响,所以我们后面会重点在这方面做一些优化。
  • 我们已经在 Cellar 存储节点的数据副本间做了 Raft 复制,可以保证数据强一致,后面我们会在 Cellar 的中心点内部也做一个 Raft 复制,这样就不用依赖于 ZooKeeper 做分布式仲裁、元数据存储了,我们的架构也会变得更加简单、可靠。
  • Squirrel 和 Cellar 虽然都是 KV 存储,但是因为它们是基于不同的开源项目研发的,所以 API 和访问协议不同,我们之后会考虑将 Squirrel 和 Cellar 在 SDK 层做整合,虽然后端会有不同的存储集群,但业务侧可以用一套 SDK 进行访问。

在系统层面,我们正在调研并去落地一些 Kernel Bypass 技术,像 DPDK、SPDK 这种网络和硬盘的用户态 IO 技术。它可以绕过内核,通过轮询机制访问这些设备,可以极大提升系统的 IO 能力。存储作为 IO 密集型服务,性能会获得大幅的提升。

在硬件层面,像支持 RDMA 的智能网卡能大幅降低网络延迟和提升吞吐;还有像 3D XPoint 这样的闪存技术,比如英特尔新发布的 AEP 存储,其访问延迟已经比较接近内存了,以后闪存跟内存之间的界限也会变得越来越模糊;最后,看一下计算型硬件,比如通过在闪存上加 FPGA 卡,把原本应该 CPU 做的工作,像数据压缩、解压等,下沉到卡上执行,这种硬件能在解放 CPU 的同时,也可以降低服务的响应延迟。

作者简介

美团点评高级技术专家,2014 年加入美团。

来源:美团技术团队

0 人点赞