基于 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
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"", KEYS[1], ARGV[1]) == ARGV[2] then
redis.call(""hset"", KEYS[1], 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, 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 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);
}
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
基于 Redis 实现 CAS 操作的更多相关文章
- 基于Redis的CAS服务端集群
为了保证生产环境CAS(Central Authentication Service)认证服务的高可用,防止出现单点故障,我们需要对CAS Server进行集群部署. CAS的Ticket默认是以Ma ...
- apscheduler(定时任务) 基于redis持久化配置操作
apscheduler(定时任务) 基于redis持久化配置操作 安装模块 pip install apscheduler 导入模块配置 ## 配置redis模块 from apscheduler.j ...
- 基于redis的cas实现
cas是我们常用的一种解决并发问题的手段,小到CPU指令集,大到分布式存储,都能看到cas的影子.本文假定你已经充分理解一般的cas方案,如果你还不知道cas是什么,请自行百度 我们在进行关系型数据库 ...
- 基于redis的cas集群配置(转)
1.cas ticket统一存储 做cas集群首先需要将ticket拿出来,做统一存储,以便每个节点访问到的数据一致.官方提供基于memcached的方案,由于项目需要,需要做计入redis,根据官方 ...
- 基于redis的cas集群配置
1.cas ticket统一存储 做cas集群首先需要将ticket拿出来,做统一存储,以便每个节点访问到的数据一致.官方提供基于memcached的方案,由于项目需要,需要做计入redis,根据官方 ...
- 基于Redis的CAS集群
单点登录(SSO)是复杂应用系统的基本需求,Yale CAS是目前常用的开源解决方案.CAS认证中心,基于其特殊作用,自然会成为整个应用系统的核心,所有应用系统的认证工作,都将请求到CAS来完成.因此 ...
- 基于CAS操作的非阻塞算法
非阻塞算法(non-blocking algorithms)定义 所谓非阻塞算法是相对于锁机制而言的,是指:一个线程的失败或挂起不应该引起另一个线程的失败或挂起的一种算法.一般是利用硬件 ...
- 【连载】redis库存操作,分布式锁的四种实现方式[三]--基于Redis watch机制实现分布式锁
一.redis的事务介绍 1. Redis保证一个事务中的所有命令要么都执行,要么都不执行.如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行.而一旦客户端发 ...
- 基于redis分布式缓存实现(新浪微博案例)
第一:Redis 是什么? Redis是基于内存.可持久化的日志型.Key-Value数据库 高性能存储系统,并提供多种语言的API. 第二:出现背景 数据结构(Data Structure)需求越来 ...
随机推荐
- python语法基础-并发编程-协程-长期维护
############### 协程 ############## # 协程 # 小知识点, # 协程和进程和线程一样都是实现并发的手段, # 开启一个线程,创建一个线程,还是需要开销, ...
- yum源本地部署完后网络部署报错
错误信息 已加载插件:fastestmirror Determining fastest mirrors * base: mirrors.aliyun.com * extras: mirrors.al ...
- BBuBBBlesort!
题目描述 Snuke got an integer sequence of length N from his mother, as a birthday present. The i-th (1≦i ...
- python开发常见应用第一卷(OS遍历文件并存储文件路径到数据库)
之前我们爬取完指定网站的图片后,会将它门保存到服务器或本地的数据库中,真正的工作中大多数是采用分布式的方式来爬取的,所以这些图片会分布在很多的主机上面,当被引用时需要根据IP+图片路径来引用并且加载, ...
- 关于QGIS打开SHP文件属性表乱码
解决方案是从网上看到的,一个台湾的朋友给出了具体的解决方法.但他说的方法的最后一步对我来说不适用,我稍作修改 具体如下:在线安装插件:Shapefile Encoding Fixer. 加载shp文件 ...
- 求求你,下次面试别再问我什么是 Spring AOP 和代理了!
https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_9403056301388627935% ...
- Redmine it!
redmine插件开发简介 最稳妥的学习应该是先看官方文档,官方还给了一个具体的插件开发教程,不过如果一步不差按照教程敲代码,其实会发现还是有些问题的,需要稍稍改动. 这里,我自己编写了一个简单的插件 ...
- linux上apache的安装
1.简介 Apache HTTP Server(简称Apache或httpd)是Apache软件基金会的一个开放源代码的网页服务器软件,旨在为unix,windows等操作系统中提供开源httpd服务 ...
- 我的第一个Quartz代码
创建Maven项目 打开Eclipse->File->Project->Maven ->Maven Project直接下一步输入Group Id和Artifact Id , ...
- Luogu_2279_[HNOI2003]消防局的设立
题目描述 2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地.起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状 ...