分区:如何把数据存储到多个Redis实例中
分区就是把你的数据分割到多个Redis实例中的一个过程,因此每个实例仅仅包含部分键。这篇文章第一部分介绍分区概念,第二部分将介绍Redis分区的用法。
(译者注:Redis集群是分区事实上标准)
为什么分区是非常有用的
分区在Redis中主要有两个目的:
- 分区利用多台机器的内存构建一个更大数据库。如果不使用分区,数据库大小受限于单个计算机内存。
- 分区可以在多核和多计算机之间弹性扩展计算能力,并且分区可以在多计算机和网络适配器之间弹性扩展网络带宽。
分区基础:
有多种的分区标准。假设我们有4个Redis实例 R0,R1,R2,R3,很多表示用户的键例如 user:1,user:2等等,我们可以找到不同方式选择实例存储指定的键。换句话说有不同的系统映射一个指定的键到一个给定的Redis服务器。
一个最简单的方法是使用范围分区,并且通过映射某一范围的对象到特定的Redis实例。例如,我可以指定ID 0到10000的用户存储到实例R0,而ID 10001到20000的用户存储到实例R2等等。
该方案实际上是可以应用在实践中的,尽管他的缺点是需要一张映射对象范围与实例的表。这张表需要进行维护,并且我们需要为每种类型对象建立一张表,所以范围分区在Redis中常常是不受欢迎的,因为比其他分区方法更低效。
- 一个范围分区替代方法是哈希分区。此方案适用于任何形式键,无需键格式形如object_name:<id>,就是这么简单:
使用哈希方法(例如crc32哈希方法)将键名转换成数字。例如一个键名是foobar,crc32(foobar)输出结果形如93024922。 - 我是使用取模操作将该数字转换成0到3的数字,以便映射到四个Redis实例中的一个。93024922对4取余数等于2,这样我知道foobar键应该存储到R2实例中。注意:模操作返回除法运算的余数,大部分编程语言使用%(取余)就可以了。
通过这两个例子,你应该可以想到还有很多其他方法进行分区。一个更先进的哈希分区是一致性哈希,并且是由几个Redis客户端和代理实现的。
分区不同实现方式
分区可以由一个软件栈的不同部分完成。
- 客户端分区:客户端直接选择正确节点读写指定键。很多Redis客户实现了这种分区方式。
- 代理辅助分区:是指我们的客户端通过Redis协议把请求发送给代理,而不是直接发送给真正的Redis实例服务器。这个代理会确保我们的请求根据配置分区策略发送到正确的Redis实例上,并返回给客户端。Redis和Memcached的代理都是用Twemproxy (译者注:这是twitter开源的一个代理框架)来实现代理服务分区的。
- 查询路由:是指你可以把一个请求发送给一个随机的实例,这时实例会把该查询转发给正确的节点。通过客户端重定向(客户端的请求不用直接从一个实例转发到另一个实例,而是被重定向到正确的节点),Redis集群实现了一种混合查询路由。
Redis分区缺点:
Redis分区在有些方面做的并不好:
- 不支持多个键的操作。比如你不能操作映射在两个Redis实例上的两个集合的交叉集。(其实可以做到这一点,但是需要间接的解决).
- Redis不支持多个键的事务。
- Redis是以键来分区,因此不能使用单个大键对数据集进行分片,例如一个非常大的有序集。
- 如果使用分区,数据的处理会变得复杂,比如你必须处理多个RDB和AOF文件,在多个实例和主机之间持久化你的数据。
- 添加和删除节点也会变得复杂。例如通过在运行时添加和删除节点,Redis集群通常支持透明地再均衡数据,但是其他系统像客户端分区或者代理分区的特性就不支持该特性。不过Pre-sharding(预分片)可以在这方面提供帮助。
作为数据存储还是作为缓存使用?
使用Redis存储数据或者缓存数据在概念上是相同的,但是Redis被当作数据存储使用时有一个显著限制。当Redis被当作数据存储服务器使用的时候意味着对于相同的键值必须被映射到相同的实例上面,但是如果把Redis当作数据缓存器,使用多个不同节点,一个给定节点挂掉并不是个大问题,改变键值和实例映射表可以提升系统的可用性(也就是系统处理查询请求的能力)。
如果一个指定键的首选节点不可用,一致性哈希可以为指定键切换到其他的节点上。同样的,你添加一个新的节点,部分新的键值开始存储到新添加的节点上面。
主要的概念如下:
- 如果Redis只作为可伸缩缓存服务器来使用,那么用一致性哈希是非常容易的。
- 若果Redis被作为数据持久化服务器,需要提供节点和键值的固定映射,还有节点数目必须是固定的,不能改变。否则当增加或删除节点时,我们需要一个系统来为键重新分配节点,从2015年4月1日开始,Redis集群提供该特性。
预分片
从分区的概念中,我们知道分区有一个缺点:除非只把Redis当作缓存服务器来使用,否则添加和删除Redis节点都会非常复杂。相反使用固定的键值和实例映射更加简单。
然而数据存储会经常需要变化。今天我只需要10个Redis节点(实例),但是明天我可能会需要50个节点。
因为Redis足够轻量和小巧(一个备用实例使用1M的内存),解决这个问题的简单方法就是一开始就使用大量的实例节点。即使你开始只有一个服务器,你可以换成分布式的结构,通过分区分方式在单个服务器上来运行多个Redis实例。
你一开始可以选择的实例可数可以非常大。例如,32或者64个实例能够满足绝大多数的用户,并且可以为其提供足够的增长空间。
通过这样的方法,当摸得数据存储需求增加时,你只需要更多的Redis服务器,然后把一个节点移动到另外的服务器上面。一旦你添加了额外的服务器,你需要将一半的Redis的实例移动到第二个服务器,以此类推。
你可以使用Redis 的主从复制来减少服务的停止时间:
- 在新服务器上开启新的redis空实例。
- 将节点的数据配置移动到新的从服务器上
- 停止你的redis客户端。
- 在新的服务器上更新IP地址到移动过来的节点配置文件中。
- 发送SLAVEOF NO ONE 命令到新服务器的从节点。
- 使用新的配置重启客户端。
- 最后关闭老服务器上不再使用的节点。
分区实践
到目前为止,我们讲了分区的原理。但是该如何实战?你应该使用什么样的系统?
Redis集群
推荐使用Redis集群获得自动分片和高可用性。Redis集群是2015年4月1日版本发布的可用和生成就绪特性。可以从集群教程中获取更多信息。
一旦Redis集群是可用的,并且一个Redis集群兼容客户端支持您的编程语言,Redis集群就是Redis分区事实上标准。
Redis集群混合使用了查询路由和客户端分区。
Twemproxy 框架
Twemproxy是一个由Twitter开发的适合Memached ASCII和Redis协议的代理。它是单线程工作,使用C语言实现的,速度非常快。并且是基于Apache 2.0 协议的开源软件。
Twemproxy支持自动在多个redis节点分区,如果某个节点不可用,将会被自动屏蔽(这将改变键值和节点映射表,所以如果你把Redis当作缓存服务器使用,你应该使用这个功能)。
你可以启用多个代理,让你的客户端得到可用的连接,这样不会发生单点故障。
Twemproxy基本上是Redis和客户端的一个中间层,通过简化使用让我们使用可靠的分区。
你可以在antirez的博客获取有关Twemproxy的更多知识。
客户端一致性哈希实现。
替代Twemproxy的一种方案是使用客户端一致性哈西或者其他类似的算法。有很多Redis客户端支持一致性哈希,比如Redis-rb和Predis。
请检查Redis客户端全量列表,以确定是否有适合于你的编程语言、成熟的一致性哈希实现的客户端。