[Redis] 解决多个 Redis 服务同步删除有关联的 key

Posted 爱音乐的孩子是小白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Redis] 解决多个 Redis 服务同步删除有关联的 key相关的知识,希望对你有一定的参考价值。

以下内容基于 ChatGPT 的回答。

我有多个 redis 服务,比如有 A, B, C 三个,他们供不同的业务服务使用。有一个需求是,A 服务中删除一个key后,希望能同时删除 B, C 服务中有关联的 key ,但这个 key 名称不一样相同,关联关系是可配置的。请问有什么办法实现?

解决方案

可以使用 Redis 的发布订阅功能来实现这个需求。

具体操作步骤如下:

  • 在 A 服务中删除 key 后,向一个固定的频道(比如叫做 "delete_key")发布一条消息,消息内容为被删除的 key 名称。

  • 在 B、C 服务中订阅 "delete_key" 这个频道,并在收到消息后检查是否有关联的 key 需要被删除。关联关系可以预先定义好并存储在一个配置文件中。

需要注意的是,在 B、C 服务中删除 key 的操作最好也通过发布消息的方式进行,这样可以避免出现删除不一致的情况。

此外,如果需要更加高可靠的实现,可以考虑使用 Redis Cluster 或 Sentinel 来实现分布式的数据同步。

1. 配置关联的 key

在Redis中,可以使用Hash类型的数据结构来存储关联的key。具体操作步骤如下:

  • 创建一个Hash数据结构,用于存储与某个key相关联的其他key。例如,使用以下命令创建一个名为"related_keys"的Hash:

    HSET related_keys key1 "related_key1 related_key2"
    

    上述命令将创建一个关联key为"key1"的Hash,并将"related_key1"和"related_key2"这两个key与"key1"关联起来。

  • 当需要删除"key1"时,可以先通过以下命令获取与之关联的其他key:

    HGET related_keys key1
    

    该命令返回的结果为"related_key1 related_key2",其中包含了需要被删除的所有关联key。

  • 遍历返回结果中的所有关联key,并逐个进行删除操作。例如,可以使用以下命令删除"related_key1":

    DEL related_key1
    

需要注意的是,当存在多个Redis服务器实例时,需要将上述操作在所有实例中执行,以确保数据同步和关联key的删除成功。同时,为了提高可靠性,建议选择一种分布式锁机制来保证在多个实例中只有一个实例可以对关联key进行删除操作。

2. 订阅消息,并实现删除 key1 相关联的key

可以使用Redis的发布/订阅功能实现在Redis服务实例上订阅消息。具体操作步骤如下:

  • 在需要接收订阅消息的Redis服务实例中,执行以下命令进行订阅操作:

    SUBSCRIBE channel
    

    其中,"channel"表示要订阅的消息通道名称。当有其他Redis客户端向该通道发送消息时,该实例将接收到相应的消息。

  • 在需要删除"key1"相关联的key的Redis服务实例中,通过以下命令向指定通道发布一条消息:

    PUBLISH channel "message"
    

    其中,"channel"表示要发布的消息通道名称;"message"表示要发送的消息内容,可自定义。在本例中,可以将"message"设置为需要删除的key列表,以便接收订阅消息的实例进行相应的删除操作。

  • 在接收订阅消息的Redis服务实例中,通过编写相应的逻辑代码,在接收到订阅消息后进行相应的删除操作。具体实现方式可以参考上述回答中提到的方法,即使用Hash数据结构存储关联key,并通过分布式锁机制保证原子性操作。

需要注意的是,当存在多个Redis服务实例时,需要在所有实例中执行相应的订阅和删除操作,以确保数据同步和关联key的删除成功。同时,还需要在编写代码时考虑错误处理和异常情况的处理,以保证系统的可靠性。

3. 过 Redis 的 Lua 脚本实现

在 Redis 服务实例本身上实现订阅删除消息并删除关联 key 的功能,可以通过 Redis 的 Lua 脚本功能来实现。

以下是一个示例 Lua 脚本,它订阅 "delete" 频道,并在接收到消息后删除相关联的 key:

-- 定义回调函数
local function delete_key(channel, message)
    redis.call(\'del\', message)
end

-- 订阅 "delete" 频道并注册回调函数
redis.call(\'subscribe\', \'delete\')
redis.call(\'config\', \'set\', \'notify-keyspace-events\', \'KEA\')
redis.call(\'psubscribe\', \'__keyevent@0__:del\', \'delete\')

-- 进入循环等待消息
while true do
    local message = redis.call(\'brpop\', \'__redis__:pubsub:delete\', 0)[2]
    delete_key(nil, message)
end

在这个示例中,我们首先定义一个回调函数 delete_key,它使用 Redis 的 del 命令删除与接收到的消息相关联的 key。接下来,我们通过 subscribe 命令订阅 "delete" 频道,并通过 config 命令设置 notify-keyspace-events 选项,使 Redis 在键空间事件(Key-space Notification)发生时发送消息通知给订阅者。然后,我们通过 psubscribe 命令订阅 __keyevent@0__:del 这个键空间事件,以及 "delete" 频道。在这个示例中,我们使用了专门的频道 "redis:pubsub:delete" 来接收 "delete" 消息。最后,我们进入一个循环等待消息的过程,当接收到消息时,回调函数 delete_key 负责处理删除逻辑。

由于 Redis 是单线程模型的数据库,在执行 Lua 脚本期间,Redis 会终止所有其他客户端对服务实例的操作,因此将此脚本放置在 Redis 服务实例上可以保证数据一致性。同时,使用 Lua 脚本的好处是,它可以在服务端快速处理大量的事件,降低了客户端的负载压力。

4. 挂载 Lua 脚本

要在 Redis 服务实例上挂载 Lua 脚本,您可以使用 Redis 的 SCRIPT LOAD 命令,将脚本加载到 Redis 服务器的脚本缓存中,并返回一个 SHA1 散列值,该散列值用于以后执行该脚本。

以下是在 Redis 服务实例上加载 Lua 脚本的示例命令:

$ redis-cli
127.0.0.1:6379> SCRIPT LOAD "local function delete_key(channel, message) redis.call(\'del\', message) end; redis.call(\'subscribe\', \'delete\'); redis.call(\'config\', \'set\', \'notify-keyspace-events\', \'KEA\'); redis.call(\'psubscribe\', \'__keyevent@0__:del\', \'delete\'); while true do local message = redis.call(\'brpop\', \'__redis__:pubsub:delete\', 0)[2]; delete_key(nil, message); end"
"1c564ce8dc8d8bd5f5338f69a4cc10ab86b1b30c"

在这个示例中,我们首先使用 redis-cli 连接到 Redis 服务实例,并调用 SCRIPT LOAD 命令加载 Lua 脚本。该命令返回一个 SHA1 散列值 "1c564ce8dc8d8bd5f5338f69a4cc10ab86b1b30c",表示成功将脚本加载到 Redis 服务实例的脚本缓存中。该散列值可以在以后使用 EVALSHA 命令来执行该脚本。

在实际生产场景中,您可以将 Lua 脚本保存到文件中,然后使用 Redis 的 SCRIPT LOAD 命令动态加载脚本。或者,您可以在 Redis 服务实例启动时自动加载脚本,具体方式取决于您的部署环境和需求。

让 redis 服务启动时自动加载脚本

要让 Redis 服务在启动时自动加载 Lua 脚本,可以通过配置 Redis 配置文件实现。Redis 配置文件通常被称为 redis.conf,不同平台和版本的 Redis 可能会将其存储在不同的位置。在 Ubuntu 系统上,Redis 的配置文件通常位于 /etc/redis/redis.conf

以下是配置 Redis 启动时自动加载 Lua 脚本的步骤:

  • 打开 Redis 配置文件 在终端中使用文本编辑器(如 nano 或 vim)打开 Redis 配置文件:

    $ sudo nano /etc/redis/redis.conf
    
  • 开启脚本参数 找到配置文件中的 lua-time-limit 参数,并将其从默认值 5000 改为 -1,表示不限制 Lua 脚本的执行时间。同时,确保 lua-time-limit 参数前的注释没有被取消:

    # Lua script engine maximum execution time in milliseconds
    lua-time-limit -1
    
  • 配置加载脚本 在配置文件末尾,添加以下两行命令,其中 是您保存 Lua 脚本的路径:

    # Load Lua script on startup
    lua-load <path-to-your-script>
    
  • 保存并退出
    Ctrl + X 组合键,输入 Y 确认保存修改,然后按 Enter 键退出编辑器。

  • 重启 Redis 服务
    使用以下命令重启 Redis 服务,以使配置文件的更改生效:

    $ sudo systemctl restart redis
    

完成以上步骤后,Redis 服务在启动时将自动加载您指定的 Lua 脚本。同时,由于我们已经开启了 -1lua-time-limit 参数,在执行脚本时不受时间限制。

Redis 订阅发布功能简介

Redis 的订阅和发布功能从版本 2.0 开始就得到支持。在早期的 Redis 版本中,该功能只能用于发布和订阅普通字符串,而在后续版本中,Redis 引入了更多的数据类型,例如列表、哈希表和集合,这些数据类型也可以作为订阅和发布的消息。

在 Redis 中,可以使用 SUBSCRIBE 命令订阅指定的频道,同时使用 PUBLISH 命令向一个或多个频道发布消息。当有新消息发布到订阅的频道时,Redis 将自动将其发送给所有已订阅该频道的客户端。

除了 SUBSCRIBEPUBLISH 命令外,Redis 还提供了许多其他与频道和消息有关的命令,例如 UNSUBSCRIBEPSUBSCRIBEPUNSUBSCRIBE 等。通过使用这些命令,您可以更方便地管理订阅和发布的消息。

现在,Redis 已经发展成为一款功能强大的内存数据库,并且广泛应用于各种场景,包括高速缓存、队列服务、实时统计和分析等。在许多互联网公司中,Redis 已经成为构建高可用、高性能应用程序的标准之一。

Redis系列--主从同步

Redis为了解决单点故障带来的数据安全问题,和提高读数据操作的高并发性,实现了一主多从的主从同步机制,即部署一个master节点负责读写操作及向slave节点同步数据,部署多个slave节点负责读操作及接收来自master节点的同步操作命令。

主从同步配置:

主从同步机制的配置还是比较简单的:在slave节点的redis.conf配置文件中配置slaveof masterip masterport选项或者直接使用slaveof  masterip masterport命令(在Redis 5.0版本后,使用replicaof命令),我们还可以配置slave节点的只读性:slave-read-only yes。

需要注意的是在主从同步模式下,即便master节点宕机了,slave节点也不会变为master节点进行写操作必须配合哨兵机制或者集群模式来实现高可用,主从同步只是高可用的基础。


主从同步的优点:
  • 以多个数据副本冗余的方式,实现数据的热备份。

  • 避免了单点故障的数据安全问题。

  • 读写分离,负载均衡,提高性能及并发性。

  • slave节点只归属一个master节点,数据单向流动。


主从同步策略:
  • 全量同步:也叫快照同步,一般发生在slave节点初始化阶段,或者slave节点与master节点连接断开,重新连接后,有可能需要把master节点的全部数据复制一份。

    Redis系列--主从同步

    • slave向master发送sync命令。

    • master执行bgsave命令生成rdb文件,并将后续接收到的命令写缓存区。

    • bgsave执行完毕,master将rdb文件发送给slave,slave载入rdb文件。

    • master将缓存区的命令发送给slave执行。

我们可以看出 量同 步的开销很大,master生成rdb文件、向slave发送rdb文件、slave载入rdb文件都会消耗大量的资源,因此,除非不得已或者必要,不要执行sync命令。

  • 命令传播:在完成全量同步后,slave节点就和master节点的状态保持一致了,但是当master节点接收了写命令,状态变为不一致,master节点需要将导致状态不一致的命令发送给slave节点去执行,将状态恢复成一致。

  • 增量同步:在2.8版本之前,slave节点与master节点断线重连后,需要进行全量同步,这种做法是非常低效的,为了解决这个问题,Redis 2.8版本实现了psync命令

        psync命令有两种模式:

    • 完整增量同步(full resynchronization):过程和全量同步基本一致,主要用来完成slave节点的初次数据同步。

    • 部分增量同步(partial resynchronization)主要用在slave与master断线重连后,master将断线期间的写命令发送给slave节点执行,保持状态一致。


增量同步的实现:

  • 组成部分:

    • 复制偏移量offset:master和slave上分别维护一个复制偏移量,通过比对偏移量就可以知道master和slave状态是否一致。

    • 复制缓冲区:由master维护的一个固定长度的FIFO队列,存放写命令,因为长度固定,所以只能存放最近的命令,后入队的命令会将先入队的命令弹出队列。

    • 实例ID:master和slave都有一个运行ID。

  • 实现细节:

    • master进行命令传播时,除了将写命令发送slave之外,还将写命令放入复制缓冲区。

    • master每执行一次写命令,就将自己的offest+N,N为写命令占用的字节数,复制缓冲区会为队列上的每个字节记录相应偏移量。

    • slave每次接收一条写命令,也会将自己的offset+N,N为写命令的字节数。如果master和slave上的offset一致,说明两者状态是一致的,反之则不一致。

    • 当slave断线重连后,发送psync命令,向master报告自己当前的offset,master判断报告的offset是否还在复制缓冲区内,如果在则发起部分增量同步,发送位于offset之后的写命令即可,如果不在,说明命令丢失,发起完整增量同步

    • 实例ID在节点启动时产生,由40个随机的16进制组成,当slave首次从master复制数据时,master将自己的ID发送给slave,由slave保持;当slave断线重连时,会将自己保存的ID发送给重连接的master,master判断ID与自己的ID是否一致,如果一致,说明master没改变,尝试部分增量同步,如果不一致,说明master已经改变,需要完整增量同步。

  • 复制缓冲区的大小:

    复制缓冲区的大小默认为1M,当master执行大量写命令或者slave断线时间较长时都会使队列溢出,从而导致增量同步的无效,因此需要预计和设置复制缓冲区的大小,才能使增量同步发挥作用。

主从同步是Redis实现高可用的基础,了解了主从同步的原理和方式,对学习Redis高可用会有很大的帮助。

PS:如有任何问题或疑问,请留言告诉我。

以上是关于[Redis] 解决多个 Redis 服务同步删除有关联的 key的主要内容,如果未能解决你的问题,请参考以下文章

Redis复制:主从同步

redis 数据库主从不一致问题解决方案

redis主从同步与读写分离

redis的安装,以及主从实现同步

Redis 高可用篇:你管这叫主从架构数据同步原理?

Redis系列--主从同步