lua脚本执行redis命令能保证“原子性”吗?就能保证“数据一致性”

前言

redis 的 lua 脚本大家都了解过或者用过吧。 lua 脚本执行具有原子性特点,使其在分布式锁、限流、库存扣减 等场景广泛使用!

redis 中 原子性 这一概念经常被错误的理解成 “要么全部执行,要么全都不执行”。以及很多同学把redis 中的事务 等同于数据库中的事务。

✔本篇文章就带大家理解一下redis 中的事务 和 lua 脚本有啥区别,以及命令执行过程发生错误会如何处理。同时也把 Redis Pipeline 带上一起分析了吧。

lua 脚本、事务、pipline 对比

基本概念

1. Redis Lua 脚本

Redis 从 2.6 版本开始支持 Lua 脚本,允许开发者将一系列 Redis 命令封装在一个 Lua 脚本中。Redis 会以原子方式执行这个脚本,在脚本执行期间,不会执行其他客户端的命令。

2. Redis 事务

❗❗❗Redis 的事务不支持回滚,这个和数据库事务可回滚是有区别的

Redis 事务通过 MULTIEXECDISCARDWATCH 等命令实现。使用 MULTI 开启事务后,后续的命令会被放入队列,直到调用 EXEC 时,队列中的命令会被依次执行。WATCH 命令可以用于实现乐观锁,确保事务执行时被监视的键未被其他客户端修改。

3. Redis Pipeline

Pipeline(管道)是一种客户端优化技术,允许客户端将多个命令一次性发送给 Redis 服务器,服务器处理完这些命令后再一次性将结果返回给客户端。这样可以减少客户端与服务器之间的网络往返次数,提高性能。

✔原子性保证,执行中出错怎么处理

❗❗❗这里说的原子性,指的是不可拆分,不可中断的原子性操作。所以,需要注意的是,不管是Redis的事务还是Lua,都没办法回滚,一旦执行过程中有命令失败了,都是不支持回滚的。

1. Lua 脚本

具有原子性。在脚本执行过程中,不会被其他客户端的命令打断。

如果脚本执行过程中出现错误,整个脚本的执行会停止,且没有回滚机制,已执行的部分操作不会被撤销

示例 :

redis.call('set', 'key1', 'value1')
redis.call('incr', 'key1')  -- 这里会报错,因为 key1 是字符串
redis.call('set', 'key2', 'value2')

在这个脚本中,incr 命令会报错,脚本执行会停止,set key2 value2 不会执行,但 set key1 value1 已经生效。

2. 事务

Redis 事务保证了命令执行的原子性,执行过程,不会被其他客户端的命令插入。

如果某个命令出错,其他命令仍会继续执行,同样没有回滚机制。不过,如果在命令入队时出现语法错误(执行之前),整个事务会失败

执行之前是在什么时候:
命令可能排队失败,因此在调用 EXEC 之前可能会出现错误。例如,命令可能在语法上是错误的(错误的参数数量、错误的命令名称等),或者可能存在一些关键情况,如内存不足情况(如果服务器使用 maxmemory 指令配置为具有内存限制)。
https://redis.io/docs/latest/develop/interact/transactions/#errors-inside-a-transaction

示例:

MULTI
SET key1 value1
INCR key1  -- 对字符串执行 INCR 会在执行时出错
EXEC

执行结果中,SET 命令成功,INCR 命令报错,但事务不会回滚 SET 操作。

3. Pipeline

Pipeline 不保证原子性。

它只是将多个命令批量发送,服务器按顺序执行这些命令,但这些命令之间是相互独立的。一个命令执行失败不会影响其他命令的执行。
示例:

public static void main(String[] args) {
        // 创建 Jedis 实例并连接到 Redis 服务器
        try (Jedis jedis = new Jedis("localhost", 6379)) {
            // 创建 Pipeline 对象
            Pipeline pipeline = jedis.pipelined();

            // 向 Pipeline 中添加命令
            pipeline.set("key1", "value1");
            Response<String> getResponse = pipeline.get("nonexistent_key");
            Response<Long> incrResponse = pipeline.incr("counter");

            // 执行 Pipeline 中的所有命令
            List<Object> results = pipeline.syncAndReturnAll();

            // 打印执行结果
            System.out.println(results);

            // 也可以通过 Response 对象单独获取每个命令的结果
            System.out.println("Get result: " + getResponse.get());
            System.out.println("Incr result: " + incrResponse.get());
        }
    }

在这个 Pipeline 中,get('nonexistent_key') 可能会失败,但 setincr 命令依然会正常执行。

性能表现

1. Lua 脚本

Lua 脚本将多个操作封装在一个脚本中,减少了客户端与服务器之间的网络往返次数。并且脚本在服务器端执行,减少了命令解析的次数,通常在处理复杂逻辑时性能更优

2. 事务

事务也能减少网络往返次数,因为多个命令可以一次性发送。但事务中的每个命令都需要在服务器端进行解析和执行,在处理大量命令时可能会有一定性能开销

3. Pipeline

Pipeline 主要通过减少网络往返时间来提高性能。对于大量独立的命令,使用 Pipeline 可以显著提高执行效率。但由于命令之间没有原子性保证,在需要保证数据一致性的场景下可能不适用

适用场景

1. Lua 脚本

适用于需要实现复杂业务逻辑、保证操作原子性的场景,如分布式锁、限流、库存扣减等。通过 Lua 脚本可以将多个操作封装在一起,避免了多次网络交互和并发问题。

2. 事务

适用于简单的批量操作,需要保证一组命令原子执行的场景,如同时更新多个相关的键值对。同时,WATCH 命令可以用于实现乐观锁,处理并发更新的情况。

3. Pipeline

适用于需要批量执行大量独立命令的场景,如批量插入数据、批量查询数据等。通过 Pipeline 可以提高操作效率,减少网络延迟的影响。

总结

主要描述了Redis 事务、lua脚本、pipeline 执行过程中出错是如何处理的。以及 对redis 的原子操作以及事务的概念做了阐述,区别于数据库事务。

记住 redis 事务 、 lua脚本在执行期间出错,不可回滚❗❗❗

### RedisLua 脚本原子性实现 在 Redis 中,Lua 脚本原子性是由 Redis 的单线程模型以及 Lua 解释器的工作方式共同保障的。当一个客户端发送 `EVAL` 或者 `EVALSHA` 命令执行一段 Lua 脚本时,这段脚本会在服务器端作为一个整体被执行,并且在这个期间不会有其他的任何命令能够插队进来干扰其运行过程[^2]。 #### 卢亚脚本作为不可分割单元 一旦开始执行,直到完成整个脚本之前都不会有其他来自不同连接上的指令得到处理;也就是说,在这段时间里该进程对于其它请求而言处于阻塞状态。这种设计使得即使是在高并发环境下也能确保每次调用都是安全可靠的[^3]。 #### 数据库事务对比 需要注意的是,虽然说 Redis 使用这种方式实现了所谓的“原子性”,但这并不完全等同于传统意义上数据库系统的 ACID 特性里的原子性概念——后者还涉及到失败后的回滚机制等问题。然而由于 Redis 是内存存储结构加上特定的应用场景考量,所以并没有提供完整的事务支持功能[^4]。 ```lua -- 示例:简单的计数器增加操作 local current_value = redis.call('GET', KEYS[1]) if not current_value then current_value = 0 else current_value = tonumber(current_value) end current_value = current_value + ARGV[1] redis.call('SET', KEYS[1], tostring(current_value)) return current_value ``` 上述代码展示了如何利用 Lua 脚本保证一系列 Redis 操作的一致性和完整性。通过将多个命令封装在一个 Lua 脚本内并一次性提交给 Redis 执行,可以有效防止中间状态暴露在外从而破坏数据一致性[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

提前退休了-程序员阿飞

兄弟们能否给口饭吃

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值