专栏首页bluesummerStackExchange.Redis学习笔记(四) 事务控制和Batch批量操作

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 条评论
登录 后参与评论

相关文章

  • NetCore在Docker中发布及运行 安装构建镜像启动容器DockerfileDocker-ComposeHttp连接请求过多问题

    之前写过一篇关于Docker的文章,回头看了一眼自己差点没有看明白...最近有时间又仔细研究了一遍(主要是生产环境真的要用到了...),顺便从0学习了一下Lin...

    蓝夏
  • 基于NPOI的Excel导入导出类库

    支持多sheet导入导出。导出字段过滤,合并行。特性配置导入验证,非空验证,唯一验证,错误标注等

    蓝夏
  • RabbitMQ学习总结

    蓝夏
  • V8 引擎空指针引用漏洞的新型利用技术

    去年,英国国家网络安全中心(NCSC)报告了一个V8编译器中存在的安全漏洞,随后Google便悄悄修复了该漏洞。这个漏洞ID为1003286,漏洞的具体信息可以...

    FB客服
  • 图计算黑科技:打开中文词嵌入训练实践新模式

    导语 | 在自然语言处理领域,文本表示学习技术可以帮助我们将现实世界转化为计算机可以处理的数据,以求更精准地建立学习模型。而在中文搜索场景下,同音词、易混词、错...

    腾讯云ES团队
  • 高可用架构设计(2) -hystrix要解决的分布式系统可用性问题以及其设计原则

    高可用性这个topic,然后咱们会用几讲的时间来讲解一下如何用hystrix,来构建高可用的服务的架构

    JavaEdge
  • Unix网络编程之IO模型

    首先,我们要了解IO模型先要知道在底层操作系统是通过哪些设备来实现数据的传输,其次要了解IO模型中哪些是发生阻塞调用操作,然后有了上述的基本认知之后,开始来了解...

    keithl
  • Java官网曝本地文件包含(LFI)漏洞,可读取超过460位Oracle公司员工邮箱

    微信号:freebuf 意大利安全研究人员Christian Galeone最近发现一枚Java官网存在的重大安全漏洞,该漏洞可读取网站敏感数据,包括超过460...

    FB客服
  • 从 Java 代码如何运行聊到 JVM 和对象的创建-分配-定位-布局-垃圾回收

    概括一下:程序员小张编写好的 Java 源代码文件经过 Java 编译器编译成字节码文件后,通过类加载器加载到内存中,才能被实例化,然后到 Java 虚拟机中解...

    IT技术小咖
  • Hyperf 初体验之事件机制(Event and Listener )

    下面说下具体如何使用。事件的用处还是非常多的。比如用户注册成功 发送一份激活邮件、或者第三方支付返回支付回调 我们也可以使用事件.....

    hedeqiang

扫码关注云+社区

领取腾讯云代金券