Redis Cluster 是一种服务器 sharding 分片技术。
Redis3.0 版本开始正式提供,解决了多 Redis 实例协同服务问题,时间较晚,目前能证明在大规模生产环境下成功的案例还不是很多,需要时间检验。
Redis Cluster 中,Sharding 采用 slot(槽) 的概念,一共分成 16384 个槽。对于每个进入 Redis 的键值对,根据 key 进行散列,分配到这 16384 个 slot 中的某一个中。使用的 hash 算法也比较简单,CRC16 后 16384 取模。
Redis Cluster 中的每个 node 负责分摊这 16384 个 slot 中的一部分,也就是说,每个 slot 都对应一个 node 负责处理。例如三台 node 组成的 cluster,分配的 slot 分别是 0-5460,5461-10922,10923-16383,
- M: 434e5ee5cf198626e32d71a4aee27bc4058b4e45 127.0.0.1 : 7000 slots: 0 - 5460(5461 slots) master M: 048a0c9631c87e5ecc97a4ce5834d935f2f938b6 127.0.0.1 : 7001 slots: 5461 - 10922(5462 slots) master M: 04ae4184b2853afb8122d15b5b2efa471d4ca251 127.0.0.1 : 7002 slots: 10923 - 16383(5461 slots) master
添加或减少节点时?
当动态添加或减少 node 节点时,需要将 16384 个 slot 重新分配,因此槽中的键值也要迁移。这一过程,目前处于半自动状态,需要人工介入。
节点发生故障时
如果某个 node 发生故障,那它负责的 slots 也就失效,整个 Redis Cluster 将不能工作。因此官方推荐的方案是将 node 都配置成主从结构,即一个 master 主节点,挂 n 个 slave 从节点。
这非常类似 Redis Sharding 场景下服务器节点通过 Sentinel(哨兵) 监控架构主从结构,只是 Redis Cluster 本身提供了故障转移容错的能力。
通信端口
Redis Cluster 的新节点识别能力、故障判断及故障转移能力是通过集群中的每个 node 和其它的 nodes 进行通信,这被称为集群总线 (cluster bus)。
通信端口号一般是该 node 端口号加 10000。如某 node 节点端口号是 6379,那么它开放的通信端口是 16379。nodes 之间的通信采用特殊的二进制协议。
对客户端来说,整个 cluster 被看重是一个整体,客户端可以连接任意一个 node 进行操作,就像操作单一 Redis 实例一样,当客户端操作的 key 没有分配到该 node 上时,Redis 会返回转向指令,指向正确的 node。
如下。在 127.0.0.1 上部署了三台 Redis 实例,组成 Redis cluster,端口号分别是 7000,7001,7002。在端口号为 7000 的 Redis node 上设置 <foo,hello>,foo 对应的 key 值重定向到端口号为 7002 的 node 节点上。get foo 命令时,也会重定向到 7002 上的 node 节点上去取数据,返回给客户端。
- [root@centos1 create - cluster]#redis - cli - c - p 7000 127.0.0.1 : 7000 > set foo hello - >Redirected to slot[12182] located at 127.0.0.1 : 7002 OK 127.0.0.1 : 7000 > get foo - >Redirected to slot[12182] located at 127.0.0.1 : 7002 "hello"
方案 2:Redis Sharding 集群
Redis Sharding 是一种客户端 Sharding 分片技术。
Redis Sharding 可以说是 Redis Cluster 出来之前,业界普遍使用的多 Redis 实例集群方法。主要思想是采用哈希算法将 Redis 数据的 key 进行散列,通过 hash 函数,特定的 key 会映射到特定的 Redis 节点上。
这样,客户端就知道该向哪个 Redis 节点操作数据, 需要说明的是,这是在客户端完成的。Sharding 架构如图所示:
java redis 客户端 jedis,已支持 Redis Sharding 功能,即 ShardedJedis 以及结合缓存池的 ShardedJedisPool。Jedis 的 Redis Sharding 实现具有如下特点:
1、采用一致性哈希算法 (consistent hashing)
将 key 和节点 name 同时哈希,然后进行映射匹配,采用的算法是 MURMUR_HASH。一致性哈希主要原因是当增加或减少节点时,不会产生由于重新匹配造成的 rehashing。一致性哈希只影响相邻节点 key 分配,影响量小。更多一致性哈希算法介绍,可以参考:
2、虚拟节点
ShardedJedis 会对每个 Redis 节点,根据名字虚拟化出 160 个虚拟节点进行散列。用虚拟节点做映射匹配,可以在增加或减少 Redis 节点时,key 在各 Redis 节点移动更分配更均匀,而不是只有相邻节点受影响。如图,Redis 节点 1 虚拟化成 NODE1-1 和 NODE1-2, 散列中哈希环上。这样当 object1、object2 散列时,选取最近节点 NODE1-1 和 NODE1-2, 而 NODE1-1 和 NODE1-2 又是 NODE 节点的虚拟节点,即实际存储在 NODE 节点上。
增加虚拟节点,可以保证平衡性,即每台 Redis 机器,存储的数据都差不多,而不是一台机器存储的数据较多,其它的少。
3、ShardedJedis 支持 keyTagPattern 模式
抽取 key 的一部分 keyTag 做 sharding,这样通过合理命名 key, 可以将一组相关联的 key 放入同一 Redis 节点,避免跨节点访问。即客户端将相同规则的 key 值,指定存储在同一 Redis 节点上。
添加或减少节点时?
Redis Sharding 采用客户端 Sharding 方式,服务端的 Redis 还是一个个相对独立的 Redis 实例节点。同时,我们也不需要增加额外的中间处理组件,这是一种非常轻量、灵活的 Redis 多实例集群方案。
当然,这种轻量灵活方式必然在集群其它能力方面做出妥协。比如扩容,当想要增加 Redis 节点时,尽管采用一致性哈希,那么不同的 key 分布到不同的 Redis 节点上。
当我们需要扩容时,增加机器到分片列表中。这时候客户端根据 key 算出来落到跟原来不同的机器上,这样如果要取某一个值,会出现取不到的情况。
对于这一种情况,一般的作法是取不到后,直接从后端数据库重新加载数据,但有些时候,击穿缓存层,直接访问数据库层,会对系统访问造成很大压力。
Redis 作者给出了一个办法 --presharding。
是一种在线扩容的方法,原理是将每一台物理机上,运行多个不同端口的 Redis 实例,假如三个物理机,每个物理机运行三个 Redis 实例,那么我们的分片列表中实际有 9 个 Redis 实例,当我们需要扩容时,增加一台物理机,步骤如下:
1、在新的物理机上运行 Redis-server
2、该 Redis-server 从属于(slaveof)分片列表中的某一 Redis-Server(假设叫 RedisA)。
3、主从复制 (Replication) 完成后,将客户端分片列表中 RedisA 的 IP 和端口改为新物理机上 Redis-Server 的 IP 和端口。
4、停止 RedisA
这样相当于将某一 Redis-Server 转移到了一台新机器上。但还是很依赖 Redis 本身的复制功能,如果主库快照数据文件过大,这个复制的过程也会很久,同时也会给主 Redis 带来压力,所以做这个拆分的过程最好选择业务访问低峰时段进行。
节点发生故障时
并不是只有增删 Redis 节点引起键值丢失问题,更大的障碍来自 Redis 节点突然宕机。
为不影响 Redis 性能,尽量不开启 AOF 和 RDB 文件保存功能,因此需架构 Redis 主备模式,主 Redis 宕机,备 Redis 留有备份,数据不会丢失。
Sharding 演变成如下:
这样,我们的架构模式变成一个 Redis 节点切片包含一个主 Redis 和一个备 Redis,主备共同组成一个 Redis 节点,通过自动故障转移,保证了节点的高可用性.
Redis Sentinel 哨兵
提供了主备模式下 Redis 监控、故障转移等功能,达到系统的高可用性。
读写分离
高访问时量下,即使采用 Sharding 分片,一个单独节点还是承担了很大的访问压力,这时我们还需要进一步分解。
通常情况下,读常常是写的数倍,这时我们可以将读写分离,读提供更多的实例数。利用主从模式实现读写分离,主负责写,从负责只读,同时一主挂多个从。在 Redis Sentinel 监控下,还可以保障节点故障的自动监测。
上面分别介绍了基于客户端 Sharding 的 Redis Sharding 和基于服务端 sharding 的 Redis Cluster。
客户端 Sharding 技术
优势:服务端的 Redis 实例彼此独立,相互无关联,非常容易线性扩展,系统灵活性很强。
不足:1、由于 sharding 处理放到客户端,规模扩大时给运维带来挑战。
2、服务端 Redis 实例群拓扑结构有变化时,每个客户端都需要更新调整。
3、连接不能共享,当应用规模增大时,资源浪费制约优化。
服务端 Sharding 技术
优势:服务端 Redis 集群拓扑结构变化时,客户端不需要感知,客户端像使用单 Redis 服务器一样使用 Redis Cluster,运维管理也比较方便。
不足:Redis Cluster 正式版推出时间不长,系统稳定性、性能等需要时间检验,尤其中大规模使用场景。
能不能结合二者优势? 既能使服务端各实例彼此独立,支持线性可伸缩,同时 sharding 又能集中处理,方便统一管理?
中间件 sharding 分片技术
twemproxy 就是一种中间件 sharding 分片的技术,处于客户端和服务器的中间,将客户端发来的请求,进行一定的处理后(如 sharding),再转发给后端真正的 Redis 服务器。
也就是说,客户端不直接访问 Redis 服务器,而是通过 twemproxy 代理中间件间接访问。
tweproxy 中间件的内部处理是无状态的,起源于 twitter,不仅支持 redis,同时支持 memcached。
使用了中间件,twemproxy 可以通过共享与后端系统的连接,降低客户端直接连接后端服务器的连接数量。同时,它也提供 sharding 功能,支持后端服务器集群水平扩展。统一运维管理也带来了方便。
当然,由于使用了中间件,相比客户端直辖服务器方式,性能上肯定会有损耗,大约降低 20% 左右。
另一个知名度较高的实现是 Codis,由豌豆荚的团队开发。感兴趣的读者可以搜索相关资料。
总结:几种方案如何选择。
上面大致讲了三种集群方案,主要根据 sharding 在哪个环节进行区分
1、服务端实现分片
官方的 Redis Cluster 实现就是采用这种方式,在这种方案下,客户端请求到达错误节点后不会被错误节点代理执行,而是被错误节点重定向至正确的节点。
2、客户端实现分片
分区的逻辑在客户端实现,由客户端自己选择请求到哪个节点。方案可参考一致性哈希,基于 Memcached 的 cache 集群一般是这么做,而这种方案通常适用于用户对客户端的行为有完全控制能力的场景。
3、中间件实现分片
有名的例子是 Twitter 的 Twemproxy,Redis 作者对其评价较高。一篇较旧的博客如下:
另一个知名度较高的实现是 Codis,由豌豆荚的团队开发,作者 刘老师已经在前面的答案中推荐过了。
那么,如何选择呢?
显然在客户端做分片是自定义能力最高的。
优势在于,在不需要客户端服务端协作,以及没有中间层的条件下,每个请求的 roundtrip 时间是相对更小的,搭配良好的客户端分片策略,可以让整个集群获得很好的扩展性。
当然劣势也很明显,用户需要自己对付 Redis 节点宕机的情况,需要采用更复杂的策略来做 replica,以及需要保证每个客户端看到的集群 "视图" 是一致的。
中间件的方案对客户端实现的要求是最低的,客户端只要支持基本的 Redis 通信协议即可,至于扩容、多副本、主从切换等机制客户端都不必操心,因此这种方案也很适合用来做 "缓存服务"。
官方推出的协作方案也完整地支持了分片和多副本,相对于各种 proxy,这种方案假设了客户端实现是可以与服务端 "协作" 的,事实上主流语言的 SDK 都已经支持了。
所以,对于大部分使用场景来说,官方方案和代理方案都够用了,其实没必要太纠结谁更胜一筹,每种方案都有很多靠谱的公司在用。
此文主要参考了以下文章:
https://www.zhihu.com/question/21419897
http://blog.csdn.net/freebird_lb/article/details/7778999
秀才坤坤出品
来源: http://www.cnblogs.com/xckk/p/6134655.html