【Redis核心知识 三】Redis的事务机制

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: 【Redis核心知识 三】Redis的事务机制

其实和Mysql一样,Redis虽然作为一个非关系的K-V结构数据库,也是存在事务的,当然事务并不会有Mysql那么强,关于Mysql的数据库事务,可以看下我三年前一篇blog的介绍【数据库策略 二】数据库事务,关于事务的ACID四大特性并发的常见问题事务的隔离级别

事务简介

当多个客户端对同一个Key执行set操作的时候,客户端的get预期是会有偏差的,那么依赖于Redis的单线程特性,我们处理Redis的问题比Mysql的要简单一些。

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

事务基本操作

事务有三个基本操作,创建事务队列multi;执行事务exec;取消事务discard

事务的工作流程

执行的流程如下,当一条指令到来的时候要判断它是普通指令还是事务指令,

  • 如果是普通指令,则判断当前是否存在事务队列,如果不存在直接执行,如果存在则入队
  • 如果是事务指令,那么分为三种,multi为开启事务队列,exec为执行事务队列中的指令,执行完成后销毁队列,descrad为不执行队列中的指令,直接销毁队列

执行的时候按照先进先出的队列模式进行执行。

事务操作的注意事项

事务操作时出错的情况分为两种:指令书写错误语法性错误(例如让list实现自增),这种情况下处理机制是什么呢?

指令书写错误

若在事务队列中存在命令书写错误(类似于java编译性错误),则执行EXEC命令时,所有命令都不会执行

[root@192 redis-6.0.8]# redis-server config/redis-6379.conf
[root@192 redis-6.0.8]# redis-cli 
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set name tml
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 32
QUEUED
127.0.0.1:6379> set sex girl
QUEUED
127.0.0.1:6379> est color red
(error) ERR unknown command `est`, with args beginning with: `color`, `red`, 
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get name
"tml"
127.0.0.1:6379> get age
(nil)
127.0.0.1:6379>

可以看到,这种情况下age也没有被set成功,也就是如果一条指令书写错误,则事务队列中的所有指令均不执行。

指令语法错误

若在事务队列中存在语法性错误(类似于java的1/0的运行时异常),则执行EXEC命令时,其他正确命令会被执行,错误命令抛出异常

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name tml
QUEUED
127.0.0.1:6379> set name guochengyu
QUEUED
127.0.0.1:6379> set age 37
QUEUED
127.0.0.1:6379> incr name
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
4) (error) ERR value is not an integer or out of range
127.0.0.1:6379> get name
"guochengyu"
127.0.0.1:6379> get age
"37"
127.0.0.1:6379>

可以看到当让string类型进行自增时出现报错,但不影响其它指令的执行。

Redis事务无法回滚

通过上边两种异常的了解我们知道,当指令出现语法错误的时候,redis是不支持事务的回滚机制的。能做的只能通过持久化的备份去恢复,以及写代码的时候小心小心再小心。

锁的使用

锁可以用来控制多客户端的同时操作对事务的干预问题,同时也能控制分布式并发场景下的事务正常执行。

事务的watch监控锁

想象一个场景,如果多个客户端都想对同一个Key进行操作,如果我们只使用事务去限制,不一定能达到效果。例如我想让一个num自增1,客户端1和客户端2都接到了这个任务**【商品补货】**:客户端1创建了一个事务,并且让num自增1

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set age 50
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> exec
1) (integer) 52
127.0.0.1:6379>

结果在客户端1事务队列执行的时候,客户端2对num进行了自增得到了51,此时客户端1再执行事务的时候发现结果变为了52,事务怎么不具备原子性了呢?这都是并发导致的问题

127.0.0.1:6379> incr age
(integer) 51
127.0.0.1:6379>

为了解决这个问题,我们可以使用锁来监控key的状态,只要监控的key变化了,那么事务就取消执行,防止执行过程中的非原子性。还是上一个例子:客户端1开启锁并开启事务

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set age 50
OK
127.0.0.1:6379> watch age
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> set name tml
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>

同时客户端2在事务执行的过程中incr了num:

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> incr age
(integer) 51
127.0.0.1:6379>

那么执行事务的时候就返回了nil,事务执行失败,并且这个操作相当于销毁队列,连name的值都取不到,还有一点需要注意,当watch事务的时候,即使事务队列没有watch的key,如果key发生变化也会销毁队列,redis不会识别队列的key:

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set age 50
OK
127.0.0.1:6379> watch age
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name guochengyu
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>

例如这里,watch的是age,但是我事务里对name操作,当另一个客户端对age操作变化后,事务队列name的操作也是无效的。如果我们不想监控的话,可以使用unwatch命令,unwatch之后事务队列可以正常执行了。

分布式锁

处理补货问题的时候,可以使用watch来监控添加数量,防止重复添加,但使用watch的时候只能监控到要改变的key是否改变了,在超卖问题下,不仅要监控key是否改变了还要求各个客户端不能进行操作

这里我们需要使用setnx这个分布式锁来操作:

setnx key value

有值返回设置失败【无控制权】,无值返回设置成功【有控制权】

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> setnx lock-num 1    //拿到锁
OK
127.0.0.1:6379> expire lock-num 10
(integer) 1
127.0.0.1:6379> set num 10
OK
127.0.0.1:6379> incr num
(integer) 11
127.0.0.1:6379> del lock-num    //删除锁
(integer) 1
127.0.0.1:6379> get num
"11"
127.0.0.1:6379>

为了防止客户端拿到锁后宕机,我们一般需要给该锁设置一个过期时间,防止发生死锁。关于分布式锁的具体实践可以看我的另一篇实践的blog:【Redis实战系列 一】分布式锁实战,是基于公司工作的业务场景做的,介绍的比较全面,感兴趣的可以看下。

以上就是redis事务的全部内容,相比于Mysql可以说简单不少,而因为其事务不怎么强,一般用的也比较少,我感觉这里最重要的一个概念就是分布式锁吧,而且做过一次不错的实践,大家感兴趣可以看看。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
4月前
|
NoSQL Java Redis
实现基于Redis的分布式锁机制
实现基于Redis的分布式锁机制
|
8天前
|
缓存 NoSQL Redis
Redis 事务
10月更文挑战第18天
15 1
|
3月前
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
|
22天前
|
存储 缓存 NoSQL
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
33 2
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
|
22天前
|
设计模式 NoSQL 网络协议
大数据-48 Redis 通信协议原理RESP 事件处理机制原理 文件事件 时间事件 Reactor多路复用
大数据-48 Redis 通信协议原理RESP 事件处理机制原理 文件事件 时间事件 Reactor多路复用
34 2
|
22天前
|
SQL 分布式计算 NoSQL
大数据-42 Redis 功能扩展 发布/订阅模式 事务相关的内容 Redis弱事务
大数据-42 Redis 功能扩展 发布/订阅模式 事务相关的内容 Redis弱事务
22 2
|
27天前
|
NoSQL 关系型数据库 MySQL
Redis 事务特性、原理、具体命令操作全方位诠释 —— 零基础可学习
本文全面阐述了Redis事务的特性、原理、具体命令操作,指出Redis事务具有原子性但不保证一致性、持久性和隔离性,并解释了Redis事务的适用场景和WATCH命令的乐观锁机制。
138 0
Redis 事务特性、原理、具体命令操作全方位诠释 —— 零基础可学习
|
3月前
|
NoSQL 关系型数据库 Redis
Redis6入门到实战------ 九、10. Redis_事务_锁机制_秒杀
这篇文章深入探讨了Redis事务的概念、命令使用、错误处理机制以及乐观锁和悲观锁的应用,并通过WATCH/UNWATCH命令展示了事务中的锁机制。
Redis6入门到实战------ 九、10. Redis_事务_锁机制_秒杀
|
2月前
|
监控 NoSQL 关系型数据库
9)Redis 居然也有事务
9)Redis 居然也有事务
31 0