CAS operation based on Redis

introduction

Yes NET, we can use interlocked Compareexchange to implement CAS (Compare And Swap) operation. In the distributed scenario, we often use redis. Recently, a wechat game project before the change was run on a single machine. Some data storage is based on memory and directly based on object operation. Recently, redis is introduced to support distributed. The original memory based data will be migrated to Stored in redis, the original code used interlocked in some places Compareexchange is used to implement CAS operations. Similar functions are required after migrating to redis. Therefore, we want to implement CAS operations based on redis.

CAS

CAS (Compare And Swap) can usually be used to update the value of an object during concurrent operations. CAS is a lock free operation. CAS is equivalent to an optimistic lock, while direct locking is equivalent to a pessimistic lock. Therefore, CAS operation will be more efficient than direct locking. (-- personal understanding)

Redis Lua

Redis from 2.6 Version 0 supports Lua scripts. The execution of lua scripts is atomic. Therefore, when we implement redis based distributed lock release or CAS operations to be described below, we need to perform multiple operations. However, when we want the operations to be atomic, we can use Lua scripts (or transactions)

CAS implementation based on Redis Lua

String CAS Lua Script:

KEYS[1] corresponds to the key of redis cache of String type to be operated. ARGV[1] corresponds to the value to be compared. If the values are the same, it will be updated to ARGV[2] and return 1. Otherwise, it will return 0

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

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

    return 1

else

    return 0

end
  1. Hash CAS Lua Script:

KEYS[1] corresponds to the key of redis cache of Hash type to be operated, ARGV[1] corresponds to the field of Hash, ARGV[2] corresponds to the value to be compared, if the value is the same, it is updated to ARGV[3], and returns 1, otherwise it returns 0

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

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

    return 1

else

    return 0

end

Based on stackexchange Redis implementation

For ease of use, several easy-to-use extension methods are provided based on IDatabase, which are implemented as follows:

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, RedisKey key, RedisValue newValue, RedisValue originValue)

{

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

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

}



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

{

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

}



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

{

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

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

}

Actual use

You can refer to the following test code:

[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, field, 3, 2));

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



// set to 4 if now is 1

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

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



redis.KeyDelete(key);

}

Keywords: Database Redis Middleware lua

Added by cfemocha on Sat, 18 Dec 2021 20:07:00 +0200