首先界定一下,本文探讨的标的不是官方即将发布的Redis 3.0 Cluster,而是自行实现的一个生产环境中的Redis集群。这是一个典型的分布式系统,以下讨论将围绕CAP原理中,与P相关的数据划分和扩容两个问题展开。
目标
对这样一个系统,在数据划分和扩容方面最理想的目标是:
- 数据划分针对客户端透明
- 支持在线热扩容,扩容针对客户端透明
- 支持纵向扩容、横向扩容
数据Sharding分片
Redis在部署时的拓扑结构完全取决于其适用的业务场景。基本上可以划分为两类:
a) Cache场景
对Cache场景,服务器端最常见的部署拓扑是“一致性哈希”。一致性哈希可以极大的优化机器增删时带来的哈希目标漂移问题。同时对于Hash目标漂移时产生的严重的数据倾斜,可以利用虚拟节点来优化。
基本上,物理节点有了一定规模后,只要不是同时挂多个节点,或者同时扩容多个节点,数据分片不会有太大的扰动。穿透过Cache的请求后端存储可以抗住即可。
基本可以认为,在Cache的场景下,Redis是可以比较完美的实现前述横向扩容目标的。
b) Storage场景
Storage场景下的数据分片则会复杂一些。原因是既然作为存储就要保证数据的高可用,要实现高可用,Redis自身提供了主从Replication机制,通过多副本来保证。Redis在主从复制高可用方面也经历了较长的迭代,从最初2.4版本备受诟病的全量同步,到2.6版本终于实现了增量同步,到2.8版本的Sentinel代理。
高可用有了保证后,我们回过头来看数据分片,最简单的方式是对Key哈希后按照分片数“取模”。
略微复杂一点的话可以使用“预分片(Pre-Sharding)”的方案,有人也称呼为按“桶”进行数据划分。阿里的Tair(http://tair.taobao.org/)框架,以及豌豆荚最近放出的Codis(https://github.com/wandoulabs/codis)对数据的划分都使用了Pre-Sharding方案。
Pre-Sharding方案实际上可以理解为预先分配一个相当大的集合,对Key哈希的结果落在这个集合中,集合的每个元素又与具体的物理节点存在多对一的路由映射关系,这张路由表由一个配置中心进行维护。阿里的Tair把它叫做ConfigServer中,豌豆荚的Codis把它叫做ConfigManager,一个意思。
回过头来再细想下,一致性哈希中的虚拟节点,实际上也可以归类到Pre-Sharding方案中。换句话说,只要是key经过两次哈希,第一次Hash到虚拟节点,第二次Hash到物理节点,都可以算作Pre-Sharding。只不过区别在于,一致性哈希的第二次Hash其路由表是按照算法固定的,Tair/Codis的第二次Hash其路由表是第三方可配的。
纵向扩容 Scale Up
Redis的优雅之处其中就包括对在线纵向扩容的支持,直接一系列config set maxmemory完事(当然别忘了同步改从库,以及rewrite conf文件)。不过config set maxmemory在2.4版本有个小bug就是不支持以K/M/G为单位,不知道后续版本修复了没有。
横向扩容 Scale Out
前已述,Cache场景下横向扩容是没有问题的。
Storage场景下,我们分别根据两种Sharding方式探讨两种方案
a) 取模Sharding
假如扩容前是模N,我们提出的方案:扩容后,对N的倍数进行模运算。
以扩容后模2N为例,具体操作为:
- 首先对N个Master节点(如A、B、C),以1:1建立N个Slave同步(如D、E、F)
- 然后关闭Slave库的ReadOnly,但主从关系和顺序保持不变,客户端改为模2N(A、B、C、D、E、F)
- 然后断开主从
- 最后异步清理掉2N个库中多余的50%的Key
这个方案的可行之处在于
- 扩容前后,虽然取模的结果变了,但是目标节点的数据仍在
- 在第2步中,允许多个客户端加载模2N有先后,因为不存在Race关系。(反证法:如果存在Race关系,那么在没扩容没重启时也存在Race关系)
最后一步清理Key的方式有两种
- 如果有定期做RDB备份的话,可以异步解析RDB,挑出其中冗余的50% Key,在低峰期删除。这种方式比较彻底。
- 如果没有定期RDB备份的话,可以在低峰其起异步的工具不断randomkey(),并检查其是否冗余,若冗余则删除。一段时间后冗余Key的数量一定会大大下降,但是不彻底。
b) Pre-Sharding
Pre-Sharding中,横向扩容只需要去修改Hash路由表即可,增加物理节点仍然需要保证数据可访问,类似模倍数方案。
或者如果有Proxy中间件的话让中间件进行路由。在这里值得一提的是Codis通过patch Redis实现了以key为粒度的原子migrate操作,使得通过中间件进行路由极为便捷。
另外还有个情况要考虑,假如万一路由表用满了,Pre-Sharding也就退化为取模Sharding的模式了,还可以再采用模倍数方案。
横向扩容客户端透明
客户端与Redis物理节点间有两种方式的连接
a) 直连
直连的话,客户端必须从配置中心订阅到所需的节点变动通知。这个配置中心可以是Tair的配置中心,也可以是ZooKeeper/etcd等等,也可以是Sentinel。
其中Sentinel有个天生的问题,就是它的监控粒度是一套主从节点。如果在Storage场景中,按照取模的方式进行Shard,并使用Sentinel做配置管理。这时横向扩容的话Sentinel并不能有效的通知客户端节点Sharding数发生了变化,解决这个问题需要一部分hack工作。而ZooKeeper/etcd等通用配置中心则可定制化程度较高。
b) 通过Proxy中间件透传
类似Twitter的twemproxy(https://github.com/twitter/twemproxy),或者百度的的bdrp(https://github.com/ops-baidu/bdrp)或者前述的豌豆荚codis,以及京东的Redis集群都是使用了一层Proxy进行透传请求。这样只需要Proxy能够订阅到物理节点的变更,并自动加载即可。订阅方式同样可以走各种配置中心。
总结
针对Cache和Storage两种场景,对数据划分和扩容有不同的方案和取舍点,可以在便捷性和成本等各个方面进行权衡。
另外也可以留心下即将发布的Redis 3.0,在线下环境中折腾玩下。
1 2 3 |
|