news 2026/10/24 2:26:21

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 脚本的支持,开发者可以通过EVAL或EVALSHA命令在 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/10/23 9:53:35

Qwen2.5-7B机器翻译:高质量多语言转换

Qwen2.5-7B机器翻译&#xff1a;高质量多语言转换 1. 引言&#xff1a;为何选择Qwen2.5-7B进行机器翻译&#xff1f; 随着全球化进程的加速&#xff0c;跨语言沟通已成为企业、开发者和研究者的核心需求。传统的机器翻译系统&#xff08;如Google Translate或MarianMT&#xf…

作者头像 李华
网站建设 2026/10/23 4:21:20

Qwen2.5-7B知识检索:大规模数据查询优化

Qwen2.5-7B知识检索&#xff1a;大规模数据查询优化 1. 技术背景与问题提出 随着大语言模型在自然语言理解、代码生成和多模态任务中的广泛应用&#xff0c;如何高效地从海量上下文中提取关键信息成为工程落地的核心挑战之一。尤其是在企业级应用中&#xff0c;用户常需基于长…

作者头像 李华
网站建设 2026/10/22 19:44:00

基于CMOS的触发器电路构建:新手入门必看指南

从零开始构建CMOS触发器&#xff1a;深入理解数字电路的“记忆单元”你有没有想过&#xff0c;计算机是如何记住一个数值的&#xff1f;为什么按下键盘后&#xff0c;信息不会瞬间消失&#xff1f;这一切的背后&#xff0c;其实都离不开一种看似简单却至关重要的电路——触发器…

作者头像 李华
网站建设 2026/10/22 7:37:41

Qwen2.5-7B商业智能应用:数据洞察自动报告

Qwen2.5-7B商业智能应用&#xff1a;数据洞察自动报告 1. 背景与业务需求 在现代企业运营中&#xff0c;数据驱动决策已成为核心竞争力。然而&#xff0c;大多数企业在数据分析流程中面临一个共性痛点&#xff1a;分析师花费大量时间撰写报告、解释图表、提炼结论&#xff0c…

作者头像 李华
网站建设 2026/10/22 19:45:25

Qwen2.5-7B模型服务化:企业级API网关集成

Qwen2.5-7B模型服务化&#xff1a;企业级API网关集成 1. 背景与技术定位 1.1 大语言模型的工程化挑战 随着大语言模型&#xff08;LLM&#xff09;在自然语言理解、代码生成和多模态任务中的广泛应用&#xff0c;如何将高性能模型如 Qwen2.5-7B 高效部署并集成到企业级系统中…

作者头像 李华