Redis 基础教程

Redis 命令

Redis 高级教程

Redis 笔记

Redis 各种集群方案详解及对比


随着 Redis NoSQL 数据库的日渐流行,使用它的公司越来越多,它不仅用做缓存,同时也进行业务数据存储所用,随着 redis 存储数据量的逐渐增大,促使了其集群的发展。Redis 的集群的解决方案从早起的 Twemproxy、Codis 代理方案到 Redis 官方的 Cluster 方案。

集群方案

目前行业用的比较多的是下面几种集群架构方案:

  1. Tweet 开源的 Twemproxy 代理方式
  2. 豌豆荚 开源的 Codis 集群方案
  3. Redis 3.0 版本及之后,官方提供的 Redis Cluster 方案
  4. Redis Sentinel(哨兵模式)
  5. 客户端分片模式

Twemproxy 代理方式

Twemproxy(发音为 "two-em-proxy")又名 nutcracker,它是 2012 年初,Twitter 开源的一个 memcached 和 redis 的高效及轻量级的代理。它的构建主要是为了减少与后端数据服务器的连接数量,同时,它的 sharding 能够横向扩展分布式缓存体系结构。它本质上是一个客户端分片的解决理念,它解决的重点就是把客户端分片的逻辑统一放到了 Proxy 层而已。

twemproxy 是由 C 语言编写的,使用的是单进程单线程来处理请求,另外有一个线程来处理统计数据,但并不参与处理请求功能。

Twemproxy 架构

twemproxy 本身是一个代理层,可以将其后端的多台 Redis 或 Memcached 实例进行统一管理与分配,使应用程序只需要在 Twemproxy 上进行操作,而不用关心后面具体有多少个真实的 Redis 或 Memcached 存储。

在实际的部署中,twemproxy 本身也会部署多台,其上会架上具有负载均衡高可用的代理层,如 HAProxy、Nginx 等。具体如下图:

twemproxy 针对请求的 key 进行 hash 取值后,对服务器总数取模,然后请求到对应的节点,代理层本身和各个节点(node)保持长连接。

代理层的引入使得客户端可以直连 redis 实例一样连接 twemproxy,无需关心后端的实现及变更,对 client 友好。

Twemproxy 特点

  • 配置简单 - 安装配置非常简洁;
  • 快速 - 据测试,通过 twenproxy 连接 redis 和直连 redis 相比几乎没有性能损失;
  • 轻量化 - Twemproxy 通过连接池、内存零拷贝以及 epoll 模型实现了足够的快速和轻量化,源码较为简洁精炼;
  • 降低负载 - 通过连接池保持与前端的连接数,减少后端的连接数,使 redis 节点负载大为降低;
  • 分片 - Twemproxy 通过一致性 hash 等算法将数据进行了分片,起到了横向扩容的作用;
  • 多协议 - Twemproxy 不只是支持 redis 协议,同样也支持 memcached。

Twemproxy 缺点

  • Twemproxy 的痛点是无法在线扩容、缩容
  • 没有提供控制面板,对运维不优化。

Codis

2014 年 11 月,国内的豌豆荚针对 Twemproxy 的缺陷,开源了一套完整的集群方案 Codis。

它通过使用 go 和 C 语言在 redis 源码基础上做了二次开发,并实现了 redis 分布式、高可用集群。

Codis 架构

Codis 有 4 部分组成:

  1. Codis Proxy(codis-proxy),代理层,处理客户端请求,支持 Redis 协议;
  2. Codis Dashboard(codis-config),Codis 的管理工具,其实就是 Codis 的管理平台,可以添加/删除 Redis 节点、同时也可以添加/删除 Proxy 节点,发起数据迁移等操作。codis-config 本身还自带了一个 http server,会启动一个 dashboard,用户可以直接在浏览器上观察 Codis 集群运行状态;
  3. Codis Redis(codis-server),Codis 项目维护的一个 Redis 分支,加入了 slot 的支持和原子的数据迁移指令;
  4. Coordinator,协调节点,可以用 zookeeper/etcd 等中间件,Codis 依赖如 zookeeper 来存放数据路由表和 codis-proxy 节点的元信息,codis-config 发起的命令都会通过 zookeeper 同步到各个存活的 codis-proxy。

Codis 支持按照 namespace 区分不同的业务线,不同的业务线配置相互独立,互不影响。

Codis 采用 pre-sharding 的技术来实现数据的分片,默认分成 1024 个 slots(0-1023),对于每个 key 来说,通过如下公式确定对应的 slot id:

slot_id = crc32(key) % 1024

每个 slot 都会有一个且必须有一个特定的 server group id 来表示这个 slot 的数据由哪个 server group 来提供。此外,数据的迁移也是以 slot 为单位进行的。

Codis 特点

  • 数据热迁移 - 这也是 codis 的最大优势;
  • 运维界面优化 - 提供 slot 状态、proxy 状态、group 状态、lock、action 等的丰富监控和显示;
  • 灵活的故障处理 - 提供 API 对故障进行处理,从而让运维能够实现灵活的故障处理方案;
  • 清晰的架构 - 组件高度内聚,故障的发现和处理变得更为容易。

Codis 缺点

Codis 同样也有明显的缺点:

  • 版本滞后 - codis server 需要对 redis 在源码基础上进行二次开发,很难跟上最新版本的 redis,当前 Codis 停留在 redis 4.0 的支持上;
  • 安装部署复杂 - codis 相关的组件过多,需要一个一个依赖部署。

Redis Cluster

Redis Cluster 是 Redis 官方推荐集群方案,它是一种服务端分片技术,从 3.0 版本(2015 年 4 月 1 日发布)开始正式提供。

Redis Cluster 架构

Redis Cluster 采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他节点连接。

Redis Cluster 方案的具体内容:

  • Redis Cluster 中,Sharding 采用 slot(槽)的概念,一共分成 16384 个槽。对于每个进入 Redis 的键值对,根据 key 进行散列,然后分配到这 16384 个 slot 中的某一个中。使用的 hash 算法也比较简单,就是key 经过 CRC16 后 16384 取模,即 slot = CRC16(key) % 16384。
  • Redis Cluster 中的每个 node(节点)负责分摊这 16384 个 slot 中的一部分,也就是说,每个 slot 都对应一个 node 负责处理。当动态添加或减少 node 节点时,需要将 16384 个槽做个再分配,槽中的键值也要迁移。当然,这一过程,在目前实现中,还处于半自动状态,需要人工介入。
  • 为了增加集群的可访问性,官方推荐的方案是将node配置成主从结构,即一个 master 主节点,挂 n 个 slave 从节点。如果主节点失效,redis cluster 会根据选举算法从 slave 节点中选择一个上升为 master 节点,使整个集群继续对外提供服务。
  • Redis Cluster 的新节点识别能力、故障判断及故障转移能力是通过集群中的每个 node 都在和其它 nodes 进行通信,这被称为集群总线(cluster bus)。它们使用特殊的端口号,即对外服务端口号加 10000。例如如果某个 node 的端口号是 6379,那么它与其它 nodes 通信的端口号是 16379。nodes 之间的通信采用特殊的二进制协议,以此来优化传输速度和带宽。
  • 客户端与 redis 节点直连,不需要连接集群所有的节点,连接集群中任何一个可用节点即可。对客户端来说,整个 cluster 被看做是一个整体,客户端可以连接任意一个 node 进行操作,就像操作单一 Redis 实例一样,当客户端操作的 key 没有分配到该 node 上时,Redis 会返回转向指令,指向正确的 node,这有点儿像浏览器页面的 302 redirect 跳转。
  • redis-trib.rb 脚本(ruby 语言)为集群的管理工具,比如自动添加节点,规划槽位,迁移数据等一系列操作。
  • 节点的 fail 是通过集群中超过半数的节点检测失效时才生效。

Redis Cluster 客户端

Redis Cluster 客户端实现了smart client 方式。查询路由并非直接从一个 redis 节点到另外一个 redis,而是借助客户端转发到正确的节点。

Redis Cluster 采用这种架构的考虑,一是减少 redis 实现的复杂度;二是降低客户端等待的时间。Smart Client可以在客户端缓存 slot 与 redis 节点的映射关系,当接收到 MOVED 响应时,会修改缓存中的映射关系。请求时会直接发送到正确的节点上,减少一次交互。

Redis Cluster 特点

  • 可扩展性 - 可线性扩展到 1000 多个节点,节点可动态添加或删除,即支持动态扩容和缩容;
  • 高可用性 - 部分节点不可用时,集群仍可运作。通过增加 slave 做 standby 数据副本,能够实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 slave 到 master 的角色转换。

Redis Cluster 不足

Redis Cluster 有明显的缺点,具体如下:

  • 官方未提供图形管理工具,运维比较复杂(比如数据迁移,纯手动操作,先分配 slot,再将 slot 中对应的 key 迁移至新的节点);
  • 客户端必须支持 cluster 协议;
  • Redis 命令支持不完整(对于批量 key 的操作不适用,如 mget、mset 等命令支持不完整;不支持事务;不支持数据库切换,只能使用 0 号数据库等等);
  • 事务操作支持有限,只支持多 key 在同一节点上的事务操作,当多个 key 分布于不同的节点上时无法使用事务功能;
  • 集群管理与数据存储耦合(比如如果集群管理有 bug,需要升级整个 redis)。

Redis Sentinel(哨兵模式)

Redis Sentinel是为解决Redis的高可用(HA)问题而生的。最初对Redis 2.4.16版本进行支持,并在2.6版本时提供稳定版本Redis Sentinel 1(已废弃);当前是Sentinel 2,在2.8版本时一起发布的。

Redis Sentinel为Redis提供高可用性(HA)方案,Redis Sentinel可以在没有人为干预的情况下,阻止某种类型的故障。

Redis Sentinel同时也提供其他的功能,如监控、通知以及为客户端提供配置服务,具体如下:

  • 监控(Monitoring):Sentinel会一直检查Redis主实例和从实例是否健康运行。
  • 通知(Notification):当被监控的某个Redis实例出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知。
  • 自动故障切换(Automatic failover):当一个主服务器不能正常工作时,Sentinel会开始一次自动故障切换操作,它会将失效的主服务器的其中一个从服务器升级为新的主服务器,并让失效的主服务器的其他从服务器改为复制新的主服务器;当客户端试图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址。
  • 提供配置服务(Configuration provider):Sentinel作为一个为客户端服务发现的权威来源;客户端连接Sentinel节点目的是为了询问对一个给定服务的当前master节点的地址。如果发生故障转移,Sentinel也会将新的地址通知给客户端。

Redis 客户端分片集群模式

Redis在推出官方集群技术Redis Cluster之前,业界普遍采用客户端Redis Sharding技术。

Redis客户端分片技术的主要思想是采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上。这样,客户端就知道该向哪个Redis节点操作数据。

Redis的Java客户端jedis已支持Redis Sharding功能,即ShardedJedis以及结合缓存池的ShardedJedisPool。

Jedis的Redis Sharding实现具有如下特点:

  1. 采用一致性哈希算法(Consistent Hashing Algorithm),将key和节点name同时hashing,然后进行映射匹配,采用的算法是MURMUR_HASH。采用一致性哈希而不是采用简单类似哈希求模映射的主要原因是当增加或减少节点时,不会产生由于重新匹配造成的rehashing。一致性哈希只影响相邻节点key分配,影响量小。
  2. 为了避免一致性哈希只影响相邻节点造成节点分配压力,ShardedJedis会对每个Redis节点根据名字(如果没有,Jedis会赋予缺省名字)会虚拟化出160个虚拟节点进行散列。根据权重weight,也可虚拟化出160倍数的虚拟节点。用虚拟节点做映射匹配,可以在增加或减少Redis节点时,key在各Redis节点移动再分配更均匀,而不是只有相邻节点受影响。
  3. ShardedJedis支持keyTagPattern模式,即抽取key的一部分keyTag做sharding,这样通过合理命名key,可以将一组相关联的key放入同一个Redis节点,这在避免跨节点访问相关数据时很重要。

扩容问题

Redis Sharding这种轻量灵活方式必然在集群其它能力方面做出妥协。比如扩容,当想要增加Redis节点时,尽管采用一致性哈希,毕竟还是会有key匹配不到而丢失,这时需要键值迁移。

作为轻量级客户端sharding,处理Redis键值迁移是不现实的,这就要求应用层面允许Redis中数据丢失或从后端数据库重新加载数据。但有些时候,击穿缓存层,直接访问数据库层,会对系统访问造成很大压力。有没有其它手段改善这种情况?

Redis作者给出了一个比较讨巧的办法–presharding,即预先根据系统规模尽量部署好多个Redis实例,这些实例占用系统资源很小,一台物理机可部署多个,让他们都参与sharding,当需要扩容时,选中一个实例作为主节点,新加入的Redis节点作为从节点进行数据复制。数据同步后,修改sharding配置,让指向原实例的Shard指向新机器上扩容后的Redis节点,同时调整新Redis节点为主节点,原实例可不再使用。