Skip to main content

原子性保证

David LiuAbout 4 min

原子性保证

Redis 并不保证原子性。但是 Redis中有一些原子操作。

  • 原子性:事务中的命令要不全部成功,要不全部失败并回滚)。

  • 原子操作:原子操作是指在多线程或并发编程中,一个无法或不需要进一步分割的最小的操作单元。这种操作要么完全执行,要么完全不执行,且其执行过程中不会被其他线程或操作打断。原子操作是并发控制中用于防止竞态条件的重要概念。

    原子操作本身的定义主要关注于其不可分割的性质,并不直接包括错误处理或回滚机制。

    并不保证出错会回滚,eg.

    • 硬件级原子操作:如处理器指令实现的原子操作,如果发生错误(例如内存访问错误),这类操作通常不会执行任何回滚操作。
    • 软件级原子操作:在软件层面,原子操作通常通过锁或其他同步机制实现。如果这些操作在执行过程中遇到错误,它们不会自动回滚之前的操作,但是可以由程序员设计错误处理逻辑来决定如何响应错误。这可能包括释放获取的锁,记录错误信息,或者重试操作。

事务

命令

MULTI:开启事务

EXEC:执行事务

DISCARD:取消事务(只是清空暂存的命令队列,不能起到回滚的作用)

异常处理

组队时错误

如下,我们在组队时输入错误的指令,redis会之间将所有指令都会失效,因为这是一个问题队列。

执行时错误

执行时错误比较特殊,他在按序处理所有指令,遇到错误就按正常流程处理继续执行下去。

不支持回滚原因

大概的意思是,作者不支持事务回滚的原因有以下两个:

  • Redis 事务的执行时,错误通常都是编程错误造成的,这种错误通常只会出现在开发环境中,而很少会在实际的生产环境中出现,所以他认为没有必要为 Redis 开发事务回滚功能;
  • 不支持事务回滚是因为这种复杂的功能和 Redis 追求的简单高效的设计主旨不符合。

我认为还可以再加一点:redis并没有严格的保证了数据的原子性操作,这样的好处在于redis不需要像数据库一样还要保存回滚日志等,可以让redis执行的更快。

这里不支持事务回滚,指的是不支持事务运行时错误的事务回滚。

Lua

为什么建议 Redis+Lua 的方式? 主要有两点原因:

  • 减少了网络开销 :我们可以利用 Lua 脚本来批量执行多条 Redis 命令,这些 Redis 命令会被提交到 Redis 服务器一次性执行完成,大幅减小了网络开销。
  • 原子性 :一段 Lua 脚本可以视作一条命令执行,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰。

使用lua脚本可以在执行一串redis命令时, 实现一定原子性(lua脚本中多条指令执行过程中不会被插入新的指令), 但是并不能在命令执行出错时回滚之前的结果

出现错误会立即停止,然后返回一个错误码


Lua 脚本同样支持批量操作多条命令。一段 Lua 脚本可以视作一条命令执行,可以看作是 原子操作 。也就是说,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰,这是 pipeline 所不具备的。

并且,Lua 脚本中支持一些简单的逻辑处理比如使用命令读取值并在 Lua 脚本中进行处理,这同样是 pipeline 所不具备的。

不过, Lua 脚本依然存在下面这些缺陷:

  • 如果 Lua 脚本运行时出错并中途结束,之后的操作不会进行,但是之前已经发生的写操作不会撤销,所以即使使用了 Lua 脚本,也不能实现类似数据库回滚的原子性。
  • Redis Cluster 下 Lua 脚本的原子操作也无法保证了,原因同样是无法保证所有的 key 都在同一个 hash slot(哈希槽)上。