首发博客地址

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,1redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3redis-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多平台公布