Redis事务
Redis事务的本质是一组命令的集合
一个事务中所有命令会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
事务执行三阶段:
-
开启:以 MULTI 开始一个事务
-
入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
-
执行:由 EXEC 命令触发事务
1、事务相关命令
命令 | 描述 |
---|---|
MULTI | 开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令系列 |
EXEC | 执行事务中的所有操作命令 |
DISCARD | 取消事务,放弃执行事务块中的所有命令 |
WATCH | 监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令 |
UNWATCH | 取消WATCH对所有key的监视 |
2、相关操作
# 标准事务执行
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> set k1 hello # 命令入队
QUEUED
127.0.0.1:6379> set k2 redis # 命令入队
QUEUED
127.0.0.1:6379> EXEC # 执行事务
1) OK
2) OK
127.0.0.1:6379> get k1 # 验证是否执行
"hello"
127.0.0.1:6379> get k2
"redis"
# 事务取消
127.0.0.1:6379> MULTI # 开始事务
OK
127.0.0.1:6379> set k3 test1
QUEUED
127.0.0.1:6379> set k4 test2
QUEUED
127.0.0.1:6379> DISCARD # 取消事务
OK
127.0.0.1:6379> EXEC # 因为事务被取消了,所以执行报错
(error) ERR EXEC without MULTI
# 事务出现从错误处理
# 1、语法错误(编译器错误)
# 语法错误会使得事务提交失败,数据恢复原样
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> set k1 11
QUEUED
127.0.0.1:6379> set k2 22
QUEUED
127.0.0.1:6379> error k3 33 # 执行一条错误的命令
(error) ERR unknown command `error`, with args beginning with: `k3`, `33`,
127.0.0.1:6379> exec # 执行事务,报错,命令皆未执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1 # 验证是否执行
"hello" # 值未变
127.0.0.1:6379> get k2
"redis"
# 2、类型错误(运行时错误)
# 触发运行时错误时,事务将会跳过错误的命令继续执行,其他正常的命令可以被执行成功
# 此处可以说明:Redis单条指令保证原子性,但是Redis事务不能保证原子性
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> set k1 11
QUEUED
127.0.0.1:6379> INCR k2 # 类型错误,对string类型进行自增操作
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC # 执行事务,可以发现虽然第二条命令报错了,但是其他的命令正常执行了
1) OK
2) (error) ERR value is not an integer or out of range
3) "redis"
127.0.0.1:6379> get k1 # 获取k1的值为事务中修改后的值
"11"
3、监控
3.1、乐观锁和悲观锁
悲观锁(Pessimistic Lock):
- 很悲观,认为什么时候都会出现问题,无论做什么都会加锁
- 传统的关系型数据库里边就用到了很多这种锁机制,比如行锁、表锁,读锁,写锁等,都是在操作之前先上锁
- 在Java中,synchronized的思想也是悲观锁
乐观锁(Optimistic Lock):
- 很乐观,认为什么时候都不会出现问题,做什么都不加锁。只有在更新数据的时候去判断一下该数据在此期间是否有过变更
- 乐观锁适用于多读的应用类型,这样可以提高吞吐量
- 乐观锁策略:提交版本必须大于记录当前版本才能执行更新
乐观锁一般会使用版本号机制或CAS操作实现
一般会使用版本号机制或CAS操作实现:
-
version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
核心SQL代码:
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
-
CAS(Check And Set【先检查再动手设置】)
CAS操作方式:即 compare and set,CAS是乐观锁技术,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
3.2、Watch监控
使用
watch key
监控指定数据,相当于乐观锁加锁,其执行流程如下
上图源自:http://c.biancheng.net/view/4544.html
# 正常执行
127.0.0.1:6379> set money 100 # 设置初始金额
OK
127.0.0.1:6379> set use 0 # 设置已消费金额
OK
127.0.0.1:6379> WATCH money # 监控money,相当于给money上锁
OK
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> DECRBY money 20 # 消费20,初始金额-20
QUEUED
127.0.0.1:6379> INCRBY use 20 # 消费金额+20
QUEUED
127.0.0.1:6379> EXEC # 执行事务,正常无报错
1) (integer) 80
2) (integer) 20
# 模拟多线程修改值
# 开启两个Xshell客户端连接同一个redis
# 在第一个客户端1开始操作
127.0.0.1:6379> WATCH money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY use 20
QUEUED
127.0.0.1:6379> # 执行到此处,停止操作,模拟另一个线程修改数据,切换到另一个客户端2
127.0.0.1:6379> INCRBY money 500 # 操作money,改变其值+500
(integer) 580
127.0.0.1:6379> # 无需再操作,切回客户端1执行事务
127.0.0.1:6379> EXEC # 发现事务执行返回空,即所有命令都未执行
(nil)
127.0.0.1:6379> get money # 查询money,发现和客户端2增加后的值一致,说明客户端1后来的事务并未执行
"580"
127.0.0.1:6379> get use # 同理
"20"
UNWATCH命令用于解除WATCH的监控,但是需要注意的是:每次提交执行EXEC后都会自动释放锁,不管是否成功,所以UNWATCH无需在EXEC后执行
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/147332.html