redis集群
分为服务端集群
和客户端分片
,redis3.0以上版本实现了集群机制,即服务端集群,3.0以下使用客户端分片(Sharding)。redis3.0服务端集群使用哈希槽,计算key的CRC16结果再模16834。3.0以下版本采用Key的一致性hash算法来区分key存储在哪个Redis实例上。
1 摘要
Redis分区,简单的说就是将数据分布到不同的redis实例中,因此对于每个redis实例所存储的内容仅仅是所有内容的一个子集。分区(Partitioning)不仅仅是Redis中的概念,几乎是所有数据存储系统都会涉及到的概念,这篇文章将会在理解分区基本概念的基础之上进一步了解Redis对分区的支持。
2 我们为什么要分区
通常来说,Redis分区的好处大致有如下两个方面:
- 性能的提升: 将请求分散到多台机器,充分利用多台机器的
计算能力
和网络带宽
,有助于提高Redis总体的服务能力。 - 存储的横向扩展: 随着存储数据的增加,单台机器受限于机器本身的
存储容量
,将数据分散到多台机器上存储,使得Redis服务可以横向扩展。
总的来说,分区使得我们本来受限于单台计算机硬件资源的问题不再是问题,存储、计算资源、带宽等问题,都可以通过增加机器来解决这些问题。
3 Redis分区策略
3.1 范围分区
所谓范围分区,就是将一个范围内的key都映射到同一个Redis实例中。
假设我们有4个Redis实例(R0, R1, R2, R3),其上有许多代表用户的key,比如user:1, user:2, … 等等,那么在存储一个key的时候我们有多种方式。
我们可以将用户ID从0到10000的用户数据映射到R0实例,而将用户ID从10001到20000的对象映射到R1实例,依次类推。
范围分区的优点: 虽然简单,但是在实际应用中是很有效的。
范围分区的缺点:
- 对于每种对象类型,都需要维护一张
映射关系表
,例如需要一张表用来存储用户ID范围到Redis实例的映射关系
,比如用户ID:0-10000的是映射到R0实例……。 - 如果我们想要存储的数据的key并不能按照范围划分怎么办,比如我们的key是一组uuid,这个时候就不好用范围分区了。
因此,在实际应用中,范围分区并不是很好的选择,不用担心,我们还有更好的方法,接下来认识下哈希分区。
3.2 哈希分区
哈希分区跟范围分区相比一个明显的优点是哈希分区适合任何形式的key
,而不像范围分区一样需要key的形式为object_name:,而且哈希分区方法也很简单,一个公式就可以表达:id=hash(key)%N
。
其中id代表Redis实例的编号,公式描述的是首先根据key和一个hash函数(如crc32函数)计算出一个数值型的值。接着上面的例子,我们的第一个要处理的key是user:1,hash(user:1)的结果是93024922。
然后哈希结果进行取模,取模的目的是计算出一个介于0到3之间的值,因此这个值才可以被映射到我们的一台Redis实例上面。比如93024922%4结果是2,我们就会知道foobar将要被存储在R2上面。
当然除了上面提到的两种分区方法,还有很多其他的方法。比如一种从哈希分区演进而来的consistent hashing分区,相关信息可以参考我的另一篇文章《memcached分布式实现原理》,其已经被redis client客户端和proxies代理实现了。
根据余数计算分散的缺点
余数计算的方法简单,数据的分散性也相当优秀,但也有其缺点。那就是当添加或移除服务器时,缓存重组的代价相当巨大。 添加服务器后,余数就会产生巨变,这样就无法获取与保存时相同的服务器
, 从而影响缓存的命中率,负载会集中到数据库服务器上。
4 不同的分区实现
- 客户端分区 : 对即key在redis客户端就决定了要被存储在那台Redis实例中。
- 代理分区 : 客户端发送请求到一个代理服务器,代理服务器实现了Redis协议,因此代理服务器可以代理客户端和Redis服务器通信。代理服务器代理客户端,把请求转发到正确的Redis实例中,同时将反馈消息返回给客户端。
- 查询路由 : Redis Cluster集群实现的一种Redis分区方式。你可以将你的查询发送到任何一个Redis实例,实例会将你的查询
重定向
到正确的服务器。
(PS:对于一个给定的key,分区的工作就是选择一个正确的Redis实例,那么这个选择的过程可以由客户端、代理 或者 Redis实例来做)
5 分区的不足之处
- 多键操作是不被支持的,比如我们将要批量操作的键被映射到了不同的Redis实例中(当两个set映射到不同的redis实例上时,你就不能对这两个set执行交集操作)。
- 多键的Redis事务是不被支持的。
- 分区的最小粒度是键,因此不能将关联到一个键的很大的数据集映射到不同的实例,例如不可能将一个非常巨大的key(比如,一个非常大的sorted set)去切分数据。
- 当使用分区的时候,数据处理会很复杂,比如你需要处理多个rdb/aof文件,并且从多个实例和主机备份持久化文件。
- 增加和删除redis节点变得更复杂。Redis Cluster集群大多数支持在运行时增加、删除节点的透明数据平衡的能力,但是类似于Redis 客户端分区、代理等其他系统则不支持这项特性。然而,一种叫做
presharding
的技术对此是有帮助的。 - 单点故障:当集群中的某一台服务挂掉之后,客户端在根据一致性hash无法从这台服务器取数据。
解决单点故障的方法:
用到Redis主从复制的功能,两台物理主机上分别都运行有Redis-Server,其中一个Redis-Server是另一个的从库,采用双机热备技术
,客户端通过虚拟IP访问主库的物理IP,当主库宕机时,切换到从库的物理IP。只是事后修复主库时,应该将之前的从库改为主库(使用命令slaveof no one),主库变为其从库(使命令slaveof IP PORT),这样才能保证修复期间新增数据的一致性。
6 数据存储还是缓存?
- 如果使用Redis作为缓存,使用
一致哈希
很容易进行伸缩。如果给定key的首选节点不可用,一致哈希实现通常能够切换到其他节点。 - 当Redis用作数据存储时,
一个固定的key到实例的映射是需要的
。给定的key必须总是映射到相同的Redis实例,因此节点的数量必须是固定的,且不能改变。否则,就需要一个能够在节点之间重新平衡key的系统,当前Redis集群是可以做到这一点的。
7 Pre-Sharding解决动态扩容和数据分区问题
通过上面的介绍,我们知道Redis分区应用起来是有问题的,除非我们只是使用Redis当做缓存,否则对于增加机器或删除机器是非常麻烦的。
然而,通常我们Redis容量变动在实际应用中是非常常见的,比如今天我需要10台Redis机器,明天可能就需要50台机器了。
Redis的作者提出了一种叫做presharding
的方案来解决动态扩容和数据分区的问题
,实际就是在同一台机器上部署多个Redis实例的方式,当容量不够时将多个实例拆分到不同的机器上,这样实际就达到了扩容的效果。Pre-Sharding方法是
在每台物理机上,运行多个不同端口
的Redis实例,假如有三个物理机,每个物理机运行三个Redis实例,那么我们的分片列表中实际有9个Redis实例,当我们需要扩容时,增加一台物理机来代替9个中的一个redis,有人说,这样不还是9个么,是的,但是以前服务器上面有三个redis,压力很大的,这样做,相当于单独分离出来并且将数据一起copy给新的服务器。值得注意的是,还需要修改客户端被代替的redis的IP和端口为现在新的服务器,只要顺序不变,不会影响一致性哈希分片
。
怎么移动Redis实例呢? 当需要将Redis实例移动到独立的机器上的时候,我们可以通过下面步骤实现:
- 在新机器上启动好对应端口的Redis实例。
- 配置新端口为待迁移端口的从库。
- 待复制完成,与主库完成同步后,切换所有客户端配置到新的从库的端口。
- 配置从库为新的主库。
- 移除老的端口实例。
- 重复上述过程迁移好所有的端口到指定服务器上。
如果主库快照数据文件过大,这个复制的过程也会很久,同时会给主库带来压力。所以做这个拆分的过程最好选择为业务访问低峰时段进行
。
8