StackExchange.Redis学习笔记(四) 事务控制和Batch批量操作

Redis事物

Redis命令实现事务

Redis的事物包含在multiexec(执行)或者discard(回滚)命令中

和sql事务不同的是,Redis调用Exec只是将所有的命令变成一个单元一起执行,期间不会插入其他的命令。

这种方式不保证事务的一致性,即使中间有一条命令出错了,其他命令仍然可以正常执行,并且无法回滚

下面的例子演示了一个基本的事务操作

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name mike
QUEUED
127.0.0.1:6379> set age 20
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379> get name
"mike"

可以看到,直到调用Exec命令时,才开始执行之前的所有命令,同时会返回两个结果,discard 命令类似,就不贴代码了。

下面模拟一个会报错的命令来看一下

127.0.0.1:6379> get age
"20"
127.0.0.1:6379> get name
"mike"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr name
QUEUED
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) (integer) 21
127.0.0.1:6379> get age
"21"
127.0.0.1:6379>

我们同时将用户name和age 进行自增1操作,然而name不是数字类型,结果执行失败,但是age的自增操作仍然成功了。这无疑是个很令人不舒服的弊端,所以在写相关代码时要注意

乐观锁

前面说到通过multi命令只是保证一个事物中的所有命令可以在一起执行,显然只是实现这一点的话对于大部分的业务都是无法满足的。

所以Redis提供了Watch命令来监控一个key以达到乐观锁的效果。关于乐观锁的原理有不了解的小伙伴可以抽十分钟去科普一下

下面展示一个乐观锁实例:

这里模拟了两个客户端同时操作一个相同的键

左边为client1,我们用watch监控了name和age两个键,然后分别设置name和age的值。在exec命令之前,通过另一个客户端client2设置了name的值。

client1执行exec命令时,Redis检测到name的值已经被其他客户端改过了,因此在事物中的所有命令都会回滚。

watch命令是对整个连接有效的,用完之后可以用discard、unwatch、exec命令清除监视

StackExchange.Redis中的事物控制

在StackExchange.Redis是无法用watch multi命令来执行的,因为在并发环境下,会产生多个watch multi命令,全混在一起就乱套了。

但是StackExchange.Redis提供了一套非常简单易懂的创建事物的方式 ,下面为示例代码

 public void TestTran()
        {
            IDatabase db = StackExchangeRedisHelper.GetDatabase();
            string name = db.StringGet("name");
            string age = db.StringGet("age");
            Console.WriteLine("NAME:" + name);
            Console.WriteLine("Age:" + age);
            var tran = db.CreateTransaction();
            tran.AddCondition(Condition.StringEqual("name", name));
            Console.WriteLine("tran begin");
            tran.StringSetAsync("name", "leap");
            tran.StringSetAsync("age", 12);
            Thread.Sleep(4000);
            bool result = tran.Execute();
            Console.WriteLine("执行结果:" + result);
            Console.WriteLine("Age:" + db.StringGet("age"));
            Console.WriteLine("Name:" + db.StringGet("name"));
        }

这里通过CreateTransaction函数(multi)来创建一个事物,调用其Execute函数(exec)提交事物,其中的 "Condition.StringEqual("name", name)" 就相当于Redis命令中的watch name。

其中睡眠四秒是我需要在事物提交之前打开另一个客户端来修改name的值.最终的执行结果如下

NAME:leo
Age:20
tran begin
执行结果:False
Age:20
Name:mike

  在程序睡眠期间我用另一个客户端将name改成了mike,所以事物最终执行失败

通过查询Redis的慢日志。其调用的命令也是watch multi exec。(慢日志没有记录Exec命令,实际上是执行了的)

我们可以通过设置redis.windows-service.conf文件中的slowlog-log-slower-than的值为0让Redis记录所有的命令日志

127.0.0.1:6379> slowlog get 100
 1) 1) (integer) 293
    2) (integer) 1511257634
    3) (integer) 1
    4) 1) "GET"
       2) "name"
 2) 1) (integer) 292
    2) (integer) 1511257634
    3) (integer) 0
    4) 1) "GET"
       2) "age"
 3) 1) (integer) 291
    2) (integer) 1511257634
    3) (integer) 3
    4) 1) "SELECT"
       2) "0"
 4) 1) (integer) 290
    2) (integer) 1511257634
    3) (integer) 1
    4) 1) "SET"
       2) "age"
       3) "12"
 5) 1) (integer) 289
    2) (integer) 1511257634
    3) (integer) 1
    4) 1) "SET"
       2) "name"
       3) "leap"
 6) 1) (integer) 288
    2) (integer) 1511257634
    3) (integer) 1
    4) 1) "MULTI"
 7) 1) (integer) 287
    2) (integer) 1511257634
    3) (integer) 3
    4) 1) "GET"
       2) "name"
 8) 1) (integer) 286
    2) (integer) 1511257634
    3) (integer) 11
    4) 1) "WATCH"
       2) "name"
 9) 1) (integer) 285
    2) (integer) 1511257634
    3) (integer) 4
    4) 1) "GET"
       2) "age"
10) 1) (integer) 284
    2) (integer) 1511257634
    3) (integer) 6
    4) 1) "GET"
       2) "name"

  这里可能大家会有个疑惑,既然tran是直接调用的watch multi等命令,为什么不会有并发的顺序问题?

这是因为Tran开启后,所做的watch,stringset等操作,都会再调用Exec函数时把相应的命令封装成一个请求发送给Redis一起执行。这样每个事务之间都是独立的,就不会有问题了。

Batch批量操作

StackExchange.Redis中对于连续多次的缓存等请求,我们会多次调用相关的函数来执行Redis命令。然而这种方式有个弊端就是每一次的请求都需要等待返回结果

如果在网络状况不好的情况下,可能会造成不好的用户体验。 

对于这种问题可以用StackExchange.Redis提供的CreateBatch()解决

 public void TestPipeLine()
        {
            IDatabase db = StackExchangeRedisHelper.GetDatabase();
            var batch = db.CreateBatch();
            Task t1 = batch.StringSetAsync("name", "bob");
            Task t2 = batch.StringSetAsync("age", 100);
            batch.Execute();
            Task.WaitAll(t1, t2);
            Console.WriteLine("Age:" + db.StringGet("age"));
            Console.WriteLine("Name:" + db.StringGet("name"));
        }

batch会把所需要执行的命令打包成一条请求发到Redis,然后一起等待返回结果。这样批量操作的速度就大大提升啦!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏DOTNET

MongoDB模拟多文档事务操作

Mongodb不支持多文档原子性操作,因此依据两阶段提交协议(Two Phase Commits protocol)来模拟事务。 以两个银行账户之间的转账行为为...

4919
来自专栏搜云库

CentOs7.3 搭建 Redis-4.0.1 单机服务

CentOs7.3 搭建 Redis-4.0.1 单机服务 Redis 简介 Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据...

2475
来自专栏维C果糖

详述 IntelliJ IDEA 插件的安装及使用方法

温馨提示:IntelliJ IDEA 支持非常多的插件,熟练的使用插件,能够有效提高我们的开发效率以及用户体验。 正文 首先,进入插件安装界面: Mac:I...

4058
来自专栏黑泽君的专栏

day67_Redis学习笔记_03

  上边的配置说明当前该【从redis服务器】所对应的【主redis服务器】的IP是192.168.5.128,端口是6379。

983
来自专栏james大数据架构

android 项目中出现红色感叹号的解决方法

【问题原因】:工程中classpath中指向的包路径错误 【解决办法】:右键项目名称 BuildPath ---> Configure Build Paht.....

20010
来自专栏IT笔记

Spring Quartz任务案例源码实现

写在开始 上一篇有讲到 springTask任务案例源码实现 此篇,spring整合Quartz实现更强大的定时任务。 任务介绍 Quartz存储job方式就...

2864
来自专栏liulun

用Nim语言开发windows GUI图形界面程序

前言 本文得到了“樂師”的大力支持, 我们一起调试程序到深夜,要是没有他的帮忙, 我不知道要多久才能迈过这道坎, 另外“归心”还有其他人也提供了帮助, 他们都来...

3295
来自专栏pangguoming

Centos7下配置Redis开机自启动

最近在做作业的时候需要用到Redis缓存,由于每次重启服务器都需要重新启动Redis,也是忒烦人,于是就有了这一篇博客,好,废话不多说。 只有两个步骤: 设置...

6374
来自专栏互联网杂技

SpringBoot (一) :入门篇

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而...

1183
来自专栏Albert陈凯

Zookeeper安装部署调试命令

下载地址: http://www.apache.org/dyn/closer.cgi/zookeeper/ 我选用清华的镜像 https://mirrors...

2955

扫码关注云+社区