大数跨境
0
0

基于 Redis 实现 CAS 操作

基于 Redis 实现 CAS 操作 amazingdotnet
2020-03-08
2
导读:基于 Redis 实现支持分布式的 CAS

基于 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

 
  1. if redis.call(""get"", KEYS[1]) == ARGV[1] then

  2. redis.call(""set"", KEYS[1], ARGV[2])

  3. return 1

  4. else

  5. return 0

  6. end

Hash CAS Lua Script:

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

 
  1. if redis.call(""hget"", KEYS[1], ARGV[1]) == ARGV[2] then

  2. redis.call(""hset"", KEYS[1], ARGV[1], ARGV[3])

  3. return 1

  4. else

  5. return 0

  6. end

基于 StackExchange.Redis 的实现

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

 
  1. public static bool StringCompareAndExchange(this IDatabase db, RedisKey key, RedisValue newValue, RedisValue originValue)

  2. {

  3. return (int)db.ScriptEvaluate(StringCasLuaScript, new[] { key }, new[] { originValue, newValue }) == 1;

  4. }


  5. public static async Task<bool> StringCompareAndExchangeAsync(this IDatabase db, RedisKey key, RedisValue newValue, RedisValue originValue)

  6. {

  7. return await db.ScriptEvaluateAsync(StringCasLuaScript, new[] { key }, new[] { originValue, newValue })

  8. .ContinueWith(r => (int)r.Result == 1);

  9. }


  10. public static bool HashCompareAndExchange(this IDatabase db, RedisKey key, RedisValue field, RedisValue newValue, RedisValue originValue)

  11. {

  12. return (int)db.ScriptEvaluate(HashCasLuaScript, new[] { key }, new[] { field, originValue, newValue }) == 1;

  13. }


  14. public static async Task<bool> HashCompareAndExchangeAsync(this IDatabase db, RedisKey key, RedisValue field, RedisValue newValue, RedisValue originValue)

  15. {

  16. return await db.ScriptEvaluateAsync(HashCasLuaScript, new[] { key }, new[] { field, originValue, newValue })

  17. .ContinueWith(r => (int)r.Result == 1);

  18. }

实际使用

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

 
  1. [Fact]

  2. public void StringCompareAndExchangeTest()

  3. {

  4. var key = "test:String:cas";

  5. var redis = DependencyResolver.Current

  6. .GetRequiredService<IConnectionMultiplexer>()

  7. .GetDatabase();

  8. redis.StringSet(key, 1);


  9. // set to 3 if now is 2

  10. Assert.False(redis.StringCompareAndExchange(key, 3, 2));

  11. Assert.Equal(1, redis.StringGet(key));


  12. // set to 4 if now is 1

  13. Assert.True(redis.StringCompareAndExchange(key, 4, 1));

  14. Assert.Equal(4, redis.StringGet(key));


  15. redis.KeyDelete(key);

  16. }


  17. [Fact]

  18. public void HashCompareAndExchangeTest()

  19. {

  20. var key = "test:Hash:cas";

  21. var field = "testField";


  22. var redis = DependencyResolver.Current

  23. .GetRequiredService<IConnectionMultiplexer>()

  24. .GetDatabase();

  25. redis.HashSet(key, field, 1);


  26. // set to 3 if now is 2

  27. Assert.False(redis.HashCompareAndExchange(key, field, 3, 2));

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


  29. // set to 4 if now is 1

  30. Assert.True(redis.HashCompareAndExchange(key, field, 4, 1));

  31. Assert.Equal(4, redis.HashGet(key, field));


  32. redis.KeyDelete(key);

  33. }

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


【声明】内容源于网络
0
0
amazingdotnet
dotnet 开发知识库,了不起的 dotnet,dotnet 奇淫怪巧
内容 539
粉丝 0
amazingdotnet dotnet 开发知识库,了不起的 dotnet,dotnet 奇淫怪巧
总阅读27
粉丝0
内容539