【问题标题】:Redis as LRU cache race conditionRedis 作为 LRU 缓存竞争条件
【发布时间】:2015-03-05 20:04:54
【问题描述】:

想象以下场景:

  1. 客户端 #1 从 Redis 请求对象 A
  2. Redis 以 null 回答
  3. 客户端 #1 从数据库请求对象 A
  4. 数据库返回对象A
  5. 客户端 #1 将对象 A 存储在 Redis 中

一切正常。让我们在 4 到 5 之间添加另一个客户端:

  1. 客户端 #1 从 Redis 请求对象 A
  2. Redis 以 null 回答
  3. 客户端 #1 从数据库请求对象 A
  4. 数据库返回对象 A

    • 客户端 #2 从 Redis 请求对象 A
    • Redis 以 null 回答
    • 客户端 #2 从数据库请求对象 A
    • 数据库返回对象A
    • 客户端 #2 将对象 A 存储在 Redis 中
    • 客户端 #2 更新数据库和 Redis 中的对象 A
  5. 客户端 #1 将对象 A 存储在 Redis 中

现在对象 A 在数据库中是最新的,但在 Redis 中已过时。是否有任何模式可以防止这种行为?显然,在等待 Redis 存储其副本时锁定数据库不是解决方案。

我还应该注意我正在使用 NodeJS,它维护一个固定大小的 Redis 连接池,并且请求是异步处理的,所以我们不能假设来自不同客户端的查询顺序不会在单个连接中混合.

【问题讨论】:

    标签: database node.js caching redis


    【解决方案1】:

    如果您的数据不经常更改(非易失性),例如昨天或一个月前的历史数据,您可以放心地忽略竞争条件,因为无论谁获得了最后一次提交, 数据在客户端之间仍然相同。

    对于时间敏感的数据,尤其是交易数据,可以根据user id分离缓存,使用user id (Client#1, Client#2)作为附加密钥。这将消除客户端之间的竞争条件,而无需借助内存使用率较高的缺点进行锁定和事务同步。

    此方法可以与第一种方法结合,例如如果数据是关于用户特定的数据,您可以使用用户 ID 键,另一方面,如果数据包含共享/分组信息,例如基于角色的菜单访问,您可以使用组 ID。

    【讨论】:

      【解决方案2】:

      您正在寻找Redis transactions。关键是WATCH 命令。在初始读取之前,您在对象 A 上调用 WATCH,并将对象设置在 MULTI 内。现在你的两个场景看起来像:

      1. 客户端 #1 在 Redis 中监视对象 A
      2. 客户端 #1 从 Redis 请求对象 A
      3. Redis 以 null 回答
      4. 客户端 #1 从数据库请求对象 A
      5. 数据库返回对象A
      6. 客户端 #1 将对象 A 存储在 Redis 的 MULTI/EXEC 块中

      1. 客户端 #1 在 Redis 中监视对象 A
      2. 客户端 #1 从 Redis 请求对象 A
      3. Redis 以 null 回答
      4. 客户端 #1 从数据库请求对象 A
      5. 数据库返回对象 A

        • 客户端 #2 从 Redis 请求对象 A
        • Redis 以 null 回答
        • 客户端 #2 从数据库请求对象 A
        • 数据库返回对象A
        • 客户端 #2 将对象 A 存储在 Redis 中
        • 客户端 #2 更新数据库和 Redis 中的对象 A
      6. 客户端 #1 将对象 A 存储在 Redis 中的 MULTI/EXEC 块中,但它失败了,因为对象 A 自 WATCH 以来发生了变化

      失败后,客户端 #1 可以重试,但最好忽略失败,因为您似乎只是将其用作缓存。

      当然,客户端 #2 也应该使用 WATCH/MULTI/EXEC 模式。

      【讨论】:

      • 恐怕 watch 命令不适用于 NodeJS 中的池连接和异步处理。来自不同客户端的命令混合在一起。
      • @SebastianNowak 这是你的库或使用的问题,而不是 Node.js。我在 NodeJS 中使用 watch/multi 就好了。如果您添加有关使用情况的详细信息,我很乐意向您展示如何操作。
      • 我必须为每个客户端创建一个到 Redis 的新连接才能使 watch 工作。此处解释了此行为:stackoverflow.com/a/20186334/853906 和此处:stackoverflow.com/a/20216928/853906
      • @SebastianNowak 你能在这里定义“客户”吗?我想我很难理解到底发生了什么,因为你的陈述对我来说没有多大意义。
      【解决方案3】:

      如果对象没有更新操作(第五步),你应该使用SETNX来存储它。所以你不要存储过时的对象。

      【讨论】:

      • 有多种情况可能会失败。例如,客户端 #2 可以完全删除对象,然后客户端 #1 会将其恢复到缓存中。
      • 为什么需要从缓存存储中删除一些东西?
      猜你喜欢
      • 2022-01-05
      • 1970-01-01
      • 2015-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-14
      相关资源
      最近更新 更多