Redis作为LRU缓存竞争条件

时间:2015-03-05 20:04:55

标签: database node.js caching redis

想象一下以下情景:

  1. 客户#1从Redis
  2. 请求对象A.
  3. Redis以null
  4. 回答
  5. 客户端#1从数据库
  6. 请求对象A.
  7. 数据库返回对象A
  8. 客户端#1在Redis中存储对象
  9. 一切正常。让我们在4到5之间添加另一个客户端:

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

      • 客户#2从Redis请求对象A
      • Redis以null
      • 回答
      • 客户端#2从dtabase
      • 请求对象A.
      • 数据库返回对象A
      • 客户端#2在Redis中存储对象
      • 客户端#2更新数据库和Redis中的对象A
    8. 客户端#1在Redis中存储对象
    9. 现在,对象A在数据库中是最新的,但在Redis中已过时。是否有任何模式可以防止此类行为?显然,在等待Redis存储其副本时锁定数据库并不是一个解决方案。

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

3 个答案:

答案 0 :(得分:1)

如果您的数据经常不变(非易失性),就像昨天或一个月前的历史数据一样,您可以安全地忽略竞争条件,因为无论谁最后提交,客户的数据仍然相同。

对于时间敏感数据,尤其是交易数据,您可以使用用户ID(客户端#1,客户端#2)将基于用户ID 的缓存分开额外的钥匙。这将消除客户端之间的竞争条件,而无需使用锁定和事务同步以及更高内存使用率的缺点

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

答案 1 :(得分:0)

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

  1. 客户#1在Redis中观察对象A
  2. 客户#1从Redis
  3. 请求对象A.
  4. Redis以null
  5. 回答
  6. 客户端#1从数据库
  7. 请求对象A.
  8. 数据库返回对象A
  9. 客户端#1在Redis / EXEC块中将对象A存储在Redis中
    1. 客户#1在Redis中观察对象A
    2. 客户#1从Redis
    3. 请求对象A.
    4. Redis以null
    5. 回答
    6. 客户端#1从数据库
    7. 请求对象A.
    8. 数据库返回对象A

      • 客户#2从Redis请求对象A
      • Redis以null
      • 回答
      • 客户端#2从dtabase
      • 请求对象A.
      • 数据库返回对象A
      • 客户端#2在Redis中存储对象
      • 客户端#2更新数据库和Redis中的对象A
    9. 客户端#1在Redis中将对象A存储在MULTI / EXEC块中,但由于对象A自WATCH
    10. 以来已更改,因此失败

      失败后,客户端#1可以重试,但最好忽略失败,因为看起来你只是将它用作缓存。

      客户端#2当然也应该完成WATCH / MULTI / EXEC模式。

答案 2 :(得分:0)

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