一文讲透Redis事务
保证隔离性;
无法保证持久性;
具备了一定的原子性,但不支持回滚;
一致性的概念有分歧,假设在一致性的核心是约束的语意下,Redis 的事务可以保证一致性。
1 事务原理
序号 | 命令及描述 |
---|---|
1 | MULTI 标记一个事务块的开始。 |
2 | EXEC 执行所有事务块内的命令。 |
3 | DISCARD 取消事务,放弃执行事务块内的所有命令。 |
4 | WATCH key [key ...] 监视一个 (或多个) key ,如果在事务执行之前这个 (或这些) key 被其他命令所改动,那么事务将被打断。 |
5 | UNWATCH 取消 WATCH 命令对所有 key 的监视。 |
事务开启,使用 MULTI , 该命令标志着执行该命令的客户端从非事务状态切换至事务状态 ;
命令入队,MULTI 开启事务之后,客户端的命令并不会被立即执行,而是放入一个事务队列 ;
执行事务或者丢弃。如果收到 EXEC 的命令,事务队列里的命令将会被执行 ,如果是 DISCARD 则事务被丢弃。
redis> MULTI
OK
redis> SET msg "hello world"
QUEUED
redis> GET msg
QUEUED
redis> EXEC
1) OK
1) hello world
2 事务的 ACID
2.1 原子性
redis> MULTI
OK
redis> SET msg "other msg"
QUEUED
redis> wrongcommand ### 故意写错误的命令
(error) ERR unknown command 'wrongcommand'
redis> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
redis> GET msg
"hello world"
redis> MULTI
OK
redis> SET msg "other msg"
QUEUED
redis> SET mystring "I am a string"
QUEUED
redis> HMSET mystring name "test"
QUEUED
redis> SET msg "after"
QUEUED
redis> EXEC
1) OK
2) OK
3) (error) WRONGTYPE Operation against a key holding the wrong kind of value
4) OK
redis> GET msg
"after"
命令入队时报错, 会放弃事务执行,保证原子性;
命令入队时正常,执行 EXEC 命令后报错,不保证原子性;
2.2 隔离性
未提交读(read uncommitted)
提交读(read committed)
可重复读(repeatable read)
串行化(serializable)
EXEC 命令执行前
EXEC 命令执行后
2.3 持久性
没有配置 RDB 或者 AOF ,事务的持久性无法保证;
使用了 RDB 模式,在一个事务执行后,下一次的 RDB 快照还未执行前,如果发生了实例宕机,事务的持久性同样无法保证;
使用了 AOF 模式;AOF 模式的三种配置选项 no 、everysec 都会存在数据丢失的情况 。always 可以保证事务的持久性,但因为性能太差,在生产环境一般不推荐使用。
2.4 一致性
维基百科
Consistency ensures that a transaction can only bring the database from one valid state to another, maintaining database invariants: any data written to the database must be valid according to all defined rules, including constraints, cascades, triggers, and any combination thereof. This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct. Referential integrity guarantees the primary key – foreign key relationship.
“约束” 由数据库的使用者告诉数据库,使用者要求数据一定符合这样或者那样的约束。当数据发生修改时,数据库会检查数据是否还符合约束条件,如果约束条件不再被满足,那么修改操作不会发生。
关系数据库最常见的两类约束是 “唯一性约束” 和 “完整性约束”,表格中定义的主键和唯一键都保证了指定的数据项绝不会出现重复,表格之间定义的参照完整性也保证了同一个属性在不同表格中的一致性。
“Consistency in ACID” 是如此的好用,以至于已经融化在大部分使用者的血液里了,使用者会在表格设计的时候自觉的加上需要的约束条件,数据库也会严格的执行这个约束条件。
执行 EXEC 命令前,客户端发送的操作命令错误,事务终止,数据保持一致性;
执行 EXEC 命令后,命令和操作的数据类型不匹配,错误的命令会报错,但事务不会因为错误的命令而终止,而是会继续执行。正确的命令正常执行,错误的命令报错,从这个角度来看,数据也可以保持一致性;
执行事务的过程中,Redis 服务宕机。这里需要考虑服务配置的持久化模式。
无持久化的内存模式:服务重启之后,数据库没有保持数据,因此数据都是保持一致性的;
RDB / AOF 模式: 服务重启后,Redis 通过 RDB / AOF 文件恢复数据,数据库会还原到一致的状态。
《设计数据密集型应用》
Atomicity, isolation, and durability are properties of the database,whereas consistency (in the ACID sense) is a property of the application. The application may rely on the database’s atomicity and isolation properties in order to achieve consistency, but it’s not up to the database alone. Thus, the letter C doesn’t really belong in ACID.
保证原子性,持久性和隔离性,如果这些特征都无法保证,那么事务的一致性也无法保证;
数据库本身的约束,比如字符串长度不能超过列的限制或者唯一性约束;
业务层面同样需要进行保障 。
2.5 事务特点
保证隔离性;
无法保证持久性;
具备了一定的原子性,但不支持回滚;
一致性的概念有分歧,假设在一致性的核心是约束的语意下,Redis 的事务可以保证一致性。
3 Lua 脚本
3.1 简介
减少网络开销。将多个请求通过脚本的形式一次发送,减少网络时延。
原子操作。Redis 会将整个脚本作为一个整体执行,中间不会被其他命令插入。
复用。客户端发送的脚本会永久存在 Redis 中,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。
序号 | 命令及描述 |
---|---|
1 | EVAL script numkeys key [key ...] arg [arg ...] 执行 Lua 脚本。 |
2 | EVALSHA sha1 numkeys key [key ...] arg [arg ...] 执行 Lua 脚本。 |
3 | SCRIPT EXISTS script [script ...] 查看指定的脚本是否已经被保存在缓存当中。 |
4 | SCRIPT FLUSH 从脚本缓存中移除所有脚本。 |
5 | SCRIPT KILL 杀死当前正在运行的 Lua 脚本。 |
6 | SCRIPT LOAD script 将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。 |
3.2 EVAL 命令
EVAL script numkeys key [key ...] arg [arg ...]
script
是第一个参数,为 Lua 5.1 脚本;第二个参数
numkeys
指定后续参数有几个 key;key [key ...]
,是要操作的键,可以指定多个,在 Lua 脚本中通过KEYS[1]
,KEYS[2]
获取;arg [arg ...]
,参数,在 Lua 脚本中通过ARGV[1]
,ARGV[2]
获取。
redis> eval "return ARGV[1]" 0 100
"100"
redis> eval "return {ARGV[1],ARGV[2]}" 0 100 101
1) "100"
2) "101"
redis> eval "return {KEYS[1],KEYS[2],ARGV[1]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
下面演示下 Lua 如何调用 Redis 命令 ,通过 redis.call() 来执行了 Redis 命令 。
redis> set mystring 'hello world'
OK
redis> get mystring
"hello world"
redis> EVAL "return redis.call('GET',KEYS[1])" 1 mystring
"hello world"
redis> EVAL "return redis.call('GET','mystring')" 0
"hello world"
3.3 EVALSHA 命令
redis> EVALSHA sha1 numkeys key [key ...] arg [arg ...]
实例如下:
redis> SCRIPT LOAD "return 'hello world'"
"5332031c6b470dc5a0dd9b4bf2030dea6d65de91"
redis> EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0
"hello world"
4 事务 VS Lua 脚本
从定义上来说, Redis 中的脚本本身就是一种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快。
因为脚本功能是 Redis 2.6 才引入的, 而事务功能则更早之前就存在了, 所以 Redis 才会同时存在两种处理事务的方法。
不过我们并不打算在短时间内就移除事务功能, 因为事务提供了一种即使不使用脚本, 也可以避免竞争条件的方法, 而且事务本身的实现并不复杂。
-- https://redis.io/
为了避免 Redis 阻塞,Lua 脚本业务逻辑不能过于复杂和耗时;
仔细检查和测试 Lua 脚本 ,因为执行 Lua 脚本具备一定的原子性,不支持回滚。
END
腾讯将微信QQ故障定性为一级事故
🌟 活动推荐
微信扫码关注该文公众号作者