基于 Redis 实现 CAS 操作
基于 Redis 實現(xiàn) CAS 操作
Intro
在 .NET 里并發(fā)情況下我們可以使用 Interlocked.CompareExchange 來實現(xiàn) CAS (Compare And Swap) 操作,在分布式的情景下很多時候我們都會使用 Redis ,最近在改之前做的一個微信小游戲項目,之前是單機(jī)運行的,有些數(shù)據(jù)存儲是基于內(nèi)存的,直接基于對象操作的,最近要改成支持分布式的,于是引入了 redis,原本基于內(nèi)存的數(shù)據(jù)就要遷移到 redis 中存儲,原來的代碼里有一些地方使用了 Interlocked.CompareExchange 來實現(xiàn) CAS 操作,遷移到 redis 中之后也需要類似的功能,于是就想基于 redis 實現(xiàn) CAS 操作。
CAS
CAS (Compare And Swap) 通常可以使用在并發(fā)操作中更新某一個對象的值,CAS 是無鎖操作,CAS 相當(dāng)于是一種樂觀鎖,而直接加鎖相當(dāng)于是悲觀鎖,所以相對來說 CAS 操作 是會比直接加鎖更加高效的。(——個人理解)
Redis Lua
redis 從 2.6.0 版本開始支持 Lua 腳本,Lua 腳本的執(zhí)行是原子性的,所以我們在實現(xiàn)基于 redis 的分布式鎖釋放鎖的時候或者下面要介紹的實現(xiàn)CAS 操作的,要執(zhí)行多個操作但是希望操作是原子操作的時候就可以借助 Lua 腳本來實現(xiàn)(也可以使用事務(wù)來做)
基于 Redis Lua 實現(xiàn) CAS
String CAS Lua Script:
KEYS[1] 對應(yīng)要操作的String 類型的 redis 緩存的 key,ARGV[1]對應(yīng)要比較的值,值相同則更新成 ARGV[2],并返回 1,否則返回 0
if redis.call(""get"", KEYS[1]) == ARGV[1] thenredis.call(""set"", KEYS[1], ARGV[2])return 1 elsereturn 0 endHash CAS Lua Script:
KEYS[1] 對應(yīng)要操作的 Hash 類型的 redis 緩存的 key,ARGV[1] 對應(yīng) Hash 的 field,ARGV[2]對應(yīng)要比較的值,值相同則更新成 ARGV[3],并返回 1,否則返回 0
if redis.call(""hget"", KEYS[1], ARGV[1]) == ARGV[2] thenredis.call(""hset"", KEYS[1], ARGV[1], ARGV[3])return 1 elsereturn 0 end基于 StackExchange.Redis 的實現(xiàn)
為了方便使用,基于 IDatabase 提供了幾個方便使用的擴(kuò)展方法,實現(xiàn)如下:
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); }實際使用
使用可以參考下面的測試代碼:
[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 2Assert.False(redis.StringCompareAndExchange(key, 3, 2));Assert.Equal(1, redis.StringGet(key));// set to 4 if now is 1Assert.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 2Assert.False(redis.HashCompareAndExchange(key, field, 3, 2));Assert.Equal(1, redis.HashGet(key, field));// set to 4 if now is 1Assert.True(redis.HashCompareAndExchange(key, field, 4, 1));Assert.Equal(4, redis.HashGet(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
總結(jié)
以上是生活随笔為你收集整理的基于 Redis 实现 CAS 操作的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 五分钟了解数据库事务隔离
- 下一篇: 论ORM之EFCore初篇(快速基于本地