Redis 作为一个使用场景很高的 NoSQL 数据库,支持了较为丰富的数据类型,相比于其他关系型数据库在性能方面优势明显。互联网公司通常更加倾向于将一些热点数据放入Redis中来承载高吞吐量的访问。
单机Redis在普通的服务器上通常ops上限在5w左右,开启pipeline的情况下在20-30w左右。对于大多数中小公司来说,通常单机的Redis已经足够,最多根据不同业务分散到多台Redis。
为什么需要集群
Redis单线程特性,多请求顺序执行,单个耗时的操作会阻塞后续的操作
单机内存有限
某些特殊业务,带宽压力较大
单点问题,缺乏高可用性
不能动态扩容
Redis集群的目标就是为了实现高可用性,避免性能瓶颈,可动态扩容,易于做监控告警。
下面介绍Redis的几种主流的集群解决方案。
通常需要 smart-client 支持,在业务程序端根据预先设置的路由规则进行分片,从而实现对多个redis实例的分布式访问。
图1 客户端分片的模式
鉴于redis本身的高性能,并且有一些设计良好的第三方库,例如java开发者可以使用jedis,所以很多小公司使用此方案。
优点: 相比于使用代理,减少了一层网络传输的消耗,效率较高。
缺点: 当redis实例需要扩容或切换的情况下,需要修改业务端的程序,较为麻烦。并且需 要维护各个语言的客户端版本,如果要升级客户端成本也会比较高。出现故障时难以及时定位问题。(有些smart-client借助于zookeeper维护客户端访问redis实例的一致性)
通过统一的代理程序访问多个redis实例,比如业内曾广泛使用的 twemproxy 以及豌豆荚开源的 codis。(twemproxy是twitter开源的一个redis和memcache代理服务器,只用于作为简单的代理中间件,目前twitter内部已经不再使用)
优点: 业务程序端只需要使用普通的api去访问代理程序即可。各种组件分离,以后升级较为容易。也避免了客户端需要维持和每个redis实例的长连接导致连接数过多。
缺点: 增加了一层中间件,增加了网络和数据处理的消耗,性能下降。
Twemproxy是由Twitter开源的Redis代理,其基本原理是:Redis客户端把请求发送到Twemproxy,Twemproxy根据路由规则发送到正确的Redis实例,最后Twemproxy把结果汇集返回给客户端。
Twemproxy通过引入一个代理层,将多个Redis实例进行统一管理,使Redis客户端只需要在Twemproxy上进行操作,而不需要关心后面有多少个Redis实例,从而实现了Redis集群。
Twemproxy集群架构如图2所示。
图2 Twemproxy集群架构
Twemproxy的优点如下。
客户端像连接Redis实例一样连接Twemproxy,不需要改任何的代码逻辑。
支持无效Redis实例的自动删除。
Twemproxy与Redis实例保持连接,减少了客户端与Redis实例的连接数。
Twemproxy有如下不足。
由于Redis客户端的每个请求都经过Twemproxy代理才能到达Redis服务器,这个过程中会产生性能损失。
没有友好的监控管理后台界面,不利于运维监控。
最大的问题是Twemproxy无法平滑地增加Redis实例。对于运维人员来说,当因为业务需要增加Redis实例时工作量非常大。
Twemproxy作为最被广泛使用、最久经考验、稳定性最高的Redis代理,在业界被广泛使用。
Twemproxy不能平滑增加Redis实例的问题带来了很大的不便,于是豌豆荚自主研发了Codis,一个支持平滑增加Redis实例的Redis代理软件,其基于Go和C语言开发,并于2014年11月在GitHub上开源。
Codis包含下面4个部分。
Codis Proxy:Redis客户端连接到Redis实例的代理,实现了Redis的协议,Redis客户端连接到Codis Proxy进行各种操作。Codis Proxy是无状态的,可以用Keepalived等负载均衡软件部署多个Codis Proxy实现高可用。
CodisRedis:Codis项目维护的Redis分支,添加了slot和原子的数据迁移命令。Codis上层的 Codis Proxy和Codisconfig只有与这个版本的Redis通信才能正常运行。
Codisconfig:Codis管理工具。可以执行添加删除CodisRedis节点、添加删除Codis Proxy、数据迁移等操作。另外,Codisconfig自带了HTTP server,里面集成了一个管理界面,方便运维人员观察Codis集群的状态和进行相关的操作,极大提高了运维的方便性,弥补了Twemproxy的缺点。
ZooKeeper:分布式的、开源的应用程序协调服务,是Hadoop和Hbase的重要组件,其为分布式应用提供一致性服务,提供的功能包括:配置维护、名字服务、分布式同步、组服务等。Codis依赖于ZooKeeper存储数据路由表的信息和Codis Proxy节点的元信息。另外,Codisconfig发起的命令都会通过ZooKeeper同步到CodisProxy的节点。
Codis的架构如图3所示
在图3的Codis的架构图中,Codis引入了Redis Server Group,其通过指定一个主CodisRedis和一个或多个从CodisRedis,实现了Redis集群的高可用。当一个主CodisRedis挂掉时,Codis不会自动把一个从CodisRedis提升为主CodisRedis,这涉及数据的一致性问题(Redis本身的数据同步是采用主从异步复制,当数据在主CodisRedis写入成功时,从CodisRedis是否已读入这个数据是没法保证的),需要管理员在管理界面上手动把从CodisRedis提升为主CodisRedis。
如果觉得麻烦,豌豆荚也提供了一个工具Codis-ha,这个工具会在检测到主CodisRedis挂掉的时候将其下线并提升一个从CodisRedis为主CodisRedis。
Codis中采用预分片的形式,启动的时候就创建了1024个slot,1个slot相当于1个箱子,每个箱子有固定的编号,范围是1~1024。slot这个箱子用作存放Key,至于Key存放到哪个箱子,可以通过算法“crc32(key)%1024”获得一个数字,这个数字的范围一定是1~1024之间,Key就放到这个数字对应的slot。例如,如果某个Key通过算法“crc32(key)%1024”得到的数字是5,就放到编码为5的slot(箱子)。1个slot只能放1个Redis Server Group,不能把1个slot放到多个Redis Server Group中。1个Redis Server Group最少可以存放1个slot,最大可以存放1024个slot。因此,Codis中最多可以指定1024个Redis Server Group。
Codis最大的优势在于支持平滑增加(减少)Redis Server Group(Redis实例),能安全、透明地迁移数据,这也是Codis 有别于Twemproxy等静态分布式 Redis 解决方案的地方。Codis增加了Redis Server Group后,就牵涉到slot的迁移问题。例如,系统有两个Redis Server Group,Redis Server Group和slot的对应关系如下。
当增加了一个Redis Server Group,slot就要重新分配了。Codis分配slot有两种方法。
第一种:通过Codis管理工具Codisconfig手动重新分配,指定每个Redis Server Group所对应的slot的范围,例如可以指定Redis Server Group和slot的新的对应关系如下。
第二种:通过Codis管理工具Codisconfig的rebalance功能,会自动根据每个Redis Server Group的内存对slot进行迁移,以实现数据的均衡。
Redis3.0之后的版本开始正式支持 redis cluster,核心目标是:
性能:Redis作者比较看重性能,增加集群不能对性能有较大影响,所以Redis采用了P2P而非Proxy方式、异步复制、客户端重定向等设计,而牺牲了部分的一致性、使用性。
水平扩展:官方文档中称目标是能线性扩展到1000结点。
可用性:集群具有了以前Sentinel的监控和自动Failover能力。
数据分布:预分片
图4 Redis 3.0集群的预分片图
预先分配好 16384 个slot
slot 和 server 的映射关系存储每一个 server 的路由表中
根据 CRC16(key) mod 16384 的值,决定将一个key放到哪一个slot中
数据迁移时就是调整 slot 的分布
架构:去中心化
图5 Redis 3.0集群的去中心化示意图
无中心结构,每个节点都保存数据和整个集群的状态。
采用 gossip 协议传播信息以及发现新节点(最终一致性)。
每个节点都和其他所有节点连接,并保持活跃。
PING/PONG:心跳,附加上自己以及一些其他节点数据,每个节点每秒随机PING几个节点。会选择那些超过cluster-node-timeout一半的时间还未PING过或未收到PONG的节点。
UPDATE消息:计数戳,如果收到server的计数为3,自己的为4,则发UPDATE更新对方路由表,反之更新自己的路由表,最终集群路由状态会和计数戳最大的实例一样。
如果 cluster-node-timeout 设置较小,或者节点较多,数据传输量将比较可观。
Broadcast:有状态变动时先broadcast,后PING; 发布/订阅。
Redis node 不作为client请求的代理(不转发请求),client根据node返回的错误信息重定向请求?(需要 smart-client 支持),所以client连接集群中任意一个节点都可以。
可用性:Master-Slave
每个Redis Node可以有一个或者多个Slave,当Master挂掉时,选举一个Slave形成新的Master。
Master Slave 之间异步复制(可能会丢数据)。
采用 gossip 协议探测其他节点存活状态,超过 cluster-node-timeout,标记为 PFAIL,PING中附加此数据。当 Node A发现半数以上master将失效节点标记为PFAIL,将其标记为FAIL,broadcast FAIL。
各 slave 等待一个随机时间后 发起选举,向其他 master broadcast,半数以上同意则赢得选举否则发起下一次选举
当 slave 成为 master,先broadcast,后持续PING,最终集群实例都获知此消息
Redis 3.0集群采用了P2P的模式,完全去中心化。Redis把所有的Key分成了16384个slot,每个Redis实例负责其中一部分slot。集群中的所有信息(节点、端口、slot等),都通过节点之间定期的数据交换而更新。
Redis客户端在任意一个Redis实例发出请求,如果所需数据不在该实例中,通过重定向命令引导客户端访问所需的实例。
Redis 3.0集群的工作流程如图6所示。
图6 Redis 3.0集群的工作流程图
如图6所示Redis集群内的机器定期交换数据,工作流程如下。
(1)Redis客户端在Redis2实例上访问某个数据。
(2)在Redis2内发现这个数据是在Redis3这个实例中,给Redis客户端发送一个重定向的命令。
(3)Redis客户端收到重定向命令后,访问Redis3实例获取所需的数据。
Redis 3.0的集群方案有以下几个问题。
一个Redis实例具备了“数据存储”和“路由重定向”,完全去中心化的设计。这带来的好处是部署非常简单,直接部署Redis就行,不像Codis有那么多的组件和依赖。但带来的问题是很难对业务进行无痛的升级,如果哪天Redis集群出了什么严重的Bug,就只能回滚整个Redis集群。
对协议进行了较大的修改,对应的Redis客户端也需要升级。升级Redis客户端后谁能确保没有Bug?而且对于线上已经大规模运行的业务,升级代码中的Redis客户端也是一个很麻烦的事情。
Gossip协议通信开销
严重依赖于smart-client的成熟度
如果smart-client支持缓存slot路由,需要额外占用内存空间,为了效率需要建立和所有 redis server 的长连接(每一个使用该库的程序都需要建立这么多连接)。
如果不支持缓存路由信息,会先访问任意一台 redis server,之后重定向到新的节点。
需要更新当前所有的client。
官方只提供了一个ruby程序 redis-trib 完成集群的所有操作,缺乏监控管理工具,很难清楚目前集群的状态
数据迁移以Key为单位,速度较慢
某些操作不支持,MultiOp和Pipeline都被限定在命令中的所有Key必须都在同一Slot内
综合上面所述的几个问题,Redis 3.0集群在业界并没有被大规模使用。
国内的云服务器提供商阿里云、UCloud等均推出了基于Redis的云存储服务。
这个服务的特性如下。
(1)动态扩容
用户可以通过控制面板升级所需的Redis存储空间,扩容的过程中服务部不需要中断或停止,整个扩容过程对用户透明、无感知,这点是非常实用的,在前面介绍的方案中,解决Redis平滑扩容是个很烦琐的任务,现在按几下鼠标就能搞定,大大减少了运维的负担。
(2)数据多备
数据保存在一主一备两台机器中,其中一台机器宕机了,数据还在另外一台机器上有备份。
(3)自动容灾
主机宕机后系统能自动检测并切换到备机上,实现服务的高可用。
(4)实惠
很多情况下为了使Redis的性能更高,需要购买一台专门的服务器用于Redis的存储服务,但这样子CPU、内存等资源就浪费了,购买Redis云存储服务就很好地解决了这个问题。
有了Redis云存储服务,能使App后台开发人员从烦琐运维中解放出来。App后台要搭建一个高可用、高性能的Redis服务,需要投入相当的运维成本和精力。如果使用云存储服务,就没必要投入这些成本和精力,可以让App后台开发人员更专注于业务。
来源:转自公众号“运维之美”
新春“开工大吉”
GOPS 2019 · 深圳站
传播先进技术思想和理念,分享业内最佳实践
联系客服