基于 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机器学习:KNN-近邻算法在手写识别系统上的应用
需要识别的数字已经使用图形处理软件,处理成具有相同的色 彩和大小® : 宽髙是32像 素 *32像素的黑白图像.尽管采用文本格式存储图像不能有效地利用内 存空间,但是为了方便理解,我们还是将图像转换为 ...
- centos设置上网代理
假设我们要设置代理为 IP:PORT 1.网页上网网页上网设置代理很简单,在firefox浏览器下 Edit-->>Preferences-->>Advanced-->& ...
- Navicat for MySQL远程连接报10038的错误
#################################################### """ 1.网络检测 1)ping主机可以: 2)telnet ...
- SGD/BGD/MBGD使用python简单实现
算法具体可以参照其他的博客: 随机梯度下降: # coding=utf-8 ''' 随机梯度下降 ''' import numpy as np # 构造训练数据 x = np.arange(0., 1 ...
- Mybatis的generator自动生成代码
mybatis-generator有三种用法:命令行.ide插件.maven插件.本次使用maven生成 环境:IDEA,mysql8,maven (1):新建项目,本次以SpringBoot项目为例 ...
- Spring定义Bean的两种方式:和@Bean
前言: Spring中最重要的概念IOC和AOP,实际围绕的就是Bean的生成与使用. 什么叫做Bean呢?我们可以理解成对象,每一个你想交给Spring去托管的对象都可以称之为Bean. 今天 ...
- 规范化开发和time相关模块
1. 规范化开发 如果在开发的过程中将所有的程序放在一个py文件中,加载时会很慢,同时降低了代码的可读性,查询起来也麻烦 所以要将一个oy文件合理的分成多个py文件,在blog大目录下分为以下几个部分 ...
- Ubuntu16.04编译libjpeg-turbo库
一.环境依赖 CMake v2.8.12或以后 NASM or YASM (if building x86 or x86-64 SIMD extensions),如果使用NASM, 依赖2.10之后版 ...
- spring和mybatis整合报错:org.springframework.beans.MethodInvocationException: Property 'dataSource' threw exception; nested exception is java.lang.NoClassDefFoundError
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyExceptio ...
- nodejs express 框架 上传文件
web 项目应用express4.0框架 html 表单post 文件上传失败,后端无法获取提交文件 express不支持文件上传. 方式一 若是图片,可以将图片转码为BASE64上传 前端框架ang ...