乌鲁木齐市网站建设_网站建设公司_MySQL_seo优化
2026/1/16 22:43:36 网站建设 项目流程

先通过对比表清晰梳理核心差异,再结合 Java 17+ + Spring Boot 代码示例说明各自使用场景:

对比维度MULTI/EXEC 事务Lua 脚本
原子性弱原子性:语法错误会放弃所有命令;运行时错误(如对字符串做自增)仅失败该命令,其余继续执行强原子性:脚本内所有命令作为整体原子执行,要么全成功,要么全失败(脚本执行中Redis单线程不处理其他请求)
逻辑能力仅支持批量命令入队,无条件判断、循环、变量等逻辑支持完整Lua语法(条件、循环、变量、函数),可基于前序命令结果执行后续逻辑
网络开销多次网络交互(MULTI→命令入队→EXEC)一次网络交互(脚本传输+执行),大幅降低网络往返
错误处理无自定义错误处理,仅被动接受Redis的执行结果可在脚本内捕获错误、自定义兜底逻辑(如redis.call失败时返回特定值)
资源占用事务期间占用客户端连接,直到EXEC/放弃脚本执行期间占用Redis单线程,长脚本会阻塞所有请求(需控制脚本时长)
复用性不可复用,每次需重新入队所有命令可通过EVALSHA缓存脚本到Redis,重复执行仅传脚本SHA1值,复用性高
适用复杂度仅支持简单批量命令,无依赖逻辑支持复杂业务逻辑(依赖前序结果、条件判断、原子操作封装)

一、MULTI/EXEC 事务:适用场景与代码示例

适用场景

仅需简单批量执行无依赖的Redis命令,且能容忍“运行时错误不回滚”的场景,例如:

Java 17+ + Spring Boot 代码示例
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* MULTI/EXEC 事务示例(Spring Boot 3.x + Java 17+)
* 场景:用户下单后批量扣减库存、增加订单数(无库存判断)
*/
@Slf4j
@Service
public class RedisTransactionService {
private final RedisTemplate<String, Object> redisTemplate;// 构造器注入(Spring Boot 3.x 推荐)public RedisTransactionService(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}// Java 17 Record:事务执行结果public record TransactionResult(boolean success, String msg, List<Object> execResults) {}/*** 执行MULTI/EXEC事务:扣减库存 + 增加订单数*/public TransactionResult executeOrderTransaction(String productKey, String orderCountKey, int deductNum) {try {// RedisCallback 执行底层事务操作,try-with-resources 管理Redis连接(Spring自动兜底)List<Object> execResults = redisTemplate.execute((RedisCallback<List<Object>>) connection -> {// 开启事务connection.multi();// 命令入队:扣减库存(INCRBY 负数实现扣减)connection.incrBy(productKey.getBytes(), -deductNum);// 命令入队:增加订单数connection.incrBy(orderCountKey.getBytes(), 1);// 执行事务(原子提交)return connection.exec();});// Java 17 switch表达式:处理执行结果String msg = switch (execResults) {case null -> "事务被放弃(如WATCH监控的key被修改)";case List<Object> res when res.isEmpty() -> "事务执行无结果(语法错误)";default -> "事务执行成功";};return new TransactionResult(execResults != null && !execResults.isEmpty(), msg, execResults);} catch (Exception e) {log.error("MULTI/EXEC事务执行失败", e);// 异常时明确关闭Redis连接(Spring Data Redis已封装,此处兜底)redisTemplate.getConnectionFactory().getConnection().close();return new TransactionResult(false, "执行异常:" + e.getMessage(), null);}}// 测试入口public static void main(String[] args) {// Spring Boot 环境需通过ApplicationContext获取Bean,此处简化模拟RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(org.springframework.data.redis.connection.jedis.JedisConnectionFactory());RedisTransactionService service = new RedisTransactionService(redisTemplate);// 执行事务:扣减商品1001库存2个,订单数+1TransactionResult result = service.executeOrderTransaction("stock:1001", "order:count:1001", 2);log.info("事务结果:{}", result);}}
关键说明
  1. 资源管理:Spring Data Redis 自动管理 Redis 连接,异常时通过 getConnection().close() 明确关闭资源,符合“资源必须关闭”的要求;
  2. 原子性局限:若stock:1001是字符串(如"abc"),incrBy会失败,但order:count:1001仍会执行自增,体现“弱原子性”;
  3. WATCH 补充:MULTI/EXEC 可配合 WATCH 实现“乐观锁”(监控key,若被修改则事务放弃),但仍无法解决“基于前序结果执行后续命令”的场景。

二、Lua 脚本:适用场景与代码示例

适用场景

需要原子执行复杂逻辑(含条件判断、依赖前序结果)、或需降低网络开销的场景,例如:

  • 扣减库存前判断库存是否充足(库存不足则直接返回失败,不执行扣减);
  • 分布式锁的原子释放(判断锁归属后再删除,避免误删);
  • 限流算法(如令牌桶、漏桶,需原子计算剩余令牌)。
Java 17+ + Spring Boot 代码示例
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
/**
* Lua脚本示例(Spring Boot 3.x + Java 17+)
* 场景:扣减库存前判断库存是否充足(原子逻辑)
*/
@Slf4j
@Service
public class RedisLuaScriptService {
private final RedisTemplate<String, Object> redisTemplate;public RedisLuaScriptService(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}// Java 17 Record:Lua脚本执行结果public record LuaResult(boolean success, String msg, Long data) {}// 库存扣减Lua脚本(原子判断+扣减)private static final String DEDUCT_STOCK_LUA = """-- 获取参数:库存key、扣减数量local stockKey = KEYS[1]local deductNum = tonumber(ARGV[1])-- 获取当前库存local currentStock = tonumber(redis.call('get', stockKey))if not currentStock thenreturn -1  -- 库存key不存在end-- 判断库存是否充足if currentStock < deductNum thenreturn 0   -- 库存不足end-- 原子扣减库存redis.call('incrby', stockKey, -deductNum)return currentStock - deductNum  -- 返回扣减后库存""";/*** 执行Lua脚本扣减库存(原子逻辑)*/public LuaResult deductStockWithLua(String stockKey, int deductNum) {// 定义Redis脚本(指定返回类型为Long)RedisScript<Long> luaScript = new DefaultRedisScript<>(DEDUCT_STOCK_LUA,Long.class);try {// try-with-resources 思想:Spring自动管理脚本执行的连接资源,异常时兜底关闭Long result = redisTemplate.execute(luaScript,Collections.singletonList(stockKey),  // KEYS参数deductNum                               // ARGV参数);// Java 17 switch表达式:处理脚本返回值String msg = switch (result) {case -1L -> "库存key不存在";case 0L -> "库存不足,扣减失败";case null -> "脚本执行异常";default -> "库存扣减成功,剩余库存:" + result;};boolean success = result != null && result > 0;return new LuaResult(success, msg, result);} catch (Exception e) {log.error("Lua脚本执行失败", e);// 明确关闭Redis连接(释放资源)redisTemplate.getConnectionFactory().getConnection().close();return new LuaResult(false, "执行异常:" + e.getMessage(), null);}}// 测试入口public static void main(String[] args) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(org.springframework.data.redis.connection.jedis.JedisConnectionFactory());RedisLuaScriptService service = new RedisLuaScriptService(redisTemplate);// 先初始化库存:set stock:1001 10redisTemplate.opsForValue().set("stock:1001", 10);// 执行Lua脚本扣减3个库存LuaResult result = service.deductStockWithLua("stock:1001", 3);log.info("Lua脚本执行结果:{}", result);}}
关键说明
  1. 原子性保障:脚本内“判断库存→扣减库存”是原子操作,Redis单线程执行期间不会处理其他请求,避免“库存超卖”;
  2. 资源管理:通过redisTemplate.getConnectionFactory().getConnection().close()确保异常时释放Redis连接,符合“资源必须关闭”要求;
  3. 脚本复用:生产环境可将Lua脚本预加载到Redis(通过SCRIPT LOAD),使用EVALSHA执行(仅传SHA1值),减少网络传输开销;
  4. 性能注意:Lua脚本需控制时长(建议<10ms),避免阻塞Redis单线程。

三、核心使用场景总结

技术核心使用场景典型案例
MULTI/EXEC1. 简单批量执行无依赖的Redis命令;
2. 无需条件判断、仅需“批量提交”的场景;
3. 能容忍“部分命令失败”的场景
批量设置缓存、批量更新无依赖的计数key
Lua 脚本1. 需要原子执行含条件判断/依赖逻辑的操作;
2. 需降低网络往返开销的高频操作;
3. 自定义Redis原子操作(原生命令无法满足)
库存扣减(防超卖)、分布式锁释放、限流算法、红包拆分

四、生产环境最佳实践

  1. MULTI/EXEC

    • 避免在事务中执行过多命令(占用连接时间长);
    • 配合WATCH实现乐观锁,但仅适用于低并发场景;
    • 不依赖“事务回滚”,提前校验命令合法性(避免运行时错误)。
  2. Lua 脚本

    • 脚本长度控制在1KB内,避免大脚本阻塞Redis;
    • 脚本内避免循环(尤其是无限循环),防止Redis卡死;
    • 优先使用redis.call(失败抛出异常)而非redis.pcall(失败返回错误),便于捕获问题;
    • 通过SCRIPT LOAD+EVALSHA复用脚本,减少网络传输。
  3. 资源管理通用规则

    • 所有Redis操作必须添加异常处理,确保连接/资源正常关闭;
    • Spring Boot中优先使用RedisTemplate(自动管理连接池),而非裸用Jedis/Lettuce;
    • 连接池配置合理的最大连接数、空闲超时,避免连接泄露。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询