命令支持

哪些 Redis 命令会跨数据中心复制,哪些命令仅在本地执行且不会离开数据中心,以及哪些命令会因错误而被拒绝。

命令规则遵循模块代际,而不是复制模式

在 Redis 7.2 上,同一复制模块同时为灾难恢复(peerof)和 Active-Active(mesh)提供服务,并在两种模式下以完全相同的方式对每个命令进行分类。模式改变的是哪些成员接受应用写入,因此也决定了下文所述冲突解决行为实际发生的频率。

Redis 6.0 运行旧版模块,该模块仅支持灾难恢复,复制的命令集较小,并且不执行任何防护措施。请参阅Redis 6.0 — 旧版模块。

按模式汇总

灾难恢复(peerof)Active-Active(mesh)
Redis 版本6.0、7.27.2
因错误而被拒绝(Redis 7.2)始终拒绝 MOVE、SWAPDB、RESTORE;任一对等成员连接时拒绝 FLUSHALL/FLUSHDB相同
仅在本地执行且不会复制在 Redis 7.2 上相同 — 请参阅仅在本地执行且不会复制相同
复制的写入命令在 Redis 7.2 上相同;在 Redis 6.0 上命令集较小与 Redis 7.2 灾难恢复相同
实际的冲突解决很少发生 — 只有上游接受应用写入,因此通常不会产生并发冲突写入持续发生 — 每个成员都接受写入,因此复制的写入命令中的每条规则都会生效
两种模式执行相同的拒绝规则

这些拒绝规则由模块的命令过滤器执行;在 Redis 7.2 上启用跨数据中心复制时,该过滤器始终会安装。因此,即使灾难恢复实例只有一个写入方,MOVE、SWAPDB 或 RESTORE 在灾难恢复实例上的执行也会像在 Active-Active 成员上一样失败。依赖这些命令的应用必须在实例加入复制组之前完成修改。

因错误而被拒绝

这些命令在 Redis 7.2 上完全不会执行。客户端会收到错误,并且不会在本地或复制端写入任何内容。

命令被拒绝的时机原因
MOVE、SWAPDB启用复制后始终被拒绝在逻辑数据库之间移动键,或交换两个完整的数据库,没有任何对等成员可以重现的逐键排序。并发写入会在每个成员上以不同方式交错,导致数据集永久分歧,并且没有任何机制可以修复这种分歧。
RESTORE(键级别)启用复制后始终被拒绝序列化值携带自身嵌入的写入时间戳。复制该值会使该时间戳与承载它的帧的时间戳发生冲突,从而让过时的负载在对等成员上胜过更新的数据;仅在本地执行则会使执行该命令的成员悄然产生分歧。RESTORE-ASKING 是另一个命令,由 Redis Cluster 重新分片在内部使用,不会被拒绝。
FLUSHALL、FLUSHDB实例连接了任一对等成员时——包括已配置上游或已连接下游清空操作仅在本地执行,永远不会被复制。在活动复制组中执行清空操作会使该成员与其余成员产生分歧,并丢弃它为先前删除的键保留的墓碑,因此来自对等成员的延迟写入随后可能会重新创建一个其他成员都已删除的键。

错误分别为:

ERR move is not supported in active-active — its effect cannot converge across peers; Redis Enterprise refuses it the same way
ERR flushall is refused while this node has peers attached (1 upstream, 0 downstream) — FLUSH is local-only and must not run inside a live active-active mesh; detach all peers first, then flush the isolated node

要清空复制组,请移除连接(或移除 ActiveRedisMesh 资源),在每个已隔离的实例上执行清空操作,然后重新创建它们。或者,重新创建这些实例。

FUNCTION RESTORE 不在此列表中——它受支持。只有键级别的 RESTORE 会被拒绝。

在本地执行,永不复制

这些命令的运行方式与未修改的 Redis 完全相同。它们不会报错,也不会被拒绝——只是不会产生跨数据中心的影响,因此每个数据中心中与之相关的状态都是独立的。

命令说明
所有 stream 命令——XADD、XDEL、XTRIM、XSETID,以及每个消费者组操作(XGROUP、XREADGROUP、XACK、XCLAIM、XAUTOCLAIM)每个数据中心的 stream 都是自包含的。消费者组可以在数据中心内部完整运行,但不会在数据中心之间收敛,因为待处理条目和消费者所有权状态位于节点本地。如果需要跨数据中心传递,请使用消息队列组件。
PUBLISH、SPUBLISH 和 SUBSCRIBE 系列Pub/sub 是临时的,并且按连接存在。如果需要跨数据中心传递,请使用消息队列组件。
MIGRATE、RESTORE-ASKINGRedis Cluster 的重新分片命令对。重新分片是按数据中心执行的拓扑操作;复制该操作会导致每个对等数据中心永久丢失已迁移的键。
WAIT、WAITAOF、CONFIG、DEBUG、CLIENT、ACL,以及服务器管理命令不是写入命令。
MULTI、EXEC、DISCARD、WATCH不会保留事务边界。事务中的每个命令都会单独复制——请参阅无跨数据中心原子性。
作用于 stream 键的键生命周期命令会被隔离

由于对等成员中同名的键是彼此无关的本地 stream,当通用键命令作用于 stream 键时,这些命令不会跨数据中心:

  • 为 stream 键设置 TTL 会被拒绝——复制的过期操作会删除对等成员中无关的本地 stream。
  • 对 stream 键执行 PERSIST 会在本地执行,但不会复制。
  • 涉及 stream 键的 DEL、RENAME 和 COPY 不会复制。

复制的写入命令

此处列出的所有命令都会复制到复制组中的每个其他成员。冲突行为列描述了两个成员同时写入同一个键时如何进行协调。在灾难恢复模式下,只有上游成员接收应用写入,因此这些规则适用,但很少会被触发;在 Active-Active 模式下,这些规则会持续被触发。

全文使用以下五种冲突规则:

规则行为
最后写入者获胜时间戳较高的写入获胜;如果时间戳相同,则由较高的 serviceID 获胜。Hashes 和 sorted sets 会针对每个字段和每个成员分别应用此规则,而不是针对整个键应用。此规则依赖于时钟同步——请参阅时钟同步。
累加计数器(PN-Counter)分别跟踪每个成员的贡献,值是所有成员贡献的总和,因此来自不同数据中心的并发递增都会计入。
添加获胜集合(观测-删除集合)删除操作只会取消它已看到的添加操作,因此,在另一个成员上并发执行的添加操作不会因该删除而消失。
元素级列表每个列表元素都有自己的标识,因此不同数据中心中的并发列表更改会收敛为一个一致的列表。最终顺序是确定性的,但不一定是插入顺序。
TTL 寄存器键的过期时间与其值分别进行解析:因果上较晚的更改获胜,即使它缩短了 TTL;两个真正并发的更改则解析为较长的截止时间。PERSIST 表示无限截止时间,因此会胜过任何并发的有限 TTL。

Strings

命令冲突行为说明
SET最后写入者获胜条件式 SET(NX/XX)会在执行写入的成员上进行评估;如果执行成功,对等成员会收到无条件的 SET,如果未执行,则不会收到任何内容。
SETNX最后写入者获胜设置键时,会以无条件的 SET 形式复制。
SETEX、PSETEX最后写入者获胜相对 TTL 会在复制前转换为绝对截止时间,因此对等成员不会根据自身时钟重新计算。
SETRANGE、APPEND、GETSET最后写入者获胜复制的是最终的绝对值,而不是修改操作。
GETDEL最后写入者获胜读取操作在本地执行;删除操作会被复制。
GETEX最后写入者获胜仅复制 TTL 更改。
INCR、DECR、INCRBY、DECRBY累加计数器不同数据中心中的并发递增会进行求和,而不是相互覆盖。键的 TTL 会保留。
INCRBYFLOAT累加计数器以完整的浮点精度进行合并。
MSET、MSETNX按键采用最后写入者获胜拆分为独立的按键写入——会进行复制,但在数据中心之间不具备原子性。
COPY最后写入者获胜复制为对目标键的重建。

Hashes

命令冲突行为说明
HSET、HMSET按字段采用最后写入者获胜两个数据中心向同一个 hash 写入不同字段时,双方的写入都会保留。
HSETNX按字段采用最后写入者获胜设置字段时,会以 HSET 的形式复制。
HDEL添加获胜集合在另一个成员上并发写入的字段会在删除操作后保留。
HINCRBY、HINCRBYFLOAT按字段采用累加计数器同一字段的并发递增会进行求和。

Sets

命令冲突行为说明
SADD添加获胜集合
SREM添加获胜集合同一成员并发且尚未被观察到的 SADD 会保留。
SMOVE添加获胜集合拆分为源端的删除操作和目标端的添加操作。
SPOP最后写入者获胜复制为实际弹出的成员的删除操作。在数据中心之间具有至少一次语义——请参阅至少一次弹出。
SINTERSTORE、SUNIONSTORE、SDIFFSTORE在目标端采用最后写入者获胜结果在本地计算,目标键会在对等成员上重建。

Sorted sets

命令冲突行为说明
ZADD按成员采用最后写入者获胜带标志的 ZADD(NX/XX/GT/LT/INCR)会在本地进行评估,并且只会复制分数实际发生变化的成员及其最终的确切分数。
ZINCRBY累加计数器在对等成员上作为相对递增量应用,同时保留由 ZADD 设置的基础分数。
ZREM添加获胜集合
ZREMRANGEBYSCORE、ZREMRANGEBYRANK、ZREMRANGEBYLEX最后写入者获胜
ZPOPMIN、ZPOPMAX、BZPOPMIN、BZPOPMAX、ZMPOP、BZMPOP最后写入者获胜复制为实际弹出的成员的删除操作。在数据中心之间具有至少一次语义。阻塞变体在接收成员上以非阻塞方式运行。
ZUNIONSTORE、ZINTERSTORE、ZDIFFSTORE、ZRANGESTORE在目标端采用最后写入者获胜目标键会在对等成员上重建。

Lists

命令冲突行为说明
LPUSH、RPUSH、LPUSHX、RPUSHX元素级列表
LINSERT元素级列表
LSET元素级列表该单个元素的值采用最后写入者获胜。
LTRIM、LREM元素级列表
LPOP、RPOP、BLPOP、BRPOP、LMPOP、BLMPOP元素级列表在数据中心之间具有至少一次语义。阻塞变体在接收成员上以非阻塞方式运行。
RPOPLPUSH、BRPOPLPUSH、LMOVE、BLMOVE元素级列表复制为源端的删除操作加上目标端的插入操作。
不要跨数据中心将列表用作恰好一次队列

弹出操作具有至少一次语义:在两个数据中心同步之前,同一个元素可能会在这两个数据中心中分别被弹出。请从单个数据中心消费,或使消费者具备幂等性。

Keyspace

命令冲突行为说明
DEL、UNLINK最后写入者获胜带有较新时间戳的并发写入会覆盖删除操作。UNLINK 会同步运行,而不是在后台运行。
EXPIRE、EXPIREAT、PEXPIRE、PEXPIREATTTL 寄存器在复制之前转换为绝对截止时间。因果上较晚的更改会获胜,即使它缩短了 TTL;两个并发更改会解析为更长的截止时间。NX/XX/GT/LT 会在写入成员上进行评估,未满足的条件不会复制任何内容。
PERSISTTTL 寄存器会覆盖任何并发的有限 TTL;因果上较晚的 PEXPIREAT 仍会重新设置键的过期时间。
RENAME、RENAMENX在目标端采用最后写入者获胜复制为重建目标键并删除源键,因此目标端自身的冲突解决仍能保持正确。
SORT ... STORE在目标端采用最后写入者获胜不带 STORE 的 SORT 是读取操作,不会被复制。

位图、HyperLogLog 和地理空间

命令冲突行为说明
SETBIT最后写入者获胜复制为计算得到的绝对值,因此另一个成员上并发的位设置可能会丢失。
BITFIELD最后写入者获胜复制为计算得到的绝对值。INCRBY 子命令不会被视为累加计数器。BITFIELD_RO 是读取操作。
BITOP在目标端采用最后写入者获胜
PFADD可交换HyperLogLog 寄存器只会增长,因此并发添加最终会收敛。
PFMERGE可交换结果会在对等成员上并入目标端,因此并发的 PFADD 和 PFMERGE 最终会收敛为真实并集。
GEOADD按成员采用最后写入者获胜有条件的 GEOADD(NX/XX/CH)只会复制实际发生更改的成员。
GEORADIUS ... STORE、GEORADIUSBYMEMBER ... STORE、GEOSEARCHSTORE在目标端采用最后写入者获胜不带 STORE 时,这些是读取操作,不会被复制。

脚本和函数

命令冲突行为说明
EVAL、EVALSHA、FCALL脚本内按命令处理复制的是脚本执行的各个单独写入操作,而不是脚本调用本身。每个写入操作遵循其自身命令的规则。EVAL_RO、EVALSHA_RO 和 FCALL_RO 不会写入任何内容。
SCRIPT LOAD、SCRIPT FLUSH最后写入者获胜会复制脚本注册表,因此在一个数据中心加载的脚本会在其他数据中心中可用。
FUNCTION LOAD、FUNCTION DELETE、FUNCTION FLUSH、FUNCTION RESTORE按库采用最后写入者获胜函数库注册表按库复制。FUNCTION LIST、FUNCTION DUMP 和 FUNCTION STATS 是本地读取操作。
脚本不会获得事务性复制

由于脚本执行的每个写入操作都会单独复制,因此脚本遵循与其他客户端相同的按命令处理规则。脚本不会因为是脚本而自动具备幂等性。

与独立 Redis 不同的行为

请围绕这些行为设计应用逻辑。它们是复制模型的结果,而不是缺陷。

至少一次弹出

LPOP、RPOP、SPOP、ZPOPMIN、ZPOPMAX 及其阻塞和多键变体,在单个成员上是恰好一次的,但在整个复制组中至少一次。两个成员在同步前并发弹出时,可能各自消费同一个元素。从单个成员消费,或使消费者具备幂等性。

不具备跨数据中心的原子性

MULTI/EXEC、MSET 和 MSETNX 按键或按命令复制。所有键都会到达,成员也会收敛,但对等成员不会以原子方式应用整个组——对等成员可能会短暂观察到事务的一部分。

对一个键执行不同类型的写入会静默解决

对同一个键执行不同类型的并发写入(一个数据中心中的 SET key,另一个数据中心中的 HSET key)会按时间戳解决;如果时间戳相同,则由较高的 serviceID 获胜。失败方的数据会被丢弃,不会进行合并,也不会产生错误。

已删除的数据会一直占用内存,直到每个成员都确认收到

删除操作会以墓碑的形式保留——用于记录某个键、字段或元素已被删除——这样对等成员的延迟写入就无法使已删除的数据重新出现。只有在每个活动对等成员都确认收到墓碑且其最短存留时间达到五分钟后,墓碑才会释放。因此,长时间无法访问的对等成员会使其他每个成员上的墓碑持续存在——对于永久离线的数据中心,请按照限制和风险中的说明将其退役。较大的时钟偏差会缩小这一保护范围;请参阅时钟同步。

模块的簿记键可见

模块会将部分复制簿记信息作为普通 Redis 键存储在保留的 __arcr_ 前缀下。这些键会被 DBSIZE 计数,并由 SCAN 和 KEYS 返回,直到被回收;它们会在每个成员上独立派生,因此其数量和时间在不同数据中心之间合法地存在差异。任何比较不同数据中心数据集的工具都必须排除整个 __arcr_ 前缀。应用程序不得写入该前缀。

Redis 6.0 — 旧版模块

Redis 6.0 运行冻结的旧版模块,仅支持 Disaster Recovery。其行为与 Redis 7.2 有两点在运维上值得注意的差异。

它复制的命令集合更小。 它复制的命令如下:

分组命令
键空间DEL、UNLINK、RENAME、RENAMENX、EXPIRE、EXPIREAT、PEXPIRE、PEXPIREAT、PERSIST
字符串SET、SETNX、SETEX、PSETEX、APPEND、SETRANGE、GETSET、MSET、MSETNX、INCR、DECR、INCRBY、DECRBY、INCRBYFLOAT
哈希HSET、HSETNX、HMSET、HDEL、HINCRBY、HINCRBYFLOAT
集合SADD、SREM、SMOVE、SPOP、SINTERSTORE、SUNIONSTORE、SDIFFSTORE
有序集合ZADD、ZINCRBY、ZREM、ZREMRANGEBYSCORE、ZREMRANGEBYRANK、ZREMRANGEBYLEX、ZUNIONSTORE、ZINTERSTORE、ZPOPMIN、ZPOPMAX、BZPOPMIN、BZPOPMAX
列表LPUSH、RPUSH、LPUSHX、RPUSHX、LINSERT、LSET、LTRIM、LREM、LPOP、RPOP、BLPOP、BRPOP、RPOPLPUSH、BRPOPLPUSH、SORT ... STORE
位图SETBIT、BITFIELD、BITOP
HyperLogLogPFADD、PFMERGE
地理空间GEOADD、GEORADIUS ... STORE、GEORADIUSBYMEMBER ... STORE
脚本SCRIPT LOAD、SCRIPT FLUSH

Redis 6.0 之后引入的命令,以及在本地执行、从不复制下列出的命令,都不会由旧版模块复制——包括所有流命令、发布/订阅、MIGRATE 以及 MULTI/EXEC 边界。

它没有任何防护机制。 旧版模块不会拒绝任何命令。FLUSHALL、FLUSHDB、MOVE、SWAPDB 和 RESTORE 会被包装并复制到下游,而不是被拒绝。

在 Redis 6.0 上,flush 会到达下游

由于旧版模块会复制这些命令,在 Redis 6.0 上游执行的 FLUSHALL 或 FLUSHDB 也会清空每个下游实例。不会进行确认,也不会报错。在 Redis 6.0 上,应将这两个命令视为会破坏整个复制组的命令,并考虑使用 ACL 限制它们。

在 Redis 7.2 上,只要对等成员已连接,就会拒绝相同的命令——请参阅拒绝并返回错误。

若要将现有的 Redis 6.0 组迁移到新模块,请按照将 Disaster Recovery 组从 Redis 6.0 升级到 Redis 7.2中的说明操作。

相关页面