news 2026/4/15 11:48:14

Redis其实并不是线程安全的

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis其实并不是线程安全的

文章目录

    • 一、Redis的原子性为什么会出问题
    • 二、Redis事务命令
    • 三、为什么用lua脚本就能解决呢?
    • 四、Lua脚本介绍
    • 五、在 Spring Boot 中集成 Redis + Lua 脚本实现下单原子性
    • 结语:

一、Redis的原子性为什么会出问题

Redis 不是单线程的吗?那所有操作不就天然原子了吗?为什么还需要 Lua 脚本来保证原子性?

Redis 的“单线程”是指命令的执行是串行的,但“多个命令组成的逻辑”并不是原子的。

举个例子,这是你的下单模块代码:

stock=redis.get("stock")# 命令1ifstock>0:redis.decr("stock")# 命令2create_order()# 本地逻辑

虽然 Redis 是单线程,但命令1 和 命令2 是两个独立的请求,执行过程如下:

客户端A:GETstock → 返回1客户端B:GETstock → 返回1← 在A执行DECR前,B也读到了1! 客户端A:DECRstock → stock=0客户端B:DECRstock → stock=-1

结果就超卖了,很明显redis的确是原子性的,但是这个下单的过程不是原子性的。

二、Redis事务命令

Redis 提供了 MULTI/EXEC 事务,但是能解决这个问题吗?

Redis 提供了一组用于实现事务的命令,允许客户端将多个命令打包,然后一次性、按顺序地执行。Redis 的事务并不支持回滚,但能保证这些命令在执行期间不会被其他客户端的请求打断,具有原子性地排队执行。

以下是 Redis 事务相关的常用命令:


1.MULTI标记事务块的开始,执行MULTI后,后续的命令不会立即执行,而是被放入一个队列中,返回值:总是返回OK

>MULTI OK

2.EXEC执行事务块中的所有命令,一旦调用EXEC,Redis 将按顺序执行从MULTI开始以来的所有排队命令,返回一个数组,包含每个命令的执行结果;如果事务未正常启动,则返回错误。

>MULTI>INCR foo>INCR bar>EXEC1)(integer)12)(integer)1

补充说明

  • Redis 事务不支持回滚:如果某个命令在EXEC阶段出错,该命令会报错,但其余命令仍会继续执行。

一句话来说就是:MULTI 命令将多个 Redis 命令打包成一个事务队列,在 EXEC时按顺序原子性地执行,支持批量、顺序、隔离执行,但不支持错误回滚;配合 WATCH 可实现乐观锁和应用层重试。

如需进一步了解,可参考 Redis 官方文档 - Transactions。


我们看看MUIT命令要怎么做

MULTIGETstockDECRstock # ← 无论GET结果是什么,DECR都会执行EXEC

结果是否定的,Redis 有事务MULTI / EXEC,但它只是打包命令,并不支持回滚或条件判断,所以并不能实现逻辑的原子性


三、为什么用lua脚本就能解决呢?

Redis是单线程的,把整个下单的逻辑封装到脚本里面实现了逻辑和命令的双重原子性保证。

都是脚本封装命令,不能用python吗?python脚本使用场景也更广

Redis 允许在内部执行 Lua 脚本来保证多步操作的原子性,因为 Lua 脚本是在 Redis 进程内、无 I/O 阻塞、可预测地一次性执行完的;而Python 等外部脚本无法在 Redis 内部运行,必须通过网络多次交互,破坏了原子性。


四、Lua脚本介绍

1.背景
Lua是一种轻量级、嵌入式脚本语言,由巴西的Pontifical Catholic University of Rio de Janeiro的团队在1993年开发。它最初设计用于嵌入到其他应用程序中作为脚本引擎,比如游戏开发、自动化脚本和配置工具。Lua的发音是“loo-ah”,源自葡萄牙语,意思是“月亮”。


2.功能
Lua 脚本是一种轻量级、高效的嵌入式脚本语言,以简单语法和强大表数据结构著称,常用于游戏开发、自动化和嵌入式系统。Redis 从 2.6 版本开始内置了对 Lua 脚本的支持,开发者可以通过EVALEVALSHA命令在 Redis 服务器端执行一段 Lua 代码。

最核心的优势在于原子性:整个 Lua 脚本作为一个不可分割的单元执行,在运行期间 Redis 会阻塞其他命令,确保脚本中的所有 Redis 操作(通过redis.call()redis.pcall()调用)不会被其他客户端中断。这解决了多客户端并发时常见的“读取-修改-写入”竞态问题,比如实现原子递增、库存扣减、分布式锁、限流、CAS(Check-And-Set)等复杂逻辑。

相比 Redis 原生的MULTI/EXEC事务,Lua 脚本更灵活,支持条件判断、循环、变量计算,执行效率更高,但也要求脚本尽量短小,避免长时间阻塞服务器。

五、在 Spring Boot 中集成 Redis + Lua 脚本实现下单原子性

以订单下单为例,在 Spring Boot 项目中集成 Redis 和 Lua 脚本非常合适,用于高并发场景如电商下单。以下是步骤:

  1. 添加依赖
    pom.xml(Maven)中:

    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><!-- 或 lettuce,根据偏好 --></dependency></dependencies>
  2. 配置 Redis
    application.yml

    spring:redis:host:localhost# 或你的 Redis 服务器port:6379password:yourpassword# 如果有
  3. 注入 RedisTemplate
    Spring Boot 自动提供RedisTemplate<String, Object>或自定义。用于执行 Lua 脚本。

  4. 编写 Lua 脚本
    创建资源文件src/main/resources/scripts/place_order.lua

-- 输入:KEYS[1] = 库存键, KEYS[2] = 订单键-- ARGV[1] = 购买数量, ARGV[2] = 订单ID, ARGV[3] = 用户ID, ARGV[4] = 其他订单数据(JSON字符串)localinventory_key=KEYS[1]localorder_key=KEYS[2]localquantity=tonumber(ARGV[1])localorder_id=ARGV[2]localuser_id=ARGV[3]localorder_data=ARGV[4]-- e.g., '{"price":100,"item":"book"}'-- 获取当前库存localcurrent_inventory=tonumber(redis.call('GET',inventory_key)or0)-- 检查库存ifcurrent_inventory<quantitythenreturn{0,"库存不足"}-- 返回错误end-- 扣减库存redis.call('DECRBY',inventory_key,quantity)-- 创建订单(用HSET存储哈希)redis.call('HSET',order_key,'id',order_id,'user_id',user_id,'quantity',quantity,'data',order_data)-- 可选:设置过期时间或推入订单队列-- redis.call('EXPIRE', order_key, 3600) -- 1小时过期return{1,"下单成功",order_id}-- 返回成功
  1. 服务层实现
    在 Service 类中加载并执行脚本:
    @ServicepublicclassOrderService{@AutowiredprivateRedisTemplate<String,Object>redisTemplate;publicObjectplaceOrder(StringproductId,intquantity,StringuserId,StringorderData){// 加载 Lua 脚本RedisScript<List>script=newDefaultRedisScript<>(newClassPathResource("scripts/place_order.lua"),List.class);// 准备键和参数StringinventoryKey="product:inventory:"+productId;StringorderId=generateOrderId();// 自定义生成 IDStringorderKey="order:"+orderId;List<String>keys=Arrays.asList(inventoryKey,orderKey);// 执行脚本Listresult=redisTemplate.execute(script,keys,quantity,orderId,userId,orderData);// 处理结果if((long)result.get(0)==1){return"下单成功: "+result.get(2);}else{return"错误: "+result.get(1);}}privateStringgenerateOrderId(){// 实现 ID 生成,如 UUIDreturnjava.util.UUID.randomUUID().toString();}}

结语:

Lua 脚本为 Redis 提供了“服务器端可编程能力”,让原本只能执行简单命令的 Redis 变成了一个支持复杂原子业务逻辑的强大引擎,广泛应用于高并发场景如秒杀、排行榜、实时统计等,学会使用lua脚本让你的工程能力更上一层。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 12:02:56

Science最新文章:大型语言模型时代的科学生产

Scientific production in the era of large language models大型语言模型时代的科学生产随着生产过程的快速演变&#xff0c;科学政策必须考虑机构如何实现转型大语言模型对科学研究影响的宏观评估背景尽管生成式人工智能在各学科领域迅速普及&#xff0c;但其实际影响的实证证…

作者头像 李华
网站建设 2026/4/15 12:02:56

Qwen2.5-7B智能问卷分析:开放文本回答归类

Qwen2.5-7B智能问卷分析&#xff1a;开放文本回答归类 1. 引言&#xff1a;为何需要大模型处理开放文本&#xff1f; 在用户调研、产品反馈、教育评估等场景中&#xff0c;开放性问题&#xff08;如“您对本次服务有何建议&#xff1f;”&#xff09;能获取比选择题更丰富、真…

作者头像 李华
网站建设 2026/4/15 6:23:34

nanopb集成常见问题深度剖析

深入嵌入式通信核心&#xff1a;nanopb 集成实战全解析 在物联网设备加速落地的今天&#xff0c;一个看似微小的技术选择—— 数据如何打包与传输 ——往往决定了整个系统的稳定性、功耗表现乃至开发效率。当你的 STM32 或 ESP32 节点需要通过 LoRa、BLE 或 Wi-Fi 向云端上报…

作者头像 李华
网站建设 2026/4/15 12:04:35

Qwen2.5-7B领域迁移:专业术语快速适配方法

Qwen2.5-7B领域迁移&#xff1a;专业术语快速适配方法 1. 引言&#xff1a;为何需要Qwen2.5-7B的领域迁移能力&#xff1f; 1.1 大模型通用性与垂直领域需求的矛盾 尽管像 Qwen2.5-7B 这样的大语言模型在通用任务上表现出色&#xff0c;但在医疗、金融、法律、工程等专业领域…

作者头像 李华
网站建设 2026/4/15 13:13:17

Modbus协议工业级脉冲模块,为农业自动化实践保驾护航

工业级脉冲输出模块(一种能产生和控制脉冲电信号输出的设备)是农业自动化领域的核心控制部件&#xff0c;它通过发送精密、可控的电子脉冲指令来直接驱动各类执行机构(如阀门、电机)&#xff0c;从而实现了对水、肥、药及能源的精准管理。一、 应用逻辑 工业级脉冲输出模块是农…

作者头像 李华
网站建设 2026/4/15 13:13:53

企业级数据架构管控与资产化运营:一套可落地的数据治理方案

在数字经济下半场&#xff0c;企业对数据治理的需求已从“有没有”转向“好不好、用不用得起来”。面对“数据孤岛难打破、质量管控难落地、价值转化难闭环”等行业深水区痛点&#xff0c;依托多年实战积淀与技术创新&#xff0c;打造兼具“行业适配性、技术领先性、落地实效性…

作者头像 李华