什么是分区
分区就是将所存储的数据按照一定的规则存储在不同的存储服务或介质上,通过降低单服务或介质的数据量级来提升数据处理能力,从而达到拥有数据处理横向扩容的能力的目的。
还可简单的理解为分区就是将数据拆分到多个 Redis 实例的过程,每个Redis 拥有一部分数据。
为什么要分区
单点缓存节点受机器内存、网卡带宽和单节点请求量限制,随着请求量和数据量的增加,不能承担更高并发。
如果只使用一个redis实例时,其中保存了服务器中全部的缓存数据,这样会有很大风险,如果单台redis服务宕机了将会影响到整个服务。
解决的方法就是我们可以采用分区的技术,将原来一台服务器维护的整个缓存,现在换为由多台服务器共同维护内存空间,所以就需要考虑分区。
在数据量非常大时,依照分区算法将数据量分散到若干主机的 Redis 实例上,进而减轻单台 Redis 实例的压力。这样在某个节点故障的情况下,其他节点也可以提供服务,保证了一定的可用性。就好比不要把鸡蛋放在同一个篮子里,当一个篮子掉在地上,不至于一个不剩。
分区技术能够以更易扩展的方式使用多台计算机的存储能力(这里主要指内存的存储能力)和计算能力:
- 存储上:分区技术通过使用多台计算机的内存来承担更大量的数据,如果没有分区技术,那么 Redis 的存储能力将受限于单台主机的内存大小。
- 计算能力上:分区技术通过将计算任务分散到多核或者多台主机中,能够充分利用多核、多台主机的计算能力。
如果只使用一个redis实例时,其中保存了服务器中全部的缓存数据,这样会有很大风险,如果单台redis服务宕机了将会影响到整个服务。
分区方案
分区可以在程序的不同层次实现。
客户端分区
在客户端就已经决定数据会被存储到哪个 Redis 节点或者从哪个 Redis 节点读取。许多 Redis 客户端实现了客户端分区。
在客户端配置多个缓存的节点,通过缓存写入和读取算法策略来实现分布式,从而提高缓存可用性。
写数据:需要把被写入缓存的数据分散到多个节点中,即进行数据分区。
读数据:可利用多组的缓存来做容错,提升缓存系统可用性。这里可用主从、多副本两种策略,专为解决不同问题而提。
优点:客户端分片最明显的好处在于降低了集群的复杂度,而服务器之间没有任何关联性,数据分片由客户端来负责实现。
缺点:客户端实现分片则客户端需要知道当前集群下不同Redis实例的信息,当新增Redis实例时需要支持动态分片,多数Redis需要重启才能实现该功能。
客户端常见实现方案有范围、取模、哈希、随机,根据不同的特性决定不同的场景。
代理分区
在应用代码和缓存节点之间增加代理层,客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些 Redis 实例,然后根据Redis的响应结果返回给客户端。
客户端发送请求到一个可以理解 Redis 协议的代理上,而非直接发到 Redis 实例。代理会根据配置好的分区模式,来保证转发我们的请求到正确的 Redis 实例,并返回响应给客户端。Redis 和 Memcached 的代理 Twemproxy 都实现了代理协助的分区。
优点:降低了客户端的复杂度,客户端不用关心后端Redis实例的状态信息。
缺点:多了一个中间分发环节,所以对性能有些取的损失。
示例图如下:
查询路由
客户端随机地请求任意一个 Redis 实例,然后由 Redis 将请求转发给正确的Redis节点。
Redis 集群在客户端的帮助下,实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。当前还不能完全适用于生产环境。
优点:支持高可用,任意实例都有主从,主挂了从会自动接管。
缺点:需要客户端语言实现服务器集群协议,但是目前大多数语言都有其客户端实现版本。
Redis 集群模式就是使用查询路由的方式操作key。
分区实现
Redis实现分区至少存在三个数据分片,每个分片称为 master。假设整个集群有 N 个节点,那么每个节点都和其它 N-1 个节点保持连接和心跳。节点之间采用 Gossip 协议进行通信。通信主要确认节点是否存活、节点的数据版本、投票选择新的主节点等内容。
Redis 的数据如何分布在分片上呢?
Redis 集群中有 16384 个槽(slot),每个数据都会被存储在指定都槽内。当用户 set 一个 key 时,Redis 会调用 CRC16 函数对16384进行取模运算,以此来决定数据存放的槽位。当数据正确存储后,通过 Gossip 协议向其他节点广播该数据当位置,所以 Redis 没有中心路由的概念。
这种结构很容易添加或者删除节点。
举例:
- 如果新添加一个节点D,我需要从节点 A、B、C 中分部分槽到 D 上。
- 如果我想移除节点 A,需要将 A 中的槽移到 B 和 C 节点上,然后将没有任何槽的 A 节点从集群中移除即可。
由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。
以上从理论上讨论了 Redis 分区,但是实际上是怎样的呢?你应该采用哪种实现方案呢?
Redis 集群
Redis 集群是自动分区和高可用的首选方式。一旦 Redis 集群以及支持 Redis 集群的客户端可用,Redis 集群将会成为 Redis 分区的事实标准。
Redis 集群是查询路由和客户端分区的一种混合模式。
Twemproxy
Twemproxy 是 Twitter 开发的一个支持 Memcached ASCII 和 Redis 协议的代理。它是单线程的,由 C 语言编写,运行非常快,基于 Apache 2.0 许可证。
Twemproxy 支持在多个 Redis 实例间自动分区,如果代理的其中一个Redis节点不可用时,会自动将该节点排除中,这会改变 <键,实例> 映射,所以应该只在将 Redis 作为缓存是才使用该特性。
Twemproxy本身不存在单点问题,因为可以启动多个Twemproxy实例,然后让你的客户端去连接任意一个Twemproxy实例。
从根本上说,Twemproxy 是介于客户端和 Redis 实例之间的中间层,由它来处理分区功能应该不算复杂,并且应该算比较可靠的。
一致性哈希
Twemproxy 之外的可选方案,是使用实现了客户端分区的客户端,通过一致性哈希或者别的类似算法。有多个支持一致性哈希的 Redis 客户端,例如 Redis-rb 和 Predis。
分区优缺点
Redis 的一些特性与分区在一起使用时不是很好。
- 分区是多台 Redis 共同作用的,如果其中一台出现了宕机现象,则整个分片都将不能使用,虽然是在一定程度上缓减了内存的压力,但是没有实现高可用。
- 涉及多个 key 的操作通常不支持。例如:无法直接对映射在两个不同 Redis 实例上的 key 执行交集
- 涉及多个 key 的事务不能使用
- 分片的粒度是键,所以不能使用一个很大的键来分片数据集,例如一个很大的 sorted set
- 当使用分区时,数据处理变得更复杂。例如:你需要处理多个 RDB/AOF 文件,备份数据时需要聚合多个实例和主机的持久化文件
- 添加和删除容量也很复杂。例如,Redis 集群具有运行时动态添加和删除节点的能力来支持透明地再均衡数据,但是其他方式,像客户端分片和代理都不支持这个特性。但有一种称为预分片的技术在这一点上能帮上忙。
以上是关于 Redis 分区的信息,Redis 中还有 预分区/片 概念,可以了解一下。