Redis 攻略面经(七)-- Redis 事务的使用和特点

1,088 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第十九天,点击查看活动详情

📣 大家好,我是Zhan,一名个人练习时长一年半的大二后台练习生🏀

📣 这篇文章是复习 Redis 缓存实践中的问题 的学习笔记📙

📣 如果有不对的地方,欢迎各位指正🙏🏼

📣 与君同舟渡,达岸各自归🌊


👉引言

我们知道MySQL的事务,知道Java中的事务,但是似乎很少提到Redis中的事务,但是在Redis中我们也要保证命令按照顺序串行化执行队列中的命令,那么Redis是如何在命令层次和底层实现事务的呢?

Redis 事务的本质是一次性、顺序型、排他性的执行一个队列中的一系列命令。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

那么本文就从应用层面来探讨一下Redis 事务的使用:


🛢 Redis 基本命令

mysql中的事务开始是begin,结束时commit,那么Redis怎么开启事务和结束事务,又有哪些不同于MySQL的地方呢?

  • MULTI:开启事务,也就是从这个位置开始,所有的命令都会逐个放进队列中,也就是相当于MySQL的begin
  • EXEC:执行事务中的所有操作命令,也就是提交事务,相当于MySQL中的commit
  • DISCARD:取消事务,也就是回滚事务,相当于MySQL中的rollback
  • WATCH:监视一个或多个key,如果事务在执行前,这个key被其他命令修改,就会中断事务,这就是区别于MySQL的地方
  • UNWATCH:取消所有的watch监视

🕶 WATCH & CAS

既然有区别于MySQL的命令,那么它有什么使用场景呢?其实它和CAS有关,我们来看看具体怎么使用:

先给一个场景,因为我们的对策都来源于问题,那么有了问题,我们才会去设计对应的命令去解决,那么场景如下:

对于一个商品,它的存货只有一件了,但是现在两个线程都进来了,两个线程都判断存货大于0,都通过了校验,进行了下单操作,导致最后出现了两个订单,且订单的余量变为了-1

但是有了WATCH我们就很好解决这个问题了,我们先回顾一下watch的作用:监视一个或多个key,如果事务在执行前,这个key被其他命令修改,就会中断事务,根据这个作用我们可以写出下面这段代码:

WATCH capacity
capacity = GET capacity
capacity = capacity - 1
MULTI 
SET capacity &capacity
EXEC

有了上面的代码,如果在WATCH执行之后,EXEC执行之前,有其他的客户端修改了容量的值,当前客户端的事务就会失效,程序要做的就是不断的重试,直到没有发生碰撞为止

那WATCH实现的底层逻辑是什么呢?

其实就是我们所说的CAS:compare and swap

  • 在开始事务前监听一个键值对,记录下来这个值
  • 在提交事务后,拿到键值对的当前值
  • 进行两个值的比较(Compare):
    • 如果值没有发生变化,就执行事务
    • 如果值发生了变化就回滚事务
  • 最后取消监控,流程图如下:

🎈 事务的回滚

大家如果有使用关系型数据库的经验,可能知道Redis在事务失败的时候不进行回滚,而是继续执行余下的命令后觉得有点奇怪

那可能大家有疑问:DISCARD不是回滚吗?但其实这里所谓的回滚是指:在EXEC执行过程中,出现了命令的错误,会继续往下执行,不会因此中断,所以其实我们可以发现这些问题属于是开发者出现的编程错误,而不是生产环境的问题,因此不需要对回滚进行支持,因为回滚不能解决编程错误带来的问题,例如对错误的类型执行了INCR,很明显这不是回滚能处理的

🎯 事务的四大特性

🔴 原子性

首先通过上文知道,运行期的编程错误是不会回滚的,所以其实很多文章由此说Redis事务违背原子性的

但是Redis官方文档给出的解释是:所有的命令,要么全部执行,要么全部不执行,而不是全部执行成功

🟢 一致性

redis事务可以保证命令失败的情况下得以回滚,数据能恢复到没有执行之前的样子,是保证一致性的,除非redis进程意外终结。

🟡 隔离性

redis事务是严格遵守隔离性的,原因是redis是单进程单线程模式,可以保证命令执行过程中不会被其他客户端命令打断。但是,Redis不像其它结构化数据库有隔离级别这种设计。

🟠 持久性

redis事务是不保证持久性的,这是因为redis持久化策略中不管是RDB还是AOF都是异步执行的,不保证持久性是出于对性能的考虑。


📕 事务的其他实现方式

Redis 事务除了命令,有其他的实现方式嘛?

其实对于一个Java后台程序员,与我接触最多的Redis事务其实是Lua脚本,很少使用到MULTI,DISCARD,EXEC这种命令。

基于Lua脚本,Redis可以保证脚本内的命令一次性、按顺序的执行,当然,它也不提供事务运行错误的回滚,与上述的情况一致


💬 总结

本文讲了Redis事务的命令行使用以及一些介绍:

  • 对于Redis命令:我们提到了基本的事务开启、提交、弃用三种命令
  • 然后介绍了WATCH这个特殊的命令,介绍了它的使用场景和使用原理
  • 对于事务的回滚:Redis的事务回滚机制相对于其他的很特殊,我们也做了讲解
  • 对于事务的四大特性:我们分别讲述了四个特性的实现,并且提到并没有实现持久性
  • 对于事务的其他实现方法:这就是我们后台程序员可能接触比较多的Lua脚本 相信读完今天的文章,大家能对Redis 事务的使用,以及Redis事务的特点有了更深一步的理解~

🍁 友链


✒写在最后

都看到这里啦~,给个点赞再走呗~,也欢迎各位大佬指正以及补充,在评论区一起交流,共同进步!也欢迎加微信一起交流:Goldfish7710。咱们明天见~