加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

基于 Redis 实现 CAS 操作

发布时间:2020-12-16 01:15:04 所属栏目:百科 来源:网络整理
导读:基于 Redis 实现 CAS 操作 Intro 在 .NET 里并发情况下我们可以使用 Interlocked.CompareExchange 来实现 CAS (Compare And Swap) 操作,在分布式的情景下很多时候我们都会使用 Redis ,最近在改之前做的一个微信小游戏项目,之前是单机运行的,有些数据存

基于 Redis 实现 CAS 操作

Intro

在 .NET 里并发情况下我们可以使用 Interlocked.CompareExchange 来实现 CAS (Compare And Swap) 操作,在分布式的情景下很多时候我们都会使用 Redis ,最近在改之前做的一个微信小游戏项目,之前是单机运行的,有些数据存储是基于内存的,直接基于对象操作的,最近要改成支持分布式的,于是引入了 redis,原本基于内存的数据就要迁移到 redis 中存储,原来的代码里有一些地方使用了 Interlocked.CompareExchange 来实现 CAS 操作,迁移到 redis 中之后也需要类似的功能,于是就想基于 redis 实现 CAS 操作。

CAS

CAS (Compare And Swap) 通常可以使用在并发操作中更新某一个对象的值,CAS 是无锁操作,CAS 相当于是一种乐观锁,而直接加锁相当于是悲观锁,所以相对来说 CAS 操作 是会比直接加锁更加高效的。

Redis Lua

redis 从 2.6.0 版本开始支持 Lua 脚本,Lua 脚本的执行是原子性的,所以我们在实现基于 redis 的分布式锁释放锁的时候或者下面要介绍的实现CAS 操作的,要执行多个操作但是希望操作是原子操作的时候就可以借助 Lua 脚本来实现(也可以使用事务来做)

基于 Redis Lua 实现 CAS

String CAS Lua Script:

KEYS[1] 对应要操作的String 类型的 redis 缓存的 key,ARGV[1]对应要比较的值,值相同则更新成 ARGV[2],并返回 1,否则返回 0

if redis.call(""get"",KEYS[1]) == ARGV[1] then
    redis.call(""set"",KEYS[1],ARGV[2])
    return 1
else
    return 0
end

Hash CAS Lua Script:

KEYS[1] 对应要操作的 Hash 类型的 redis 缓存的 key,ARGV[1] 对应 Hash 的 field,ARGV[2]对应要比较的值,值相同则更新成 ARGV[3],并返回 1,否则返回 0

if redis.call(""hget"",ARGV[1]) == ARGV[2] then
    redis.call(""hset"",ARGV[1],ARGV[3])
    return 1
else
    return 0
end

基于 StackExchange.Redis 的实现

为了方便使用,基于 IDatabase 提供了几个方便使用的扩展方法,实现如下:

public static bool StringCompareAndExchange(this IDatabase db,RedisKey key,RedisValue newValue,RedisValue originValue)
{
    return (int)db.ScriptEvaluate(StringCasLuaScript,new[] { key },new[] { originValue,newValue }) == 1;
}

public static async Task<bool> StringCompareAndExchangeAsync(this IDatabase db,RedisValue originValue)
{
    return await db.ScriptEvaluateAsync(StringCasLuaScript,newValue })
        .ContinueWith(r => (int)r.Result == 1);
}

public static bool HashCompareAndExchange(this IDatabase db,RedisValue field,RedisValue originValue)
{
    return (int)db.ScriptEvaluate(HashCasLuaScript,new[] { field,originValue,newValue }) == 1;
}

public static async Task<bool> HashCompareAndExchangeAsync(this IDatabase db,RedisValue originValue)
{
    return await db.ScriptEvaluateAsync(HashCasLuaScript,newValue })
        .ContinueWith(r => (int)r.Result == 1);
}

实际使用

使用可以参考下面的测试代码:

[Fact]
public void StringCompareAndExchangeTest()
{
    var key = "test:String:cas";
    var redis = DependencyResolver.Current
        .GetRequiredService<IConnectionMultiplexer>()
        .GetDatabase();
    redis.StringSet(key,1);

    // set to 3 if now is 2
    Assert.False(redis.StringCompareAndExchange(key,3,2));
    Assert.Equal(1,redis.StringGet(key));

    // set to 4 if now is 1
    Assert.True(redis.StringCompareAndExchange(key,4,1));
    Assert.Equal(4,redis.StringGet(key));

    redis.KeyDelete(key);
}

[Fact]
public void HashCompareAndExchangeTest()
{
    var key = "test:Hash:cas";
    var field = "testField";

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

    // set to 3 if now is 2
    Assert.False(redis.HashCompareAndExchange(key,redis.HashGet(key,field));

    // set to 4 if now is 1
    Assert.True(redis.HashCompareAndExchange(key,field));

    redis.KeyDelete(key);
}

References

  • https://redis.io/commands/eval
  • https://redisbook.readthedocs.io/en/latest/feature/scripting.html
  • 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

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读