自从研究了redis的监控工具之后,对于redis的集群实现方案又回头做了一个系统的研究。

首先,先说一下redis,是一个高性能的key-value类型的NoSQL数据库,支持较为丰富的数据类型,单机Redis在普通的服务器上通常ops上限在5w左右,开启pipeline的情况下在20-30w左右。对于大多数中小公司来说,通常单机的Redis已经足够,最多根据不同业务分散到多台Redis。但是随着数据的越来越多,也迫切需求支持分布式集群。

redis集群

目前redis集群已经有了较大的应用,包括官方也推出的redis3.0版本的集群部署方案。

redis集群解决的问题

  • redis单线程特性,多请求顺序执行,单个耗时的操作会阻塞后续的操作,集群可以并发处理,更高效

  • 单机内存有限

  • 单点问题,缺乏高可用性

  • 不能动态扩容

分区的实现

  1. 范围分区:将固定范围的数据key放到对应的实例中。这个方案不是太好,需要维护映射关系。

  2. hash分区(一致性分区):用哈希函数(例如crc32)将key转化为一个数字,然后取模来分配到不同的实例中。

codis分区和redis3.0分区都是分了N个slot,先对key到slot进行hash分区再对slot进行范围分区或者hash分区。

四种部署方案

目前集群主要有四种部署方案

  1. 客户端分片

  2. 代理分片:twenproxy代理

  3. codis

  4. redis cluster

客户端分片

就是通过客户端来实现分布式分配到不同的主机上的redis实例,也就是客户端分片,来实现分布式集群。

客户端分片是不用经过中间件,但是缺点太多了,所有的逻辑都在客户端,这样就导致了可运维性很差,首先,客户端存在着语言的差异,还有逻辑过分依赖于客户端,升级应用和扩展业务都依赖于客户端的开发,还存在着客户端版本和故障问题难以排查的问题。

代理分片

这边主要介绍的是曾被广泛使用的twenproxy作为中心代理的集群部署方案(twemproxy是twitter开源的一个redis和memcache代理服务器,只用于作为简单的代理中间件,目前twitter内部已经不再使用)。

该集群主要是通过中间件twenproxy来实现,客户端只要通过中间件twenproxy提供的api和端口来进行操作,然后twenproxy提供路由能力(所有的key通过一致性哈希算法分布到集群中所有的redis实例中)到后台redis集群中的不同实例中,实现分布式集群。

twenproxy还提供了一些其他保障集群稳定的功能

  • 支持无效Redis实例的自动删除

  • 代理与每个redis实例维持长连接,减少客户端和redis实例的连接数

  • 代理是无状态的,可以任意部署多套,避免单点问题

  • 默认启用pipeline,连接复用,提高效率,性能损失在 10% - 20%

这套部署方案,采用组件分离,升级容易,但是这个部署方案有一个很大的缺陷:无法动态扩容,不能平缓的增减redis实例,而且中间件也会消耗性能,在并发上,要求代理数量和实例一致甚至更多才能更好的发挥并发能力。造成运维工作量很大。

codis

codis是豌豆荚开源的一款redis分布式集群的实现方案,它是在redis2.8.21的基础上使用go和c改出来的,其实也是类似于twenproxy的代理模式,但是它可以实现平滑的新增实例,也就是动态扩容。

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四大组成部分

  • codis-proxy 是客户端连接的 Redis 代理服务, codis-proxy 本身实现了 Redis 协议, 表现得和一个原生的 Redis 没什么区别 (就像 Twemproxy), 对于一个业务来说, 可以用Keepalived等负载均衡软件部署多个 codis-proxy实现高可用, codis-proxy 本身是无状态的。

  • codis-config 是 Codis 的管理工具, 支持包括, 添加/删除 Redis 节点, 添加/删除 Proxy 节点, 发起数据迁移等操作. codis-config 本身还自带了一个 http server, 会启动一个 dashboard, 用户可以直接在浏览器上观察 Codis 集群的运行状态,可以完善运维。

  • codis-server 是 Codis 项目维护的一个 Redis 分支, 基于 2.8.21 开发, 加入了 slot 的支持和原子的数据迁移指令. Codis 上层的 codis-proxy 和 codis-config 只能和这个版本的 Redis 交互才能正常运行。

  • ZooKeeper:Codis依赖于ZooKeeper存储数据路由表的信息和Codis Proxy节点的元信息。另外,Codisconfig发起的命令都会通过ZooKeeper同步到CodisProxy的节点。

依然有中间件的性能消耗问题,但是解决了动态扩容问题。这个思路是目前大多数公司在用的。

redis3.0

1.预分片

  • 预先分配好 16384 个slot
  • slot 和 server 的映射关系存储每一个 server 的路由表中
  • 根据 CRC16(key) mod 16384 的值,决定将一个key放到哪一个slot中
  • 数据迁移时就是调整 slot 的分布

2.设计架构

redis3.0集群部署方案是官方在发布redis3.0版本的时候提供的,它采用了p2p模式,完全去中心化。

实现

  • 无中心化结构,每个节点都保存数据和整个集群的状态。
  • 采用 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连接集群中任意一个节点都可以。

高可用

  • 每个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,最终集群实例都获知此消息

存在的问题

  • Gossip协议通信开销
  • 严重依赖于smart-client的成熟度
    • 如果smart-client支持缓存slot路由,需要额外占用内存空间,为了效率需要建立和所有 redis server 的长连接(每一个使用该库的程序都需要建立这么多连接)。
    • 如果不支持缓存路由信息,会先访问任意一台 redis server,之后重定向到新的节点。
    • 需要更新当前所有的client。
  • 官方只提供了一个ruby程序 redis-trib 完成集群的所有操作,缺乏监控管理工具,很难清楚目前集群的状态
  • 数据迁移以Key为单位,速度较慢
  • 某些操作不支持,MultiOp和Pipeline都被限定在命令中的所有Key必须都在同一Slot内