作者
李竟成 编辑
雨多田光 标签
分布式缓存 前言
R2M是京东金融线上大规模应用的分布式缓存系统,目前管理的机器总内存容量超过60TB,近个RedisCluster集群,多个Redis实例。
其主要功能包括:全web可视化运维、缓存集群一键部署、资源池统筹管理、在线扩容及快速数据迁移、多机房切换及容灾、完善的监控及告警、RedisAPI兼容等。
本文将从R2M系统架构、资源管理、集群扩容与迁移、数据冷热交换、多机房容灾等多方面进行深入剖析,希望读者能有所收获。
业务使用及运维上的优点 极简化接入R2M接入简单,以Java客户端为例,只需在业务程序中引入客户端jar包,配置好appname及ZK地址,即可使用。
配置自动下发R2M客户端提供了诸如连接池、读写超时时间、重试次数等配置,有些情况下,需要对这些配置进行调优,比如、双十一大促来临时,有些业务需要减小客户端读写超时时间,避免超时问题引发的连锁反应。
为了修改配置,业务不得不重新上线所有的客户端,不仅费时费力,还有可能引发故障。因此,为了避免这个问题,R2M提供了配置自动下发功能,业务客户端无需重新上线,管理端修改配置后,所有客户端即时生效。
多种数据类型API支持完全兼容Redis各数据类型API接口,提供包括哈希、集合、有序集合、列表、发布/订阅等近百个接口。
支持数据冷热交换在保证高性能和数据一致性的前提下,实现了基于LFU热度统计的数据动态冷热交换,使得冷数据被交换到SSD上,热数据被加载到Redis上,取得高性能和低成本的高平衡。
全web可视化运维R2M实现了包括集群部署、扩容、数据迁移、监控、集群下线、数据清理、故障节点替换及机器下线等在内的所有功能的可视化运维,运维效率及可用性大大提升。
多机房一键切换通过改造RedisCluster支持多机房灾备、防止集群脑裂,以及各系统组件针对多机房切换选举的支持,实现了在Web控制台的多机房一键切换。
多种方式的集群同步及数据迁移工具R2M提供了专门的迁移工具组件,支持从原生Redis迁移至R2M,实现了基于RedisRDB文件传输及增量同步的迁移机制。
同时,还可以指定规则比如按照前缀匹配或者反向匹配进行部分数据迁移。由于内部历史原因,京东金融部分业务以前用的是京东商城的Jimdb,R2M同样也支持了从Jimdb集群进行迁移。
R2M迁移工具还支持数据实时同步的功能,包括R2M集群间的数据同步和Jimdb源集群的数据同步。
非Java语言代理我们的业务开发人员主要用的是Java,对于非Java语言的客户端,比如Python、C等等,则通过R2MProxy组件接入,各种运维操作在Proxy层进行,对业务方屏蔽。同时,Proxy也提供了高可用保障方案。
系统架构 组件功能 Webconsole是R2M缓存系统的可视化运维控制台。所有运维操作均在Webconsole进行。
Manager整个系统的管理组件。负责所有运维操作的下发、监控数据的收集等。运维操作包括集群创建、数据迁移、扩容、多机房切换等等。
Agent每台物理机上部署一个Agent组件,Agent负责本机Redis实例的部署和监控,并进行数据上报。
缓存集群节点每个集群的节点分布在不同机器上,若干个节点构成一个分布式集群,去中心化,无单点。
Client客户端由业务方引入,如:Java通过jar包方式引入。
Proxy对于非Java客户端的业务,通过Proxy提供缓存接入和服务。
缓存集群一键部署分布式集群的部署通常来说是比较麻烦的,涉及到多台机器、多个节点及一系列的动作和配置,比如部署RedisCluster,就包括节点安装与启动、节点握手、主从分配、插槽分配、设置复制这些步骤。
虽然官方提供了构建集群的工具脚本,但机器推荐、节点安装及配置仍然没有自动化,对于需要大规模部署和运维RedisCluster的企业来说,仍然不够灵活和便捷。
因此,R2M基于Golang实现了在Web控制台一键完成从机器推荐、节点部署、集群构建、到节点配置的所有动作,这个过程中每台机器上的Agent组件负责下载并安装RPM包、在指定端口上启动实例,Manager组件则负责完成集群构建和节点配置过程。
自动部署过程中,还有一些必要的验证条件和优先规则,主要是以下几点:
检查主节点数量和主备策略,是否满足分布式选举及高可用的最低配置条件:三个以上主节点、一主一从。
避免同一个集群多个节点部署在相同机器上,防止机器故障,优先推荐符合条件的机器。
根据机器可用内存,优先推荐剩余可用内存多的机器。
根据主节点数量,均衡分配插槽数量。
资源规划与统筹管理由于机房、机器、业务数量众多,要做好平台化管理,需要对资源进行合理的事先规划,事先规划包括:申请接入时业务方对容量进行预估,合理分配缓存实例大小和数量、机器的预留内存,为了方便管理和统计,通常还需要根据机房对机器进行分组、或者根据业务进行机器分组。
做好事先规划的同时,还需要对资源使用情况做到一目了然,避免出现超用或严重超配的情况。
严重超配,就是实际使用量远小于预估容量的情况,这种情况还是很多的,因为很多时候容量很难准确预估,或者有的业务开发为了保险起见或担心不好扩容,申请的容量往往远大于实际需要,对于这种情况,我们可以进行一键缩容,防止资源浪费。
对于超用,也就是机器实际资源使用量超过了标准,则是要非常注意的,缓存机器超用比如Redis机器超用可能导致非常严重的后果,比如OOM、发生数据淘汰、性能急剧下降,为了避免超用,需要进行合理的资源预留、选择合适的淘汰策略,同时平台要有完善的监控和实时的报警功能。
R2M通过对机房、分组、机器、实例的层级管理和监控,方便我们更好地对资源进行规划,并最大限度的统筹和平衡资源利用,防止资源浪费和机器超用。
扩容及数据迁移业务在申请缓存时,我们都会要求预估容量,根据预估值进行分配,但预估值经常是不准确的,计划也永远赶不上变化,业务场景拓展、业务量和数据的增长总是无法预测的。
因此,良好的扩容机制显得尤为重要,扩容做得好不好,决定了系统的扩展性好不好。在R2M中,我们将水平扩容解耦为两个步骤,添加新节点和数据迁移,添加新节点其实就是一个自动部署的过程,很多步骤与集群创建过程是相同的,那么关键就在于如何解决数据迁移问题了。
数据迁移主要解决以下问题:
如何做到不影响业务的正常读写,即业务方对迁移是基本无感知的?
如何保证迁移的速度?
当一条数据处于迁移中间状态时,如果此时客户端需要对该数据进行读写,如何处理?
迁移过程中收到读操作,是读源节点还是目标节点,如何确保客户端不会读到脏数据?
迁移过程中是写源节点还是写目标节点,如果确保不写错地方、不会由于迁移使得新值被旧值覆盖?
迁移完成,如何通知客户端,将读写请求路由到新节点上?
为了解决这些问题,我们充分吸取了RedisCluster的优点,同时也解决了它的一些不足之处和缺点。
数据迁移原理上图就是RedisCluster的数据迁移原理图,迁移过程是通过源节点、目标节点、客户端三者共同配合完成的。
RedisCluster将数据集分为个插槽,插槽是数据分割的最小单位,所以数据迁移时最少要迁移一个插槽,迁移的最小粒度是一个key,对每个key的迁移可以看成是一个原子操作,会短暂阻塞源节点和目标节点上对该key的读写,这样就使得不会出现迁移“中间状态”,即key要么在源节点,要么在目标节点。
如上图,假设正在迁移9号插槽,首先会在源节点中将9号插槽标记为“正在迁移”状态,将目标节点中9号插槽标记为“正在导入”状态,然后遍历迁移该插槽中所有的key。
此时,如果客户端要对9号插槽的数据进行访问,如果该数据还没被迁移到目标节点,则直接读写并返回,如果该数据已经被迁移到目标节点,则返回Ask响应并携带目标节点的地址,告诉客户端到目标节点上再请求一次。
如果9号插槽的数据被全部迁移完成,客户端还继续到源节点进行读写的话,则返回Moved响应给客户端,客户端收到Moved响应后可以重新获取集群状态并更新插槽信息,后续对9号插槽的访问就会到新节点上进行。这样,就完成了对一个插槽的迁移。
多节点并行数据迁移RedisCluster是基于CRC16算法做hash分片的,在插槽数量差不多且没有大key存在的情况下,各节点上数据的分布通常非常均衡,而由于数据迁移是以key为单位的,迁移速度较慢。
当数据量暴涨需要紧急扩容时,如果一个接一个地进行主节点数据迁移,有可能部分节点还没来得及迁移就已经把内存“撑爆”了,导致发生数据淘汰或机器OOM。
因此,R2M实现了多节点并行数据迁移,防止此类问题发生,同时也使数据迁移耗时大大缩短,另外,结合Redis3.0.7之后的pipeline迁移功能,可以进一步减少网络交互次数,缩短迁移耗时。
可控的数据迁移水平扩容添加新节点后,为了做数据/负载均衡,需要把部分数据迁移到新节点上,通常数据迁移过程是比较耗时的,根据网络条件、实例大小和机器配置的不同,可能持续几十分钟至几个小时。
而数据迁移时可能对网络造成较大的压力,另外,对于正在迁移的slot或keys,RedisCluster通过ASK或MOVED重定向机制告诉客户端将请求路由至新节点,使得客户端与Redis多发生一次请求响应交互。
并且通常客户端的缓存读写超时比较短(通常在~ms以内),在多重因素的作用下,有可能造成大量读写超时情况,对在线业务造成较大的影响。
基于此,我们实现了迁移任务暂停和迁移任务继续,当发现迁移影响业务时,随时可以暂停迁移,待业务低峰期再继续进行剩余的数据迁移,做到灵活可控。
自动扩容R2M还提供了自动扩容机制,开启自动扩容后,当机器可用内存足够时,如果实例已使用容量达到或超过了预设的阀值,则自动对容量进行扩大。
对于一些比较重要的业务,或不能淘汰数据的业务,可以开启自动扩容。当然,自动扩容也是有条件的,比如不能无限制的自动扩容,实例大小达到一个比较高的值时,则拒绝自动扩容,还要预留出一部分内存进行Fork操作,避免机器发生OOM。
数据冷热交换存储由于我们线上存在很多大容量(几百GB或几个TB)缓存集群,缓存机器内存成本巨大,线上机器总内存容量已达到66TB左右。
经过调研发现,主流DDR3内存和主流SATASSD的单位成本价格差距大概在20倍左右,为了优化基础设施(硬件、机房机柜、耗电等)综合成本,因此,我们考虑实现基于热度统计的数据分级存储及数据在RAM/FLASH之间的动态交换,从而大幅度降低基础设施综合成本,并达到性能与成本的高平衡。
R2M的冷热交换存储的基本思想是:基于key访问次数(LFU)的热度统计算法识别出热点数据,并将热点数据保留在Redis中,对于无访问/访问次数少的数据则转存到SSD上,如果SSD上的key再次变热,则重新将其加载到Redis内存中。
思想很简单,但实际上做起来就不那么容易了,由于读写SATASSD相对于Redis读写内存的速度还是有很大差距的,设计时为了避免这种性能上的不对等拖累整个系统的性能、导致响应时间和整体吞吐量的急剧下降,我们采用了多进程异步非阻塞模型来保证Redis层的高性能,通过精心设计的硬盘数据存储格式、多版本key惰性删除、多线程读写SSD等机制来最大限度的发挥SSD的读写性能。
多进程异步模型,主要是两个进程,一个是SSD读写进程,用于访问SSD中的key,一个是深度改造过的Redis进程,用于读写内存key,同时如果发现要读写的key是在SSD上,则会将请求转发给SSD读写进程进行处理。
Redis进程这一层,最开始我们其实是基于Redis3.2做的,但Redis4出了RC版本之后尤其是支持了LFU、Psync2、内存碎片整理等功能,我们就果断的切到Redis4上进行改造开发了。
SSD读写进程,起初是基于开源的SSDB进行开发,不过由于SSDB的主从复制实现性能很差,数据存储格式设计还不够好,与RedisAPI有很多的不兼容问题,最终除了基本的网络框架外,基本重写了SSDB。
另外由于SSDB默认采用的存储引擎是leveldb,结合功能特性、项目活跃度等方面的原因,我们改成了比较流行的RocksDB,当然,其实它也是发源于leveldb的。
目前我们内部已完成了该项目的开发,并进行了全面的功能、稳定性和性能测试,即将上线。由于冷热交换存储涉及的内容较多,由于篇幅原因,这里不再详述。该项目已命名为swapdb,并在Github开源