redis~多行语句的原子性_事务性

高并发下 Redis 事务的原子性分析

1. 代码结构分析

redisTemplate.execute(new SessionCallback<Object>() {
 @Override
 public <String, Long> Object execute(RedisOperations<String, Long> operations) {
 operations.multi(); // 开启事务
 operations.opsForValue().increment((String) key); // 命令1:自增
 operations.expire((String) key, 1, TimeUnit.HOURS); // 命令2:设置过期时间
 operations.exec(); // 提交事务
 return null;
 }
});

2. 原子性保证机制

在 Redis 事务中:

  • MULTI/EXEC 是原子操作
    Redis 会将 multiexec 之间的所有命令放入队列,一次性原子执行
  • 命令顺序保证
    命令按 increment → expire 顺序执行,不会被打断。

3. 高并发下的行为

场景是否会出现 expire 不执行原因
正常情况❌ 不会事务保证所有命令一起提交
Redis 宕机✅ 可能宕机导致事务未提交
网络中断✅ 可能客户端未收到 EXEC 指令
命令错误✅ 可能语法错误导致事务失败
内存不足✅ 可能OOM 导致命令执行失败

4. **潜在风险点

graph TD A[客户端发起事务] --> B{Redis接收MULTI} B --> C[缓存命令队列] C --> D{执行EXEC} D -->|成功| E[所有命令生效] D -->|失败| F[所有命令丢弃]
  1. 事务中断风险

    • Redis 宕机或网络断开时事务未提交
    • 内存不足导致命令执行失败
  2. 键过期重置问题

    // 每次自增都会重置过期时间到1小时
    // 可能导致key永不过期(频繁访问时)
    

5. **优化建议

(1) 使用 Lua 脚本保证绝对原子性
-- KEYS[1]=key, ARGV[1]=expire_seconds
local count = redis.call('INCR', KEYS[1])
if count == 1 then
 redis.call('EXPIRE', KEYS[1], ARGV[1])
end
return count

Spring 代码实现:

String script = 
 "local c = redis.call('INCR', KEYS[1])\n" +
 "if c == 1 then\n" +
 " redis.call('EXPIRE', KEYS[1], ARGV[1])\n" +
 "end\n" +
 "return c";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(
 redisScript, 
 Collections.singletonList(key), 
 TimeUnit.HOURS.toSeconds(1) // 转换为秒
);
(2) 使用 SET + NX 优化(首次设置过期时间)
// 原子操作:不存在时初始化并设置过期时间
Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(
 key, "0", 1, TimeUnit.HOURS
);
// 后续只需自增
if (setIfAbsent != null && setIfAbsent) {
 redisTemplate.opsForValue().increment(key);
} else {
 // 已有值时直接自增(不重置过期时间)
 redisTemplate.opsForValue().increment(key);
}

6. 各方案对比

方案原子性性能过期时间重置实现复杂度
原始事务方案部分保证会重置
Lua 脚本完全保证首次设置
SET NX + INCR分段保证首次设置

7. 结论

  1. 原始代码在正常情况下是原子的
    在 Redis 正常运行且无外部故障时,incrementexpire 会作为一个整体执行。

  2. 高并发下可能失效的场景

    • Redis 服务崩溃/重启
    • 客户端与 Redis 网络断开
    • 内存不足导致命令执行失败
    • 命令语法错误(如 key 类型错误)
  3. 生产环境建议
    优先使用 Lua 脚本

    • 绝对原子性保证
    • 避免过期时间被重置
    • 单次网络往返减少延迟

在千万级 QPS 的生产环境中,Lua 脚本方案的性能比事务高 30%~50%,且能避免事务中断导致的数据不一致问题。

作者:张占岭原文地址:https://www.cnblogs.com/lori/p/18904416

%s 个评论

要回复文章请先登录注册