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

© 版权声明
THE END
支持一下吧
点赞9 分享
评论 抢沙发
头像
请文明发言!
提交
头像

昵称

取消
昵称表情代码快捷回复

    暂无评论内容