分布式开发

Redis 能否保证数据高可靠性

2020-05-23  本文已影响0人  殷天文

记录下工作中关于Redis的一些思考,主要关于Redis的事务,脚本,持久化

本文讨论的问题:

  1. Redis的事务或者执行lua脚本可以像关系型数据库事务那样,要么全部提交,要么全部回滚吗?

  2. 当脚本或者事务执行过程中发生宕机Redis中的数据会丢失吗?

原子性

在Redis的开发文档中可以了解到,Redis的事务以及Redis执行lua脚本都可以保证原子性(Redis的每条命令也是原子的),那么原子性可以保证什么?能否解决我们的问题?先来看下原子性的定义

来自维基百科对关系型数据库事务原子性的定义

事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行

来自百度百科原子操作的定义

原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断

Redis 所保证的原子性正是如此

Redis Transactions

如何使用Redis 事务

> MULTI // 开启事务
OK
> SET KEY VALUE // 执行命令,此时的命令只是入队,会在 EXEC 之后原子性的执行事务中所有命令
QUEUED
> EXEC  / DISCARD // 执行或者取消事务

Redis的事务保证

事务中可能发生的错误

EXEC 之前的错误,客户端可以通过检查服务端的响应来解决,当发生入队失败时(Redis响应值非QUEUED),客户端DISCARD当前事务

从Redis 2.6.5开始,Redis 会记录事务期间发生的错误,并拒绝执行事务,在执行EXEC时返回错误信息并自动丢弃该事务

但是EXEC 之后的错误,即使导致部分命令执行失败,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 的三种同步策略

即使使用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 和其他实例。

这种方式的好处

使用方式

-- 在执行Redis命令前调用,成功启用 script effects replication 返回true
redis.replicate_commands()

总结

所以说Redis无论是事务还是脚本,并不能做到像关系型数据事务一样,所以针对数据一致性要求较高的业务场景,并不适合使用Redis

而且从持久化方面来考虑,这也不是Redis的强项,Redis的优势正是基于内存,所以读写性能高。虽然宕机的可能性看似很极端,通常我们使用了某个服务后,我们会尽可能的保证它的高可用,但是我们需要知道Redis的"持久化"并不能保证我们的数据绝对安全,所以当我们的业务场景对数据一致性,持久化要求很高的时候,关系型数据库依旧是很好的选择

参考

Redis 设计与实现 AOF
Redis 设计与实现 事务
Redis 命令执行过程(上)
Redis 命令执行过程(下)

上一篇下一篇

猜你喜欢

热点阅读