记录下工作中关于Redis的一些思考,主要关于Redis的事务,脚本,持久化
本文讨论的问题:
Redis的事务或者执行lua脚本可以像关系型数据库事务那样,要么全部提交,要么全部回滚吗?
当脚本或者事务执行过程中发生宕机Redis中的数据会丢失吗?
原子性
在Redis的开发文档中可以了解到,Redis的事务以及Redis执行lua脚本都可以保证原子性(Redis的每条命令也是原子的),那么原子性可以保证什么?能否解决我们的问题?先来看下原子性的定义
来自维基百科对关系型数据库事务原子性的定义
事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行
来自百度百科原子操作的定义
原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断
Redis 所保证的原子性正是如此
- 不可分割,保证事务和脚本执行过程中,不会被其他客户端的命令打断
- 事务和脚本中的命令,要么都执行,要么都不执行
Redis Transactions
如何使用Redis 事务1
2
3
4
5> MULTI // 开启事务
OK
> SET KEY VALUE // 执行命令,此时的命令只是入队,会在 EXEC 之后原子性的执行事务中所有命令
QUEUED
> EXEC / DISCARD // 执行或者取消事务
Redis的事务保证
在Redis事务的执行过程中,永远不会执行另一个客户端发出的请求
所有命令都会执行,或者不执行。如果在执行
EXEC
之前,客户端与服务端失去响应,那么事务中所有的命令都不会执行,相反如果执行EXEC
保证所有的命令都执行。
事务中可能发生的错误
EXEC 之前:命令无法Queued,例如语法错误(错误的参数数量,错误的命令),或者一些其他错误,例如内存不足(Redis使用了maxmemory 限制最大内存)
EXEC 之后:命令也可能会失败,例如针对string类型使用list的命令
EXEC 之前的错误,客户端可以通过检查服务端的响应来解决,当发生入队失败时(Redis响应值非QUEUED),客户端DISCARD当前事务
从Redis 2.6.5开始,Redis 会记录事务期间发生的错误,并拒绝执行事务,在执行EXEC时返回错误信息并自动丢弃该事务
但是EXEC 之后的错误,即使导致部分命令执行失败,Redis还是会执行其他的剩余命令
Redis 事务的不回滚机制
虽然Redis保证了原子性,但是他的事务并不会像关系型数据库那样,在Redis事务中如果某条命令发生了错误,其他的命令会依旧执行,这点相比较关系型数据库来说不免有些”奇怪”了
Redis开发文档中给出的解释如下
只有语法错误才可能导致命令执行失败,大多数情况都是编程错误导致的
因为不需要回滚,使得 Redis 更简单 更高效
关于事务的更多内容👉 https://redis.io/topics/transactions
Redis lua
2.6.0 版本后,Redis 增加了对lua脚本的支持,脚本和Redis事务一样保证了原子性,执行脚本时不会执行其他脚本或Redis命令(所以不要让脚本运行时间过长),同样脚本也没有回滚机制,当脚本中出现lua的异常,或者Redis命令错误,也无法保证全部执行成功
Redis lua 和事务有点类似,但是有些场景使用事务是无法做到的,例如我想对Redis中的数据先读,然后根据原有数据变更,整个过程想要保证原子性,由于事务在EXEC之前无法获取返回值,使用lua 就非常合适
关于Redis lua 更多内容 👉 https://redis.io/commands/eval
Redis 执行命令时宕机数据会丢失吗
看到了这,第一个问题我们已经清楚了,Redis并没有回滚的能力,但是通常情况下,这些需要回滚的场景都是编码错误,我们是可以避免的。我们继续探寻第二个问题的答案
Redis是基于内存的数据库,所以当发生宕机或者停止后重新启动时,Redis会使用磁盘上的持久化文件来恢复数据,所以是否能恢复数据,能恢复多少数据,取决于使用哪一种持久化策略
简单说下两种持久化策略
RDB
按指定的时间间隔将内存中数据集写入快照
AOF
AOF会记录服务器接收的每个写入操作。当Redis命令执行成功后,命令会被传播到AOF程序中。AOF 的三种同步策略
- appendfsync always :每次有数据修改发生时都会同步到AOF文件
- appendfsync everysec :每秒钟同步一次,AOF的默认策略
- appendfsync no :将数据同步将给操作系统管理,通常linux系统30s会同步一次数据,但这取决于操作系统
即使使用always 也无法保证写入的每一条命令都被持久化,从命令执行成功到数据保存到硬盘之间,还是有一段非常小的间隔
回到我们的问题上,如果使用RDB,毫无疑问,数据只能恢复到上次的备份
即使使用AOF的话,如果在Redis事务执行期间宕机,那么这次事务还是相当于”没有执行”,由于命令还没来及写入AOF,在服务恢复后更不可能恢复数据。对于Redis客户端而言,会收到服务端的异常响应
写入AOF的过程也是会被打断的,Redis 文档中提到,如果Redis服务器突然崩溃,导致出现了”半写状态”的AOF文件时,服务器重新启动时,会检测到这种情况,并且退出提示用户使用 redis-check-aof
修复 AOF 文件。”半写状态”的事务或者命令会被删除,服务器可以重新启动。
如果写入AOF过程被打断,对于客户端而言可能是毫无感知的(看了下Redis命令执行相关,AOF应该是发生在响应客户端之后)
所以第二个问题,我们也清楚了,Redis 并不能保证我们写入的数据都安全的持久化
关于持久化的更多内容👉 https://redis.io/topics/persistence
扩展:脚本如何持久化
这里说的是AOF的情况
在Redis 5 之前,默认是将脚本本身传播到AOF中。这种传播方式的好处,不需要将脚本转成Redis命令,在写入AOF或者其他Redis实例时不会占用过多的带宽和CPU
复制脚本不允许脚本中出现随机性的写入,因为这会导致通过AOF恢复数据时,数据不一致,在这点上Redis做了一些限制,由于不是本文重点就不多说了,可以参考Redis开发文档
从Redis 3.2 开始新增了一种脚本复制方式 script effects replication
(Redis 5 开始默认使用这种方式处理脚本)。这种模式下,Redis 会收集脚本中所有修改数据的命令。当脚本执行完成后,这些命令被包装成一个事务,传播到AOF 和其他实例。
这种方式的好处
当脚本执行很慢的时候,会影响加载AOF重建数据的时间,这种情况使用
script effects replication
效率更高这种方式会允许脚本中存在随机性的写入
使用方式1
2-- 在执行Redis命令前调用,成功启用 script effects replication 返回true
redis.replicate_commands()
总结
所以说Redis无论是事务还是脚本,并不能做到像关系型数据事务一样,所以针对数据一致性要求较高的业务场景,并不适合使用Redis
而且从持久化方面来考虑,这也不是Redis的强项,Redis的优势正是基于内存,所以读写性能高。虽然宕机的可能性看似很极端,通常我们使用了某个服务后,我们会尽可能的保证它的高可用,但是我们需要知道Redis的”持久化”并不能保证我们的数据绝对安全,所以当我们的业务场景对数据一致性,持久化要求很高的时候,关系型数据库依旧是很好的选择