Redis事务及实现乐观锁

事务

  • 事务是一个单独的隔离操作:事务中所有命令都会序列化并按照顺序执行,不会被其他客户端发送来的命令打断
  • 事务是一个原子操作:事务中的命令要么全被执行,要么全失败

① 原子性 ( Atomic )

  • 事务的操作作为整体执行,要么全部执行,要么全部失败

    ② 一致性 ( Consistency )

  • 数据在执行前和执行后处于一致状态

    ③ 隔离性 ( Isolation )

  • 多个事务之间是隔离的,互不影响

    ④ 持久性 ( Durability )

  • 一旦事务提交了,会持久化写到数据库,对数据库的修改是永久性的

Redis事务

Redis事务本质

一组命令的集合(multi、exec、watch等)!一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行。

因Redis是单进程的,所以也理解为事务总是带有隔离性的,但事务没有隔离级别的概念

Redis单条命令是保证原子性的,但是事务不保证原子性,也没有回滚

redis 事务的三个阶段

  • 开启事务(multi)
    • 执行后,客户端可以继续向服务器发送 任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有 队列中的命令才会被执行。
  • 命令入队(···)
  • 执行事务(exec)
    • 执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排 列。 当操作被打断时,返回空值 nil 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 开启事务
127.0.0.1:6379> multi
OK

-- 命令入队
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED

-- 执行事务
127.0.0.1:6379> exec
1) OK
2) OK
3) "v2"
4) OK

Redis事务相关命令引发的问题

1. 编译型异常

编译型异常(代码有问题,命令有错),十五中所有的命令都不会被执行!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED

-- 错误的命令
127.0.0.1:6379> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED

-- 执行事务报错
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

-- 所有的命令都不会执行
127.0.0.1:6379> get k4
(nil)

2.运行时异常

运行时异常(1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec

-- 虽然第一条执行报错了,但是后面的依旧执行成功!
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"

总结:

  • 如果在一个事务中的命令出现错误(编译型异常),那么所有的命令都不会执行
  • 如果在一个事务中出现运行错误(运行时异常),那么正确的命令会被执行

Redis实现乐观锁

  • WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个 或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC 命令

首先来模拟一个正常的环境,假设有100元本钱,支出20元,本钱剩余80元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK

-- 监视money对象
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 out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

模拟在执行前,有另一个客户端对money进行修改操作

可以看到,事务执行失败!

exec会自动解锁,所以我们再尝试重新加锁,此时就会正常执行!

  • 也可以使用unwatch命令取消watch对所有key 的监控。
  • 另外discard命令可以清空事务队列,并放弃执行事务,且客户端会从事务状态中退出。

Jedis再次理解事务

新建maven项目,导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<repositories>
<repository>
<id>alimaven</id>
<name>Maven Aliyun Mirror</name>
<url>https://repo.maven.apache.org/maven2/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
</dependencies>

正常执行的事务

模拟异常

可以看到1/0发生了运行时异常,被catch捕获到,然后放弃执行事务

请我喝杯咖啡吧~

支付宝
微信