我想确保当我使用
set
设置值时,该键必须存在
PHP 的假代码:
$expired_at = strtotime(date('Y-m-d')) + 24 * 3600;
$key = "store_of_today";
$left = $redis->get($key);
if ($left === null) {
$redis->set($key, 100);
$redis->expireat($key, $expired_at);
$left = 100;
}
// something run ...
// here, I must ensure that the key is exist
// for example, I got the value by using $redis->get($key) at 23:59:59:999;
// then the code run over 1ms, and now is 00:00:00:015 (run 16ms here)
// the key is expired, so I can't set data to redis
// even though I used $redis->exist($key) to check the key is exist or not,
// but this code still need time to run, so I can't ensure the key is exist
$redis->set($key, $new_left);
我知道redis有
setnx
,好像没有像setifexist
那样的功能
一年后,我找到了一种用 LUA 脚本解决这个问题的方法
$get_lua = <<<LUA
local key = KEYS[1]
local expireAt = ARGV[1]
local value = redis.call('GET', key)
if value == false then
redis.call('SET', key, 100)
redis.call('EXPIREAT', key, expireAt)
value = 100
end
return value
LUA;
$set_lua = <<<LUA
local key = KEYS[1]
local value = ARGV[1]
local expireAt = ARGV[2]
redis.call('SET', key, value)
redis.call('EXPIREAT', key, expireAt)
LUA;
$redis = new Redis();
$expired_at = strtotime(date('Y-m-d')) + 864000; // end of today
$key = "store_of_today";
$left = $redis->eval($get_lua, [$key, $expired_at], 1);
// code
$redis->eval($set_lua, [$key, $new_left, $expired_at], 1);
redis 确保 lua 脚本是原子操作,因此
$set_lua
总是会在一次原子操作中设置一个值,然后在键上设置 expireAt
,如果 expireAt
是过去的时间戳,则该键将无效。