作者介绍:唐聪,后台开发,目前主要负责部门内公共组件建设、超级会员等产品基础系统开发等。
在2015年末,为了解决各类业务大量的排行榜的需求,我们基于redis实现了一个通用的排行榜服务,良好解决了各类业务的痛点,但是随着业务发展到2016年中,其中一个业务就申请了数百万排行榜,并随着增长趋势,破千万指日可待,同时各业务也希望能直接使用redis丰富数据结构来解决更多问题(如存储关关系链、地理位置等)。
当时面临的问题与挑战如下:
面对以上挑战,经过多维度的方案选型对比,最终选择了基于codis(3.x版本),结合内部需求和运营环境进行了定制化改造,截止到目前,初步实现了一个支持单机/分布式存储、平滑扩缩容、超大key迁移、高可用、业务自动化接入调度部署、多维度监控、配置管理、容量管理、运营统计的redis服务平台,在CMEM、Grocery不能满足业务需求的场景下,接入了SNG增值产品部等五大部门近30+业务集群(图一),350+实例, 2T+容量。
下文将从方案选型、整体架构、自动化接入、数据迁移、高可用、运营实践等方面详细介绍我们在生产环境中的实践情况。
图一 部分接入业务列表
Redis因其丰富的数据结构、易用性越来越受到广大开发者欢迎,根据DB-Engines的最新统计,已经是稳居数据库产品的top10(见图一)。云计算服务产商AWS、AZURE、阿里云、腾讯云都提供了Redis产品,各云计算产商主流方案都是基于开源Redis内核做定制化优化,解决Redis不足之处,在提升Redis稳定性、性能的同时最大程度兼容开源Redis。单机主备版各厂商差异不大,都是基于原生Redis内核,但是集群版,AWS使用的原生的Redis自带的Cluster Mode模式(加强版),,阿里云基于Proxy、原生Redis内核实现,路由等元存储数据保存在RDS,架构类似Codis, 腾讯云集群版是基于内部Grocery。了解完云计算产商解决方案,再看业界开源、公司内部,上文提到我们面临问题之一就是单机容量瓶颈,因此需要一款集群版产品,目前业界开源的主流的Redis集群解决方案有Codis,Redis Cluster,Twemproxy,公司内部的有SNG Grocery、IEG的TRedis,从以下几个维度进行对比,详细结果如表一所示(2016年10月时数据):
图二 数据库流行度排名
Feature | Codis | Redis Cluster | TwemProxy | Grocery Redis | TRedis |
---|---|---|---|---|---|
存储引擎 | 基于原生Redis扩展增加迁移相关指令 | 原生Redis | 原生Redis | 多阶哈希+LinkTable | Rocksdb/LSM |
数据分布算法 | 哈希槽crc16(key) % 1024 | 哈希槽crc16(key) % 16384 | ketama/modula/random | 一致性hash | 哈希槽 |
平滑扩缩容 | 支持 | 支持 | 不支持 | 支持 | 支持 |
Value大小限制 | 无 | 无 | 无 | 1M | 无 |
ZSET实现 | Skiplist + Hash | Skiplist + Hash | Skiplist + Hash | Skiplist + Hash | Skiplist+Hash(内存),key-value(磁盘) |
开发语言(Proxy) | Go | 采用无中心节点设计,无Proxy | C | C++ | C/C++(基于TwemProxy) |
单线程/多线程/多进程(Proxy) | 多线程 | 无 | 单线程 | 多进程 | 单线程/多线程 |
超大key迁移(>512M) | 不支持 | 不支持 | 不支持 | - | - |
机型 | 内存型 | 内存型 | 内存型 | 内存型或SSD IO型 | SSD IO型 |
Client | 任意 | 需要支持cluster语义 | 任意 | 提供SDK | 任意 |
Pipeline | 支持 | 不支持 | 支持 | 支持 | 支持 |
运维成本 | 低 | 高 | 高 | 无 | 无 |
定制开发成本 | 低 | 高 | 高 | 无 | 无 |
表一 Redis集群产品对比
云计算产商和业界开源、公司内部的解决方案从整体架构分类,分别是基于Proxy中心节点和无中心节点,在这点上我们更偏爱基于Proxy中心节点架构设计,运维成本更低、更加可控,从存储引擎分类,分别是基于原生Redis内核和第三方存储引擎(如Grocery的多阶HASH+LinkTable、TRedis的Rocksdb),在这点上我们更偏爱基于原生Redis内核,因为我们要解决业务场景就是Grocery和CMem无法满足的地方,我们业务大部分使用的数据结构是ZSET且Key一般超过1M,几十万级元素的ZSET Key是常态,Grocery的Value 1M大小限制无法满足我们的需求,同时我们需要ZSET的ZRank的时间复杂度是O(LogN),基于RocksDb的存储引擎时间复杂度是O(N),因此这也是无法接受的。随着业务发展,容量势必会发生变化,因此扩缩容是常态,而TwemProxy并不支持平滑扩缩容,因此也无法满足要求。最后,我们需要结合内部运营环境和需求做定制化改造,在零运维的支持下,通过技术手段,最大程度自动化治理、运营众多多业务集群,而Codis代码结构清晰,开发语言又是现在比较流行的Go,无论是运行性能、还是开发效率都较高效,因此我们最终选择了Codis.
基于Codis定制开发而成的Redis服务平台整体架构如图二所示,其包含以下组件:
图三 整体架构
当面对成百上千乃至上万个Redis实例时,人工根据业务申请单去过滤无效节点、筛选符合业务要求的节点、再从候选节点中找出最优节点等执行一些列繁琐枯燥流程,这不仅会导致工作乏味、效率低,而且更会大大提升系统的不稳定性,引发运营事故。当繁琐、复杂的流程变成自动化后,工作就会变得充满乐趣,图三是业务接入调度流程,用户在运维管理系统提单接入后,调度器会定时从CDB中读取待调度的业务申请单,首先是筛选过滤流程,此流程包含一系列模块,在设计上是可以动态扩展,目前实现的筛选模块如下:
Health: 健康探测模块,过滤宕机、裁测下线的节点IP
Lable:标签模块,根据业务申请单匹配部署环境(测试、现网)、部署城市、业务模块、Redis存储类型(单机版、分布式存储版)
Instance: 检查当前节点上是否有空余的Redis实例(筛选Redis实例时)
Capacity: 检查当前节点CPU、Memory是否超过安全阀值
Role:检查当前节点角色是否满足要求(如Redis实例所属节点机器必须是Redis Node),角色分为三类Proxy Node,Redis Node,Dashboard Node
以上筛选模块,适用Proxy、Redis、Dashboard节点的筛选,在完成以上筛选模块后,返回的是符合要求的候选节点,对候选节点我们又需要对其评分,从中评出最优节点,目前实现的评分模块有最小内存调度、最大内存调度、最小CPU调度、随机调度等。
图四 业务接入调度流程
通过运行以上一系列筛选和评分模块后,就可以准确、快速的获取到新集群的Dashboard、Proxy、Redis的部署节点地址,但是离自动化交付给业务使用还差一个重要环节(部署)。目前主要是通过以下三个方面来解决自动化部署,其一,Codis本身是基于配置文件部署的,每新增一个业务集群必须在配置文件指定集群名字,新建一个PKG包,维护成本非常高,我们通过监听指定网卡+核心配置项迁移到ZooKeeper,实现配置管理API化,同时部署包标准统一化。其二,在各节点上都会部署Agent,Agent会定时采集上报各节点信息入库到容量表,无需人工干预,容量管理自动化,未使用的实例形成一个小型资源buffer池。其三,部署是个多阶段的流程,需要分解成各状态,并保证每个状态都是可重入、幂等性的,当所有状态完成后,则调度结束,某状态失败时,下次调度检查到申请单非完成状态,会自动重试失败的流程,直至完成,拆分后的部署状态流程图如图四所示。
通过以上两个核心流程,自动化调度分配实例+自动化部署,我们可以将部署时间从最开始的15min+,优化到秒级,在大大提升工作效率的同时,提升了系统稳定性、避免了人为操作错误引起的运营事故。
图五 自动化部署流程
扩缩容是存储系统的常归化操作,理想中的数据迁移应该是尽量不影响线上业务正常读写访问、支持任意大小的Key、优异的迁移性能、保证迁移前后的数据一致性,但是Codis在2016年末的时候数据迁移功能差强人意。首先是迁移速度慢,其次是只支持同步迁移,较大的Key迁移会阻塞Redis主线程,影响线上业务正常读写,最后是不支持超大Key迁移(>512M)。虽然各种最佳实践不断强调需要避免大Key,的确大Key可能会是系统潜在的一个风险点(如大key删除、迁移、热点访问等),但是在不少业务场景下,业务层是无法高效、简单的完成分Key的,Redis本身也在不断的优化,降低大Key风险,比如4.0版本提供了异步删除Key功能,倘若存储层能快速完成大Key迁移,这不仅会大大简化业务端的复杂度,更会提升Redis稳定性、可用性,但是内存型存储系统在大Key迁移的上复杂度比非内存型存储系统多一个数量级,这也是为什么Redis到现在还未实现大Key迁移和异步迁移的功能。
大Key若能拆分成小Key分批次异步迁移、并在迁移过程中该Key可读、不可写,只要迁移速度够快,这对业务而言是可以接受的,在2016年末的时候我跟Codis核心作者spinlock交流了大key迁移的想法,令人惊喜膜拜的是,他在农历春节期间就快马加鞭实现了异步迁移原型,在这过程中我们协助其测试、反馈BUG和瓶颈、不断改进、优化迁移性能,最终异步迁移不仅支持任意大小Key迁移,而且迁移性能相比同步迁移要快5-6倍,我们也是第一个在线上大规模应用实践Redis异步迁移的,更令人可喜的是此异步迁移方案击败了Redis作者antirez之前计划的多线程方案,将正式合入Redis 4.2版本。
在介绍异步迁移方案实现前,先介绍下Codis是如何保证过程中数据一致性和为什么同步迁移慢。如何保证迁移过程中各Proxy读取到的数据一致性?Codis主要迁移流程如图五所示,其采用了多阶段状态机实现,类似分布式事务中的多阶段提交协议,其核心流程如下。
通过多阶段的状态提交和细粒度、ms级别的锁,Codis优雅的解决了迁移过程中的数据一致性。
图六 Codis迁移状态流程图
图七 Redis同步迁移流程
再看为什么同步迁移慢,图七是迁移一个1000万元素的ZSET耗时分析,当Client发起迁移指令后,源端将整个ZSET序列化成payload花费了10.27s,通过网络传输给目的端Redis花费1.65s,目的端Redis收到数据后,将其反序列化成内存中的数据结构,花费了36.65s,最后源端Redis删除迁移完成的Key又花费了6.11s,而整个迁移过程中,源端Redis是完全阻塞的,不能提供任何读写访问。因此,异步迁移方案若要提升迁移性能,必须在以上四个流程上面做优化。
异步迁移的流程如图八所示,面对同步迁移的四个核心点,异步迁移的解决方案如下:
图八 Redis异步迁移流程
1000万的ZSET,同步迁移需要54.87s,而异步迁移只需要8.3s,在不阻塞在线业务的前提下,性能提升6倍多,以我们生产环境某全球9000w排行榜为例,之前单机主备版加载到内存都需要20分钟,而用异步跨机器迁移只需要180s左右, 更详细的迁移介绍可参看附录spinlock的Codis新版本特性介绍。
各组件中跟用户请求相关性很强的组件分别是Proxy、Redis、元数据存储(ZooKeeper),相关性较弱的是Dashboard。
Proxy:多机多IDC部署,调度服务会根据IDC ID,自动打散相同proxy,尽量保证同一集群proxy部署在不同IDC,通过L5和CMLB进行容灾。
Redis:基于Redis Sentinel进行主备自动化切换。
ZooKeeper:高可用分布式协调服务,一半以上节点存活即可提供服务,同时只有在Proxy启动时和运行过程中发生数据迁移才会依赖ZooKeeper,绝大部分正常请求不受ZooKeeper 集群状态影响。
Dashboard: 负责协调集群状态变更及一致性,目前在设计上是个单点,但是只有在就集群运行过程中发生数据迁移才会依赖它,因此是弱相关性, 后续还可以优化成多节点部署,通过ZooKeeper的分布式锁来保证只有一个节点能提供服务,当提供服务的节点故障时,通过一系列流程(如需通知Proxy,Dashboard变更等)实现Dashboard自动化故障切换。
重点介绍redis的主备自动切换流程,常见的Master-Slave存储系统自动切换方案一般有如下三种:
图九 Redis主备自动切换流程
图九流程简要分析如下:
图十 Redis Ops曲线
图十一 master/slave offset差异曲线
2 . 低负载优化
3 .多租户
4.数据安全及备份
基于Codis为核心的Redis服务平台高效解决了SNG大量业务的痛点(不限制Key大小,原生的Redis内核,高性能),提高了开发效率,助力产品更快发展,但是因人力有限(半个开发投入,在业务项目人力紧张的时候,零投入),还有若干待完善的地方,如不支持冷热分离等。 在千呼万唤中,目前公司内的存储组自研的CKV+(基于共享内存实现Redis各类数据结构)的单机主从版也终于上线,集群版也在紧锣密鼓的开发中,CKV+较好的解决了Redis内存使用率、跨IDC部署、数据备份及同步机制的一些不足之处,后续业务也将有更多的选择!最后感谢antirez,spinlock的无私贡献!
文章来源于小时光茶社微信公众号
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。