Redis 使用 Lua 脚本进行原子操作

介绍

最近使用 Redis 的时候有一个需求,只有值发生变化的时候才更新,如果要更新的值和现在的值是一样的就不用更新,有点类似于 SET NX,只是 SET NX 只有值不存在的时候才会 SET,我的需求则是要检查要 SET 的值和 Redis 里的值,如果不一样就 SET,一样就直接返回

使用

我实现了针对 String 和 Hash 的 SET 检查,核心就是我们的 Lua 脚本

实现代码如下:

对于 Hash 会多一个参数 —— hash field name, 对于 string 则直接是 value 了,就会比 hash 少一个参数

private const string HashSetWhenValueChangedLuaScript = @"
if redis.call(""HGET"", KEYS[1], ARGV[1]) == ARGV[2] then
    return 0
else
    redis.call(""HSET"", KEYS[1], ARGV[1], ARGV[2])
    return 1
end
";

private const string StringSetWhenValueChangedLuaScript = @"
if redis.call(""GET"", KEYS[1]) == ARGV[1] then
    return 0
else
    redis.call(""SET"", KEYS[1], ARGV[1])
    return 1
end
";

实现起来也比较简单,就是先取一下 Redis 中的数据,如果和输入的值是一样就返回 0,不一样则更新值,然后返回 1

StackExchange.Redis 使用 API

在 StackExchange.Redis 中可以使用 ScriptEvaluate/ScriptEvaluateAsync 来执行 Lua 脚本,为了方便使用我把他们封装成了扩展方法,实现如下:

public static bool StringSetWhenValueChanged(this IDatabase db, RedisKey key, RedisValue value)
{
    return (int)db.ScriptEvaluate(StringSetWhenValueChangedLuaScript, new[] { key }, new[] { value }) == 1;
}

public static async Task<bool> StringSetWhenValueChangedAsync(this IDatabase db, RedisKey key, RedisValue value)
{
    return await db.ScriptEvaluateAsync(StringSetWhenValueChangedLuaScript, new[] { key }, new[] { value })
        .ContinueWith(r => (int)r.Result == 1);
}

public static bool HashSetWhenValueChanged(this IDatabase db, RedisKey key, RedisValue field, RedisValue value)
{
    return (int)db.ScriptEvaluate(HashSetWhenValueChangedLuaScript, new[] { key }, new[] { field, value }) == 1;
}

public static async Task<bool> HashSetWhenValueChangedAsync(this IDatabase db, RedisKey key, RedisValue field, RedisValue value)
{
    return await db.ScriptEvaluateAsync(HashSetWhenValueChangedLuaScript, new[] { key }, new[] { field, value }).ContinueWith(r => (int)r.Result == 1);
}

案例

使用示例可以参考下面的测试用例:

[Fact]
public void StringSetWhenValueChangedTest()
{
    var key = $"{nameof(StringSetWhenValueChangedTest)}";
    var redis = DependencyResolver.Current
        .GetRequiredService()
        .GetDatabase();
    redis.StringSet(key, 1);

    // update to 1 if now is not 1
    Assert.False(redis.StringSetWhenValueChanged(key, 1));
    Assert.Equal(1, redis.StringGet(key));

    // update to 2 if now is not 2
    Assert.True(redis.StringSetWhenValueChanged(key, 2));
    Assert.Equal(2, redis.StringGet(key));
}

[Fact]
public void HashSetWhenValueChangedTest()
{
    var key = $"{nameof(HashSetWhenValueChangedTest)}";
    var field = "testField";

    var redis = DependencyResolver.Current
        .GetRequiredService()
        .GetDatabase();
    redis.HashSet(key, field, 1);

    Assert.False(redis.HashSetWhenValueChanged(key, field, 1));
    Assert.Equal(1, redis.HashGet(key, field));

    Assert.True(redis.HashSetWhenValueChanged(key, field, 2));
    Assert.Equal(2, redis.HashGet(key, field));
}

小结

在使用 Lua 脚本的时候,如果要使用不等于的逻辑需要小心一些,和其他语言不同,需要使用 ~= 而非 != 来表示不等

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值