7

Redis集群的数据划分与扩容探讨

 3 years ago
source link: http://engineering.xueqiu.com/blog/2014/12/26/redis-capacity-planning/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Redis集群的数据划分与扩容探讨

Dec 26th, 2014 | Comments

首先界定一下,本文探讨的标的不是官方即将发布的Redis 3.0 Cluster,而是自行实现的一个生产环境中的Redis集群。这是一个典型的分布式系统,以下讨论将围绕CAP原理中,与P相关的数据划分和扩容两个问题展开。

对这样一个系统,在数据划分和扩容方面最理想的目标是:

  1. 数据划分针对客户端透明
  2. 支持在线热扩容,扩容针对客户端透明
  3. 支持纵向扩容、横向扩容

数据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为例,具体操作为:

  1. 首先对N个Master节点(如A、B、C),以1:1建立N个Slave同步(如D、E、F)
  2. 然后关闭Slave库的ReadOnly,但主从关系和顺序保持不变,客户端改为模2N(A、B、C、D、E、F)
  3. 然后断开主从
  4. 最后异步清理掉2N个库中多余的50%的Key
取模Sharding扩容

这个方案的可行之处在于

  1. 扩容前后,虽然取模的结果变了,但是目标节点的数据仍在
  2. 在第2步中,允许多个客户端加载模2N有先后,因为不存在Race关系。(反证法:如果存在Race关系,那么在没扩容没重启时也存在Race关系)

最后一步清理Key的方式有两种

  1. 如果有定期做RDB备份的话,可以异步解析RDB,挑出其中冗余的50% Key,在低峰期删除。这种方式比较彻底。
  2. 如果没有定期RDB备份的话,可以在低峰其起异步的工具不断randomkey(),并检查其是否冗余,若冗余则删除。一段时间后冗余Key的数量一定会大大下降,但是不彻底。
b) Pre-Sharding

Pre-Sharding中,横向扩容只需要去修改Hash路由表即可,增加物理节点仍然需要保证数据可访问,类似模倍数方案。

或者如果有Proxy中间件的话让中间件进行路由。在这里值得一提的是Codis通过patch Redis实现了以key为粒度的原子migrate操作,使得通过中间件进行路由极为便捷。

另外还有个情况要考虑,假如万一路由表用满了,Pre-Sharding也就退化为取模Sharding的模式了,还可以再采用模倍数方案。

横向扩容客户端透明

客户端与Redis物理节点间有两种方式的连接

直连的话,客户端必须从配置中心订阅到所需的节点变动通知。这个配置中心可以是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
我们在招聘 “高级架构师 / DBA / Java工程师” 等职位
详细JD可以参考 http://xueqiu.com/about/jobs
感兴趣的同学欢迎投简历到 [email protected]

Posted by 高磊

Dec 26th, 2014Redis

« 2013,一个「移动」公司 GC 优化的一些总结 »


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK