部分参考链接

Transaction

StackExchange.Redis Transaction

hashest

正文

Redis 是一种基于内存的单线程数据库。意味着所有的命令是一个接一个的执行。


考虑只有一个Redis实例,也就是Redis本身没有做分布式。


通过SETNX命令,set if not exist的缩写。那么多个服务在调用的时候可以通过同一个key申请一个lock(也就是调用命令成功返回1),然后根据相应条件做释放(比如时间到期,or手动释放),也就是delete key。

Redis本身有MULTI命令,标记开启一个事务。开启之后后面的命令会在调用EXEC命令的时候以一个集合的方式整体执行,也就是原子性(不保证都成功)。

现在有个需求,用redis实现Check and Set,也就是先读取里面的值,然后设置(比如做个+=val);并发的问题是必须要考虑的。

用redis描述大致是这样的。这里假设redis没有incr这个自增命令。

val = GET mykey
val = val + 1
SET mykey $val

直接这样做,并发问题是肯定有的。所以,按照上面的知识,应该有2种方法来避免这个并发问题。

基于SENTX命令。

copy一下文档的demo

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis>

第一次调用setnx,设置mykey的value为hello,返回1,表示成功。

第二次调用setnx,设置mykey的value为world,因为第一次调用并没有释放mykey,所以返回0,表示设置失败。

最后获取mykey的值,返回的是hello。

最后记得要去释放mykey。

这其实是一个悲观锁,也就是一个进程获取到锁之后要等释放别的进程才能继续。

基于MULTI命令。

  1. 先看一个简单的应用

    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> incr foo
    QUEUED
    127.0.0.1:6379> incr bar
    QUEUED
    127.0.0.1:6379> exec
    1) (integer) 1
    2) (integer) 1

    第一步调用MULTI命令,表示开始多个命令的输入。返回OK,表示开始接收。

    第二步调用incr foo,给foo对应的值做自增。返回queued,表示已加入队列。

    第二步调用incr bar,给bar对应的值做资政,返回queued,表示已加入队列。

    最后调用exec命令,表示执行队列中的命令。返回每个命令的结果。

  2. 有错误了怎么办

    首先错误分两种

    • 在enqueue的时候出错,最常见的就是参数错误。比如下面这个例子
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set a 1234
    QUEUED
    127.0.0.1:6379> set a 1 1 1 1 1 1 11
    QUEUED
    127.0.0.1:6379> exec
    1) OK
    2) (error) ERR syntax error
    127.0.0.1:6379>

    第二个set a 1 1 1 1 1 1 11命令是有语法错误,所以,在执行exec的时候会返回语法错误。第一个是成功的。所以,如果在后面get a 是会返回1234,为成功的设置。

    假设报错的命令在中间,后面的命令也是会执行的。

    • 还有就是直接命令就不对的。看个例子
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set a 11
    QUEUED
    127.0.0.1:6379> aaa
    (error) ERR unknown command `aaa`, with args beginning with:
    127.0.0.1:6379> exec
    (error) EXECABORT Transaction discarded because of previous errors.

    先set a,进入队列。

    执行aaa命令,这个命令不存在。直接报错。

    执行exec,事务因为之前的错误,exec中止。

  3. 为什么没有回滚

    通过上面的例子,看到redis对multi的操作是没有回滚的,或许有点奇怪。根据文档描述,有两个原因。

    • redis的命令执行只有在语法错误或者数据类型出错的时候会失败,而不是在enqueue的时候。这意味着失败是由程序设置错误导致的。那么,这种错误肯定是在开发环境中就应该容易被发现,而不是在生产环境。
    • 为了快。
  4. WATCH 命令的乐观锁

    结合watch命令我们也可以实现上面的需求。

    WATCH mykey
    --Begin---
    ##下面两行是客户端命令
    val = GET mykey
    val = val + 1
    --End---
    MULTI
    SET mykey $val
    EXEC

    解释一下,先获取一下mykey的监控。然后客户端获取mykey的值,(是客户端,不是命令服务端)。然后赋值自增。然后服务端开启MULTI, 设置新的值。执行。

    假设在MULTI和Exec之间,mykey的值被别的client修改,exec会返回(nil)。

    下面做个演示:

    先在redis-cli上执行以下命令

    127.0.0.1:6379> watch a
    OK
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set a 13
    QUEUED

    如上,已经开启WATCH,然后设置a =13 进入队列。

    然后在本地的redis desktop manager上去修改这个值。

    然后再在服务器上执行 exec,

    127.0.0.1:6379> exec
    (nil)

    返回的是nil,表示没有成功。如果没有客户端去更新,执行exec是返回OK。

  5. redis-scripting-and-transactions

    在Redis 2.6之后,引入了Redis script来实现事务的功能。通常来说script方式速度会相对快一点(没有做测试)。不过既然multi已经出来很久了,所以,不太可能会移除这个命令。

在StackExchange.Redis中使用

显然,也分两种,基于setnx 或者 MULTI + WATCH。分别对应的是IDatabaseAsync.LockTakeAsyncIDatabaseAsync.CreateTransaction这里结合了Polly这个库用于重试,毕竟,悲观锁,我多拿几次总能拿到的;乐观锁,执行的命令,我多试几次,总能成功的。

  • LockTakeAsync

    public async Task<T> TakeLockAsync<T>(string key, string token, Func<object, Task<T>> func, object obj)
    where T : class
    {
    var db = GetDb(redisConfigModel.LockDbIndex);//获取IDatabaseAsync对象
    //定义获取锁的策略
    var policy = Policy
    .HandleResult<bool>(w => !w)
    .WaitAndRetryForeverAsync(
    sleepDurationProvider: attemp => TimeSpan.FromSeconds(3), //两次重复尝试的间隔
    onRetry: (delegeteRst, ts) =>
    {
    //可以记录日志啥的
    }
    );
    //竞争获取锁。
    await policy.ExecuteAsync(async () => await db.LockTakeAsync(key, token, TimeSpan.MaxValue));
    try
    {
    return await func(obj);//获取到锁之后的具体执行的方法。
    }
    finally
    {
    await db.LockReleaseAsync(key, token); //最后一定要释放
    }
    }

    LockTakeAsync的时候根据key对应的token值是否已经被获取来作为条件。

  • CreateTransaction

    StackExchange.Redis 用multiplexer类实现Redis的一些列命令。我们的代码不能直接简单的映射到watch命令,因为,单纯调用watch是肯定成功的,这样会导致大家都"成功"(假的)。这里用的Condition的方式来实现。

    public async Task AddAfterReadAsync(string key, int value, string hashField = "hash_field")
    {
    //处理policy的结果为false的情况,一直重试。
    var policy = Policy.HandleResult<bool>(w => !w).RetryForeverAsync();
    //执行
    await policy.ExecuteAsync(async () =>
    {
    var db = GetDb(redisConfigModel.LockDbIndex);
    var trans = db.CreateTransaction();
    var oldValue = Convert.ToInt32(await db.StringGetAsync(key));
    trans.AddCondition(Condition.HashNotExists(key,
    hashField)); //这里确保hashField不存在。也可以用Condition.KeyNotExists(key)
    //这里不能await,因为每个命令的结果只有在执行了execute后才知道。
    trans.StringSetAsync(key, (oldValue + value).ToString());
    var execSuccess = await trans.ExecuteAsync();
    return execSuccess;
    });
    }

小结

这是一篇和redis有关的锁,事务的文章。写了我一整个下午。看完,感觉也没有多少东西。感觉开头链接中关于hashset还是有点意思的。

一篇和Redis有关的锁和事务的文章的更多相关文章

  1. 用Redis构建分布式锁-RedLock(真分布)

    在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简单的实现其实只需采用稍微增 ...

  2. 用Redis实现分布式锁 与 实现任务队列(转)

    这一次总结和分享用Redis实现分布式锁 与 实现任务队列 这两大强大的功能.先扯点个人观点,之前我看了一篇博文说博客园的文章大部分都是分享代码,博文里强调说分享思路比分享代码更重要(貌似大概是这个意 ...

  3. Redis实现分布式锁

    http://redis.io/topics/distlock 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但 ...

  4. Redis实现分布式锁与任务队列

    Redis实现分布式锁 与 实现任务队列 这一次总结和分享用Redis实现分布式锁 与 实现任务队列 这两大强大的功能.先扯点个人观点,之前我看了一篇博文说博客园的文章大部分都是分享代码,博文里强调说 ...

  5. redis咋么实现分布式锁,redis分布式锁的实现方式,redis做分布式锁 积极正义的少年

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  6. Redis实现分布式锁的正确姿势

    分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介绍Re ...

  7. 用redis实现分布式锁,秒杀案例(转)

    分布式锁的简单实现代码: 需要的jar包: jedis-2.9.0.jar. commons-pool2-2.4.2.jar import java.util.List; import java.ut ...

  8. 基于Redis的分布式锁真的安全吗?

    说明: 我前段时间写了一篇用consul实现分布式锁,感觉理解的也不是很好,直到我看到了这2篇写分布式锁的讨论,真的是很佩服作者严谨的态度, 把这种分布式锁研究的这么透彻,作者这种技术态度真的值得我好 ...

  9. 基于redis的分布式锁(转)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

随机推荐

  1. Python3 网络编程基础1

    目录 开发架构 C/S架构 B/S架构 OSI模型 应用层 表示层 会话层 传输层 网络层 数据链路层 物理层 TCP协议 socket 开发架构 C/S架构 client 和 server, 既客户 ...

  2. 每周一练 之 数据结构与算法(LinkedList)

    这是第三周的练习题,原本应该先发第二周的,因为周末的时候,我的母亲大人来看望她的宝贝儿子,哈哈,我得带她看看厦门这座美丽的城市呀. 这两天我抓紧整理下第二周的题目和答案,下面我把之前的也列出来: 1. ...

  3. 每周一练 之 数据结构与算法(Dictionary 和 HashTable)

    这是第五周的练习题,上周忘记发啦,这周是复习 Dictionary 和 HashTable. 下面是之前分享的链接: 1.每周一练 之 数据结构与算法(Stack) 2.每周一练 之 数据结构与算法( ...

  4. Java并发编程杂记(1)

    高并发: cpu -- 缓存 -- 内存 资源利用率 公平性 便利性   生活举例 --- 串行任务中的异步性:我在烧水的时候看书 --- 平衡点   安全性问题 --- 产生竞态条件 共享数据 -- ...

  5. java发送邮件基础方法(另附部分主流邮箱服务器地址、端口及设置方法)

    java发送邮件基础方法,可通过重载简化参数 import java.io.File; import java.io.UnsupportedEncodingException; import java ...

  6. salt

    更新于 3.25 23:16 salt简介 SaltStack是一个服务器基础架构集中化管理平台,具备配置管理.远程执行.监控等功能,基于Python语言实现,结合轻量级消息队列(ZeroMQ)与Py ...

  7. HTTP响应的结构是怎么样的?

    HTTP响应由三个部分组成:状态码(Status Code):描述了响应的状态.可以用来检查是否成功的完成了请求.请求失败的情况下,状态码可用来找出失败的原因.如果Servlet没有返回状态码,默认会 ...

  8. 《Java算法》判重算法-整数判重

    判重算法-整数判重 /** * 判断大于1,小于63的整数是否出现重复数字. * * 算法逻辑:先获取8 根据移位(1 << arrInt[i]) 得到2进制数100000000 , * ...

  9. swoole运行模式加速laravel应用的详细介绍

    本篇文章给大家带来的内容是关于swoole运行模式加速laravel应用的详细介绍,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 一.Swoole Swoole号称重新定义了PHP, ...

  10. Vsftpd运行的两种模式-xinetd运行模式和 standalone模式

    vsftpd运行的两种模式-xinetd运行模式和 standalone模式 vsftpd提供了standalone和inetd(inetd或xinetd)两种运行模式. standalone一次性启 ...