【Redis-04】Redis事务、Lua
0. 前言
为了保证多条命令组合的原子性,Redis提供了简单的事务功能以及集成Lua脚本来解决这个问题。本节首先简单介绍Redis中事务的使用方法以及它的局限性,之后重点介绍Lua语言的基本使用方法,以及如何将Redis和 Lua脚本进行集成,最后给出Redis管理Lua脚本的相关命令。
1. Redis的事务定义
Redis事务是一个单独的隔离操作:
- 事务中的所有命令都会序列化、按顺序地执行。
- 事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- Redis事务的主要作用就是串联多个命令防止别的命令插队。
熟悉关系型数据库的用户应该对事务比较了解,简单地说,事务表示一组动作,要么全部执行,要么全部不执行。例如在社交网站上用户A关注了用户B,那么需要在用户A的关注表中加入用户B,并且在用户B的粉丝表中添加用户A,这两个行为要么全部执行,要么全部不执行,否则会出现数据不一致的情况。
2.Multi、Exec、discard
Redis提供了简单的事务功能,将一组需要一起执行的命令放到multi和exec两个命令之间。multi命令代表事务开始,exec命令代表事务结束,它们之闻的命令是原子顺序执行的,例如下面操作实现了上述用户关注问题。
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> sadd user:a:follow user:b
QUEUED
127.0.0.1:6379(TX)> sadd user:b:fans user:a
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 1
2) (integer) 1
组队的过程中可以通过discard来放弃组队。
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> sadd zhangsan 23sui
QUEUED
127.0.0.1:6379(TX)> discard
OK
2.1 举🌰
- 命令错误
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> sadd k1 v1
QUEUED
127.0.0.1:6379(TX)> sadd k2
(error) ERR wrong number of arguments for 'sadd' command
127.0.0.1:6379(TX)> sadd k3 c
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
3.事务的错误处理
组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
4.悲观锁
悲观锁(Pessimistic Lock) ,顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
5.乐观锁
乐观锁(Optimistic Lock) , 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。 乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
6. WATCH key [key …]
在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
有些应用场景需要在事务之前,确保事务中的key没有被其他客户端修改过,才执行事务,否则不执行(类似乐观锁)。Redis提供了watch命令来解决这类问题,表3-2展示了两个客户端执行命令的时序。
可以看到“客户端-1”在执行multi之前执行了watch命令,“客户端-2”在“客户端-1“执行exec之前修改了key值,造成事务没有执行(exec结果为nil),整个代码如下所示:
127.0.0.1:6379> set key "java" // 客户端1
OK
127.0.0.1:6379> watch key // 客户端1
OK
127.0.0.1:6379> multi // 客户端1
OK
127.0.0.1:6379> append key python // 客户端2
(integer) 10
127.0.0.1:6379(TX)> append key jedis // 客户端1
QUEUED
127.0.0.1:6379(TX)> exec // 客户端1
(nil)
127.0.0.1:6379> get key // 客户端1
"javapython"
Redis提供了简单的事务,之所以说它简单,主要是因为它不支持事务中的回滚特性,同时无法实现命令之间的逻辑关系计算,当然也体现了 Redis的“keep it simple”的特性,下一小节介绍的Lua脚本同样可以实现事务的相关功能,但是功能要强大很多。
======================================================
在第二个终端中watch multi incrby exec 后nil
这是因为乐观锁原因,终端一执行后,版本号会改变。
7. unwatch
取消 WATCH 命令对所有 key 的监视。
如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。
8. Redis事务三特性
1.单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
2.没有隔离级别的概念
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
3.不保证原子性
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
9. Lua
9.1 数据类型及其逻辑处理
Lua语言提供了如下几种数据类型:booleans(布尔)
、numbers(数值)
、strings(字符串)
、tables(表格)
,和许多高级语言相比,相对简单。下面将结合例子对Lua的基本数据类型和逻辑处理进行说明。
-
字符串
-
数组
-
哈希