(译)分片:如何将数据存储在不同的Redis实例中

本文翻译自Partitioning: how to split data among multiple Redis instances

分片就是将你的数据切分成多个部分存放在多个Redis实例中的过程,因此每个实例中将只有你的所有键的一个子集。本文档的第一部分将向你介绍分片的概念,第二部分将向你展示Redis的分片方案。

为什么分片很有用?

Redis中的分片主要有两个目标:

它使用多台计算机的内存综合来构建更大型的数据库。如果不分片,你将只能使用单个计算机所能支持的内存量。它允许将计算能力扩展至多核以及多台计算机,并将网络带宽扩展到多台计算机和网络适配器上。

分片的基础概念

这里有一些不同的分片标准。假设我们有4个Redis实例:R0R1R2R3,且代表用户的键的名称类似user:1、user:2,以此类推,我们有几个不同的方法来选择一个实例并将一个给定的键存储在这个实例中。换句话说,即有不同的方式将一个给定的键映射到一个给定的Redis服务器上。

最简单的一种方式是范围分片,将一定范围的对象映射到指定的Redis实例上,例如,我们可以让ID范围在0-10000内的用户映射到R0,ID范围在10001-20000内的用户映射到R1,以此类推。

该系统在实际工作中可行,然而,它的缺点是需要一个将对象范围映射到实例的表。对于每一类对象,我们都需要管理这样一个表,因此,范围分片在Redis中往往是不可取的,因为它比其他的分片方式更低效。

一个范围分片的替代方案是哈希分片。这种模式对任何键都有效,它不需要键采用object_name:<id>的形式,而且它很简单:

  • 使用哈希函数(比如,crc32哈希函数)将键名称转换为一个数字。例如,如果键名称为foobar,crc32(foobar)将输出类似93024922这样的数字。
  • 对这个数字使用取模操作,将它转换成0-3之间的整数,以便这个数字可以映射到我们的其中一个Redis实例上。93024922对4取模等于2,因此键foorbar将被存储在R2上。注意:取模操作返回的是一个除法操作的余数,它在很多编程语言中被实现为%操作符。

还有很多其他方式来执行分片操作,但看了这两个例子后你应该有一些想法了。一个基于哈希分片的高级方式被称为一致性哈希,它是由几个Redis客户端和代理实现的。

分片的不同实现

分片在软件上可以被分为几个不同的部分。

  • 客户端分片意味着由客户端直接选择正确的节点进行写操作或读取一个给定的键。很多Redis客户端都实现了客户端分片。
  • 代理辅助分片意味着我们的客户端将请求发送给一个能够支持Redis协议的代理,而不是直接将请求发送给正确的Redis实例。代理在确认后将根据已配置的分区模式将我们的请求发送给正确的Redis实例,之后会将回复回送给客户端。Redis和Memcached代理twemproxy实现了辅助代理分区。
  • 请求路由意味着你可以将请求随机发送给一个实例,这个实例确认后将会把这个请求发送给正确的节点。Redis集群实现了一个混合形式的请求路由,这里面有来自客户端的帮助(请求不是从一个Redis实例直接发送给另一个实例,而是将客户端重定向到正确的节点)。

分片的缺点

在使用分片时,Redis的有些功能表现不佳:

  • 涉及多个键的操作通常不受支持。例如,你无法对两个存在于不同Redis实例上的集合执行交集操作(实际上这里有两种方法来完成这个操作,但都是间接的)。
  • 无法使用涉及多个键的Redis事务。
  • 分片的粒度是键,因此无法将类似一个非常大的有序集合这样的大键分片到多个实例上。
  • 当使用了分片时,数据处理会更加复杂,例如你不得不处理多个RDB/AOF文件,并且为了备份你的数据就需要将多个实例和主机上的持久化文件进行聚合。
  • 增加和减少容量都很复杂。例如,Redis集群支持在运行时增减节点来几乎透明地对数据进行平衡,但其他一些系统比如客户端分片和代理辅助分片不支持这种功能。然而,一种叫做预分片的技术在这里会有所帮助。

Redis是作为数据存储还是缓存?

虽然Redis的分片不管在将Redis作为数据存储使用或者作为缓存使用在概念上都是相同的,但在将Redis作为数据存储使用时有一个重要的限制。当Redis被用来作为数据存储使用,一个给定的键必须总是被映射到相同的Redis实例上。当Redis被用来作为缓存,因一个给定节点不可用而转而使用另一个节点也不是什么大问题,我们可以改变键-实例映射关系来提高系统可用性(也就是系统对我们的查询的响应能力)。

如果对于一个给定键,首选节点不可用的话,一致性哈希实现通常能够将其切换到其他节点上。类似地如果你添加了一个新节点,一部分新键将开始被存储在这个新节点上。

这里的主要概念如下:

  • 如果Redis被当成一个缓存使用,使用一致性哈希进行扩容和缩容是很容易的。
  • 如果Redis被当成一个数据存储使用,使用了一个固定的键-节点映射,则节点数量是固定的,不能改变。否则,我们需要一个在节点被添加或移除时能够在节点之间进行再平衡键的系统,目前只有Redis集群能够做到 —— Redis集群在2015-04-01之后在正产环境中可用。

预分片

我们发现关于分片的一个问题是,除非我们将Redis作为一个缓存使用,否则添加或删除节点可能很麻烦,使用固定的键-实例映射要简单得多。

然而,数据存储的需求可能会随着时间变化。今天我只要10个Redis节点就能搞定,但明天我可能就需要50个节点。

由于Redis非常的小和轻量级(空闲实例值占用1MB内存),一个解决该问题的简单方法是在一开始就启动大量实例。即使你只使用一台服务器,你也可以决定从一开始就采用分布式的方法进行部署,使用分片将多个Redis实例运行在一台服务器上。

而且从一开始你就可以选择一个非常大的实例数量。例如,32或64个实例对大多数用户都是一个不错的选择,这个数量可以为将来的增长预留足够的空间。

这样,d昂你的数据存储需求增加,需要更多的Redis服务器,你需要做的就是简单地将Redis实例从一台服务器移动到另一台。一旦你添加了第一台额外的服务器,你将需要把一半的Redis实例从第一台移动到第二台,以此类推。

使用Redis复制你将能够在很少或者没有停机时间的情况下为你的用户们移动数据:

  • 在新服务器上启动新Redis实例。
  • 通过将这些新实例设置为你旧实例的的从节点来移动数据。
  • 停止你的客户端。
  • 用新的服务器IP更新被移动实例的配置。
  • 对新服务器上的从节点发送SLAVEOF NO ONE命令。
  • 使用更新后的配置重启你的客户端。
  • 最后关闭旧服务器上那些不再使用的Redis实例。

Redis分片的各种实现

到目前为止,我们涵盖了Redis分片的理论部分,但在实践中要如何做呢?我们应该使用哪种系统?

Redis集群

Redis集群是获得自动分片和高可用性的首选方式。它从2015-04-01开始就在生产环境中可用了。你可以在集群教程中获取更多有关Redis集群的信息。

一旦Redis集群可用,而且如果你使用的编程语言中有一个Redis集群兼容的客户端,那么Redis集群将变为Redis分片的事实标准。

Redis集群是请求路由和客户端分片之间的一种混合方式。

Twemproxy

Twemproxy是一个由Twitter为Memcached ASCII和Redis协议开发的一个代理。它是单线程的,使用C编写而成,并且它非常快。它是一个使用Apache 2.0许可证的开源软件。

Twemproxy支持在多个Redis实例中自动分片,如果节点不可用,会将节点移出(这将改变键-实例映射,因此你应该只在将Redis作为一个缓存使用时采用这种方式)。

由于你可以启动多个代理且可以指示你的客户端连接到接收连接的第一个节点,因此使用代理不会造成单点失败问题。

基本上Twemproxy是一个介于客户端和Redis实例之间的中间层,这将可靠地为我们以最小的复杂度处理分片。

你可以从antirez的这篇blog中阅读到更多关于Twemproxy的信息。

支持一致性哈希的客户端

一种Twemproxy的替代方案是,使用支持采用一致性哈希或其他类似算法进行分片的客户端。这里有多个Redis客户端支持一致性哈希,尤其是Redis-rbPredis

请查看Redis客户端的完整列表来确认是否有一个你的编程语言下支持的一致性哈希的成熟的客户端实现。