分区:如何在多个 Redis
实例之间拆分数据。
分区是将数据拆分给多个 Redis
实例的过程,因此每个实例将只包含键的一个子集。 本文档的第一部分将向您介绍分区的概念,第二部分将向您展示 Redis
分区的替代方法。
在 Redis
服务中使用分区主要有两个目的:
Redis
处理更大的数据集,不进行分区,数据集的大小就会被限制在一台计算机的内存。有多种分区方式。假设我们有4个 Redis
实例,分别是R0
, R1
, R2
, R3
,以及许多代表用户的键,如:user:1
, uesr:2
, …等,我们可以使用多种方法来决定哪个实例存储哪个键。
最简单的方法是范围分区(range partitioning
),将一定范围的对象映射到指定的 Redis
实例。例如,用户 ID
从0 ~ 10000分给R0
,10001 ~ 20000 分给R1
,依次类推。
上面这个方法有一个缺点:需要一个表记录如何将范围对象映射给实例。并且每种对象都需要这样一个表,这样的话,分区效率很低,所以通常都不用range partitioning
方法来分区。
哈希分区(hash partitioning
) 可以用来替换 range partitioning
。哈希分区适用于任何键,并不要求键的格式是 object_name:id
。
hash
函数将其转换为数字。例如,使用crc32("foobar")
,将之转传承93024922。hash
之后的数字取模,将其转换成0 ~ 3之间的数字,这对应着 R0 ~ R3
。例如,93024922模4等于2,所以这个键应该存在 R2
实例中。还有许多其他方法可以执行分区,但是通过这两个示例,您应该了解一下。 哈希分区的一种高级形式称为 一致性哈希
,由一些Redis
客户端和代理实现。
client side partitioning
)意味着客户端直接选择在哪个节点上写入或读取指定的键。 许多 Redis客户端
实现客户端分区。proxy assisted partitioning
)意味着我们的客户将请求发送到能够使用 Redis
协议的代理,而不是直接将请求发送到正确的 Redis
实例。 代理将确保根据配置的分区架构(configured partitioning schema
)将请求转发到正确的 Redis
实例,并将答复发送回客户端。 Redis
和 Memcached
代理Twemproxy
实现了代理辅助分区。query routing
)意味着你可以将查询发送到随机实例,该实例将确保将查询转发到正确的节点。 Redis Cluster
在客户端的帮助下实现了混合形式的查询路由(请求不会从Redis实例直接转发到另一个实例,而是会将客户端重定向到正确的节点)。Redis
的某些功能在分区中不能很好地发挥作用:
Redis
实例,不能再这样的键之间求交集(实际上,有很多方法可以执行此操作,但不能直接执行)。Redis
事务。Sorted set
)对数据集进行分片。RDB
/ AOF
文件,并且要备份数据,则需要从多个实例和主机聚合持久性文件。Redis Cluster
支持大多数透明的数据重新平衡,并能够在运行时添加和删除节点,但是其他系统(例如客户端分区和代理)不支持此功能。 但是,在这方面,一种称为“预分片
”的技术会有所帮助。从概念上来看,无论将 Redis
用作数据存储还是用作缓存,Redis
中的分区都是相同的,但是在将其用作数据存储时存在很大的限制。
当 Redis
用作数据存储时,指定的键必须始终映射到同一 Redis
实例。 当 Redis
用作缓存时,如果给定的节点不可用,则使用不同的节点也不是什么大问题,因为我们希望提高系统的可用性。
如果指定的键的首选节点不可用,则通常可以通过一致性哈希切换到其他节点。 同样,如果您添加新节点,则部分新键将开始存储在新节点上。
Redis
用作缓存,则可以使用一致哈希来进行向上和向下缩放。Redis
用作存储,则使用固定的键到节点的映射,因此节点数必须固定并且不能变化。否则,需要一个能够在添加或删除节点时在节点之间重新平衡键的系统,并且目前只有Redis Cluster
能够做到这一点。从上面的了解可知,分区的一个问题是:除非将 Redis
作为缓存,否则添加和删除节点十分棘手。(当然,使用固定的key-instances映射
也没这个问题。)
但是,数据存储常常需要变化,所以固定的key-instances映射
不太符合实际。
由于 Redis
的占用空间非常小且重量轻,所以从一开始就可以在多个 Redis
实例使用分区。
为了尽量少的添加和删除节点,从一开始就使用大量的 Redis
实例。例如,对于大多数用户而言,32或64个实例可以解决问题,并将为增长提供足够的空间。这样,随着数据存储需求的增加以及您需要更多的 Redis
服务器,您要做的就是将实例从一台服务器移动到另一台服务器。 一旦额外添加了第一台服务器,您将需要将一半 Redis
实例从第一台服务器移动到第二台服务器,依此类推。
使用 Redis
副本,您将可以为用户减少停机时间或减少停机时间。
Redis Cluster
是获得自动分片和高可用性的首选方法。Redis Cluster
是 query routing
和 client side partitioning
的混合体。
Twemproxy
支持在多个 Redis
实例之间进行自动分区,并在节点不可用的情况下弹出可选节点(这将更改key-instances映射
,因此仅在将 Redis
用作缓存时才应使用此功能)。
基本上,Twemproxy
是客户端和 Redis
实例之间的中间层,它将以最小的额外复杂性可靠地为我们处理分区。
Twemproxy
的替代方法是使用通过一致性哈希
或其他类似算法实现客户端分区的客户端。 有多个 Redis
客户端支持一致性哈希
,特别是 Redis-rb
和 Predis
。
那么什么是一致性哈希呢?
还是拿前面的例子来说。4个 Redis
实例,使用简单哈希的过程如下:
hash
函数将键转换成数字。Redis
的4个实例R0、 R1、 R2、 R3。但是,因为4个 Redis
实例不够用了,又加了1个 Redis
实例,那么前面的 key-instances的映射
关系就会发生改变了,如果要解决这个问题就需要重新对所用的键的哈希值对5取模,来决定映射到哪一个 Redis
实例上,而且在新映射的机器中没有之前存的键了。
当机器数量发生变动的时候,几乎所有的数据都会移动,就 Redis
而言可能造成缓存雪崩
(在这一时刻,所有缓存都没了,因为新映射的机器中没有存对应的键)。此时的问题是,当增加或者删除节点时,对于大多数记录,保证原来分配到的某个节点,现在仍然应该分配到那个节点,将数据迁移量的降到最低,这就是一致性哈希要做的事情。在这里我们不指定是数据库还是什么,反正都是分布式存储节点。
一致性哈希
就是解决的上述问题。从 2 个方向去考虑:
一致性 Hash
算法也是使用取模的思想,只是,刚才描述的取模法是对节点数量进行取模,而一致性Hash算法是对 232 取模,什么意思呢?简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0 ~ 232-1(即哈希值是一个32位无符号整形),整个哈希环如下,从 0 ~ 232-1 代表的分别是一个个的节点,这个环也叫哈希环
。
ip
或者其他进行哈希,并对 232 取模,找到这些节点在哈希环中的位置;Redis
中的键进行哈希,并对 232 取模,找到这个键在哈希环中的位置;当服务器节点过少时,使用一致性哈希
可能会出现 数据倾斜问题
(也就是,被缓存的对象大部分都在某一台服务器上)。采用 虚拟节点机制
,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点
。具体做法可以在服务器IP
或主机名
的后面增加编号来实现。
[1] Redis 官方文档 partitioning
[2] 一致性哈希
[3] 面试必备:什么是一致性Hash算法?