本文基于Redis 6.0.9版本,前提至少 Redis 3.0或更高版本。
目录
1.分区:如何在多个Redis实例之间拆分数据
分区是将数据拆分为多个Redis实例的过程,因此每个实例将只包含键的一个子集。 本文档的第一部分将向您介绍分区的概念,第二部分将向您展示Redis分区的替代方法。
1.1.为什么分区有用
对Redis进行分区有两个主要目标:
- 它允许使用许多计算机的内存总和来创建更大的数据库。 如果不进行分区,则只能使用一台计算机可以支持的内存量。
- 它允许将计算能力扩展到多核和多台计算机,并将网络带宽扩展到多台计算机和网络适配器。
1.2.分区基础
有不同的分区标准。 想象一下,我们有四个Redis实例R0, R1, R2, R3和许多表示用户的键,例如 user:1
, user:2
, ...等,我们可以找到不同的方法来选择在哪个实例中存储给定的键 。 换句话说,存在将给定键映射到给定Redis服务器的不同系统。
执行分区的最简单方法之一是范围分区,它是通过将对象范围映射到特定的Redis实例来实现的。 例如,我可以说从ID 0到ID 10000的用户将进入实例R0,而从ID 10001到ID 20000的用户将进入实例R1,依此类推。
该系统可以工作并且可以在实践中实际使用,但是,它的缺点是需要一个将范围映射到实例的表。 需要管理该表,并且每种对象都需要一个表,因此Redis中的范围分区通常是不可取的,因为它比其他替代分区方法效率低得多。
范围分区的替代方法是哈希分区(hash partitioning)。 此方案适用于任何键,而无需使用 object_name:<id> 形式的键,并且非常简单:
- 获取键名称并使用哈希函数(例如crc32哈希函数)将其转换为数字。 例如,如果键是foobar,则crc32(foobar)将输出类似93024922的信息。
- 对这个数字使用取模运算,以便将其转换为0到3之间的数字,以便可以将此数字映射到我的四个Redis实例之一。 93024922模4等于2,所以我知道我的键foobar应该存储到R2实例中。 注意:模运算返回除法运算的余数,并以%运算符在许多编程语言中实现。
还有许多其他方法可以执行分区,但是通过这两个示例,您应该会明白。 哈希分区的一种高级形式称为一致性哈希,由一些Redis客户端和代理实现。
1.3.分区的不同实现
分区可能是软件堆栈中不同部分的责任。
- 客户端分区(Client side partitioning)意味着客户端直接选择要在其中写入或读取给定键的正确节点。 许多Redis客户端实现客户端分区。
- 代理辅助分区(Proxy assisted partitioning)意味着我们的客户将请求发送到能够使用Redis协议的代理,而不是直接将请求发送到正确的Redis实例。 代理将确保根据配置的分区架构将请求转发到正确的Redis实例,并将答复发送回客户端。 Redis和Memcached代理Twemproxy实现了代理辅助分区。
- 查询路由(Query routing)意味着您可以将查询发送到随机实例,该实例将确保将查询转发到正确的节点。 Redis Cluster在客户端的帮助下实现了混合形式的查询路由(请求不会从Redis实例直接转发到另一个实例,而是会将客户端重定向到正确的节点)。
1.4.分区的缺点
Redis的某些功能在分区中不能很好地发挥作用:
- 通常不支持涉及多个键的操作。 例如,如果两个集合存储在映射到不同Redis实例的键中,则无法执行它们的相交(实际上,有很多方法可以执行此操作,但不能直接执行)。
- 不能使用涉及多个键的Redis事务。
- 分区粒度是关键,因此无法使用单个大键(如非常大的排序集)对数据集进行分片。
- 使用分区时,数据处理会更加复杂,例如,您必须处理多个RDB / AOF文件,并且要备份数据,则需要从多个实例和主节点聚合持久性文件。
- 添加和删除容量可能很复杂。 例如,Redis Cluster在很大程度上支持透明的数据重新平衡,并能够在运行时添加和删除节点,但是其他系统(例如客户端分区和代理)不支持此功能。 但是,在这方面,一种称为“预分片(Pre-sharding)”的技术会有所帮助。
1.5.数据存储还是缓存?
尽管无论将Redis用作数据存储还是用作缓存,Redis中的分区在概念上都是相同的,但是将其用作数据存储时存在很大的限制。 当Redis用作数据存储时,给定的键必须始终映射到同一Redis实例。 当Redis用作缓存时,如果给定的节点不可用,则使用不同的节点也不是什么大问题,因为我们希望提高系统的可用性(即 系统来回复我们的查询)。
如果给定键的首选节点不可用,则一致性哈希实现通常可以切换到其他节点。 同样,如果添加新节点,则部分新键将开始存储在新节点上。
这里的主要概念如下:
- 如果将Redis用作缓存,则使用一致的散列就可以向上和向下缩放。
- 如果将Redis用作存储,则使用固定的“keys-to-nodes”映射,因此节点数必须固定并且不能变化。 否则,需要一个能够在添加或删除节点时在节点之间重新平衡键的系统,并且目前只有Redis Cluster能够做到这一点,Redis Cluster于2015年4月1日正式上市并可以投入生产。
1.6.预分片
我们了解到分区的问题是,除非我们将Redis用作缓存,否则添加和删除节点可能很棘手,并且使用固定的键实例映射要简单得多。
但是,数据存储需求可能会随时间变化。 今天,我可以使用10个Redis节点(实例),但是明天我可能需要50个节点。
由于Redis的占地空间非常小且重量轻(备用实例使用1 MB的内存),因此解决此问题的简单方法是从头开始使用许多实例。 即使仅从一台服务器启动,也可以决定从一开始就运行在分布式环境中,并使用分区在单个服务器上运行多个Redis实例。
您可以从一开始就将实例数量选择为很大。 例如,对于大多数用户而言,32或64个实例可以解决问题,并将为增长提供足够的空间。
这样,随着数据存储需求的增加以及您需要更多的Redis服务器,您要做的就是将实例从一台服务器移动到另一台服务器。 添加第一个其他服务器后,您将需要将一半Redis实例从第一台服务器移动到第二台服务器,依此类推。
使用Redis复制,您将可以在最少停机时间或没有停机的情况下进行迁移:
- 在新服务器中启动空实例。
- 移动将这些新实例配置为源实例的从节点的数据。
- 停止你的客户端。
- 使用新的服务器IP地址更新移动实例的配置。
- 将 SLAVEOF NO ONE 命令发送到新服务器中的从节点。
- 使用新的更新配置重新启动客户端。
- 最后,关闭旧服务器中不再使用的实例。
2.Redis分区的实现
到目前为止,我们已经在理论上介绍了Redis分区,但是实践呢? 您应该使用什么系统?
2.1.Redis集群(Redis Cluster)
Redis Cluster是获得自动分片和高可用性的首选方法。 自2015年4月1日起,该版本将普遍可用并投入生产。您可以在Cluster教程中获得有关Redis Cluster的更多信息。
一旦Redis Cluster可用,并且如果符合您语言的Redis Cluster兼容客户端可用,则Redis Cluster将成为Redis分区的事实上的标准。
Redis Cluster是查询路由和客户端分区之间的混合体。
2.2.Twemproxy
Twemproxy是Twitter上针对Memcached ASCII和Redis协议开发的代理。 它是单线程的,它是用C语言编写的,并且速度非常快。 它是根据Apache 2.0许可条款发行的开源软件。
Twemproxy支持在多个Redis实例之间进行自动分区,并在节点不可用时自动弹出节点(这将更改key-instances映射,因此仅在将Redis用作缓存时才应使用此功能)。
这不是单点故障,因为您可以启动多个代理并指示客户端连接到第一个接受连接的客户端。
基本上,Twemproxy是客户端和Redis实例之间的中间层,它将以最小的额外复杂性可靠地为我们处理分区。
您可以在此antirez博客文章中了解有关Twemproxy的更多信息。
2.3.支持一致性哈希的客户端
Twemproxy的替代方法是使用通过一致性的哈希或其他类似算法实现客户端分区的客户端。 有多个Redis客户端支持一致性的哈希,尤其是Redis-rb和Predis。
请检查Redis客户端的完整列表,以检查是否有成熟的客户端对您的语言执行一致性的哈希实现。