Redis集群方案大全
由于Redis出众的性能,丰富的数据类型,其在众多的互联网企业中得到广泛的应用。Redis在3.0版本前只支持单实例模式,虽然现在的服务器内存可以到100GB、200GB的规模,但是单实例模式限制了Redis没法满足业务的需求,Redis是单进程一定程度导致了QPS上不去,另外例如新浪微博就曾经用Redis存储了超过1TB的数据。Redis 3.0开始已经支持原生集群了,但在3.0没发布之前各大企业为了解决Redis的存储瓶颈,纷纷推出了各自的Redis集群方案。这些方案的核心思想是把数据分片(sharding)存储在多个Redis实例中,每一片就是一个Redis实例。比如推特的twemproxy,豌豆荚的Codis,都是不错的解决方案。虽然现在Redis原生集群出来了,但是并不是特别完美,比如无法很好地支持各种语言的驱动。
下面介绍Redis常见的集群解决方案。
第一种:客户端分片
客户端分片是把分片的逻辑放在Redis客户端实现,通过Redis客户端预先定义好的路由规则,把对Key的访问转发到不同的Redis实例中,最后把返回结果汇集。这种方案的模式如图1所示。
客户端分片的模式
客户端分片的好处是所有的逻辑都是可控的,不依赖于第三方分布式中间件。开发人员清楚怎么实现分片、路由的规则,不用担心踩坑。
客户端分片方案有下面这些缺点。
- 这是一种静态的分片方案,需要增加或者减少Redis实例的数量,需要手工调整分片的程序。
- 可运维性差,集群的数据出了任何问题都需要运维人员和开发人员一起合作,减缓了解决问题的速度,增加了跨部门沟通的成本。
- 在不同的客户端程序中,维护相同的分片逻辑成本巨大。例如,系统中有两套业务系统共用一套Redis集群,一套业务系统用Java实现,另一套业务系统用PHP实现。为了保证分片逻辑的一致性,在Java客户端中实现的分片逻辑也需要在PHP客户端实现一次。相同的逻辑在不同的系统中分别实现,这种设计本来就非常糟糕,而且需要耗费巨大的开发成本保证两套业务系统分片逻辑的一致性。
此解决方案再生产环境中并不常见。
第二种:Twemproxy
Twemproxy是由Twitter开源的Redis代理,但Twemproxy内部已经不再使用,也停止开发了。其基本原理是:Redis客户端把请求发送到Twemproxy,Twemproxy根据路由规则发送到正确的Redis实例,最后Twemproxy把结果汇集返回给客户端。
Twemproxy通过引入一个代理层,将多个Redis实例进行统一管理,使Redis客户端只需要在Twemproxy上进行操作,而不需要关心后面有多少个Redis实例,从而实现了Redis集群。
Twemproxy集群架构如图2所示。
Twemproxy集群架构优缺点。
Twemproxy的优点如下:
- 支持Redis和memcached两种集群代理。
- 后端Redis和memcached无需任何改动,只需要提供IP和端口给Twemproxy即可,另外Twemproxy使用非常简单。
- 客户端像连接Redis实例一样连接Twemproxy,不需要改任何的代码逻辑。
- 支持无效Redis实例的自动删除。
- Twemproxy与Redis实例保持连接,减少了客户端与Redis实例的连接数。
Twemproxy有如下不足:
- 由于Redis客户端的每个请求都经过Twemproxy代理才能到达Redis服务器,这个过程中会产生性能损失,但由于Twemproxy是c写的所以性能非常好,几乎可以忽略中专的性能损耗。但是又由于Twemproxy是单进程模式所以无法很好利用多核CPU。
- 没有友好的监控管理后台界面,不利于运维及监控。
- 2 * 1Gbps 网卡的lvs机器,最大能支撑140万pps。
- 节点无法做高可用节点,需要利用keepalived来实现。或者使用LVS+Twemproxy+Redis三层架构,明显硬件成本增加,响应时间增长。
- 无法对后端主从模式的Redis节点做高可用,实现自动切换。
- 最大的问题是Twemproxy无法平滑地增加Redis实例,对于运维人员来说,当因为业务需要增加Redis实例时非常蛋疼。
Twemproxy作为最被广泛使用、最久经考验、稳定性最高的Redis代理,在业界被广泛使用。但是大多数公司都会在Twemproxy的基础上进行改进,增加多线程模式,修复Bug,加入Redis自动主从切换等等特性,我所知道的唯品会所使用的twemproxy是经过二次开发的,还有B站也是用Twemproxy作为Redis集群,但也是经过二次开发的,Githup上开源了,名称叫bilitw。
如果你能够很好地利用LVS+keepalived就可以实现twemproxy节点高可用并且是双主模式。另外,如果你能够很好利用Redis的哨兵(Sentinel)功能,你也能够做好后端主从模式的Redis故障时自动切换,自行写一个程序通过订阅Sentinel实现当Redis发生主从切换时能够立马替换twemproxy配置文件并重新reload即可。整个架构如下图所示:
第三种:客户端分片,利用Sentinel技术实现高可用
上面介绍了一种基于客户端分片的方案,但是看完Twemproxy后,发现基于客户端分片完全就是个垃圾。但是客户端分片如果基于Redis的Sentinel(哨兵)技术的话就完全不一样了,可以实现twemproxy实现不了的功能,那就是Redis主从故障时自动切换。架构图如下:
Redis Node由一组Redis Instance组成,一组Redis Instatnce可以有一个Master Instance,多个Slave Instance。
整体设计
1、数据Hash分布在不同的Redis Instatnce上。
2、M/S的切换采用Sentinel。
3、写:只会写master Instance,从sentinel获取当前的master Instane。
4、读:从Redis Node中基于权重选取一个Redis Instance读取,失败/超时则轮询其他Instance。
5、通过RPC服务访问,RPC server端封装了Redis客户端,客户端基于jedis开发。
6、批量写/删除:不保证事务。
代码实现逻辑大概如下,具体可参考基于Redis Sentinel实现Redis集群:
一、RedisKey
1、每个业务生成一个family。
2、物理保存一些经过hash计算的key。
3、RedisKey是由family+key组成,最后返回用户的是经过hash过的RedisKey。
二、写流程
1、计算Redis Key Hash值。
2、根据Hash值获取Redis Node编号。
3、从sentinel获取Redis Node的Master。
4、写数据到Redis。
三、读流程
1、计算Redis Key Hash值。
2、根据Hash值获取Redis Node编号。
3、根据权重选取一个Redis Instatnce。
4、轮询读。
四、权重计算
如果需要权重,可以在初始化的时候,会给每个Redis Instatnce赋一个权重值weight,然后根据权重获取Redis Instance。
第四种:豌豆荚的Codis
Twemproxy不能平滑增加Redis实例的问题和不能够自动实现后端Redis主从切换的问题带来了很大的不便,于是豌豆荚自主研发了Codis,一个支持平滑增加Redis实例的Redis代理软件,其基于Go和C语言开发,并于2014年11月在GitHub上开源,如今已经发展到3.0了,GITHUB地址:https://github.com/CodisLabs/codis。
Codis是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有显著区别 (不支持的命令列表), 上层应用可以像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作, 所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务。
Codis 3.x 由以下组件组成:
- Codis Server:基于 redis-2.8.21 分支开发。增加了额外的数据结构,以支持 slot 有关的操作以及数据迁移指令。具体的修改可以参考文档redis的修改。
- Codis Proxy:客户端连接的 Redis 代理服务, 实现了 Redis 协议。 除部分命令不支持以外(不支持的命令列表),表现的和原生的 Redis 没有区别(就像 Twemproxy)。
- 对于同一个业务集群而言,可以同时部署多个 codis-proxy 实例;
- 不同 codis-proxy 之间由 codis-dashboard 保证状态同步。
- Codis Dashboard:集群管理工具,支持 codis-proxy、codis-server 的添加、删除,以及据迁移等操作。在集群状态发生改变时,codis-dashboard 维护集群下所有 codis-proxy 的状态的一致性。
- 对于同一个业务集群而言,同一个时刻 codis-dashboard 只能有 0个或者1个;
- 所有对集群的修改都必须通过 codis-dashboard 完成。
- Codis Admin:集群管理的命令行工具。
- 可用于控制 codis-proxy、codis-dashboard 状态以及访问外部存储。
- Codis FE:集群管理界面。
- 多个集群实例共享可以共享同一个前端展示页面;
- 通过配置文件管理后端 codis-dashboard 列表,配置文件可自动更新。
- Codis HA:为集群提供高可用。
- 依赖codis-dashboard实例,自动抓取集群各个组件的状态;
- 会根据当前集群状态自动生成主从切换策略,并在需要时通过codis-dashboard完成主从切换。
- Storage:为集群状态提供外部存储。
- 提供Namespace概念,不同集群的会按照不同product name进行组织;
- 目前仅提供了 Zookeeper和Etcd两种实现,但是提供了抽象的 interface 可自行扩展。
Codis架构图
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获得动态扩容/缩容的能力,增减redis实例对client完全透明、不需要重启服务,不需要业务方担心 Redis 内存爆掉的问题. 也不用担心申请太大, 造成浪费. 业务方也不需要自己维护 Redis.
Codis最大的优势在于支持平滑增加(减少)Redis Server Group(Redis实例),能安全、透明地迁移数据,扩容可以直接界面的 “Auto Rebalance” 按钮,缩容只需要将要下线的实例拥有的slot迁移到其它实例,然后在界面上删除下线的group即可。这也是Codis有别于Twemproxy等静态分布式Redis解决方案的地方。
Codis是如何分片的?
Codis采用Pre-sharding的技术来实现数据的分片,默认分成1024个slots(0-1023),对于每个key来说,通过以下公式确定所属的Slot Id:SlotId = crc32(key) % 1024。
每一个slot都会有一个且必须有一个特定的server group id来表示这个slot的数据由哪个server group来提供,数据的迁移也是以slot为单位的。
例如,系统有两个Redis Server Group,那么Redis Server Group和slot的对应关系如下。
Redis Server Group slot 1 1~500 2 501~1024
当增加了一个Redis Server Group,slot就要重新分配了,Codis分配slot有两种方法。
第一种:通过Codis管理工具Codisconfig手动重新分配,指定每个Redis Server Group所对应的slot的范围,例如可以指定Redis Server Group和slot的新的对应关系如下。
Redis Server Group slot 1 1~500 2 501~700 3 701~1024
第二种:通过Codis管理工具Codisconfig的rebalance功能,会自动根据每个Redis Server Group的内存对slot进行迁移,以实现数据的均衡。
第五种:Redis 3.0 Cluster
Redis在3.0版正式引入了集群这个特性。
Redis集群是一个提供在多个Redis间节点间共享数据的程序集。
Redis集群是一个分布式(distributed)、容错(fault-tolerant)的Redis内存K/V服务,集群可以使用的功能是普通单机Redis所能使用的功能的一个子集(subset),比如Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误。还有比如set里的并集(unions)和交集(intersections)操作,就没有实现。通常来说,那些处理命令的节点获取不到键值的所有操作都不会被实现。在将来,用户或许可以通过使用MIGRATE COPY命令,在集群上用计算节点(Computation Nodes) 来执行多键值的只读操作, 但Redis集群本身不会执行复杂的多键值操作来把键值在节点间移来移去。
Redis集群不像单机版本的Redis那样支持多个数据库,集群只有数据库0,而且也不支持SELECT命令。
Redis集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令。
Redis集群的优点:
- 无中心架构,分布式提供服务。
- 数据按照slot存储分布在多个redis实例上。
- 增加slave做standby数据副本,用于failover,使集群快速恢复。
- 实现故障auto failover,节点之间通过gossip协议交换状态信息;投票机制完成slave到master角色的提升。
- 支持在线增加或减少节点。
- 降低硬件成本和运维成本,提高系统的扩展性和可用性。
Redis集群的缺点:
- client实现复杂,驱动要求实现smart client,缓存slots mapping信息并及时更新。
- 目前仅JedisCluster相对成熟,异常处理部分还不完善,比如常见的“max redirect exception”。
- 客户端的不成熟,影响应用的稳定性,提高开发难度。
- 节点会因为某些原因发生阻塞(阻塞时间大于clutser-node-timeout),被判断下线。这种failover是没有必要,sentinel也存在这种切换场景。
Redis集群的特点:
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
- 节点的fail是通过集群中超过半数的节点检测失效时才生效。
- 客户端与redis节点直连,不需要中间proxy层;客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
- redis-cluster没有使用一致性hash,而是引入了哈希槽的概念,把所有的物理节点映射到[0-16383]slot上(哈希槽),cluster负责维护。
Redis集群的分片特征在于没有使用一致性hash,而是引入了哈希槽的概念,将键空间分拆了16384个槽位,每一个节点负责其中一些槽位,集群的最大节点数量也是16384 个(推荐的最大节点数量为1000 个)。当16384个槽位都有主节点负责处理时,集群进入”稳定“上线状态,可以开始处理数据命令。当集群没有处于稳定状态时,可以通过执行重配置(reconfiguration)操作,使得每个哈希槽都只由一个节点进行处理。重配置指的是将某个/某些槽从一个节点移动到另一个节点。一个主节点可以有任意多个从节点,这些从节点用于在主节点发生网络断线或者节点失效时, 对主节点进行替换。
Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的使用公式CRC16(Key)&16383计算key属于哪个槽:HASH_SLOT = CRC16(key) mod 16384,其CRC16其结果长度为16位。集群的每个节点负责一部分hash槽。举个例子,比如当前集群有3个节点,那么:
- 节点A包含0到5500号哈希槽。
- 节点B包含5501到11000号哈希槽。
- 节点C包含11001到16384号哈希槽。
这种结构很容易添加或者删除节点,比如如果我想新添加个节点D,我需要从节点A, B, C中的部分槽分到D上。如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可.。由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。
Redis客户端在任意一个Redis实例发出请求,如果所需数据不在该实例中,通过重定向命令引导客户端访问所需的实例。工作流程如下。
1、Redis客户端在Redis1实例上访问某个数据。
2、在Redis1内发现这个数据是在Redis2这个实例中,给Redis客户端发送一个重定向的命令。
3、Redis客户端收到重定向命令后,访问Redis2实例获取所需的数据。
第六种:云提供商Redis集群服务
国内的云服务器提供商阿里云、UCloud等均推出了基于Redis的云存储服务,这个服务的特性如下。
1)动态扩容
用户可以通过控制面板升级所需的Redis存储空间,扩容的过程中服务部不需要中断或停止,整个扩容过程对用户透明、无感知,这点是非常实用的,在前面介绍的方案中,解决Redis平滑扩容是个很烦琐的任务,现在按几下鼠标就能搞定,大大减少了运维的负担。
2)数据多备
数据保存在一主一备两台机器中,其中一台机器宕机了,数据还在另外一台机器上有备份。
3)自动容灾
主机宕机后系统能自动检测并切换到备机上,实现服务的高可用。
4)实惠
很多情况下为了使Redis的性能更高,需要购买一台专门的服务器用于Redis的存储服务,但这样子CPU、内存等资源就浪费了,购买Redis云存储服务就很好地解决了这个问题。
有了Redis云存储服务,能使App后台开发人员从烦琐运维中解放出来。App后台要搭建一个高可用、高性能的Redis服务,需要投入相当的运维成本和精力。如果使用云存储服务,就没必要投入这些成本和精力,可以让App后台开发人员更专注于业务。