关于后端:深入解读Redis系列Redis系列五切片集群详解

3次阅读

共计 6004 个字符,预计需要花费 16 分钟才能阅读完成。

首发博客地址

https://blog.zysicyj.top/

系列文章地址


如果 Redis 内存很大怎么办?

假如一台 32G 内存的服务器部署了一个 Redis,内存占用了 25G,会产生什么?

此时最显著的体现是 Redis 的响应变慢,甚至十分慢。

这是因为 RDB 快照是通过 fork 子线程来实现的,fork 操作工夫和 Redis 数据量成正相干,而 fork 时会阻塞主线程。

随着数据量的减少,fork 耗时也会减少。所以,当对 25G 的文件进行 fork 时,Redis 的响应就会变慢。

针对这种大数据量的存储,有什么其余的计划呢?

什么是切片集群?

Redis 分片集群是一种将 Redis 数据库扩散到多个节点上的形式,以提供更高的性能和可伸缩性。在分片集群中,数据被分为多个片段,每个片段存储在不同的节点上,这些节点能够是物理服务器或虚构服务器。

Redis 分片集群的次要目标是将数据分布在多个节点上,以便能够通过并行处理来进步读写吞吐量。每个节点负责解决一部分数据,并且在须要时能够进行扩大以适应更多的负载。此外,分片集群还提供了故障容错和高可用性的性能,即便其中一个节点产生故障,其余节点依然能够持续工作。

比方咱们将 25GB 的数据均匀分成 5 份(当然,也能够不做均分),应用 5 个实例来保留,每个实例只须要保留 5GB 的数据。如下图所示:

这样,每个实例只有 5GB 内存,执行 fork 的时候就快得多,不会阻塞主线程。

理论业务中,大数据量通常是无奈防止的。而切片集群,就是一个十分好的计划。

如何保留更多数据?

咱们能够纵向扩大也能够横向扩大

纵向扩大

即降级单个 Redis 实例的配置,如内存、硬盘、带宽、CPU 等

横向扩大

即减少 Redis 实例的个数

那么,纵向扩大和横向扩大的区别是什么呢?

纵向扩大(Scale Up)和横向扩大(Scale Out)是常见的两种扩大形式,用于晋升零碎的性能和解决能力。它们有着不同的特点和实用场景。

  1. 纵向扩大:
    纵向扩大是通过减少单个节点的硬件资源来晋升零碎性能。具体来说,是减少服务器的解决能力、存储容量或内存大小等。这能够通过降级服务器的 CPU、内存、硬盘等硬件设施来实现。

长处:

  • 简略不便:纵向扩大只须要降级现有的服务器,不须要进行零碎的重构和数据迁徙。
  • 老本绝对较低:绝对于横向扩大,纵向扩大的老本通常更低,因为只须要购买更高配置的硬件设施。

毛病:

  • 无限的扩大能力:纵向扩大的扩大能力受限于单个节点的硬件资源,无奈有限扩大。
  • 单点故障:如果纵向扩大的节点产生故障,整个零碎的可用性将会受到影响。

实用场景:

  • 对于单个节点负载较高、须要解决大量并发申请的利用场景,纵向扩大能够提供更好的性能和响应能力。
  • 当数据集较小,能够被一个节点的硬件资源包容时,纵向扩大是一种经济无效的形式。
  1. 横向扩大:
    横向扩大是通过减少多个节点来晋升零碎的性能和解决能力。每个节点能够是一台独立的服务器或者虚拟机。数据在多个节点上进行分片存储,各个节点独特解决申请,并共享负载。

长处:

  • 有限扩大能力:横向扩大能够通过减少更多节点来实现有限的扩大能力,能够依据需要动静增加或移除节点。
  • 高可用性:因为数据分布在多个节点上,即便其中一个节点产生故障,其余节点依然能够持续工作,提供高可用性。

毛病:

  • 复杂性减少:横向扩大须要进行数据分片和负载平衡的设计和实现,减少了零碎的复杂性。
  • 老本较高:绝对于纵向扩大,横向扩大须要购买更多的服务器或虚拟机,老本较高。

实用场景:

  • 对于须要解决大量并发申请、数据集较大的利用场景,横向扩大能够提供更好的性能和可伸缩性。
  • 当须要保证系统的高可用性和故障容错能力时,横向扩大是一种可行的计划。

纵向扩大和横向扩大是两种不同的扩大形式,各自有着不同的长处和实用场景。在理论利用中,应依据具体需要和限度,抉择适合的扩大形式来晋升零碎性能和可伸缩性。

在面向百万、千万级别的用户规模时,横向扩大的 Redis 切片集群会是一个十分好的抉择。

Redis 是如何做分片的

Redis 通过一种称为哈希槽(hash slot)的机制来实现分片集群。哈希槽将整个数据集分成固定数量的槽,每个槽都有一个惟一的编号,通常是从 0 到 16383。

在 Redis 分片集群中,有多个节点(主节点和从节点),每个节点负责存储其中一部分的槽数据。节点之间通过集群间通信协议进行数据的交互和同步。

当一个客户端发送一个命令到 Redis 分片集群时,集群会依据命令波及的键的哈希值将命令路由到正确的槽上。这个槽所在的节点负责解决这个命令并返回后果给客户端。

具体的分片过程如下:

  1. 客户端发送命令到 Redis 分片集群中的任意一个节点。
  2. 节点依据命令波及的键的哈希值计算出对应的槽号。
  3. 节点依据槽号确定该槽所在的节点,并将命令路由到该节点。
  4. 该节点解决命令并返回后果给客户端。

当节点退出或来到集群时,Redis 分片集群会主动进行数据的从新分片和迁徙,以保持数据的平衡和高可用性。具体的过程如下:

  1. 当一个新节点退出集群时,集群会将一部分槽从现有节点迁徙到新节点上,以均衡数据负载。
  2. 当一个节点来到集群时,集群会将该节点负责的槽迁徙到其余可用节点上,以保证数据的可用性。

通过哈希槽的机制,Redis 分片集群实现了数据的分片和主动迁徙,以提供高可用性、扩展性和容错性。同时,节点间的通信和数据同步保障了集群的一致性和可用性。

具体说说哈希槽

Redis 哈希槽是 Redis 集群中用于分片数据的一种机制。哈希槽的概念能够简略了解为一种数据分片的形式,将所有的数据扩散存储在多个节点上,以实现数据的高可用和扩展性。

Redis 集群中共有 16384 个哈希槽,每个槽能够存储一个键值对。当有新的键值对须要存储时,Redis 应用一致性哈希算法将键映射到一个哈希槽中。每个 Redis 节点负责管理一部分哈希槽,节点之间通过 Gossip 协定来进行信息替换,以保障集群的一致性。

在 Redis 集群中,当一个节点宕机或者新减少一个节点时,哈希槽会重新分配。集群会主动将宕机节点上的槽重新分配给其余节点,并且保障每个节点调配的槽数尽量均等。这样能够保证数据的高可用性和负载平衡。

应用 Redis 哈希槽的益处是能够不便地扩大集群的容量,当数据量增大时,能够通过减少节点来分担数据的存储压力。同时,因为哈希槽的调配是主动的,所以对于应用程序而言是通明的,不须要额定的逻辑来解决数据分片。

手动调配哈希槽

示意图中的切片集群一共有 3 个实例,假如有 5 个哈希槽,咱们能够通过上面的命令手动调配哈希槽:实例 1 保留哈希槽 0 和 1,实例 2 保留哈希槽 2 和 3,实例 3 保留哈希槽 4。

redis-cli -h 172.16.19.3 –p 6379 cluster addslots 0,1
redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3
redis-cli -h 172.16.19.5 –p 6379 cluster addslots 4

在集群运行的过程中,key1 和 key2 计算完 CRC16 值后,对哈希槽总个数 5 取模,再依据各自的模数后果,就能够被映射到对应的实例 1 和实例 3 上了。

在手动调配哈希槽时,须要把 16384 个槽都调配完,否则 Redis 集群无奈失常工作。

客户端如何定位数据?

在 Redis 集群中,客户端定位数据的过程如下:

  1. 客户端依据键应用一致性哈希算法(Consistent Hashing)计算哈希值。
  2. 依据哈希值,客户端将键映射到某个哈希槽。
  3. 客户端向集群的其中一个节点发送命令申请。
  4. 接管到申请的节点依据哈希槽的调配信息,确定哪个节点负责管理该哈希槽。
  5. 负责该哈希槽的节点将命令申请转发给对应的数据节点。
  6. 数据节点执行命令,将后果返回给负责该哈希槽的节点。
  7. 负责该哈希槽的节点将后果返回给客户端。

通过这个过程,客户端能够定位到存储在 Redis 集群中的数据,并且能够与集群进行交互。这种形式使得客户端能够间接与任意一个节点进行通信,而不须要晓得具体的数据分布和节点拓扑。

一致性哈希算法是用来解决数据分片和负载平衡的罕用办法,它能够将数据平均地散布到不同的节点上,防止某个节点负载过高。同时,当节点产生故障或者新增节点时,一致性哈希算法能够最小化数据的迁徙量,使得集群能够疾速调整和复原。

须要留神的是,Redis 集群的客户端不须要手动实现一致性哈希算法,因为该算法曾经由 Redis 集群外部实现。客户端只须要应用对应的库或驱动程序,如 redis-py-cluster 库,来连贯 Redis 集群,并且间接应用一般的 Redis 命令进行数据操作。库会主动解决数据的定位和节点间的转发。

Moved 重定向命令

在 Redis 集群中,当客户端向一个节点发送命令申请时,如果该节点不负责解决该命令所波及的哈希槽,它会返回一个 MOVED 重定向谬误。

MOVED 重定向谬误蕴含了正确的节点信息,通知客户端应该向哪个节点从新发送命令。客户端能够依据 MOVED 谬误中的信息,更新本人的节点映射表,而后从新发送命令到正确的节点。

以下是一个应用 Python 的 redis-py 库解决 MOVED 重定向谬误的示例:

import redis

# 创立 Redis 集群连贯
cluster = redis.RedisCluster(host='127.0.0.1', port=7000)

# 存储数据
cluster.set('key1', 'value1')

# 获取数据
try:
    value = cluster.get('key1')
    print(value)
except redis.exceptions.RedisClusterError as e:
    if isinstance(e, redis.exceptions.ResponseError) and e.args[0].startswith('MOVED'):
        # 解析 MOVED 错误信息
        _, slot, addr = e.args[0].split()
        host, port = addr.rsplit(':', 1)
        # 更新节点映射表
        cluster.connection_pool.nodes.set_node(host, int(port))
        # 从新发送命令
        value = cluster.get('key1')
        print(value)

# 敞开连贯
cluster.close()

在以上示例中,如果客户端收到一个 MOVED 谬误,它会解析错误信息,获取正确的节点地址,并更新节点映射表。而后,客户端能够从新发送命令到正确的节点进行数据操作。

须要留神的是,MOVED 重定向谬误只会在 Redis 集群模式下产生,单机模式不会呈现该谬误。因而,只有在应用 Redis 集群时,才须要解决 MOVED 重定向谬误。在理论开发中,能够应用相应的库或驱动程序来主动解决 MOVED 谬误,而无需手动编写解决逻辑。

ASK 命令

在 Redis 集群中,当客户端向一个节点发送一个不可解决的命令时,节点会返回一个 ASK 谬误,批示客户端应该向指定的节点发送命令。客户端能够依据 ASK 谬误中的信息,更新本人的节点映射表,并将命令发送到正确的节点上。

以下是一个应用 Python 的 redis-py 库解决 ASK 命令的示例:

import redis

# 创立 Redis 集群连贯
cluster = redis.RedisCluster(host='127.0.0.1', port=7000)

# 存储数据
cluster.set('key1', 'value1')

# 获取数据
try:
    value = cluster.get('key1')
    print(value)
except redis.exceptions.RedisClusterError as e:
    if isinstance(e, redis.exceptions.ResponseError) and e.args[0].startswith('ASK'):
        # 解析 ASK 错误信息
        _, slot, addr = e.args[0].split()
        host, port = addr.rsplit(':', 1)
        # 更新节点映射表
        cluster.connection_pool.nodes.set_node(host, int(port))
        # 从新发送命令
        value = cluster.get('key1')
        print(value)

# 敞开连贯
cluster.close()

在以上示例中,如果客户端收到一个 ASK 谬误,它会解析错误信息,获取正确的节点地址,并更新节点映射表。而后,客户端能够从新发送命令到正确的节点进行数据操作。

须要留神的是,ASK 命令只会在 Redis 集群模式下产生,单机模式不会呈现该谬误。因而,只有在应用 Redis 集群时,才须要解决 ASK 命令。在理论开发中,能够应用相应的库或驱动程序来主动解决 ASK 谬误,而无需手动编写解决逻辑。

举个例子

能够看到,因为负载平衡,Slot 2 中的数据曾经从实例 2 迁徙到了实例 3,然而,客户端缓存依然记录着“Slot 2 在实例 2”的信息,所以会给实例 2 发送命令。实例 2 给客户端返回一条 MOVED 命令,把 Slot 2 的最新地位(也就是在实例 3 上),返回给客户端,客户端就会再次向实例 3 发送申请,同时还会更新本地缓存,把 Slot 2 与实例的对应关系更新过去。

须要留神的是,在上图中,当客户端给实例 2 发送命令时,Slot 2 中的数据曾经全副迁徙到了实例 3。在理论利用时,如果 Slot 2 中的数据比拟多,就可能会呈现一种状况:客户端向实例 2 发送申请,但此时,Slot 2 中的数据只有一部分迁徙到了实例 3,还有局部数据没有迁徙。在这种迁徙局部实现的状况下,客户端就会收到一条 ASK 报错信息,如下所示:

GET hello:key
(error) ASK 13320 172.16.19.5:6379

这个后果中的 ASK 命令就示意,客户端申请的键值对所在的哈希槽 13320,在 172.16.19.5 这个实例上,然而这个哈希槽正在迁徙。此时,客户端须要先给 172.16.19.5 这个实例发送一个 ASKING 命令。这个命令的意思是,让这个实例容许执行客户端接下来发送的命令。而后,客户端再向这个实例发送 GET 命令,以读取数据。

ASK 命令详解

在下图中,Slot 2 正在从实例 2 往实例 3 迁徙,key1 和 key2 曾经迁徙过来,key3 和 key4 还在实例 2。客户端向实例 2 申请 key2 后,就会收到实例 2 返回的 ASK 命令。

ASK 命令示意两层含意:第一,表明 Slot 数据还在迁徙中;第二,ASK 命令把客户端所申请数据的最新实例地址返回给客户端,此时,客户端须要给实例 3 发送 ASKING 命令,而后再发送操作命令。

和 MOVED 命令不同,ASK 命令并不会更新客户端缓存的哈希槽调配信息。所以,在上图中,如果客户端再次申请 Slot 2 中的数据,它还是会给实例 2 发送申请。这也就是说,ASK 命令的作用只是让客户端能给新实例发送一次申请,而不像 MOVED 命令那样,会更改本地缓存,让后续所有命令都发往新实例。

本文由 mdnice 多平台公布

正文完
 0