【问题标题】:Redis Lua script implementing CAS (check-and-set)?实现 CAS(检查和设置)的 Redis Lua 脚本?
【发布时间】:2016-07-26 07:14:11
【问题描述】:

我只是想了解 Redis/Lua 脚本,我想知道是否有人发现以下代码有问题。

这是我尝试实现非常简单的“CAS”语义:用一个键和两个参数调用它。它将检查服务器上与该键关联的值是否第一个参数开头,如果是,则将设置键的新值设置为第二个参数并返回 1 否则它将返回0;如果键与字符串以外的某种类型的数据相关联,则 Redis 将返回并出错,就像您尝试对此类键/值组合执行 SET 命令一样。如果在调用之前该键不存在,则该函数将返回 0(失败)。

这是脚本:

local x=string.len(ARGV[1]);
if redis.call('GETRANGE', KEYS[1], 0, x-1) == ARGV[1] then
    redis.call('SET', KEYS[1], ARGV[2]);
    return 1;
    end;
return 0

这是一个在前缀值为“bar”的键“foo”上调用脚本的示例(在 redis-cli 中):

eval "local x=string.len(ARGV[1]); if redis.call('GETRANGE', KEYS[1], 0, x-1) == ARGV[1] then redis.call('SET', KEYS[1], ARGV[2]); return 1; end; return 0" 1 foo bar barbazzle

我认为这种情况的使用模式可能是您希望同时存储“防护令牌”和带有键的值......如果并发客户端持有正确的防护,则允许并发客户端尝试更新该值令牌。

这似乎是一种安全的使用模式来代替 WATCH/MULTI/EXEC 语义? (似乎您可以获取当前值,在本地代码中拆分围栏令牌,构建一个新值,然后尝试使用看起来比 WATCH/MULTI/EXEC 调用更容易混淆的语义随时更新密钥)。

(我知道我的脚本的语义与 memcached CAS 命令略有不同;这是故意的)。

这确实通过了我有限的测试......所以我真的在问任何潜在的并发/原子性问题以及 Lua 中是否有任何愚蠢的东西——因为我过去几乎没有接触过 Lua)。

【问题讨论】:

  • 好问题,完美呈现,只要键是字符串,就原子性/并发性/其他方面而言,您将完全没问题
  • 有一个有趣的 discussion 与 Salvatore Sanfilippo 讨论了为什么 CAS 方法没有在 Redis 设计中实现。

标签: lua redis cas


【解决方案1】:

根据Redis's documentation,您在原子性方面会很好:

Redis 使用相同的 Lua 解释器来运行所有命令。 Redis 还保证以原子方式执行脚本:在执行脚本时不会执行其他脚本或 Redis 命令。这种语义类似于 MULTI / EXEC 的语义。从所有其他客户端的角度来看,脚本的效果要么仍然不可见,要么已经完成。

但是,如果脚本太慢,则会导致问题。所以脚本最适合需要一些逻辑和原子性的轻量级操作。

你可能陷入的另一个漏洞是,如果脚本在中间以某种方式失败,那么你所做的那些调用将无法回滚,尽管脚本会返回错误。

例如: 你有一个这样的脚本:

redis.call('set', 'foo', 1)
redis.call('rpush', 'foo', 2)

脚本执行会返回错误,但是foo已经在redis中设置为1


与您的问题无关的东西:我注意到您使用了

eval "your_raw_code" key_count keys argv

其实你可以在终端中调用 eval 中的 lua 脚本文件:

> redis-cli eval "$(cat path/to/script/script_name.lua)" key_count keys argv

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    • 2015-10-27
    • 2013-12-22
    • 2011-07-12
    • 1970-01-01
    • 2013-11-11
    相关资源
    最近更新 更多