初识 Redis 数据分区

分区:如何在多个 Redis 实例之间拆分数据。

1. 前言

1.1 什么是分区?

分区是将数据拆分给多个 Redis 实例的过程,因此每个实例将只包含键的一个子集。 本文档的第一部分将向您介绍分区的概念,第二部分将向您展示 Redis 分区的替代方法。

1.2 为什么要用分区?

Redis 服务中使用分区主要有两个目的:

  • 有了分区就可以使用多台计算机的内存总和,允许 Redis 处理更大的数据集,不进行分区,数据集的大小就会被限制在一台计算机的内存。
  • 它允许将计算能力扩展到多核和多台计算机,并将网络带宽扩展到多台计算机和网络适配器。

2. 分区基础知识

有多种分区方式。假设我们有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 客户端和代理实现。

2.1 分区的不同实现

  • 客户端分区client side partitioning)意味着客户端直接选择在哪个节点上写入或读取指定的键。 许多 Redis客户端 实现客户端分区。
  • 代理辅助分区proxy assisted partitioning)意味着我们的客户将请求发送到能够使用 Redis 协议的代理,而不是直接将请求发送到正确的 Redis 实例。 代理将确保根据配置的分区架构configured partitioning schema)将请求转发到正确的 Redis 实例,并将答复发送回客户端。 RedisMemcached 代理Twemproxy 实现了代理辅助分区。
  • 查询路由query routing)意味着你可以将查询发送到随机实例,该实例将确保将查询转发到正确的节点。 Redis Cluster 在客户端的帮助下实现了混合形式的查询路由(请求不会从Redis实例直接转发到另一个实例,而是会将客户端重定向到正确的节点)。

2.2 分区的缺点

Redis 的某些功能在分区中不能很好地发挥作用:

  • 通常不支持涉及多个键的操作。 例如,将1个键映射到多个Redis 实例,不能再这样的键之间求交集(实际上,有很多方法可以执行此操作,但不能直接执行)。
  • 不能使用涉及多个键的 Redis 事务。
  • 分区粒度是关键,因此无法使用单个大键(如非常大的 Sorted set )对数据集进行分片。
  • 使用分区时,数据处理会更加复杂。例如,你必须处理多个RDB / AOF 文件,并且要备份数据,则需要从多个实例和主机聚合持久性文件。
  • 添加和删除容量可能很复杂。 例如,Redis Cluster 支持大多数透明的数据重新平衡,并能够在运行时添加和删除节点,但是其他系统(例如客户端分区和代理)不支持此功能。 但是,在这方面,一种称为“预分片”的技术会有所帮助。

2.3 数据存储还是缓存?

从概念上来看,无论将 Redis 用作数据存储还是用作缓存,Redis 中的分区都是相同的,但是在将其用作数据存储时存在很大的限制。

Redis 用作数据存储时,指定的键必须始终映射到同一 Redis 实例。 当 Redis 用作缓存时,如果给定的节点不可用,则使用不同的节点也不是什么大问题,因为我们希望提高系统的可用性。

如果指定的键的首选节点不可用,则通常可以通过一致性哈希切换到其他节点。 同样,如果您添加新节点,则部分新键将开始存储在新节点上。

  • 如果将 Redis 用作缓存,则可以使用一致哈希来进行向上和向下缩放。
  • 如果将 Redis 用作存储,则使用固定的键到节点的映射,因此节点数必须固定并且不能变化。否则,需要一个能够在添加或删除节点时在节点之间重新平衡键的系统,并且目前只有Redis Cluster 能够做到这一点。

2.4 预分片(presharding)

从上面的了解可知,分区的一个问题是:除非将 Redis 作为缓存,否则添加和删除节点十分棘手。(当然,使用固定的key-instances映射也没这个问题。)

但是,数据存储常常需要变化,所以固定的key-instances映射不太符合实际。

由于 Redis的占用空间非常小且重量轻,所以从一开始就可以在多个 Redis 实例使用分区。

为了尽量少的添加和删除节点,从一开始就使用大量的 Redis 实例。例如,对于大多数用户而言,32或64个实例可以解决问题,并将为增长提供足够的空间。这样,随着数据存储需求的增加以及您需要更多的 Redis 服务器,您要做的就是将实例从一台服务器移动到另一台服务器。 一旦额外添加了第一台服务器,您将需要将一半 Redis 实例从第一台服务器移动到第二台服务器,依此类推。

使用 Redis 副本,您将可以为用户减少停机时间或减少停机时间。

3. Redis 分区的实现

3.1 Redis 集群

Redis Cluster 是获得自动分片和高可用性的首选方法。Redis Clusterquery routingclient side partitioning 的混合体。

3.2 Twemproxy

Twemproxy支持在多个 Redis 实例之间进行自动分区,并在节点不可用的情况下弹出可选节点(这将更改key-instances映射,因此仅在将 Redis 用作缓存时才应使用此功能)。

基本上,Twemproxy 是客户端和 Redis 实例之间的中间层,它将以最小的额外复杂性可靠地为我们处理分区。

3.3 支持一致哈希的客户端

Twemproxy 的替代方法是使用通过一致性哈希或其他类似算法实现客户端分区的客户端。 有多个 Redis 客户端支持一致性哈希,特别是 Redis-rbPredis

那么什么是一致性哈希呢?

还是拿前面的例子来说。4个 Redis 实例,使用简单哈希的过程如下:

  1. 使用 hash 函数将键转换成数字。
  2. 将此数字对4取模,结果为0、1、2、3分别对应 Redis 的4个实例R0、 R1、 R2、 R3。

但是,因为4个 Redis 实例不够用了,又加了1个 Redis 实例,那么前面的 key-instances的映射 关系就会发生改变了,如果要解决这个问题就需要重新对所用的键的哈希值对5取模,来决定映射到哪一个 Redis 实例上,而且在新映射的机器中没有之前存的键了。

当机器数量发生变动的时候,几乎所有的数据都会移动,就 Redis 而言可能造成缓存雪崩(在这一时刻,所有缓存都没了,因为新映射的机器中没有存对应的键)。此时的问题是,当增加或者删除节点时,对于大多数记录,保证原来分配到的某个节点,现在仍然应该分配到那个节点,将数据迁移量的降到最低,这就是一致性哈希要做的事情。在这里我们不指定是数据库还是什么,反正都是分布式存储节点。

一致性哈希就是解决的上述问题。从 2 个方向去考虑:

  1. 节点宕机时,数据记录会被定位到下一个节点上;
  2. 新增节点时,相关区间内的数据记录就需要重新哈希。

一致性 Hash算法也是使用取模的思想,只是,刚才描述的取模法是对节点数量进行取模,而一致性Hash算法是对 232 取模,什么意思呢?简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0 ~ 232-1(即哈希值是一个32位无符号整形),整个哈希环如下,从 0 ~ 232-1 代表的分别是一个个的节点,这个环也叫哈希环

  1. 根据节点ip或者其他进行哈希,并对 232 取模,找到这些节点在哈希环中的位置;
  2. 根据Redis中的键进行哈希,并对 232 取模,找到这个键在哈希环中的位置;
  3. 从这个键的位置顺时针找到的第一个节点就是应该映射的节点了。

当服务器节点过少时,使用一致性哈希可能会出现 数据倾斜问题(也就是,被缓存的对象大部分都在某一台服务器上)。采用 虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。具体做法可以在服务器IP主机名的后面增加编号来实现。

4. 参考文献

[1] Redis 官方文档 partitioning
[2] 一致性哈希
[3] 面试必备:什么是一致性Hash算法?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章