美文网首页分布式开发
Redis 能否保证数据高可靠性

Redis 能否保证数据高可靠性

作者: 殷天文 | 来源:发表于2020-05-23 11:18 被阅读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的事务保证

    • 在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 效率更高

    • 这种方式会允许脚本中存在随机性的写入

    使用方式

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

    总结

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

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

    参考

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

    相关文章

      网友评论

        本文标题:Redis 能否保证数据高可靠性

        本文链接:https://www.haomeiwen.com/subject/bnqfohtx.html