写点什么

携程 Redis 治理演进之路

  • 2020-12-29
  • 本文字数:4353 字

    阅读完需:约 14 分钟

携程Redis治理演进之路

一、背景


携程 Redis 集群规模和数据规模在过去几年里快速增长,我们通过容器化解决了 Redis 集群快速部署的问题,并根据实际业务进行的一系列尝试,比如二次调度,自动化漂移等,在内存超分的情况下保证了宿主机的可靠性。


扩缩容方面,我们主要通过垂直扩缩容的方式解决 Redis 集群容量的问题,但随着集群规模扩大,这种方式逐渐遇到了瓶颈。一方面,单个 Redis 实例过大,会带来较大的运维风险和困难;另一方面,宿主机容量有上限,不能无止境的扩容。考虑到运维便利性和资源利用率的平衡,我们希望单个 Redis 实例的上限为 15GB。但实际操作中却很难做到:a. 某些业务发展很快,经常性需要给 Redis 进行扩容,导致单个实例大小远超 15GB;b. 一些业务萎缩,实际使用量远低于初始申请的量,造成资源的浪费。


如何有效控制 Redis 实例大小呢?接下来本文将带着这个问题,逐步讲解携程 Redis 治理和扩缩容方面的演进历程。


二、Redis 水平扩分拆


在携程开始使用 Redis 很长一段时间里,一直只有垂直扩缩容,原因有两点:


第一,一开始业务规模比较小,垂直扩缩容可以满足需求。垂直扩缩容对于 Redis 来说只是 Maxmemory 的配置更改,对业务透明;


第二,水平拆分/扩缩容的实现难度和成本较高。


之前文章《携程Redis治理演进之路》中已经提到,携程访问所有的 Redis 集群使用的是自主研发的 CRedis,而部署在应用端的 CRedis 通过一致性 hash 来访问实际承载数据的 Redis 实例。但一致性 hash 是无法支持直接水平扩缩容的。因为无论增加一个节点或者删除一个节点,都会导致整个 hash 环的调整。



图 1


如图所示,假设原始有 4 个分片(图 1)。当添加一个节点后,它会导致某一部分的 key 本来是写到 nodeC 上而现在会被写到 nodeE 上,也就是无法命中之前的节点。从客户端的角度来看,key 就像是丢失了。而变动的节点越多,key 丢失的也越多,假设某个集群从 10 分片直接添加到 20 分片,它直接会导致 50%的 key 丢失。删除一个节点同理,就不再赘述。


因此尽管一致性 hash 是个比较简单优秀的集群方案,但无法直接水平扩容一直困扰着运维和架构团队。为此,CRedis 团队在 2019 年提出了水平拆分的方案。


CRedis 水平分拆的思路比较朴素,因为在一致性 hash 同一个水平位置增加节点会导致数据丢失,那么不改变原来层次节点的 hash 规则,以某个节点为 hash 的起点,再来进行一次一致性 hash,演变成树的结构(图 2)。



图 2


如上图所示,将树形结构从一层拓展成二层,如果继续拆分新的叶子 Group,则可以将树形结构拓展到三层,拆分方案可以支持到十层。叶子 Group 是物理分片,直接对应的 Redis 实例,分支 Group 是虚拟分片,当 Hash 命中到分支 Group 后,并没有找不到对应的 Redis 实例,需要再继续向下寻找,直到找到叶子 Group 为止。



图 3


CRedis 水平分拆上线后,DBA 将现存的绝大部分超过 15G 的实例都拆分成更小的实例,在一段时间内缓解了大内存实例的运维治理压力。但随着 Redis 规模的快速增长,不断有大的实例集群出现,此外 CRedis 水平分拆的缺点也逐渐暴露出来:


1)持续的周期很长,对多个 Group 进行拆分的话,每个 Group 的数据需要同时复制几份同样的实例。比如 60G 的某个实例(图 3),如果想拆到 5G 一个,那么下级的 Group 必须有 12 个,而拆分要先将该实例的数据先同步为 12 个 60G 的实例,再根据 key 的命中规则清理该 12 个 60G 的实例中不会命中的 key,最终演变成 12 个 5G 的实例。一般 60G 的 group 实例拆分需要 3 个小时-6 个小时,如果一个集群的分片非常多,加上观察对业务影响的时间,可能要持续上几天或一两周,并且只能是有人值守的串行操作。


2)拆分过程中需要 2 次迁移,如上面所说的,拆分中中间态实例对于内存的要求是非常大的,拆分完成后对内存的需求会急剧下降,因此每次拆分都涉及到 2 次迁移,尽管迁移不会影响业务,但对于执行操作拆分的运维人员来说,心智负担比较大,而且一不小心也会导致线上事故。


3)拆分后无法还原回去,也就是说假设业务分拆后收缩,对 Redis 的需求变小了,但它实际拆分后的分片还在那边,所申请的空间还并没有释放掉,客观上浪费了资源,降低了 Redis 总体的利用率。


4)只支持扩容,不支持缩容,这点上面也提到了,除了一些集群过大需要分拆外,还有一些申请远超需求的实例需要缩容,而水平分拆对于这点无能为力。


5)拆分一次,就多一次的性能损耗,因为需要多计算一次 hash,虽然耗时不大,但是对于性能敏感的业务还是有影响。


由此可见,水平分拆的方案虽然解决了实例过大的问题,但不能缩容的弊端也逐渐凸现了出来。尤其是在今年因疫情影响需要降本增效的背景下,一方面资源比充足,一方面宿主机上跑的都是无法缩容的实例。那么是否有更好的解决方案呢?答案是有的。


三、Redis 水平扩缩容

3.1 设计思路



图 4


既然缩分片比较困难,我们首先想到的是业务双写集群的方法,也就是业务同时双写 2 个新老集群,新老集群的分片数是不一样的,并且大小配置也不一样。比如之前申请 4 个分片现在发现资源过剩,让业务创新申请一个新的 2 个分片的集群,由业务来控制灰度写哪个集群(图 4)。最终会迁移到新集群上,而新集群大小是满足当前业务需求的,从而达到了缩容的目的。


双写集群的方案虽然解决我们部分的问题,但对于业务的侵入比较深,此外由于双写集群引入了业务配合观察的时间,整体流程也比较长。所以,我们需要寻找更好的解决方案。


既然业务双写集群可以达到要求,基础设施如果代替业务做完这部分岂不是更好?借鉴业务双写集群的思路和云原生的不可变基础设施的理念,我们首先想到的是通过新集群替换老集群而不是原地修改集群;另外,为了在公有云上节省 Redis 成本,我们积累了 kvrocks 的实践经验,两者相结合,设计了一种高效的水平扩缩容的方案。


本方案的核心是引入了一个基于 kvrocks 改造的中间态 binlogserver,它既是一个老集群的 Slave 节点,又充当了新集群的客户端。一方面,它会从 Redis Master 复制全量和增量数据;另一方面,它又充当客户端的角色,将复制来的数据按照新集群的一致性 HASH 规则写往新的集群。大致的步骤如下,具体的步骤流程可以参考下面的图所示(图 5)。


1)根据当前 V1 集群的分片启动对应个数 binlogserver,并获取 V2 集群的一致性 HASH 规则和 group。


2)每个 binlogserver 成为 V1 集群单个分片中 Master 的 Slave,执行 salveof 后保存 V1 中 Master 传过来的 RDB 文件并解析,对于每个 RDB 文件,解析还原成 Redis 命令,并按 CRedis 的一致性 hash 规则写入到 V2 中,对于后续 V1 集群传播过来的命令,同样同步到 V2 中。


3)当这个过程都完成并且 binlog 追的差不多的时候,为了数据一致性,可以停止 V1 的写(客户端报错)后由 CRedis 推送 V2 的配置或直接推送 V2 的配置(客户端不报错但数据可能会丢或不一致),APP 端将会顺序切换到 V2 上来;此过程对用户完全透明,应用端无需做任何操作。



图 5


通过 Redis 的水平扩缩容方案,我们解决了之前的几个痛点问题:


1)持续时间大大缩短,基本上跟 V1 集群最大实例的大小正相关,因为是并发执行,跟集群分片数无关。根据实际的运维数据来看,集群单个实例为 20G,集群扩缩容在 10 分钟之内完成,而低于 10G 的,5 分钟即可完成,大大缩短了扩缩容的周期,并且业务在毫无感知的情况下即可完成扩缩容。由于可以做到秒级切换集群,即使扩缩容后对业务有影响也可以快速回退,因为回退也只是更改了集群的路由指向。


2)扩缩容过程只需要 1 次切换集群指向,0 次迁移,没有中间态,也无需通过大内存宿主机来实现分拆。


3)对于扩容的集群,很方便再来一次缩容还原回去,缩容同理。对于那些已经水平拆分过的集群,也可以通过这种方式还原回去。


4)既可以扩容也可以缩容,甚至还可以不扩容也不缩容按集群来迁移,比如《携程Cilium+BGP云原生网络实践》一文中提到的云原生网络安全控制试点项目。由于原来 Redis 集群下面的实例可能同时部署在 openstack 网络和 cilium 网络,但云原生安全只能控制 cilium 网络下的实例,这种情况下就需要迁移 Redis 实例。如果按之前的运维方式,要按分片来一组组迁移,整个工程可能持续较长时间,并且耗费较多人力,而水平扩缩容可以将一个集群一次性快速迁移到 cilium 网络,省时省力。


5)扩缩容后无性能损耗。


3.2 运维数据


水平扩缩容方案上线 4 个月来,已经成功完成了 200 多次的扩容和缩容。今年某个业务突然请求量暴增十几倍,相关集群经历了多次扩容,每次扩容大多在 10 分钟内完成,有效地支撑了业务发展。


另一方面,针对申请分片非常多而大但实际使用量非常小的集群,我们也借助水平扩缩容的能力快速地缩小了分片数和申请量。通过这些缩容,有效地提升了整体的资源利用率。


3.3 一些坑


单个 key 过大导致 key 被驱逐


在实际水平扩缩容过程中,我们发现有些集群,单个实例中可能会有巨大的 key(大于 3G),由于 V2 集群的大小是根据 V1 大小实时算出来的平均值,一旦 V1 中某个实例过大,可能会导致写到 V2 中的某个实例大小大于预期的平均值,从而引起某些 key 被驱逐。因此,针对这种情况:


1)加强大 key 的检测逻辑,对于超过 512M 的 key 会有告警邮件告知所有者。


2)V2 中所有实例的 maxmemory 在分拆之前不设置限制,统一都调到 60G,防止 V2 中 key 分配不均导致 key 驱逐。


3)水平扩缩容后,在 V1 和 V2 切换过程中,检测 V2 中的实例是否发生过驱逐,如果有则默认分拆失败,不进行切换。


mget 扩容后会导致性能下降


对于极个别的场景,我们还发现,mget 请求耗时会有明显上升,主要原因还是在于,扩容之前 mget 需要访问的实例数少,而分拆后访问的实例数变多。一般这种情况,我们建议业务控制单次 mget 的 key 的数量,或者将 string 类型改造为 hash 类型,通过 hmget 来访问数据,保证每次只会访问到一个实例,这样扩容后其吞吐量是随着分片数量线性增加,而延迟不会有增加。


四、总结和未来规划

4.1 Xpipe 支持


目前水平扩缩容和漂移以及二次调度等一系列治理工具和策略组成了一个比较完善的闭环,有效地支撑了生产几千台宿主机,几万带超分能力 Redis 实例的运维治理。


但目前受制于 xpipe 的架构,对于接入了 xpipe 的集群,必须先扩缩容后再将 DR 端的 xpipe 人工补齐,自动化程度还不足,而补齐 xpipe 的时间比较长,比如之前是就近读本机房的 Redis 集群的 APP,在扩缩容后可能一段时间里只能跨机房读取,必然导致延迟上升。而这种延迟上升又会影响我们对于水平扩缩容逻辑是否正确,是否需要回退的判断。因此后续我们会针对 xpipe 集群,也做到和普通集群一样,也就是 V2 集群在扩缩容写流量之前就是带 DR 架构的集群。


4.2 持久化 KV 存储的支持


除了 Redis 本身受业务欢迎使用广泛外,我们还发现有些业务需要相比 Redis 更可靠的 KV 存储方式,比如数据保存在磁盘上而不是保存在内存里,再比如业务需要支持一些增减库存逻辑,对某个 key 的独占访问,实现语义近似 INCRBY 操作,但实际上是对于一些字符串进行 merge 操作。此外数据可靠性要求更高,master 宕机不能丢失数据等。针对这些需求目前我们已经也有一些实践经验,将在后续文章中分享。


文章转载自:携程技术(ID:ctriptech)

原文链接:携程Redis治理演进之路


2020-12-29 07:005399

评论 1 条评论

发布
用户头像
666
2020-12-29 11:34
回复
没有更多了
发现更多内容

NineData x 华为云正式上线

NineData

数据库 华为云 企业动态 语言 & 开发 NineData

浅谈ByteHouse Projection优化实践

字节跳动数据平台

OLAP Clickhouse bytehouse

凝聚全球顶尖力量,助力开源行业发展 | 2023开放原子全球开源峰会开幕式暨高峰论坛亮点抢先看!

开放原子开源基金会

开源

对线面试官-线程池(三)

派大星

Java 面试

基于 prefetch 的 H5 离线包方案 | 京东云技术团队

京东科技开发者

ios H5 andiod prefetch_related 企业号 6 月 PK 榜

ChatGPT与软件架构(5) - 网络安全

俞凡

人工智能 架构 网络安全 ChatGPT

从Docker和Kubernetes看Containerd

鲸品堂

Docker 容器 Containerd 企业号 6 月 PK 榜

原来kafka也有事务啊,再也不担心消息不一致了

JAVA旭阳

kafka

“AI Earth”人工智能创新挑战赛:助力精准气象和海洋预测Baseline[3]:TCNN+RNN模型、SA-ConvLSTM模型

汀丶人工智能

人工智能 数据挖掘 机器学习 LSTM 6 月 优质更文活动

太赞了!阿里技术团队《Java 面试官手册》突击版对外开放!

Java java面试 Java八股文 Java面试题 Java面试八股文

“AI Earth”人工智能创新挑战赛:助力精准气象和海洋预测Baseline[1]、NetCDF4使用教学、Xarray 使用教学,针对气象领域.nc文件读取处理

汀丶人工智能

人工智能 数据挖掘 机器学习 深度学习 6 月 优质更文活动

时序数据库 openGemini 线下meetup · 北航站来啦,欢迎大家报名!

华为云开源

数据库 前端

GitHub星标20k+的Java指南,号称"Star收割机"

Java java面试 Java八股文 Java面试题 Java面试八股文

电动车厂家会生产制造共享电动车吗?

共享电单车厂家

共享电动车厂家 共享电单车生产 本铯电动车厂家 电动车生产厂家

容器化部署四大优势简单说明-行云管家

行云管家

容器化 部署 IT运维 容器化部署

软件测试/测试开发丨接口测试学习笔记分享

测试人

程序员 软件测试 协议 接口测试 http和https

20个Golang片段让我不再健忘 | 京东云技术团队

京东科技开发者

Java Go 语言 企业号 6 月 PK 榜

Github 上最值得学习的 Springboot核心笔记,硬核简直了

Java spring Spring Boot 框架

原来kafka也有事务啊,再也不担心消息不一致了

Java kafka 事务

制作Jdk镜像

tiandizhiguai

Docker k8s 镜像

如何轻松应对复杂的分布式系统日志收集和分析

xfgg

ELK 日志收集架构 6 月 优质更文活动

BH1750 传感器实战教学 —— 硬件设计篇

矜辰所致

传感器 硬件设计实战 光照传感器 6 月 优质更文活动

直播app源码开发的稳定控制知识

山东布谷科技

软件 App 开发 搭建平台 直播app系统

【618备战巡礼】“三高”之第一高--如何打造高可用系统 | 京东云技术团队

京东科技开发者

高可用 集群 高可用架构 618 企业号 6 月 PK 榜

INFINI Easysearch 完成龙芯架构兼容性认证

极限实验室

搜索引擎 国产化 龙芯 easysearch 极限科技

百度APP iOS端包体积50M优化实践(三) 资源优化

百度Geek说

ios 开发语言 Object-c 企业号 6 月 PK 榜 6 月 优质更文活动

阿里Java调优笔记爆火,7大模块优化实战,请查收

Java 性能优化 性能调优

学习MyBatis的异常处理机制

Java mybatis

目前青岛只有一家正规等保测评机构吗?在哪里?

行云管家

青岛 等级保护 等保测评

“AI Earth”人工智能创新挑战赛:助力精准气象和海洋预测Baseline[2]:数据探索性分析(温度风场可视化)、CNN+LSTM模型建模

汀丶人工智能

人工智能 数据挖掘 机器学习 LSTM RNN回归 6 月 优质更文活动

携程Redis治理演进之路_文化 & 方法_携程技术_InfoQ精选文章