Redis进阶

1.持久化

1.1 背景

Redis 是内存数据库,所有数据存储在内存中。为了防止数据丢失,Redis 提供了多种持久化机制,将内存数据保存到磁盘。Redis 主要有三种持久化方式:RDB、AOF 和混合持久化。当要插入一个新数据的时候就需要吧这个数据同时写入到内存和硬盘,虽然是两边都需要写入数据但根据策略的不同可以保证整体的高效率。硬盘中的数据只是在redis重启的时候用来恢复内存中的数据。

1.2 持久化的两种策略

1.2.1 RDB(Redis DataBase)

RDB是定期的将Redis内存中的所有数据一次性地写入到硬盘中,生成一个"快照"。后续Redis一旦重启(内存数据丢失),就可以根据"快照"来将内存中的数据恢复。

RDB的触发方式

(1)自动触发

在redis.conf文件中进行配置 例如

save 900 1 # 900 秒内至少 1 次写操作

save 300 10 # 300 秒内至少 10 次写操作

save 60 10000 # 60 秒内至少 10000 次写操作

(2)手动触发

save # 阻塞主线程(不推荐)

执行save的时候,redis就会全力以赴的进行"快照生成"操作,此时就会阻塞redis的客户端的命令,导致类似于keys *的后果

bgsave # fork 子进程,后台生成(推荐 用的较多)

问题:bgsave是如何实现在后台完成内存"快照"的?

Redis 主进程 fork() 一个子进程,子进程负责把内存数据写成 RDB 文件,主进程继续处理客户端请求,依靠写时复制(COW)保证数据一致性。

执行bgsave命令的细节

当执行 BGSAVE 命令时,Redis 首先会检查当前是否已经存在正在执行快照相关操作的子进程,例如正在进行另一个 BGSAVE 或 BGREWRITEAOF 操作。如果检测到已有这样的子进程在运行,Redis 将不会重复创建新的子进程,而是直接返回一个提示信息,从而避免同时进行多个后台持久化任务导致系统资源竞争和性能下降。如果不存在正在执行的快照子进程,Redis 主进程会调用 fork() 系统调用创建一个子进程。该子进程会获得主进程内存数据的副本,并在后台独立地将当前内存中的数据序列化并写入一个临时 RDB 文件,通常文件名为类似 temp-<进程ID>.rdb 的形式。与此同时,主进程会继续正常处理客户端的请求,从而实现持久化过程对服务的无阻塞。当子进程完成快照数据的写入并确保文件已同步至磁盘后,它会通过 rename() 系统调用将临时文件原子性地重命名为配置所指定的 RDB 文件名(默认为 dump.rdb)。这一原子替换操作确保了 RDB 文件的完整性和一致性:即使在最终重命名前发生崩溃,原有的 dump.rdb 文件依然保持不变,不会出现中间状态或数据损坏。整个流程结束后,子进程退出,主进程记录本次持久化完成的相关日志,从而完成一次完整且安全的 RDB 镜像生成过程。

执行save命令的细节

当执行 SAVE 命令时,Redis 会立即启动一个同步阻塞式的持久化过程。与 BGSAVE 不同,SAVE 不会创建子进程,而是由 Redis 主进程自身直接负责将当前内存中的数据完整序列化并写入 RDB 文件。在这一过程中,主线程会完全停止处理所有客户端请求,直至整个 RDB 文件写入完成。数据写入时同样会先生成临时文件,通常以类似 temp-<进程ID>.rdb 的形式命名,并通过文件系统提供的 fsync 机制确保数据落盘。待所有数据持久化到临时文件后,Redis 再通过 rename() 系统调用原子性地将其重命名为最终的 RDB 文件名(如 dump.rdb),替换原有文件。由于整个过程在主线程中同步执行,其间任何新的命令都无法被响应,因此 SAVE 通常仅用于内存数据量较小或可接受短暂服务中断的场景,生产环境中一般推荐使用后台运行的 BGSAVE 来避免服务阻塞。

Redis中rdb文件存放的路径

Redis生成的rdb文件是存放在redis的工作目录中的,可以通过redis配置文件来进行设置。

关于RDB文件的损坏与检查

rdb是二进制文件,将内存中需要"快照"的数据使用压缩的形式进行保存。

如果rdb文件被改坏了,但是看起来redis服务器存储的数据没有收到影响是因为具体的影响取决于rdb文件中的哪一块地方被损坏。

一旦rdb文件被破坏,redis服务器就无法启动。Redis提供了rdb文件的检测工具------redis-check-rdb(debian版本在/usr/bin目录下)

通过图中的软连接我们可以发现其实就是redis-server充当了这个检查rdb文件的角色

演示:使用redis-check-rdb来检查dump.rdb文件是否出现错误

如果检测结果出现RDB looks OK!的字样就代表我们的dump.rdb文件没有问题。

RDB文件生成的时机

Redis生成快照操作,不单单是手动执行命令才触发,也可以自动触发

(1)通过刚才配置文件中save执行M时间内修改了N次

(2)通过shutdown命令(redis内的一个命令)关闭redis服务器也会触发

(3)redis进行主从复制的时候,主节点也会自动生成rdb快照,然后把rdb快照文件内容传输给从节点。(后面细说)

注意:执行flushall命令会清除rdb文件内容

Redis.conf文件修改需要注意的方面

修改redis.conf配置文件后一定要重启服务器才可以生效;如果想要立即生效可以通过命令的方式修改配置文件。

注意:redis.conf文件中save设置位save "" 表示关闭自动生成快照

RDB文件的使用场景

(1)RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适合备份、全量复制等场景。例如每隔一段时间执行一次bgsave备份将RDB文件复制到远端机器或者文件系统中用于灾备

(2)Redis加载RDB恢复数据要远远快于AOF的方式

(3)RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork创建子进程,属于重量级操作,频繁执行的成本极高。

(4)RDB文件使用特定的二进制格式保存,Redis版本演进过程中有多个RDB版本,兼容性可能会有风险。

1.2.2 AOF(Append Only File)

产生背景

RDB是时间点快照,两次快照之间可能因为redis服务器意外终止导致数据丢失,因此AOF就有用武之地了。

注意:AOF默认是处于关闭状态,需要我们手动修改配置文件来启动它。(修改appendonly为yes)

AOF是一个文本文件,每次进行的操作都会被记录到文本文件中,本质是通过一些特殊符号作为分隔符来对命令的细节做出区分。

问题:AOF是否会影响到redis的性能?

实际上并没有影响到redis处理请求的速度

原因:

1.AOF机制并非直接让工作线程将数据直接写入硬盘而是先写入一个内存的缓冲区,等积累一波后再统一写入硬盘中,这大大降低了写入硬盘的次数。

2.硬盘上读写数据时,顺序读写的速度是比较快的(但还是慢于读写内存),但随机访问则速度相比顺序读写会慢一些。AOF属于的是顺序读写,因此速度比较快。

注意:如果该文本文件的内容仅存在于缓冲区而尚未写入磁盘,此时若发生断电等意外情况,将可能导致数据丢失。

Redis提供了多种AOF缓冲区同步文件策略,由参数appendfsync控制。

AOF的缓冲区同步策略

频率越高,数据可靠性就越高,性能越低;频率越低,数据可靠性就越低,性能更高。

AOF的重写机制

为什么AOF需要有重写机制?

随这Redis提供服务的时间增长,AOF文件会持续的记录所有的读写操作,但是这会是文件的体积膨胀,导致磁盘空间占用过大、Redis重启恢复时间过长、备份和传输效率变低。

基本思想:使用最终状态代替历史操作!AOF重写不是分析现有AOF文件,而是读取当前数据库状态,生成一个新的、最精简的AOF文件。

AOF的触发方式

(1)手动触发 调用bgrewriteaof命令

(2)自动触发 根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机(可在redis.conf中进行配置)

auto-aof-rewrite-min-size:表示触发重写时AOF的最小文件大小,默认64MB

auto-aof-rewrite-percentage:代表当前AOF占用大小相比上次重写时增加的比例

bgrewriteaof的核心过程

当触发重写(如果已经正在进行AOF重写就会直接返回)时,Redis会fork一个子进程来负责重写任务,父进程继续处理客户端请求。子进程并非分析或编辑原有AOF文件,而是直接读取当前数据库中的所有键值对数据,并依据这些数据的最终状态,生成对应的Redis命令,写入到一个新的临时AOF文件中。例如,一个曾被多次增减的计数器,在重写后只会被记录为一条设置其最终值的SET命令,从而消除所有中间操作冗余。在子进程进行重写的同时,父进程接收到的新写命令会除了照常记入原有的AOF缓冲区外,还会被额外写入一个专门的"AOF重写缓冲区"。当子进程完成新AOF文件的创建后,父进程会将重写缓冲区中累积的增量命令追加到新文件末尾,确保重写期间的数据不丢失。最后,Redis以原子操作方式用全新的、体积更小的AOF文件替换旧文件,完成整个重写。

问题:为什么父进程还要继续写旧AOF文件呢?

AOF重写期间父进程继续写入旧AOF文件,核心目的是确保数据安全性和服务的持续可用性,这是一个精心设计的"双保险"机制。如果父进程在重写期间停止写入旧AOF文件,那么从重写开始到新AOF文件生成完毕的这段时间内,所有新的写操作将仅存在于内存和重写缓冲区中。一旦重写过程因任何意外(如子进程崩溃、机器宕机或磁盘故障)而失败,这段"空窗期"内的数据将永久丢失,因为旧AOF文件没有记录,而新AOF文件又未成功生成。因此,持续写入旧AOF文件相当于为整个重写过程提供了一个可靠的"安全网",确保了在任何故障场景下,都至少有一个完整的操作日志可用于数据恢复。

注意:如果执行bgrewriteaof的时候发现redis正在生成rdb文件,这时aof重写操作就会等待知道rdb快照生成完毕后再进行aof重写操作。
问题:为什么RDB没有像AOF在fork时候提供一个缓冲区来记录写入硬盘时候新到的请求操作?

它们的本质不同。AOF的核心是记录操作日志,其目标是确保数据的连续性和完整性。重写缓冲区是为了弥补一个时间差:在子进程生成"最终状态"快照的这段时间内,主进程还在处理新请求。如果不记录这些新请求,它们就会永久丢失,破坏AOF作为操作日志的连续性。因此,缓冲区是AOF保证日志连续不中断这一设计目标的必然要求。RDB的核心是生成内存快照,其目标是捕获某个精确时间点的完整数据状态。它的设计哲学就是"定格瞬间",这个瞬间之后的数据本就不属于这个快照。为RDB的fork过程添加缓冲区,反而会破坏"时间点一致性"这一核心特性------你无法界定最终文件代表的是fork瞬间的状态,还是fork完成瞬间的状态。

1.2.3 混合持久化

Redis 的混合持久化是自 4.0 版本起引入的一项重要特性,它巧妙地融合了 RDB 和 AOF 两种传统持久化方式的优势,旨在解决各自存在的痛点。在它出现之前,管理员需要在 RDB 的快照恢复速度与 AOF 的增量数据安全之间做出艰难取舍。RDB 虽然能生成紧凑的二进制快照文件,加载恢复极快,但两次快照之间的数据丢失风险较高;而 AOF 通过记录每一个写操作命令来确保数据的完整性,理论上可以达到秒级甚至零数据丢失,但其日志文件通常体积庞大,且在故障恢复时需要逐条重放所有命令,过程非常缓慢。

混合持久化底层细节

在生成AOF文件时,不再仅仅以纯操作命令的形式记录,而是先写入一个完整的数据快照(RDB格式),然后紧接着在这个快照之后,追加记录快照生成期间以及之后的所有写操作命令(AOF格式)。最终,这个文件的前半部分是二进制的RDB数据,后半部分是AOF格式的增量命令,从而形成了一个"混合体"。

如何开启混合持久化

首先,你需要启用AOF持久化,这是混合持久化的基础前提。在Redis的配置文件redis.conf中,通过设置 aof-use-rdb-preamble yes 来打开混合持久化开关。当这个功能开启后,每当Redis需要执行BGREWRITEAOF操作来重写膨胀的AOF文件时,其内部流程就会发生改变。重写过程不再是直接分析内存数据并转换为AOF命令,而是会先像执行BGSAVE命令一样,将当前内存中的完整数据快照以RDB格式写入一个新的AOF文件开头。随后,在快照生成期间以及重写过程完成之前,主进程处理的新的写操作命令,会继续以AOF格式缓冲起来,并在RDB数据写入完成后,立即追加到这个新文件的末尾。这样一来,最终生成的AOF文件就兼具了RDB和AOF的特性。

混合持久化的优点和缺点

第一个优点是快速的数据恢复。由于文件开头是一个完整的RDB快照,Redis在重启加载数据时,可以非常迅速地解析并还原出某个时间点的完整数据集,这比从头到尾重放一个庞大的纯AOF命令日志要快得多。

第二个优点是更高的数据安全性和可读性。RDB文件之后追加的AOF日志,记录了从快照点开始的所有增量更改,这意味着数据丢失的风险被降到了只丢失快照之后那短暂一瞬(通常仅一秒)写入的程度,数据安全性几乎与纯AOF模式相当。同时,AOF部分的命令日志也保留了良好的可读性,便于人工审阅或调试。

这个混合了两种格式的AOF文件,其可读性变得复杂了。文件开头是紧凑的二进制RDB数据,对于人类来说是不可读的;只有后面追加的AOF部分才是文本命令。另外,该特性依赖于AOF重写机制来触发,因此它并不能改变AOF文件本身会随时间增长,以及重写过程可能带来额外资源消耗的特性。

注意:当Redis上同时存在AOF文件和RDB快照的时候,以AOF机制为主!

2. 事务

2.1 认识Redis的事务

原子性方面,Redis 事务的原子性定义与数据库不同。其核心是"批量执行"的原子性:通过 EXEC 命令,队列中的所有操作会被作为一个不可分割的单元连续执行,执行期间不会被其他客户端的命令打断。然而,它不具备操作失败时的回滚能力。如果事务中的某个命令在运行时出错(例如对错误数据类型进行操作),该命令会失败,但已执行成功的命令不会被撤销,后续命令也会继续执行。Redis 的设计哲学认为,命令执行错误属于编程错误,应在开发阶段解决,因此未引入复杂的回滚机制。

一致性方面,Redis 事务不提供数据库意义上的"一致性"保障。数据库的一致性通常指事务必须使数据库从一个一致状态转变到另一个一致状态,并遵循所有预定义的完整性约束。而 Redis 没有内置的数据完整性约束机制(如外键、唯一性约束等)。因此,事务本身无法保证操作的业务逻辑一致性,它仅仅是一个命令执行框架。一致性需要由应用层的业务逻辑来确保。

隔离性方面,Redis 的事务模型有其独特的实现方式。由于 Redis 使用单线程处理命令,在任何时刻都只会执行一个命令。在事务的 EXEC 阶段,队列中的所有命令会被连续、不被中断地执行,这自然实现了最高级别的"串行化"隔离。然而,需要注意的是,在事务命令从入队到执行(MULTI 到 EXEC)的等待期间,其他客户端的命令是可以执行的,这可能会导致数据在事务执行前被修改。为此,Redis 提供了 WATCH 命令作为一种乐观锁,用于检测并处理这种并发冲突。

持久性方面,事务的持久性与 Redis 自身的持久化配置完全无关。事务执行成功后,数据修改仅保存在内存中。是否将数据写入磁盘(持久化)取决于 Redis 服务器的 RDB 或 AOF 配置,与事务机制本身无关。如果 Redis 未配置持久化或数据未及时落盘,发生故障时事务结果仍然会丢失。

注意:Reids的事务主要的意义就是为了"打包",从而避免其他客户端的命令插队到中间。

Redis中实现事务是为每个客户端引入了一个队列,当开启事务的时候,此时客户端输入的命令就会发送给服务器并且进入这个队列。当遇到了"执行事务"命令(EXEC)的时候,此时就会将队列中的任务按照顺序依次执行(在redis主线程中完成 主线程会将事务中的操作执行完再去处理别的客户端)。

2.2 事务操作

WATCH 命令

作用:监视一个或者多个键,用于实现乐观锁机制。它允许在执行事务前检测被监视的键是否被其他客户端修改,确保事务执行期间数据的原子性和一致性。

语法:WATCH key [key ...]

返回值:始终返回 OK,表示监视已设置。

重要特性:

如果在 EXEC 命令执行前,任何一个被 WATCH 监视的键被其他客户端修改,则当前客户端的事务执行会失败,EXEC 返回 nil。

当 EXEC 或 DISCARD 命令执行后,所有之前设置的 WATCH 效果会自动清除,无需显式调用 UNWATCH。

如果客户端连接断开,所有 WATCH 也会自动清除。

WATCH 机制是实现 Redis 乐观锁的核心,相比传统数据库的悲观锁,性能更高且不会造成阻塞。

MULTI 命令

作用:标记事务块的开始。在此命令之后的所有命令都会按顺序进入事务队列,但不会立即执行,直到遇到 EXEC 命令。

语法:MULTI

返回值:返回 OK。

重要特性:

MULTI 开启事务后,客户端进入事务模式,后续命令返回 QUEUED 表示已加入队列。

事务模式会一直持续,直到遇到 EXEC、DISCARD 或客户端断开连接。

在事务模式下,除了与事务相关的命令(EXEC、DISCARD、WATCH、UNWATCH),其他所有命令都会被排队。

错误处理:如果在 MULTI 后输入的命令有语法错误(如参数错误),整个事务在 EXEC 时会失败。

EXEC 命令

作用:执行事务队列中的所有命令。如果事务执行成功,返回包含所有命令执行结果的数组;如果事务被 WATCH 机制中断或已丢弃,则返回特殊值。

语法:EXEC

返回值:

成功时:返回一个数组,包含事务队列中每个命令的执行结果,按命令入队顺序排列。

失败时:如果事务因为 WATCH 的键被修改而失败,返回 nil。

如果事务因语法错误而被丢弃,返回错误信息。

重要特性:

EXEC 执行后,无论成功还是失败,都会自动清除所有 WATCH 的键。

事务中的命令按顺序执行,执行过程中不会被其他客户端的命令打断。

事务不提供回滚机制:即使某个命令执行失败,后续命令仍会继续执行。

执行事务后,Redis 的连接会从事务模式返回到正常模式。

DISCARD 命令

作用:取消当前事务,清空事务队列中的所有命令,并将连接从事务模式返回到正常模式。

语法:DISCARD

返回值:始终返回 OK。

重要特性:

执行 DISCARD 后,所有在 MULTI 之后入队的命令都会被丢弃,不会执行。

与 EXEC 相同,DISCARD 也会自动清除所有通过 WATCH 设置的监视。

如果在没有开启事务的情况下调用 DISCARD,会返回错误。

使用 DISCARD 可以安全地中止一个不再需要的事务,释放相关资源。

UNWATCH 命令

作用:手动取消对所有键的监视。通常在不执行事务但想清除 WATCH 状态时使用。

语法:UNWATCH

返回值:始终返回 OK。

重要特性:

清除所有先前通过 WATCH 命令设置的键监视。

在执行 EXEC 或 DISCARD 后,监视会自动清除,因此通常不需要显式调用 UNWATCH。

在以下情况下可能需要显式调用 UNWATCH:

在 MULTI 之前决定不执行事务

需要监视一组新的键

客户端希望释放与监视相关的资源

即使没有设置任何 WATCH,调用 UNWATCH 也是安全的,不会产生错误。

3. 主从复制

3.1 为什么要引入分布式系统------单点问题

单点问题指的是系统中存在一个单一的关键组件,一旦这个组件发生故障,就会导致整个系统不可用或数据丢失。在 Redis 的上下文中,当我们仅部署一个独立的 Redis 服务器(即单实例)来支撑整个应用时,这个 Redis 实例就成了一个典型的单点故障。

单点问题的具体表现

1.服务完全中断(可用性问题)

如果这台唯一的 Redis 服务器所在的物理机宕机、网络中断、Redis 进程崩溃,所有依赖 Redis 的应用程序将立刻无法连接。这会导致缓存击穿(请求直接压向后端数据库)、会话丢失(用户被迫登出)、消息队列堆积等一系列连锁反应,使核心业务功能停摆。

2.数据永久丢失的风险

虽然 Redis 提供了 RDB 和 AOF 两种持久化机制,但它们都存在风险窗口。RDB 是定期快照,在两次快照之间宕机会丢失期间的所有数据更新。AOF 虽然更安全,但如果配置为每秒同步,仍然可能丢失最后一秒的数据。如果服务器磁盘本身损坏,且没有异地备份,所有数据都可能无法恢复。

3.性能瓶颈与可扩展性限制

单台服务器的硬件资源(CPU、内存、网络I/O、磁盘I/O)存在上限。随着业务增长,数据量和并发请求量不断攀升,单实例 Redis 迟早会遇到性能天花板。此时,无论是读压力还是写压力,都无法通过简单增加从节点来分担,垂直升级硬件(纵向扩展)不仅成本高昂,而且存在极限。

在分布式系统中,往往希望有多个服务器来部署redis服务从而构成一个redis集群,此时就可以让这个集群给整个分布式系统中其他的服务提供更稳定、更高效的数据存储功能。

3.2 主从模式

主从复制采用一主多从的星型拓扑结构。一个Redis实例作为主节点,负责处理所有写操作;一个或多个实例作为从节点,通过复制机制同步主节点的数据,并主要承担读请求的处理。

优势

由于主从节点的数据时刻和主节点保持一致,因此其他的客户端从从节点中读取到的数据是和主机点读取到的数据是一致的,因此可以将读数据的请求进行均匀分配给每个节点从而来缓解读取数据的压力。

如果是从节点挂彩,对于整体来说可能只是分配到各个节点的查询请求量变多了一些,并不会有太大的影响;如果是主节点挂彩,服务器就只能进行读数据但不能进行写入数据。

3.3 部署主从节点

通过单云服务器来演示主从结构(在单云服务器上运行多个redis-server进程)

如何启动多个redis-server进程?

通过让不同的redis-server进程绑定不同的端口号就可以了。主要是两种绑定端口的方式:1.可以在启动程序的时候,通过命令来指定端口号,即--port选项 2.可以直接在配置文件中设定端口

首先创建一个文件夹来存储从节点redis服务器的配置文件,然后修改配置文件中的port设置和daemonize为yes

主从节点配置的方式

1.在配置文件中加入slaveof{mastHost}{masterPort}随Redis启动生效(推荐 永久生效)

2.在redis-server启动命令时加入--slaveof{masterHost}{masterHost}生效

3.直接使用redis命令:slaveof{masterHost}{masterHost}生效

注意:更改完毕后redis.conf文件后需要重启redis服务器才能生效
关于redis-server的终止

如果是使用redis-server命令的方式启动,需要使用kill -9 + pid来终止

如果是service redis-server start方式启动,需要使用service redis-server stop进行停止,如果是kill -9 + pid来终止则会导致服务重新启动

服务器需要确保服务的稳定性和高可用性。为避免因意外故障导致进程终止而需人工介入重启,我们部署了守护进程来持续监控主服务进程。一旦检测到服务异常退出,守护进程将立即自动重启该服务,从而保障系统的持续可用性。

配置好从节点的redis.conf文件后启动对应的从节点

在这台机器上,Redis 集群由三个实例组成,分别运行在本地端口 6379、6380 和 6381。其中,6379 实例作为主节点,负责处理所有写操作并向从节点同步数据。6380 和 6381 实例作为从节点,通过 TCP 连接主动与主节点建立同步通道,分别使用本地临时端口与 6379 保持数据一致性。整体上,6379 是主节点,而 6380 和 6381 是从节点,三者通过本地 TCP 连接维持主从复制关系,确保数据在集群中同步更新。

在客户端使用info replication命令可以查询主从节点的描述

offset字段是表示从节点同步数据的进度,因为主节点会收到源源不断的写入请求,从节点需要从主节点进行数据同步,这个同步的过程是需要时间的,offset就需要记录同步的进度。

lag字段表示主从节点之间的延时

master_repild是Redis 复制系统的核心标识符,它是一个40个字符的十六进制字符串,唯一表示一个复制流。

主节点的replication信息

从节点的replication信息

slaveof no one命令(redis客户端输入)

核心功能在于将一个从节点(slave/replica)转变为独立的主节点。当执行此命令时,Redis 会立即中断与当前主节点的复制连接,停止接收任何数据同步,但同时会完整保留已经同步的所有数据。此后,该节点将不再受原主节点的控制,转而成为一个可以独立处理读写请求的主节点,其角色标识(role)会从 "slave" 变为 "master",并生成一个全新的复制流标识(master_replid)以标志新的数据流起点。

注意:使用slaveof命令只是临时修改,重启redis服务器后仍然为原主从结构

3.4 特点

安全性

对于数据比较重要的节点,主节点会通过设置requirepass参数进行密码验证,这时所有的客户端访问时必须使用auth命令实行校验。从节点与主节点的复制连接通过一个特殊标识的客户端来完成,因此需要配置从节点的masterauth参数与主节点密码保持一致,这样从节点才可以正确地连接到主节点并发起复制流程

只读

默认情况下,从节点使用lave-read-only=yes配置为只读模式。由于复制只能从主节点到从节点,对于从节点的任何修改主节点都无法感知,修改从节点还会造成主从数据不一致的情况。因此不建议修改从节点的只读模式

传输延迟

因为主从节点一般都是部署在不同的服务器上,因此延迟是一个问题。Redis提供repl-disable-tcp-nodelay参数用于控制是否关闭TCP_NODELAY。(TCP内部有一个nagle算法------通过合并小数据包来减少网络拥塞,但会增加延迟)

当关闭时,主节点产生的命令数据无论大小都会及时的传输给从节点,这样主从延时会变小但这会增加网络带宽的消耗。适合于主从网络环境良好的场景。

当开启时,主节点会合并较小的TCP数据包然后一起发送从而减少带宽资源消耗,但是这会增大主从节点之间的延迟。适合于主从节点网络环境复杂情况。

3.5 主从复制的拓扑结构

一主一从结构

如果写数据的请求太多可能会对主节点造成一些压力,可以通过关闭主节点的AOF,只在从节点上开启AOF。但是这种设计方式有一个缺陷------当主节点一旦挂彩,不可以让其自动重启,因为主节点中没有最新的AOF文件,最新的AOF文件在从节点中,一旦进行主从同步就会将从节点数据删除。因此当主节点挂了后,主节点需要从从节点中获取AOF文件再启动。

一主多从结构

Redis的一主多从星形架构通过读写分离机制有效提升了系统的读扩展能力,这对于读密集型应用场景具有显著价值。从节点分担了读请求压力,特别是可以将耗时的分析型查询(如KEYS、大范围SCAN)导向专用的从节点,从而避免其影响主节点和其他从节点的稳定性,保障了核心服务的响应速度。

缺点:这种架构在写入压力较大时存在明显瓶颈。主节点需要将每个写命令同步到所有从节点,网络开销与从节点数量成正比。当从节点过多或网络延迟较大时,主节点的负载会显著加重,甚至可能影响其处理正常请求的能力。同时,所有从节点的数据均为异步复制,存在秒级的数据延迟,这对一致性要求极高的业务是一个挑战。

树形结构

树形主从结构(分层复制)通过构建数据同步的中间层,为大规模Redis部署提供了重要优化路径。

核心优势在于显著降低了顶层主节点的直接负载和网络出口压力。当需要挂载数十甚至上百个从节点时,如果全部采用星形连接,主节点的网络带宽和CPU资源将完全被复制流所占据。树形结构将同步流量分散到各层级的"中间主节点"(如您提到的B节点),使得顶层主节点只需同步给少数几个下游节点,从而保护了其处理正常请求的能力。同时,这种结构在跨地域部署时尤其有效,例如在北京的主节点同步给上海的核心从节点,上海节点再同步给华东地区的各业务从节点,可以大幅减少昂贵的跨地域带宽占用和网络延迟。

这种结构也引入了不可忽视的缺点。最显著的问题是数据延迟的级联放大。由于每一层复制都是异步的,底层从节点(如D、E)的数据会比中间节点(B)有延迟,而中间节点的数据又比顶层主节点(A)有延迟,这导致链路末端的从节点数据陈旧度最高,可能无法满足对数据实时性要求严格的业务。其次,架构的复杂度与故障风险同步提升。中间层节点(B、C)兼具主从角色,一旦它们发生故障,其下层的整个子树都将中断数据同步,影响面更广,故障排查和恢复也更复杂。此外,运维管理的成本也更高,需要维护更复杂的监控体系来跟踪各层级的同步状态和延迟。

3.6 主从复制的整体流程

(1)保存主节点信息 开始配置主从同步关系之后,从接待你只保存主节点的地址信息,此时建立复制流程还没有开始

(2)从节点内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与主节点建立基于TCP的网络连接。如果从节点无法建立连接,定时任务会无限重试直到连接成功或者用户停止主从复制。

(3)发送ping命令 连接建立后需要通过发送PING命令来确定应用层上工作良好

(4)权限验证 如果主节点设置了requirepass参数,则需要密码验证。从节点需要配置masterauth参数来设置密码。

(5)同步数据集 对于首次建立复制的场景,主节点会把当前持有的所有数据全部发送给从节点,这步操作基本是耗时最长的。同步方式分为全量同步和部分同步。(后文介绍)

(6)命令持续复制 当从节点复制主节点的所欲数据之后,针对之后的修改命令,主节点会持续把命令发送给从节点从而让从节点执行修改命令来确保主从节点数据的统一性。

3.7 数据同步psync

Redis提供psync命令,完成数据同步的过程,无需我们手动执行psync,redis服务器会在建立好主从同步关系之后自动执行psync。

语法格式:PSYNC replicationid offset

replicationid/replid(复制id)

主节点的复制id。主节点重新启动或者从节点晋升为主节点都会自动生成一个replicationid(同一个节点每次重启时候生成的replicationid也会变化)。

从节点在和主节点建立连接后会获取主节点的replicationid。(info replication可以查看到replicationid)

可能触发master_replid1值复制到master_replid2的场景

在网络正常时,节点B作为从节点,会记录主节点A的master_replid作为自身的复制来源标识。一旦网络发生严重抖动,B在超时后误判A已失效,便会主动晋升为主节点。此时,B会为自己生成一个全新的master_replid作为其新的主节点身份,同时将之前记录的A的master_replid妥善保存至master_replid2字段中。这一设计使得后续若网络恢复,B能够凭借master_replid2中存储的历史信息,识别并重新关联到原先的主节点A,为可能的重新同步或拓扑修复保留关键依据。反之,若网络持续中断,B则依靠其崭新的master_replid独立运作,处理后续写入,自成一体,从而在分裂期间维持局部服务的可用性。

offset

主节点和从节点都会维护偏移量,主节点会收到很多的修改操作的命令,每个命令都会占据几个字节,主节点会把这些修改命令的字节数数进行累加(统计结果显示在master_repl_offset)。从节点的偏移量就描述了现在从节点中的数据同步到哪里了,从节点会每秒钟上报自身的复制偏移量给主节点,同时主节点也会保存从节点的偏移量。从节点在接收到主节点发送的命令后会累加记录自身的偏移量(统计结果在slave_repl_offset)。

replicationid和offset共同描述了一个"数据集合",如果发现两个机器的replicationid和offset都一样,那么就可以认为两个redis机器上存储的数据就是完全一致的。

psync运行流程

psync可以从主节点获取全量数据,也可以获取部分数据

主要就是看offset的值。如果offset是-1,代表获取全量数据;如果offset为正整数,则是从当前偏移量位置来获取。

获取全量数据是最稳妥的方式,但这会比较低效。如果从节点之前已经从主节点获取过部分数据,我们只需把主节点新添加的数据获取即可。但是注意,不是从节点索要哪一部分数据,主节点就一定会给予哪部分,主节点会自行定夺,查看当前是否方便给予那一部分数据。

(1)从节点发送psync命令给主节点,replid和offset的默认值为?和-1

(2)主节点根据psync参数和自身数据情况决定相应结果

如果回复+FULLRESYNC relid offset,则从节点需要进行全量复制流程

如果回复+CONTINUE,从节点进行部分复制流程。

如果回复-ERR,说明Redis主节点版本过低,不支持psync命令。从节点可以使用sync命令进行阻塞全量复制。

3.8 主从节点之间的复制策略

3.8.1 全量复制

全量复制是Redis最早支持的复制方式,也是主从第一次建立复制时必须经历的阶段。

(1)从节点发送psync命令给主节点进行数据同步,由于是第一次进行复制,从节点没有主节点的运行ID和复制偏移量,所以发送psync ? -1。

(2)主节点根据命令,解析出要进行全量复制,回复+FULLRESYNC响应。

(3)从节点接收主节点的运行信息进行保存。

(4)主节点执行bgsave命令进行RDB文件持久化。(使用RDB文件是因为其为二进制格式,更加节省空间)为了保证RDB文件是最新的,必须要重新生成最新的RDB文件。

(5)主节点发送RDB文件给从节点,从节点保存RDB文件到本地硬盘上。

(6)主节点将从生成RDB到接收完成期间执行的写命令,写入缓冲区,等从节点保存完RDB文件后,主节点再将缓冲区的数据补发给从节点,补发的数据仍然按照RDB的二进制格式追加写入到收到的RDB文件中,从而保持主从一致性。(因为主节点生成RDB文件和传输RDB文件比较耗时,这期间可能会收到很多的新操作,这些数据也必须要同步到从节点。)

(7)从节点清空自身原有的旧数据

(8)从节点加载RDB文件得到与主节点一致的数据

(9)如果从节点加载RDB完成之后并且开启了AOF持久化功能,它会进行bgrewrite操作,得到新的AOF文件。

无磁盘模式完成全量复制

在(4)中,主节点生成的RDB中的二进制数据不再保存到文件中,而是直接进行网络传输,这省略了一系列读硬盘和写硬盘的开销。

注意:及时引入了无硬盘模式,整个操作仍然是比较耗时的,网络传输开销较大。

关于replid(replicationid 命令info replication)和runld(run_id 命令info server)

Replid是Redis复制子系统的核心身份标识,主要用于在主从架构或集群模式中管理数据同步关系。每个Redis主节点都拥有一个全局唯一的Replid,它是一个由40个字符组成的十六进制字符串。这个标识符的核心作用在于为整个数据流建立一个可追溯的"血缘关系"。当从节点连接到主节点时,它会忠实记录主节点的Replid;在后续的同步过程中,从节点会凭借这个Replid来向主节点证明自己的合法复制身份,并请求进行增量同步。更为精妙的是,Redis还设计了Replid2字段,用于记录前一个主节点的Replid。这个设计如同一个"历史护照",当发生主从切换(例如故障转移)后,新的主节点会在生成自己新Replid的同时,将旧主的Replid存入Replid2。这使得那些原本属于旧集群的从节点,在重新连接到新主时,可以通过验证其持有的旧Replid是否与新主的Replid2匹配,从而快速完成身份认证并触发高效的部分重同步,避免了全量复制的资源消耗。

Run_id则是每个Redis实例在本次运行周期内的独立标识符。它是一个随机生成的160位(40字符)十六进制字符串,其生命周期与Redis服务器的单次运行进程绑定。每次Redis服务重启,都会生成一个全新的Run_id。它的主要作用范围集中在对等发现和避免脑裂后的重复操作等场景。例如,在Redis Sentinel或Redis Cluster的节点间通信中,Run_id用于唯一标识网络中的每一个实例,便于进行健康检查、消息路由和领导者选举。此外,在客户端缓存等高级功能中,Run_id的变化也会被用来通知客户端服务器的实例身份已改变,可能需要清理或重建本地缓存状态。

3.8.2 部分复制

部分复制主要是Redis针对全量复制的过高开销做出的一种优化措施。当从节点正在复制主节点时,如果出现网络抖动或者命令丢失等异常清空时,从节点会像主节点要求补发丢失的命令数据,如果主节点的复制积压缓冲区中存在此份数据就可以直接发送给从节点,以此打到保持主从节点复制一致性的目的。因为补发的数据一般远远小于全量数据,因此开销很小。

(1)当主节点之间出现网络抖动等情况时,如果超过repl-timeout时间,主节点会认为从节点故障并中断复制连接。

(2)主从连接中断期间主节点依然响应命令,但这些复制命令都因网络中断无法及时的同步到从节点,所以主节点会暂时将这些命令滞留在积压缓冲区中。

(3)当主节点和从节点之间的网络回复后,从从节点会再次连接上主节点。

(4)从节点将之前保存的replicationId和复制偏移量作为psync的参数发送给主节点,请求进行部分复制。(如果replicationId和主节点的replicationId不一样,那就需要全量复制。offset来告知主节点同步的进度,主节点会查看进度是否在积压缓冲区内。如果在积压缓冲区内,此时可以直接进行部分复制;如果超出积压缓冲区,那么就只能进行全量复制。)

(5)主节点接收到psync请求后,进行必要的验证。随后根据offset去复制积压缓冲区查找和是的数据并响应+CONTINUE给从节点。

(6)主节点将需要从节点同步的数据发送给从节点。

关于复制积压缓冲区

复制积压缓冲区是保存在主节点上的一个固定长度的队列,默认大小1MB。当主节点有连接的从节点时被创建,这时主节点响应写命令时,不但会把命令发送给从节点还会将其写入复制积压缓冲区中。

由于缓冲区本质上是先进先出的定长队列,所以可以实现保存最近已复制数据的功能,用于部分复制和复制命令丢失的数据补救。复制积压缓冲区的相关信息可以通过主节点的info replication查看。

根据统计指标,可以算出复制积压缓冲区内的可用偏移量范围:

repl_backlog_first_byte_offset,repl_backlog_first_byte_offset + repl_back_histlen

3.8.3 实时复制

主节点在建立复制连接后,主节点会把自己接收到的修改操作,通过tcp长连接的方式,源源不断的传输给从节点,从节点会根据这些请求同时修正自身的数据从而保持主从节点的数据一致性。

在主从节点复制中,心跳包机制用于确保连接处于可用状态并监控数据同步进度。具体如下:主节点(Master)行为:默认每隔 10 秒向从节点发送一次 ping 命令。从节点收到 ping 后立即返回 pong。通过这种方式,主节点能够检测到从节点是否在线和可用。

从节点(Slave/Replica)行为:默认每隔 1 秒向主节点发送一次特定请求,汇报自己的复制进度。报告内容包括当前从节点复制的数据偏移量(offset),以便主节点了解同步进度。这种上报机制确保主节点随时掌握从节点数据同步状态。

注意:如果主节点发现从节点通信延迟超过repl-timeout配置的值,则会判定从节点下线,从而断开复制客户端连接。从节点恢复连接后,心跳机制继续执行。

总结:心跳包机制本质上是一种双向的定期通信,主节点通过 ping-pong 确认从节点在线,从节点通过定期上报 offset 来反馈同步进度,从而保证主从复制的可靠性和实时性。

3.9 总结

主从复制的缺点

(1)从节点多了,复制数据的时候延时非常明显

(2)主节点挂彩,从节点不会自动晋升为主节点,只能通过人工干预的方式恢复。

关于redis主节点无法重启的问题

因为从接待你是通过手动启动的方式运行的,此时root用户下启动redis服务器,生成的aof文件也是root级别的权限,redis级别的用户是没办法对该aof文件进行操作的,这就会导致无法超过启动主节点。主从三个节点均是使用同一个aof文件这很明显是错误的,因此我们需要配置从节点的redis.conf文件来让其区分开来。

操作:

1.停止redis服务器

2.删除之前工作目录下已经生成的aof文件或者通过chown命令修改所属的用户

3.给从节点指定新的目录来充当从节点的工作目录。

4.主从模式 + 哨兵模式

4.1 背景

Redis的主从复制模式下,一旦主节点由于故障不能提供服务,需要人工进行主从切换,同时大量客户端需要被通知切换到新的主节点上,对于上了一定规模的应用来说,这种方案是无法接收的,于是Redis从2.8开始提供Redis Sentinel来解决这个问题。

4.2 关于哨兵模式

哨兵模式通过监控主从节点,实现自动故障检测、故障转移和配置更新,当主节点发生故障时,能够自动将一个从节点升级为主节点,确保服务不间断运行。

4.3 哨兵自动恢复主节点流程

当主节点出现故障的时候,Redis Sentinel能够自动完成故障的发现和故障转移,并且通知应用方,从而实现高可用。

Redis Sentinel是一个分布式架构,其中包含了若干的Sentinel节点和Redis数据节点,每个Sentinel节点会对数据节点和其余的Sentinel节点进行监控,当发现节点不可达时,会对节点做下线处理。如果是主节点不可达,Sentinel们内部会进行"协商",当大多数Sentinel节点确认主节点不可达后才会从从节点中选出一个节点来完成自动故障转移的工作,同时会将变化情况返回给Redis应用方。

问题:为什么哨兵节点需要有多个?

如果哨兵节点只有一个,那么会有以下的两种问题。一方面万一这个单独的哨兵节点如果出现问题,后续redis的节点出现问题后就无法进行自动恢复;另一方面,单独的烧饼接待你出现误判节点下线的概率较高,比较网络传输是容易出现抖动或者延迟、丢包等问题。因此在分布式系统应该避免使用"单点"。

4.4 部署

使用docker搭建环境

(1)创建三个容器作为redis的数据节点,先启动。

(2)创建三个容器作为redis的哨兵节点,后启动。

其实是可用将六个容器同时启动,可能是哨兵先启动完成也可能是数据节点先启动完成,如果是哨兵节点先启动完成就会误认为数据节点下线影响观察执行日志。当然可用编写yml文件指明启动顺序,只不过会更麻烦一些。手动先启动数据节点然后启动哨兵节点是最简单的操作方式。

演示机型为Debian13

1.安装docker和docker-compose

apt install docker-compose

2.停止正在运行的redis-server服务

service redis-server stop

3.使用docker获取redis镜像

docker pull redis:5.0.9

编写redis主从节点

(1)编写docker-compose.yml文件并进行编辑

注意:docker中可用通过容器名字作为ip地址进行互相之间访问

配置文件

version: '3.7'

services:

master:

image: 'redis:5.0.9'

container_name: redis-master

restart: always

command: redis-server --appendonly yes

ports:

  • 6379:6379(宿主机器port:虚拟机器port)

slave1:

image: 'redis:5.0.9'

container_name: redis-slave1

restart: always

command: redis-server --appendonly yes --slaveof redis-master 6379

ports:

  • 6380:6379

slave2:

image: 'redis:5.0.9'

container_name: redis-slave2

restart: always

command: redis-server --appendonly yes --slaveof redis-master 6379

ports:

  • 6381:6379

(2)启动所有容器

docker-compose up -d

如果配置有误需要重新操作可用使用docker-compose down命令停止并删除创建好的容器

(3)查看运行日志

(4)验证

连接主节点使用info replication命令查看结果

从节点1

从节点2

编写redis-sentinel节点

为了方便编写docker-compose.yml文件,我们将数据节点和哨兵节点的docker-compose.yml分开编写然后按照先启动数据节点然后启动哨兵节点的顺序启动。

(1)编写docker-compose.yml

注意:每个目录中只能存在一个docker-compose.yml文件

配置文件

version: '3.7'

services:

sentinel1:

image: 'redis:5.0.9'

container_name: redis-sentinel-1

restart: always

command: redis-sentinel /etc/redis/sentinel.conf

volumes:

  • ./sentinel1.conf:/etc/redis/sentinel.conf

ports:

  • 26379:26379

sentinel2:

image: 'redis:5.0.9'

container_name: redis-sentinel-2

restart: always

command: redis-sentinel /etc/redis/sentinel.conf

volumes:

  • ./sentinel2.conf:/etc/redis/sentinel.conf

ports:

  • 26380:26379

sentinel3:

image: 'redis:5.0.9'

container_name: redis-sentinel-3

restart: always

command: redis-sentinel /etc/redis/sentinel.conf

volumes:

  • ./sentinel3.conf:/etc/redis/sentinel.conf

ports:

  • 26381:26379

networks:

default:

external:

name: redis-data_default

(2)创建哨兵节点的配置文件

哨兵节点会在运行过程中对配置文件进行修改,因此三个容器必须映射到不同的配置文件。

创建sentinel1.conf、sentinel2.conf、sentinel3conf,三份文件的内容是完全相同的都会放到当前的redis-sentinel目录当中

bind 0.0.0.0

port 26379

sentinel monitor redis-master redis-master 6379 2

sentinel down-after-milliseconds redis-master 1000

配置文件中个字段的意义

bind 0.0.0.0 指定sentinel监听的网络接口,0.0.0.0允许所有网络接口(IP地址)连接

port 26379 指定sentinel服务断开,客户端连接sentinel时使用的端口

sentinel monitor redis-master redis-master 6379 2

格式:sentinel monitor <master-name> <ip> <port> <quorum>

redis-master(第一个)代表主节点的逻辑名称,可以自定义,sentinel通过次名称识别监控主节点

redis-master(第二个)主节点的主机名或ip地址,此处docker将容器名称自动解析成ip地址。

6379 主节点的端口号

2 法定票数 故障判断所需的最少sentinel同意数

sentinel down-after-milliseconds redis-master 1000

主节点失联判定时间

格式:sentinel down-after-milliseconds <master-name> <milliseconds>

Sentinel向主节点发送PING命令,如果在1000毫秒内没有收到主节点回复,sentinel会判断主节点下线。

哨兵节点启动之后会对sentinel.conf文件进行修改

4.5 哨兵节点功能演示

哨兵节点存在的意义是能够在redis主从结构出现问题的时候,哨兵节点自动的帮助我们选择一个新的主节点出来代替之前的主节点从而保证redis的可用状态。

Sdown 主观下线:本哨兵节点认为该主节点下线

Odwn 客观下线:经过全部哨兵节点的投票达到法定票数认为哨兵节点下线

新主节点

新从节点

手动恢复原主节点

我们发现其上线后变为从节点

哨兵重新选取主节点的流程

1.主观下线

某一个哨兵节点通过心跳包判定redis服务器是否正常工作,如果心跳包没有在规定的时间内接收到,该哨兵节点就会认为redis服务器下线。

注意:此时还不能排除是否是因为网络波动的原因导致该哨兵节点认为该redis服务器下线

2.主观下线

多个哨兵都认为主节点下线(认为主节点下线的哨兵节点达到法定票数),哨兵才会认为该主节点客观下线。

3.要让多个哨兵节点从未下线的数据节点中选取一个leader节点来充当新的主节点。

哨兵投票示例

每个哨兵都需要对所有的哨兵节点中的某一个哨兵节点投票(一般都是最先被投的就是最后的leader节点)

4.此时leader选举完毕,leader会挑选一个从节点作为新的主节点。

选取依据:

(1)优先级 每个redis数据节点都会在配置文件中有一个优先级的设定,高优先级会被选择

(2)offset 每个redis数据节点都要offset字段。offset的数值越大说明该redis数据和原主节点一致性更高

(3)run id 每个redis节点启动的时候会随机生成一串数字 选择更小run id的redis数据节点

新的节点指定好后,leader就会控制这个节点执行slave no one,使其称为master节点然后再控制其他节点执行slave of,使它们称为新节点的从节点。

5. 集群模式

5.1 集群

集群是指将多台独立的计算机通过高速网络连接起来,并借助特定的软件系统进行协同工作,使得这些计算机能够像一台单一、强大的计算资源一样被统一管理和使用。这种技术架构的核心目标在于通过资源的整合与任务的并行处理,显著提升整体的计算能力、存储可靠性以及服务的可用性。

Redis的集群就是通过多组Master/Slave,每一组Master/Slave存储数据全集的一部分从而构成一个更大的整体,称为Redis集群(Cluster)。

加入整个数据全集是1TB,引入三组Master/Slave来存储,那么每一组机器只需要存储整个数据全集的三分之一即可。

每个红框部分都可以称为一个分片,每一个分片掌管着一份数据,分片内部采用哨兵模式+主从节点的方式进行存储和监控。

5.2 数据分片算法

1.哈希求余

设有N个分片,使用[0,N-1]这样的序号进行编号。针对某个戈丁的key,先计算hash值再把结果进行取余N,得到的结果即为分片编号。

可用使用MD5算法计算hash值

MD5特点:计算结果是定长的、计算结果是分散的,对于仅有些许差别的字符串计算出的结果差异很大、计算结果不可逆

如果后续随这业务量的增加导致需要增加更多的分片来缓解压力,那么就需要对大量key进行重新映射。

优点:简单高效、数据分配均匀

缺点:一旦需要进行扩容,N改变了,原有的数据映射规则被破坏,就需要让节点直接按的数据相互传输,重新排列来满足新的映射规则。此时需要搬运的数据量比较多、开销较大

例如我们需要将原本3个分片进行扩容成4个分片

如上图所示,整个扩容一共21个key,只有3个key没有经过搬运,其他的key都是搬运过来的。

(2)一致性哈希算法

在哈希求余算法中,key属于哪一个分片是交替的,而一致性哈希算法则是将key属于哪一个分片变为连续的,这能减少搬运的的数据量。

Key映射到分片序号的过程不再是简单求余,具体过程如下:

第一步 将0->2^32-1这个数据空间映射到一个圆环上,数据按照顺时针方向增长

第二步 假设当前存在三个分片,就把分片放到圆环的某个位置上

第三步 假定有一个key,通过计算得到hash值为H,从H所在位置顺时针向下找,找到第一个分片,即为该key的所属分片。

这相当于N个分片的位置把整个圆环分成了N个管辖期间。Key的hash值落在某个区间内就归对应区间管理。

在这个情况下,如果扩容一个分片,如何处理呢?

原有分片在环上的位置不动,主要在环上新安排一个分片位置即可。

此时著需要将0号分片上的部分数据搬运给3号分片即可。1号和2号分片管理的区间是不变的。

优点:大大降低了扩容时数据搬运的规模,提高了扩容操作的效率

缺点:数据倾斜

(3)哈希槽分区算法

为了解决上述搬运成本高和数据分配不均的问题,Redis cluster引入了哈希槽(hash slots)算法。

hash_slot = crc16(key) % 16384

其中crc16是一种hash算法,16384是2^14

相当于把整个哈希值映射到16384个槽位上,然后把这些槽位比较均匀的分配给每一个分片,每个分片的节点都需要记录自己持有哪一些分片。

假设当前有三个分片,可能的分配方式为:

0号分片:[0,5461],共5462个槽位

1号分片:[5462,10923],共5462个槽位

2号分片:[10924,16383],共5460个槽位

注意:每个分片持有的槽位号可以是连续的也可以是不连续的,每个分片会使用"位图"这样的数据结构表示出当前有多少槽位号。对于16384个槽位需要2048个字节也就是2KB空间进行表示。

如果需要扩容,比如新增一个3号分片,就可以针对原有的槽位进行重新分配。

0号分片:[0,4095],共4096个槽位

1号分片:[5462,9557],共4096个槽位

2号分片:[10924,15018],共4096个槽位

3号分片:[4096,5461] + [9558,10923] + [15019,16383],共4096个槽位

在我们实际使用Redis集群分片的时候,无需手动指定哪些槽位分配给某个分片,只需要告诉某个分片应该持有多少槽位即可,Redis会自动完成后续的槽位分配工作以及对应的key搬运工作。

问题1:Redis集群是最多有16384个分片吗?

并非如此。如果一个分片对应一个槽位,这对于集群的数据均匀是很难保证的,并且集群的可用性也会大打折扣。

实际上Redis文档中建议集群的分片数目不应该超过1000个
问题2:为什么选择16384作为槽位的数目

节点之间通过心跳包进行通信。心跳包中包含了该节点持有哪些slots。slots使用位图之类的数据结构进行表示,16384个slots需要的位图大小为2KB。如果slots的数量更多,那么就需要消耗更多的空间。因为心跳包是需要持续发送的,因此如果slots过大会造成更多的网络开销。另一方面,Redis集群一般不建议超过1000个分片,16k对于最大1000个分片来说是足够使用的,同时位图的体积也是可以接受的。

5.3 集群搭建

注意:集群搭建之前需要停止之前redis的服务,防止端口冲突等问题。

拓扑结构如下:

我们创建11个redis,其中前9个用来演示集群搭建,后两个演示集群扩容。

第一步创建目录和配置

创建redis-cluster目录,在内部创建两个文件

redis-cluster/

├── docker-compose.yml

└── generate.sh

generate.sh内容如下:

cpp 复制代码
for port in $(seq 1 9); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.10${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done
# 注意 cluster-announce-ip 的值有变化. 
for port in $(seq 10 11); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done

执行命令bash generate.sh

生成的目录如下

redis-cluster/

├── docker-compose.yml

├── generate.sh

├── redis1

│ └── redis.conf

├── redis10

│ └── redis.conf

├── redis11

│ └── redis.conf

├── redis2

│ └── redis.conf

├── redis3

│ └── redis.conf

├── redis4

│ └── redis.conf

├── redis5

│ └── redis.conf

├── redis6

│ └── redis.conf

├── redis7

│ └── redis.conf

├── redis8

│ └── redis.conf

└── redis9

└── redis.conf

每个redis.conf都不相同,以redis1为例。(区别在于每个配置中配置的cluster-announce-ip不同)

port 6379

bind 0.0.0.0

protected-mode no

appendonly yes

cluster-enabled yes

cluster-config-file nodes.conf

cluster-node-timeout 5000

cluster-announce-ip 172.30.0.101

cluster-announce-port 6379

cluster-announce-bus-port 16379

cluster-enabled yes 开启集群.

cluster-config-file nodes.conf 集群节点生成的配置.

cluster-node-timeout 5000 节点失联的超时时间.

cluster-announce-ip 172.30.0.101 节点自身ip.

cluster-announce-port 6379 节点自身的业务端口.

cluster-announce-bus-port 16379 节点自身的总线端口.. 集群管理的信息交互是通过这个端口进⾏的.

第二步编写docker-compose.yml

先创建network并为其分配网段为172.30.0.0/24

配置每个节点。注意配置文件映射、端口映射以及容器的ip地址。设定程固定ip方便后续的观察和操作

cpp 复制代码
ersion: '3.7'

networks:
  mynet:
    ipam:
      config:
        - subnet: 172.30.0.0/24
services:
  redis1:
    image: 'redis:5.0.9'
    container_name: redis1
    restart: always
    volumes:
      - ./redis1/:/etc/redis/
    ports:
      - 6371:6379
      - 16371:16379
    command: redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.101
  redis2:
    image: 'redis:5.0.9'
    container_name: redis2
    restart: always
    volumes:
      - ./redis2/:/etc/redis/
    ports:
      - 6372:6379
      - 16372:16379
    command: redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.102
  redis3:
    image: 'redis:5.0.9'
    container_name: redis3
    restart: always
    volumes:
      - ./redis3/:/etc/redis/
    ports:
      - 6373:6379
      - 16373:16379
    command: redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.103
  redis4:
    image: 'redis:5.0.9'
    container_name: redis4
    restart: always
    volumes:
      - ./redis4/:/etc/redis/
    ports:
      - 6374:6379
      - 16374:16379
    command: redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.104
  redis5:
    image: 'redis:5.0.9'
    container_name: redis5
    restart: always
    volumes:
      - ./redis5/:/etc/redis/
    ports:
      - 6375:6379
      - 16375:16379
    command: redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.105
  redis6:
    image: 'redis:5.0.9'
    container_name: redis6
    restart: always
    volumes:
      - ./redis6/:/etc/redis/
    ports:
      - 6376:6379
      - 16376:16379
    command: redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.106
  redis7:
    image: 'redis:5.0.9'
    container_name: redis7
    restart: always
    volumes:
      - ./redis7/:/etc/redis/
    ports:
      - 6377:6379
      - 16377:16379
    command: redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.107
  redis8:
    image: 'redis:5.0.9'
    container_name: redis8
    restart: always
    volumes:
      - ./redis8/:/etc/redis/
    ports:
      - 6378:6379
      - 16378:16379
    command: redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.108
  redis9:
    image: 'redis:5.0.9'
    container_name: redis9
    restart: always
    volumes:
      - ./redis9/:/etc/redis/
    ports:
      - 6379:6379
      - 16379:16379
    command: redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.109
  redis10:
    image: 'redis:5.0.9'
    container_name: redis10
    restart: always
    volumes:
      - ./redis10/:/etc/redis/
    ports:
      - 6380:6379
      - 16380:16379
    command: redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.110
  redis11:
    image: 'redis:5.0.9'
    container_name: redis11
    restart: always
    volumes:
      - ./redis11/:/etc/redis/
    ports:
      - 6381:6379
      - 16381:16379
    command: redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.111

第三步 启动容器

docker-compose up -d

第四步 构建集群

redis-cli --cluster create 172.30.0.101:6379 172.30.0.102:6379 172.30.0.103:6379 172.30.0.104:6379 172.30.0.105:6379 172.30.0.106:6379 172.30.0.107:6379 172.30.0.108:6379 172.30.0.109:6379 172.30.0.110:6379 172.30.0.111:6379 --cluster-replicas 2

其中create表示创建集群,cluster-replicas 2描述集群的每个节点都有2个从节点。这个配置设置后redis就知道这九个节点被分成三个分片。Redis在构建集群的时候,哪个是主节点哪个是从节点是随机的。

配置结果

操作数据

键 k1 被哈希到第 12706 号槽位,而该槽位由 IP 为 172.30.0.103、端口为 6379 的节点负责管理。因此,最初连接的节点,无法处理此请求,必须将命令重定向到正确的节点。

通过在启动redis-cli时加上-c选项,客户端就能够发现当前key的操作不在当前分片上时能够自动重定向到对于的分片主机

主节点宕机

手动停止一个master节点,观察效果

之前101是主节点,105和106是从节点。当101节点下线后,106变成新的主节点代替了原先主节点101的位置。

再手动启动redis1节点观察效果

注意:此处的故障转移和哨兵中的故障转移机制不同

5.4 处理流程

1.故障判定

集群中的所有节点都会周期性的使用心跳包进行通信

1)节点A给节点B发送ping包,节点B就会给节点A返回一个pong包。Ping和pong除了message type属性之外,其他部分都是一样的。这里包含了集群的配置信息(该节点的id、该节点属于哪一个分片、是主节点还是从节点、从属于哪个节点、持有哪些slots位图)

2)每个节点每秒钟都会随机给一些随机的节点发起ping包,而不是向其余的全部节点发送一遍。这样是为了避免在节点很多的时候,心跳包的数量过多。

3)当节点A给节点B发起ping包,节点B不能如期回应的时候,此时A就会尝试重置和节点B的tcp连接,看是否能够重新连接成功,如果仍然连接失败,A就会把B设置为PFAIL状态(类似于主观下线)。

4)节点A判定节点B为PFAIL状态后,会通过redis内置的Gossip协议和其他节点进行沟通,向其他节点确认B的状态。(每个节点都会维护一个自己的"下线列表")

5)此时节点A发现很多其他节点认为B为PFAIL状态,并且数目超过总集群个数的一半,那么节点A就会把节点B标记为FAIL(类似于客观下线),并且会将这个消息同步给其他的节点,其他节点接收到后会同步B的FaIL状态。

某个或者某些节点宕机有时候会引起整个集群宕机

某个分片的所有主从节点都宕机

某个分片的主节点宕机但是该分片没有从节点

集群中超过半数的主节点宕机

2.故障迁移

在上述的例子中,B出现故障,并且节点A将节点B的消息告知集群中的其他节点

如果B是主节点,那么就会由B的从节点触发故障迁移;如果B是从节点,那么就不需要进行故障迁移。

故障迁移就是将从节点提拔成主节点,继续给整个redis集群提供支持。

具体流程如下

1)从节点判定自己是否具有参选资格。如果从节点和主节点已经太久没有通信(此时认为从节点数据和主节点的数据差异太大了),时间超过阈值,从而失去竞选资格。

2)具有资格的节点,会先休眠一定的时间。休眠时间 = 500ms基础时间 + [0,500ms]随机时间 + 排名 * 1000ms。Offset的值越大,排名越小。

3)其中某一个从节点的休眠时间到了就会给其他所有集群中的节点进行拉票操作,但只有集群中的主节点可以投票

4)主节点会把自己的票投给该从节点,当该从节点收到的票数超过主节点数目一半时,该从节点就会晋升为主节点。

5)同时,该从节点会将自身晋升为主节点的消息同步给其他集群的节点,从而方便其他节点更新自己保存的集群结构信息。

5.5 集群扩容

背景:随这业务的发展,现有集群可能无法容纳日益增长的数据。此时给集群中加入更多新的机器就可以使存储空间更大了。

第一步 把新的主节点加入到集群中

我们需要将redis10和redis11加入到集群中,此处我们将redis10作为主机,redis11作为从机。

sudo redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379

add-node 后的第一组地址是新节点的地址,第二组地址是集群中任意节点地址(用来指明目标集群)

使用cluster nodes命令查看节点情况

第二步 重新分配slots

把之前三组master上面的slots分配一些给新的master

redis-cli --cluster reshared 172.30.0.101.:6379

执行之后,会进入交互式操作,redis会提示用户输入以下内容:

多少个slots需要被添加到新的逐渐点

哪个节点来接收这些slots(新主节点的ID)

这些slots从那些节点搬运过来

之后就会进行集群的key搬运工作。这个过程涉及到数据搬运,可能需要消耗一定的时间。

在搬运key的过程中,对于那些不需要搬运的key,访问的时候是没有任何问题的,但是对于需要搬运的key,进行访问可能会出现短暂的访问错误。随这搬运完成后错误自然就会恢复。

问题:在搬运key的过程中,客户端是否可以访问redis集群呢?

当集群进行数据迁移时,例如将某个哈希槽从一个节点迁移到另一个节点,Redis 集群会确保迁移过程对客户端的影响最小化。具体来说,迁移是渐进式的,对于正在迁移的槽位,集群会同时跟踪源节点和目标节点的状态。在此期间,如果客户端请求访问的键恰好属于正在迁移的槽位,接收到请求的节点(无论是源节点还是目标节点)会首先检查该键是否已经迁移。如果键已存在于目标节点,节点会向客户端返回一个 ASK 重定向错误(类似于 MOVED,但仅针对本次迁移中的临时重定向),引导客户端转向正确的节点;如果键尚未迁移,则仍由源节点处理请求。这种机制确保了在迁移过程中,每个键始终可以被访问到,不会出现数据不可用的情况。因此,Redis 集群支持在不停机的情况下进行水平扩展、节点维护或负载重新平衡,实现了高可用性和持续服务能力。

第三步 给新的子节点添加从节点

主节点添加成功后,我们还需要给该主节点添加从节点来确保集群的可用性。

redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id [172.30.0.110节点的nodeid]

6. Redis的典型应用------缓存

6.1 背景

因为mysql等关系型数据库的效率比较低,所以承担的并发量比较有限,一旦请求数量多了,数据库的压力就会很大,严重的话就会引起宕机。

服务器每次处理一个请求,都会消耗一些硬件资源,任意一种资源的消耗超出了机器能提供的上限,机器就容易出问题。

如何能提高mysql承担的并发量?

1.开源 引入更多的机器,构成数据库集群

2.引入缓存 把一些频繁读取的热点数据保存到缓存,在后续查询数据的时候先到缓存中查找,当查找失败的时候才会去mysql数据库中查询,减少mysql的查询次数。

6.2 缓存的更新策略

1.定期生成

每个一定的时间,对于访问的数据进行频次统计,。挑选出访问频次最高的前N%的数据。

可以编写一套离线的流程(脚本代码)完成定时任务触发

1)完成统计热词的过程

2)根据热词找到搜索结果的数据

3)把得到的缓存数据同步到缓存服务器中

4)控制这些缓存服务器自动重启

优点:实现比较简单,过程更加可控

缺点:实时性不足。出现一些爆发性时间,有一些本来不是热词的内容成了热词,新的热词可能会给数据库带来较大压力。

2.实时生成

献给缓存设定容量上限(redis.conf文件中的maxmemory参数设定)

对于用户的每次查询,如果在Redis中查询到了就直接返回;如果在Redis中不存在就从数据库中查询并将结果写入Redis中。但是如果缓存已满(达到上限)就会触发缓存淘汰策略,将一些"相对不热门"的数据淘汰掉。

数据淘汰策略

FIFO(First In First Out) 先进先出

将缓存中存在时间最久的数据淘汰掉

LRU(Least Recently Used) 淘汰最久未使用

记录每个key的最近访问时间,将距离最近访问时间最久远的key淘汰

LFU(Least Frequently Used) 淘汰访问次数最少的

记录每个key每个一段时间的访问次数,把访问次数最少的淘汰掉

Random 随即淘汰

从所有的key中随机抽取一个淘汰掉

Redis内置的淘汰策略如下:

volatile-lru:当内存不足时,从设置了过期时间的键中,淘汰最久未被使用的键。

allkeys-lru:当内存不足时,从所有键中,淘汰最久未被使用的键。这是最常用的缓存策略之一。

volatile-lfu(4.0+):当内存不足时,从设置了过期时间的键中,淘汰访问频率最低的键。

allkeys-lfu(4.0+):当内存不足时,从所有键中,淘汰访问频率最低的键。

volatile-random:当内存不足时,从设置了过期时间的键中,随机淘汰一个键。

allkeys-random:当内存不足时,从所有键中,随机淘汰一个键。

volatile-ttl:当内存不足时,从设置了过期时间的键中,淘汰剩余生存时间(TTL)最短的键(即最先过期的键)。其行为类似于局限于过期键范围的 FIFO 策略。

noeviction(默认策略):当内存不足时,拒绝执行所有可能导致内存增加的新写入命令(如 SET, LPUSH 等),并返回错误。读命令和删除命令可正常执行。此策略确保数据不会丢失,但要求应用层具备相应的错误处理机制。

6.3 缓存使用的注意事项

缓存预热(Cache Preheating)

使用Redis作为MySQL缓存的时候,当Redis刚刚启动或者Redis大批ky失效之后,此时由于Redis自身相当于是空着的,没有什么缓存数据,那么MySQL就可能直接被访问到,从而造成较大的压力,因此就需要提前把热点数据准备好,直接写入Redis中,使Redis尽快的为MySQL撑起保护伞。

热点数据可以基于之前的统计结果生成。这份热点数据不一定非得十分"精准",只要能帮助MySQL抵挡大部分请求即可。随着程序运行的退役,缓存的热点数据会逐渐自动调整来使用当前的情况。

缓存穿透(Cache Penetration)

解决方案

1)如果发现目标key在Redis和MySQL上均不存在,仍然写入Redis中,但是value设置成非法值,避免频繁访问数据库。

2)引入布隆过滤器,每次查询Redis/MySQL之前都先判定以下key在布隆过滤器上是否存在。

缓存雪崩(Cache Avalanche)

短时间内大量的key在缓存上失效,导致缓存命中率陡降并且数据库的压力剧增,甚至直接宕机。

产生原因

  1. Redis服务下线
  2. Redis上大量的key同时过期

为什么会出现大量的key同时过期?

可能是短时间内在Redis上缓存大量的key并且设置了相同的过期时间

解决方案

  1. 部署高可用的Redis集群并且完善监控报警体系
  2. 不给key设置过期时间或者设置过期时间的时候添加随机时间因子

缓存击穿(Cache Breakdown)

大量热点key突然过期当值大量的请求直接访问到数据库上,甚至引起数据库宕机。

解决方案:

1)基于统计的方式发现热点key并且设置永不过期

2)进行必要的服务降级。例如访问数据库的时候使用分布式锁,限制同时请求数据库的并发数量。

7. 关于分布式锁

7.1 什么是分布式锁?

在一个分布式的系统中也会涉及到多个节点访问同一个公共资源的情况。此时就需要通过锁来做互斥控制,避免出现类似于"线程安全"的问题。

之前学习过C++的std::mutex只是在进程中生效,在分布式这种多个进程多个主机的场景下就无能为力了。

所谓分布式锁,也就是一个或一组单独的服务器程序给其他的服务器提供类似于"加锁"的服务。Redis是一种典型的可以用来实现分布式锁的方案。

7.2 分布式锁的基础实现

例如:买票场景,现在车站提供了若干车次,每个车次的票数都是固定的。

现在存在多个服务器节点,都可能需要处理这个买票逻辑:先查询指定车次的余票,如果余票大于零,则让余票数量减一。

显然上述的场景是存在"线程安全"的问题,需要加锁来进行控制,防止超卖的情况发生。

我们引入一个Redis作为分布式锁的管理器。如果买票服务器需要尝试买票,就需要先访问Redis,在Redis中设置一个键值对。比如key为车次,那么value随便设置一个值即可(比如1)。如果这个操作设置成功,就视为当前没有节点对该001车次加锁,那么就可以进行数据库的读写操作,操作完成后只需要将Redis上刚才的这个键值对删除掉。如果在某一个买票服务器操作数据库时,另一个买票服务器也要买票,就会尝试给Redis上写一个键值对,key同样是车次,但此时设置的时候发现该车次的key已经存在,则认为已经有其他服务器正在持有锁,此时该服务器就需要等待或者暂时放弃。

使用setnx确实可以得到"加锁"的效果(使用del"解锁")。但如果某个服务器加锁成功但在后续执行过程中因为异常导致没有执行解锁(例如服务器掉电等等),这样就会导致redis上设置的key无人删除,最终导致其他服务器无法获取锁了。

7.3 解决方法

1.引入过期时间

设置key的同时引入过期时间。即这个锁最多持有多久就应该被释放。

注意:可以使用set ex nx的方式,在设置锁的时候将过期时间设置进去。不能先setnx然后expire。在Redis中,多个命令之间无法保证原子性,会存在一个执行成功另一个失败的情况。

2.引入校验id

对于Redis中写入的加锁键值对是可以被其他节点删除的。

试想这样的一种情况

服务器内部是多线程,同一个服务器两个线程都在执行上述的解锁操作,那么着两个线程都会DEL同一个key两次。

上述情况重复DEL两次key后续问题不大,但实际上却不是这样。如果在此期间另一个服务器执行了对该key的加锁就会出现问题,因为先DEL的线程会将锁进行释放,此时另一个服务器对该key加锁会成功,紧接着另外一个DEL的线程又会将该key进行删除,这样就将之前另一个服务器加的锁进行了解锁操作。

上述的问题都是源于GET和DEL两条命令之间是非原子的。为了使结果操作原子,可以使用Reids的Lua脚本的功能。

3.Lua是一个动态弱类型语言。Lua的解释器一般使用C语言实现。Lua语法简单精炼、执行速度极快,解释器也比较轻量。

使用Lua脚本完成解锁功能

if redis.call('get',KEYS[1]) == ARGV[1] then

return redis.call('del',KEYS[1])

else

return 0

end;

上述代码可以编写成一个.lua后缀的文件,由redis-cli或者redis-plus-plus等客户端加载并发送个Redis服务器执行。

注意:一个lua脚本会被Redis服务器以原子的方式来执行

4.引入看门狗(Watch Dog)

上述的方案仍然存在一个重要问题。当我们设置的key过期时间之后,可能会存在任务还没有执行完但key却过期,导致锁提前失效。

看门狗机制通过后台守护线程定期检查锁是否仍被持有,并自动延长锁的过期时间,直到业务完成主动释放锁。

例子:

初始情况下设置过期时间为10s。同时设定看门狗线程每个3s检测一次

当3s时间到达时候,看门狗线程(属于申请锁的服务器)就会判定当前任务是否完成

如果任务完成,则直接通过lua脚本的方式原子性地释放锁;如果任务未完成,则把过期时间重写设置为10s。

这样就无需担心锁提前失效的问题了。如果服务器挂了,看门狗线程就随之挂了,此时无人进行延时,那么key自然就可以迅速过期,其他的服务器就又可以获得该锁了。

5.引入Redlock算法

背景:服务器1向master节点进行加锁操作。这个写入key的过程刚刚完成,master节点就挂了,slave节点升级成了新的master节点,但是哟与刚才写入的这个key尚未来得及同步给slave导致服务器1的加锁操作实际上是不成功的,其他的服务器仍然可以对该key加锁。

解决方法

我们引入一组Redis节点。其中每一组Redis节点都包含一个主节点和若干从接待你。并且组和组之间存储的数据是一致的,相互之间是"备份"关系(而非数据集合的一部分)。

加锁的时候,按照一定的顺序,写多个master节点。在写锁的时候需要设定超时时间,超过超时时间视为加锁失败。

只有当加锁成功的节点数超过总结点数的一半时才视该节点加锁成功。这样及时某些节点挂了也不会影响锁的正确性。

相关推荐
jiunian_cn2 小时前
【Redis】数据库管理操作
数据库·redis·缓存
惊讶的猫2 小时前
Redis 哨兵(Sentinel)介绍
redis·redis哨兵
猫头虎2 小时前
基于信创openEuler系统安装部署OpenTeleDB开源数据库的实战教程
数据库·redis·sql·mysql·开源·nosql·database
静听山水3 小时前
Redis核心数据结构-ZSet
数据结构·redis
Dontla3 小时前
黑马大模型RAG与Agent智能体实战教程LangChain提示词——6、提示词工程(提示词优化、few-shot、金融文本信息抽取案例、金融文本匹配案例)
redis·金融·langchain
難釋懷3 小时前
秒杀优化-基于阻塞队列实现秒杀优化
redis·缓存
静听山水3 小时前
Redis核心数据结构-Set
数据结构·数据库·redis
无尽的沉默4 小时前
Redis下载安装
数据库·redis·缓存
曾经的三心草4 小时前
redis-9-集群
java·redis·mybatis