重新认识一下redis 的lua脚本 和 事务
前言
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 事务通过 MULTI
、EXEC
、DISCARD
和 WATCH
等命令实现。使用 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
指令配置为具有内存限制)
示例:
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')
可能会失败,但 set
和 incr
命令依然会正常执行。
性能表现
1. Lua 脚本
Lua 脚本将多个操作封装在一个脚本中,减少了客户端与服务器之间的网络往返次数。并且脚本在服务器端执行,减少了命令解析的次数,通常在处理复杂逻辑时性能更优。
2. 事务
事务也能减少网络往返次数,因为多个命令可以一次性发送。但事务中的每个命令都需要在服务器端进行解析和执行,在处理大量命令时可能会有一定性能开销。
3. Pipeline
Pipeline 主要通过减少网络往返时间来提高性能。对于大量独立的命令,使用 Pipeline 可以显著提高执行效率。但由于命令之间没有原子性保证,在需要保证数据一致性的场景下可能不适用。
适用场景
1. Lua 脚本
适用于需要实现复杂业务逻辑、保证操作原子性的场景,如分布式锁、限流、库存扣减等。通过 Lua 脚本可以将多个操作封装在一起,避免了多次网络交互和并发问题。
2. 事务
适用于简单的批量操作,需要保证一组命令原子执行的场景,如同时更新多个相关的键值对。同时,WATCH
命令可以用于实现乐观锁,处理并发更新的情况。
3. Pipeline
适用于需要批量执行大量独立命令的场景,如批量插入数据、批量查询数据等。通过 Pipeline 可以提高操作效率,减少网络延迟的影响。
总结
主要描述了Redis 事务、lua脚本、pipeline 执行过程中出错是如何处理的。以及 对redis 的原子操作以及事务的概念做了阐述,区别于数据库事务。
记住 redis 事务 、 lua脚本在执行期间出错,不可回滚❗❗❗