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

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

Intro

之前写过一篇文章也是 Redis 使用 LUA 脚本实现分布式的 CAS 操作,可以参考:基于 Redis 实现 CAS 操作

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

Implement

我实现了针对 StringHash 的 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);
}

Sample

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

[Fact]
public void StringSetWhenValueChangedTest()
{
    var key = $"{nameof(StringSetWhenValueChangedTest)}";
    var redis = DependencyResolver.Current
        .GetRequiredService<IConnectionMultiplexer>()
        .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<IConnectionMultiplexer>()
        .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));
}

More

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

References

  • https://github.com/WeihanLi/WeihanLi.Redis/blob/dev/src/WeihanLi.Redis/RedisExtensions.cs

  • https://github.com/WeihanLi/WeihanLi.Redis/blob/dev/test/WeihanLi.Redis.UnitTest/RedisExtensionsTest.cs

  • 基于 Redis 实现 CAS 操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值