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

基于 Redis 实现 CAS 操作的更多相关文章

  1. 基于Redis的CAS服务端集群

    为了保证生产环境CAS(Central Authentication Service)认证服务的高可用,防止出现单点故障,我们需要对CAS Server进行集群部署. CAS的Ticket默认是以Ma ...

  2. apscheduler(定时任务) 基于redis持久化配置操作

    apscheduler(定时任务) 基于redis持久化配置操作 安装模块 pip install apscheduler 导入模块配置 ## 配置redis模块 from apscheduler.j ...

  3. 基于redis的cas实现

    cas是我们常用的一种解决并发问题的手段,小到CPU指令集,大到分布式存储,都能看到cas的影子.本文假定你已经充分理解一般的cas方案,如果你还不知道cas是什么,请自行百度 我们在进行关系型数据库 ...

  4. 基于redis的cas集群配置(转)

    1.cas ticket统一存储 做cas集群首先需要将ticket拿出来,做统一存储,以便每个节点访问到的数据一致.官方提供基于memcached的方案,由于项目需要,需要做计入redis,根据官方 ...

  5. 基于redis的cas集群配置

    1.cas ticket统一存储 做cas集群首先需要将ticket拿出来,做统一存储,以便每个节点访问到的数据一致.官方提供基于memcached的方案,由于项目需要,需要做计入redis,根据官方 ...

  6. 基于Redis的CAS集群

    单点登录(SSO)是复杂应用系统的基本需求,Yale CAS是目前常用的开源解决方案.CAS认证中心,基于其特殊作用,自然会成为整个应用系统的核心,所有应用系统的认证工作,都将请求到CAS来完成.因此 ...

  7. 基于CAS操作的非阻塞算法

    非阻塞算法(non-blocking algorithms)定义        所谓非阻塞算法是相对于锁机制而言的,是指:一个线程的失败或挂起不应该引起另一个线程的失败或挂起的一种算法.一般是利用硬件 ...

  8. 【连载】redis库存操作,分布式锁的四种实现方式[三]--基于Redis watch机制实现分布式锁

    一.redis的事务介绍 1. Redis保证一个事务中的所有命令要么都执行,要么都不执行.如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行.而一旦客户端发 ...

  9. 基于redis分布式缓存实现(新浪微博案例)

    第一:Redis 是什么? Redis是基于内存.可持久化的日志型.Key-Value数据库 高性能存储系统,并提供多种语言的API. 第二:出现背景 数据结构(Data Structure)需求越来 ...

随机推荐

  1. token和refresh token

    https://www.cnblogs.com/minirice/p/9232355.html 在spring boot中结合OAuth2使用JWT时,刷新token时refresh token一直变 ...

  2. auth模块用法

    Auth模块: 如果你想用auth模块   那么你就用全套 createsuperuser  创建超级用户 这个超级用户就可以拥有登陆django admin后台管理的权限 Auth模块是Django ...

  3. 吴裕雄--天生自然python学习笔记:网页解析

    抓取万水书苑网页中所有<a>标签中的超链接井显示. import requests from bs4 import BeautifulSoup url = 'http://www.wsbo ...

  4. FileZilla相关配置说明

    相关下载可以直接到官网,或者阿里云帮助:https://help.aliyun.com/knowledge_detail/36243.html?spm=5176.10695662.1996646101 ...

  5. bubble chart|Matrix Scatter|Overlay Scatter|Scatterplots|drop-line|box plot|Stem-and-leaf plot|Histogram|Bar chart|Pareto chart|Pie chart|doughnut chart|

    应用统计学 对类别数据要分类处理: Bar chart复式条形图便于对比: Pareto chart:对类别变量依据频数高低排列: Pie chart:饼图用于一个样本,可以区分类别数据 doughn ...

  6. unittest(22)- p2p项目实战(7)-do_mysql

    # 7. do_msql.py import mysql.connector from p2p_project_2020_1_21.tools import project_path from p2p ...

  7. Apollo配置中心介绍与使用指南

    转载于https://github.com/ctripcorp/apollo,by Ctrip, Inc. Apollo配置中心介绍 Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中 ...

  8. res文件夹及xml资源文件详解

    目录 一.values文件:存放字符串(strings).颜色(colors).尺寸(dimens).数组(arrays).样式(styles类似于CSS文件).类型等资源 二.drawable:存放 ...

  9. SWUST OJ 青蛙的约会之二(0481)

    青蛙的约会之二(0481) Time limit(ms): 1000 Memory limit(kb): 65535 Submission: 138 Accepted: 28   Descriptio ...

  10. 吴裕雄--天生自然KITTEN编程:角色移动