Redis 原子操作命令、应用场景与实例Redis 基于单线程串行执行命令的模型所有单个命令都具备天然原子性。通常所说的「原子操作命令」特指能一步完成「读取 - 修改 - 写入」完整逻辑、天然解决并发竞态问题的命令以及用于保障多条命令原子执行的机制。一、原生单命令原子操作按数据结构分类这类命令直接在 Redis 服务端完成完整的读写改逻辑彻底避免并发下「GET 计算 SET」的竞态问题。1. String 字符串类型最常用表格命令功能说明典型场景INCR key键值原子 1键不存在则初始化为 0 再加 1页面访问量、点赞数、订单号自增DECR key键值原子 - 1库存扣减、在线人数递减INCRBY key increment原子增加指定整数批量补库存、积分累加DECRBY key decrement原子减少指定整数批量扣减库存INCRBYFLOAT key increment原子增加浮点数金额、权重等浮点型计数SET key value NX EX seconds原子设置仅键不存在时生效同时设置过期时间分布式锁替代 SETNXEXPIRE 非原子组合GETSET key value原子设置新值并返回旧值数据幂等校验、状态更替示例# 初始化商品1001的库存为100 SET goods:1001:stock 100 # 高并发下原子扣减1件库存 DECR goods:1001:stock同类命令对比上面的goods:1001:stock就是keyDECR key自减 1DECRBY key N一次性扣减 N例DECRBY goods:1001:stock 5一次性减 5INCR / INCRBY增加库存2. Hash 哈希类型针对哈希表内单个字段做原子增减适合多维度聚合计数场景。命令功能说明典型场景HINCRBY key field increment对哈希指定字段原子增减整数购物车商品数量、用户多维度积分HINCRBYFLOAT key field increment对哈希指定字段原子增减浮点数用户余额、商品折扣计算示例# 用户1001的购物车中商品2001的数量1 HINCRBY cart:1001 goods:2001 13. List 列表类型原子完成列表间的元素转移保障消息 / 任务的可靠性。表格命令功能说明典型场景RPOPLPUSH source destination原子从源列表右端弹出推入目标列表左端可靠消息队列、任务队列重试BRPOPLPUSH source destination timeout阻塞版 RPOPLPUSH无数据时等待超时异步任务消费避免轮询空转BLPOP/BRPOP key timeout阻塞式弹出列表元素简单消息队列消费示例# 原子领取任务从待处理队列弹出移入处理中队列 # 防止消费过程中服务宕机导致消息丢失 RPOPLPUSH task:pending task:processing4. Set / ZSet 集合类型表格命令功能说明典型场景SPOP key count原子随机弹出集合中元素抽奖、随机选人SADD key member原子添加元素自动去重用户标签、UV 去重计数ZINCRBY key increment member原子增减有序集合成员的分数排行榜积分更新、内容热度排名示例# 帖子1001获得1次点赞排行榜分数1 ZINCRBY post:hot:rank 1 1001二、多命令原子保障方案当业务逻辑需要多条命令组合执行比如「先判断库存是否充足再扣减」单命令无法满足需要以下方案保障原子性。1. MULTI / EXEC 事务 WATCH 乐观锁原理将多条命令加入队列EXEC触发后一次性串行执行执行期间不会插入其他客户端命令。局限性不支持回滚语法错误会导致整体不执行但运行时错误如类型不匹配会继续执行后续命令无法在命令间做逻辑判断。WATCH监控指定键若EXEC前键被其他客户端修改事务整体放弃执行实现乐观锁。适用场景简单多键批量更新、冲突概率低的场景。示例乐观锁实现账户转账# 监控两个账户余额防止转账期间被修改 WATCH account:A account:B # 开启事务 MULTI DECRBY account:A 100 INCRBY account:B 100 # 执行事务若监控的键被修改返回nil EXEC2. Lua 脚本EVAL / EVALSHARedis 2.6 版本支持整个 Lua 脚本会作为一个整体在 Redis 主线程中原子执行是最灵活、最常用的多命令原子方案。优势支持条件判断、循环等复杂逻辑性能优于事务 WATCH注意脚本中不能执行耗时操作避免阻塞 Redis 主线程。适用场景带条件的库存扣减、分布式锁安全释放、复合业务逻辑原子执行。示例 1原子扣减库存库存不足则不扣减返回执行结果-- 返回1扣减成功返回0库存不足 local stock redis.call(GET, KEYS[1]) if not stock or tonumber(stock) tonumber(ARGV[1]) then return 0 end redis.call(DECRBY, KEYS[1], ARGV[1]) return 1执行命令EVAL local stock redis.call(GET, KEYS[1]) if not stock or tonumber(stock) tonumber(ARGV[1]) then return 0 end redis.call(DECRBY, KEYS[1], ARGV[1]) return 1 1 goods:1001:stock 2示例 2原子释放分布式锁仅锁的持有者能删除if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) end return 0三、经典业务场景完整实例场景 1固定窗口接口限流需求限制单个用户 1 分钟内最多调用接口 100 次。方案原子计数 过期时间避免并发下计数错乱。# 访问时原子计数1 INCR user:1001:api:limit # 仅首次访问设置60秒过期避免重置有效期 EXPIRE user:1001:api:limit 60 # 业务逻辑返回值 100 则触发限流场景 2分布式互斥锁需求多服务实例下同一任务同一时间只有一个实例执行。方案SET ... NX EX原子加锁 Lua 原子解锁NX保证只有一个客户端能加锁成功EX 30自动过期防止服务宕机后锁永久不释放唯一 request_id解锁时校验持有者防止误删他人的锁加锁命令SET lock:order:task uuid-xxx-123 NX EX 30解锁命令Lua 原子执行EVAL if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) end return 0 1 lock:order:task uuid-xxx-123场景 3商品超卖防护需求高并发秒杀场景下绝对不能出现负库存。方案Lua 脚本原子执行「库存校验 → 库存扣减」彻底避免超卖。单纯使用DECR会直接扣成负数无法满足业务要求校验 扣减必须原子执行因此 Lua 是最优解。场景 4可靠任务队列需求消费者领取任务后若宕机任务不丢失支持重试。方案RPOPLPUSH原子转移任务待处理队列task:pending处理中队列task:processing消费者领取任务RPOPLPUSH task:pending task:processing任务处理完成后从处理中队列删除LREM task:processing 0 task_1001若消费者宕机可定时扫描处理中队列的超时任务重新放回待处理队列。常见误区SETNX EXPIRE 是原子的错误两条命令之间若服务宕机锁会永久不过期必须使用SET ... NX EX单命令原子设置。Redis 事务支持回滚错误Redis 事务无回滚机制仅语法错误会整体放弃运行时错误会继续执行后续命令。Lua 脚本可以写复杂长逻辑错误Lua 执行期间会阻塞 Redis耗时过长会影响所有请求脚本必须简短高效。基于Spring Boot Spring Data Redis使用通用性最强的StringRedisTemplate默认已完成 Redis 基础配置所有代码可直接复用。一、基础原子命令 Java 实现Redis 单命令天然原子直接调用 Spring 封装的 API 即可无需额外处理并发。1. 字符串自增 / 自减计数、库存场景// 原子1返回自增后的值 Long viewCount stringRedisTemplate.opsForValue().increment(page:view:1001); // 原子增加指定数值补货10件库存 Long stock stringRedisTemplate.opsForValue().increment(goods:1001:stock, 10); // 原子-1扣减1件库存 Long remainStock stringRedisTemplate.opsForValue().decrement(goods:1001:stock);2. 原子加锁SET NX EX分布式锁核心注意必须使用带过期时间的重载方法保证「设值 过期」单命令原子禁止分开调用 setIfAbsent expire// 返回 true加锁成功false锁已存在 Boolean lockFlag stringRedisTemplate.opsForValue() .setIfAbsent(lock:order:1001, request-uuid-xxx, 30, TimeUnit.SECONDS);3. 其他常用原子命令// GETSET原子设置新值并返回旧值 String oldStatus stringRedisTemplate.opsForValue().getAndSet(user:1001:status, online); // Hash原子增减指定字段购物车商品数量1 Long cartNum stringRedisTemplate.opsForHash().increment(cart:1001, goods:2001, 1); // List原子转移元素可靠队列从待处理移到处理中 String task stringRedisTemplate.opsForList().rightPopAndLeftPush(task:pending, task:processing); // ZSet原子增减成员分数排行榜热度1 Double postScore stringRedisTemplate.opsForZSet().incrementScore(post:hot:rank, 1001, 1);二、事务 WATCH 乐观锁实现适用于多命令、低冲突的场景通过 WATCH 监控键实现乐观锁冲突则事务自动放弃。必须保证 WATCH、MULTI、EXEC 在同一个 Redis 连接中因此使用 SessionCallback 绑定连接/** * 账户转账原子扣减A账户、增加B账户乐观锁防止并发修改 * param amount 转账金额 * return true转账成功false并发冲突/余额不足 */ public boolean transfer(int amount) { ListObject result stringRedisTemplate.execute(new SessionCallbackListObject() { Override public ListObject execute(RedisOperations operations) throws DataAccessException { // 1. 监控两个账户键执行前被修改则事务放弃 operations.watch(Arrays.asList(account:A, account:B)); // 2. 业务校验余额是否充足 String balanceA (String) operations.opsForValue().get(account:A); if (balanceA null || Integer.parseInt(balanceA) amount) { operations.unwatch(); return Collections.emptyList(); } // 3. 开启事务命令入队 operations.multi(); operations.opsForValue().decrement(account:A, amount); operations.opsForValue().increment(account:B, amount); // 4. 执行事务被修改则返回空集合 return operations.exec(); } }); return !result.isEmpty(); }三、Lua 脚本原子执行复杂逻辑首选Lua 脚本整体在 Redis 主线程原子执行支持条件判断、循环是生产环境最常用的多命令原子方案。Spring 提供RedisScript封装自动复用脚本EVALSHA 优化性能。示例 1原子扣减库存库存不足不扣减防超卖Component public class StockService { Resource private StringRedisTemplate stringRedisTemplate; /** * Lua脚本库存校验扣减 * KEYS[1] 库存key * ARGV[1] 扣减数量 * 返回1扣减成功0库存不足 */ private static final String DEDUCT_SCRIPT local stock redis.call(GET, KEYS[1]) if not stock or tonumber(stock) tonumber(ARGV[1]) then return 0 end redis.call(DECRBY, KEYS[1], ARGV[1]) return 1; // 预编译脚本指定返回类型 private final RedisScriptLong deductScript new DefaultRedisScript(DEDUCT_SCRIPT, Long.class); /** * 原子扣减库存 */ public boolean deductStock(Long goodsId, int buyNum) { String stockKey goods: goodsId :stock; Long res stringRedisTemplate.execute( deductScript, Collections.singletonList(stockKey), String.valueOf(buyNum) ); return res ! null res 1; } }示例 2原子释放分布式锁仅持有者可删除Component public class RedisDistributedLock { Resource private StringRedisTemplate stringRedisTemplate; /** * Lua脚本校验锁持有者后删除 * KEYS[1] 锁key * ARGV[1] 锁唯一标识请求ID * 返回1释放成功0非持有者 */ private static final String UNLOCK_SCRIPT if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) end return 0; private final RedisScriptLong unlockScript new DefaultRedisScript(UNLOCK_SCRIPT, Long.class); /** * 尝试加锁 */ public boolean tryLock(String lockKey, String requestId, long expireSeconds) { Boolean flag stringRedisTemplate.opsForValue() .setIfAbsent(lockKey, requestId, expireSeconds, TimeUnit.SECONDS); return Boolean.TRUE.equals(flag); } /** * 原子解锁 */ public boolean unlock(String lockKey, String requestId) { Long res stringRedisTemplate.execute( unlockScript, Collections.singletonList(lockKey), requestId ); return res ! null res 1; } }使用方式String lockKey lock:order:create; String requestId UUID.randomUUID().toString(); try { if (redisDistributedLock.tryLock(lockKey, requestId, 30)) { // 执行业务逻辑 createOrder(); } else { throw new BusinessException(系统繁忙请稍后重试); } } finally { // 必须在 finally 中释放锁避免异常导致锁未释放 redisDistributedLock.unlock(lockKey, requestId); }四、经典业务场景完整实现场景 1固定窗口接口限流/** * 限流规则单个用户1分钟内最多访问 limit 次 * return true允许访问false触发限流 */ public boolean rateLimit(Long userId, int limit) { String limitKey user: userId :api:limit; Long count stringRedisTemplate.opsForValue().increment(limitKey); // 首次访问设置过期时间避免重置有效期 if (count ! null count 1) { stringRedisTemplate.expire(limitKey, 60, TimeUnit.SECONDS); } return count ! null count limit; }场景 2可靠任务队列防消息丢失/** * 消费者领取任务原子从待处理队列移至处理中队列 * return 任务内容null无待处理任务 */ public String pickTask() { return stringRedisTemplate.opsForList() .rightPopAndLeftPush(task:pending, task:processing); } /** * 任务完成后从处理中队列移除 */ public void finishTask(String taskId) { stringRedisTemplate.opsForList().remove(task:processing, 0, taskId); }关键注意事项连接一致性事务 WATCH 必须绑定同一个连接否则 WATCH 失效单命令、Lua 脚本天然满足同连接要求。Lua 脚本规范禁止在脚本中执行耗时操作如全量遍历、大量循环否则会阻塞 Redis 主线程影响所有请求。分布式锁规范加锁必须同时设置过期时间解锁必须校验持有者禁止直接 DEL过期时间需大于业务执行时长。事务局限性Redis 事务无回滚机制仅语法错误会整体失败运行时错误会继续执行后续命令复杂逻辑优先用 Lua。Redisson 分布式锁完整实现Spring Boot 生产级Redisson 是 Redis 官方推荐的 Java 级分布式工具集底层全部基于Lua 脚本保证原子性内置可重入、自动续期看门狗、故障容错等能力是生产环境分布式锁的首选方案远比手动实现SETNX Lua更健壮。以下示例基于Spring Boot 2.x/3.x Redisson 3.25.x所有代码可直接复用。一、Spring Boot 整合 Redisson1. 引入 Maven 依赖dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.25.0/version /dependency2. 基础配置单机 Redis 为例在application.yml中配置支持单机、哨兵、集群多种模式spring: redis: # 基础连接信息 host: 127.0.0.1 port: 6379 password: your-password database: 0 # Redisson 专属配置可选也可通过配置类自定义 redisson: config: | singleServerConfig: connectTimeout: 3000 idleConnectionTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500配置类方式更灵活推荐生产使用Configuration public class RedissonConfig { Bean public RedissonClient redissonClient() { Config config new Config(); // 单机模式 config.useSingleServer() .setAddress(redis://127.0.0.1:6379) .setPassword(your-password) .setDatabase(0) .setConnectionMinimumIdleSize(5); return Redisson.create(config); } }使用时直接注入Resource private RedissonClient redissonClient;二、7 种常用分布式锁实现与适用场景1. 可重入锁RLock最核心、最常用完全替代手写SETNX Lua锁支持原子加锁解锁可重入同一线程可多次加锁看门狗自动续期业务没执行完锁不会过期阻塞等待 / 尝试加锁两种模式代码示例/** * 方式1阻塞式加锁一直等到获取锁为止 * 看门狗默认30秒过期每10秒自动续期业务执行完才会释放 */ public void reentrantLockDemo() { String lockKey lock:order:create:1001; RLock lock redissonClient.getLock(lockKey); try { // 加锁默认看门狗机制生效自动续期 lock.lock(); // 执行业务逻辑 doBusiness(); } finally { // 必须在 finally 释放防止异常导致死锁 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } /** * 方式2尝试加锁带超时最推荐生产使用 * waitTime最大等待时间拿不到锁就放弃 * leaseTime锁持有超时时间超过自动释放指定后看门狗不生效 */ public boolean tryLockDemo() { String lockKey lock:stock:deduct:2001; RLock lock redissonClient.getLock(lockKey); try { // 尝试3秒内获取锁拿到后持有10秒自动释放 boolean getLock lock.tryLock(3, 10, TimeUnit.SECONDS); if (!getLock) { // 加锁失败返回业务提示 return false; } // 执行业务扣减库存、生成订单 deductStock(); return true; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }看门狗机制说明 不指定leaseTime时锁默认 30 秒过期Redisson 会每 10 秒自动给锁续期到 30 秒直到业务执行完成调用unlock。彻底避免「业务没执行完、锁提前过期」的问题。2. 公平锁RFairLock特性严格按照请求加锁的顺序分配锁先到先得避免线程饥饿适用场景对公平性要求高不允许某些请求一直抢不到锁的场景public void fairLockDemo() { String lockKey lock:fair:task; RFairLock fairLock redissonClient.getFairLock(lockKey); try { fairLock.lock(); // 执行业务 } finally { if (fairLock.isHeldByCurrentThread()) { fairLock.unlock(); } } }3. 读写锁RReadWriteLock特性读锁共享、写锁互斥读读不阻塞读写、写写阻塞适用场景读多写少场景大幅提升并发性能比如商品详情、配置信息更新public void readWriteLockDemo() { String lockKey lock:goods:info:1001; RReadWriteLock rwLock redissonClient.getReadWriteLock(lockKey); // 读锁多个线程可同时持有共享锁 RLock readLock rwLock.readLock(); // 写锁独占锁写的时候所有读/写都阻塞 RLock writeLock rwLock.writeLock(); // 读数据用读锁 try { readLock.lock(); queryGoodsInfo(); } finally { readLock.unlock(); } // 写数据用写锁 try { writeLock.lock(); updateGoodsInfo(); } finally { writeLock.unlock(); } }4. 联锁RMultiLock特性同时对多个锁进行加锁全部加锁成功才算成功任意一个失败则整体失败适用场景需要同时操作多个独立资源必须全部锁定才能执行业务比如同时扣减多个商品库存public void multiLockDemo() { RLock lock1 redissonClient.getLock(lock:stock:1001); RLock lock2 redissonClient.getLock(lock:stock:1002); RLock lock3 redissonClient.getLock(lock:stock:1003); // 组装联锁 RMultiLock multiLock redissonClient.getMultiLock(lock1, lock2, lock3); try { // 三个锁全部加锁成功才继续 boolean getLock multiLock.tryLock(5, 15, TimeUnit.SECONDS); if (getLock) { // 同时操作三个商品库存 batchDeductStock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { multiLock.unlock(); } }5. 红锁RRedLock特性基于多个独立 Redis 节点过半节点加锁成功才算成功解决单节点宕机导致锁失效的问题适用场景对锁可靠性要求极高、Redis 多实例部署的核心业务public void redLockDemo() { // 需要3个独立Redis节点的RedissonClient实例 RLock lock1 redissonClient1.getLock(lock:order:pay); RLock lock2 redissonClient2.getLock(lock:order:pay); RLock lock3 redissonClient3.getLock(lock:order:pay); RRedLock redLock redissonClient.getRedLock(lock1, lock2, lock3); try { // 过半节点加锁成功才算成功 boolean getLock redLock.tryLock(3, 10, TimeUnit.SECONDS); if (getLock) { doPayBusiness(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { redLock.unlock(); } }6. 信号量RSemaphore特性控制同时访问资源的并发线程数本质是分布式计数器适用场景接口限流、资源池数量控制/** * 限流同一时间最多允许10个线程访问 */ public boolean semaphoreLimit() { RSemaphore semaphore redissonClient.getSemaphore(limit:api:query); // 初始化总许可数只初始化一次 semaphore.trySetPermits(10); try { // 获取1个许可获取不到则阻塞 semaphore.acquire(); // 执行业务 doQuery(); return true; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } finally { // 释放许可 semaphore.release(); } }三、完整业务实战订单创建防重复提交生产级示例结合tryLock实现接口幂等防止用户重复点击导致重复下单Service public class OrderService { Resource private RedissonClient redissonClient; /** * 创建订单 * param userId 用户ID * param goodsId 商品ID * param buyNum 购买数量 */ public String createOrder(Long userId, Long goodsId, int buyNum) { // 锁粒度按用户商品维度避免锁太粗影响其他用户 String lockKey lock:order:create: userId : goodsId; RLock lock redissonClient.getLock(lockKey); try { // 等待2秒锁持有20秒自动释放 boolean getLock lock.tryLock(2, 20, TimeUnit.SECONDS); if (!getLock) { throw new BusinessException(订单处理中请勿重复提交); } // 1. 校验库存 checkStock(goodsId, buyNum); // 2. 扣减库存 deductStock(goodsId, buyNum); // 3. 生成订单 String orderNo generateOrderNo(); saveOrder(orderNo, userId, goodsId, buyNum); return orderNo; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BusinessException(系统繁忙请稍后重试); } finally { // 仅当前线程持有锁时才释放避免误删他人锁 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } // 以下为业务方法省略 private void checkStock(Long goodsId, int buyNum) {} private void deductStock(Long goodsId, int buyNum) {} private String generateOrderNo() { return UUID.randomUUID().toString(); } private void saveOrder(String orderNo, Long userId, Long goodsId, int buyNum) {} }四、核心原理与生产避坑1. 原子性保障Redisson 所有加锁、解锁、续期操作底层全部通过Lua 脚本在 Redis 端原子执行和手动写 Lua 原理一致但边界处理更完善。2. 可重入实现原理使用 Redis Hash 结构存储锁信息field线程唯一标识value重入次数 每次加锁重入次数 1解锁重入次数 - 1减到 0 时删除锁。3. 生产避坑点锁粒度要适中不要全业务共用一把锁也不要锁粒度过细导致管理混乱按业务资源维度拆分。必须在 finally 释放锁防止业务异常导致锁无法释放。优先使用 tryLock避免无限阻塞设置合理的等待时间和持有超时。不要在异步线程释放锁锁和线程绑定跨线程解锁会抛出异常。长业务建议指定 leaseTime防止服务宕机后锁长期占用设置大于业务预估时长的过期时间。读写锁慎用写锁写锁是独占锁频繁写会导致读性能大幅下降。
redis原子操作命令
发布时间:2026/7/9 16:57:17
Redis 原子操作命令、应用场景与实例Redis 基于单线程串行执行命令的模型所有单个命令都具备天然原子性。通常所说的「原子操作命令」特指能一步完成「读取 - 修改 - 写入」完整逻辑、天然解决并发竞态问题的命令以及用于保障多条命令原子执行的机制。一、原生单命令原子操作按数据结构分类这类命令直接在 Redis 服务端完成完整的读写改逻辑彻底避免并发下「GET 计算 SET」的竞态问题。1. String 字符串类型最常用表格命令功能说明典型场景INCR key键值原子 1键不存在则初始化为 0 再加 1页面访问量、点赞数、订单号自增DECR key键值原子 - 1库存扣减、在线人数递减INCRBY key increment原子增加指定整数批量补库存、积分累加DECRBY key decrement原子减少指定整数批量扣减库存INCRBYFLOAT key increment原子增加浮点数金额、权重等浮点型计数SET key value NX EX seconds原子设置仅键不存在时生效同时设置过期时间分布式锁替代 SETNXEXPIRE 非原子组合GETSET key value原子设置新值并返回旧值数据幂等校验、状态更替示例# 初始化商品1001的库存为100 SET goods:1001:stock 100 # 高并发下原子扣减1件库存 DECR goods:1001:stock同类命令对比上面的goods:1001:stock就是keyDECR key自减 1DECRBY key N一次性扣减 N例DECRBY goods:1001:stock 5一次性减 5INCR / INCRBY增加库存2. Hash 哈希类型针对哈希表内单个字段做原子增减适合多维度聚合计数场景。命令功能说明典型场景HINCRBY key field increment对哈希指定字段原子增减整数购物车商品数量、用户多维度积分HINCRBYFLOAT key field increment对哈希指定字段原子增减浮点数用户余额、商品折扣计算示例# 用户1001的购物车中商品2001的数量1 HINCRBY cart:1001 goods:2001 13. List 列表类型原子完成列表间的元素转移保障消息 / 任务的可靠性。表格命令功能说明典型场景RPOPLPUSH source destination原子从源列表右端弹出推入目标列表左端可靠消息队列、任务队列重试BRPOPLPUSH source destination timeout阻塞版 RPOPLPUSH无数据时等待超时异步任务消费避免轮询空转BLPOP/BRPOP key timeout阻塞式弹出列表元素简单消息队列消费示例# 原子领取任务从待处理队列弹出移入处理中队列 # 防止消费过程中服务宕机导致消息丢失 RPOPLPUSH task:pending task:processing4. Set / ZSet 集合类型表格命令功能说明典型场景SPOP key count原子随机弹出集合中元素抽奖、随机选人SADD key member原子添加元素自动去重用户标签、UV 去重计数ZINCRBY key increment member原子增减有序集合成员的分数排行榜积分更新、内容热度排名示例# 帖子1001获得1次点赞排行榜分数1 ZINCRBY post:hot:rank 1 1001二、多命令原子保障方案当业务逻辑需要多条命令组合执行比如「先判断库存是否充足再扣减」单命令无法满足需要以下方案保障原子性。1. MULTI / EXEC 事务 WATCH 乐观锁原理将多条命令加入队列EXEC触发后一次性串行执行执行期间不会插入其他客户端命令。局限性不支持回滚语法错误会导致整体不执行但运行时错误如类型不匹配会继续执行后续命令无法在命令间做逻辑判断。WATCH监控指定键若EXEC前键被其他客户端修改事务整体放弃执行实现乐观锁。适用场景简单多键批量更新、冲突概率低的场景。示例乐观锁实现账户转账# 监控两个账户余额防止转账期间被修改 WATCH account:A account:B # 开启事务 MULTI DECRBY account:A 100 INCRBY account:B 100 # 执行事务若监控的键被修改返回nil EXEC2. Lua 脚本EVAL / EVALSHARedis 2.6 版本支持整个 Lua 脚本会作为一个整体在 Redis 主线程中原子执行是最灵活、最常用的多命令原子方案。优势支持条件判断、循环等复杂逻辑性能优于事务 WATCH注意脚本中不能执行耗时操作避免阻塞 Redis 主线程。适用场景带条件的库存扣减、分布式锁安全释放、复合业务逻辑原子执行。示例 1原子扣减库存库存不足则不扣减返回执行结果-- 返回1扣减成功返回0库存不足 local stock redis.call(GET, KEYS[1]) if not stock or tonumber(stock) tonumber(ARGV[1]) then return 0 end redis.call(DECRBY, KEYS[1], ARGV[1]) return 1执行命令EVAL local stock redis.call(GET, KEYS[1]) if not stock or tonumber(stock) tonumber(ARGV[1]) then return 0 end redis.call(DECRBY, KEYS[1], ARGV[1]) return 1 1 goods:1001:stock 2示例 2原子释放分布式锁仅锁的持有者能删除if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) end return 0三、经典业务场景完整实例场景 1固定窗口接口限流需求限制单个用户 1 分钟内最多调用接口 100 次。方案原子计数 过期时间避免并发下计数错乱。# 访问时原子计数1 INCR user:1001:api:limit # 仅首次访问设置60秒过期避免重置有效期 EXPIRE user:1001:api:limit 60 # 业务逻辑返回值 100 则触发限流场景 2分布式互斥锁需求多服务实例下同一任务同一时间只有一个实例执行。方案SET ... NX EX原子加锁 Lua 原子解锁NX保证只有一个客户端能加锁成功EX 30自动过期防止服务宕机后锁永久不释放唯一 request_id解锁时校验持有者防止误删他人的锁加锁命令SET lock:order:task uuid-xxx-123 NX EX 30解锁命令Lua 原子执行EVAL if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) end return 0 1 lock:order:task uuid-xxx-123场景 3商品超卖防护需求高并发秒杀场景下绝对不能出现负库存。方案Lua 脚本原子执行「库存校验 → 库存扣减」彻底避免超卖。单纯使用DECR会直接扣成负数无法满足业务要求校验 扣减必须原子执行因此 Lua 是最优解。场景 4可靠任务队列需求消费者领取任务后若宕机任务不丢失支持重试。方案RPOPLPUSH原子转移任务待处理队列task:pending处理中队列task:processing消费者领取任务RPOPLPUSH task:pending task:processing任务处理完成后从处理中队列删除LREM task:processing 0 task_1001若消费者宕机可定时扫描处理中队列的超时任务重新放回待处理队列。常见误区SETNX EXPIRE 是原子的错误两条命令之间若服务宕机锁会永久不过期必须使用SET ... NX EX单命令原子设置。Redis 事务支持回滚错误Redis 事务无回滚机制仅语法错误会整体放弃运行时错误会继续执行后续命令。Lua 脚本可以写复杂长逻辑错误Lua 执行期间会阻塞 Redis耗时过长会影响所有请求脚本必须简短高效。基于Spring Boot Spring Data Redis使用通用性最强的StringRedisTemplate默认已完成 Redis 基础配置所有代码可直接复用。一、基础原子命令 Java 实现Redis 单命令天然原子直接调用 Spring 封装的 API 即可无需额外处理并发。1. 字符串自增 / 自减计数、库存场景// 原子1返回自增后的值 Long viewCount stringRedisTemplate.opsForValue().increment(page:view:1001); // 原子增加指定数值补货10件库存 Long stock stringRedisTemplate.opsForValue().increment(goods:1001:stock, 10); // 原子-1扣减1件库存 Long remainStock stringRedisTemplate.opsForValue().decrement(goods:1001:stock);2. 原子加锁SET NX EX分布式锁核心注意必须使用带过期时间的重载方法保证「设值 过期」单命令原子禁止分开调用 setIfAbsent expire// 返回 true加锁成功false锁已存在 Boolean lockFlag stringRedisTemplate.opsForValue() .setIfAbsent(lock:order:1001, request-uuid-xxx, 30, TimeUnit.SECONDS);3. 其他常用原子命令// GETSET原子设置新值并返回旧值 String oldStatus stringRedisTemplate.opsForValue().getAndSet(user:1001:status, online); // Hash原子增减指定字段购物车商品数量1 Long cartNum stringRedisTemplate.opsForHash().increment(cart:1001, goods:2001, 1); // List原子转移元素可靠队列从待处理移到处理中 String task stringRedisTemplate.opsForList().rightPopAndLeftPush(task:pending, task:processing); // ZSet原子增减成员分数排行榜热度1 Double postScore stringRedisTemplate.opsForZSet().incrementScore(post:hot:rank, 1001, 1);二、事务 WATCH 乐观锁实现适用于多命令、低冲突的场景通过 WATCH 监控键实现乐观锁冲突则事务自动放弃。必须保证 WATCH、MULTI、EXEC 在同一个 Redis 连接中因此使用 SessionCallback 绑定连接/** * 账户转账原子扣减A账户、增加B账户乐观锁防止并发修改 * param amount 转账金额 * return true转账成功false并发冲突/余额不足 */ public boolean transfer(int amount) { ListObject result stringRedisTemplate.execute(new SessionCallbackListObject() { Override public ListObject execute(RedisOperations operations) throws DataAccessException { // 1. 监控两个账户键执行前被修改则事务放弃 operations.watch(Arrays.asList(account:A, account:B)); // 2. 业务校验余额是否充足 String balanceA (String) operations.opsForValue().get(account:A); if (balanceA null || Integer.parseInt(balanceA) amount) { operations.unwatch(); return Collections.emptyList(); } // 3. 开启事务命令入队 operations.multi(); operations.opsForValue().decrement(account:A, amount); operations.opsForValue().increment(account:B, amount); // 4. 执行事务被修改则返回空集合 return operations.exec(); } }); return !result.isEmpty(); }三、Lua 脚本原子执行复杂逻辑首选Lua 脚本整体在 Redis 主线程原子执行支持条件判断、循环是生产环境最常用的多命令原子方案。Spring 提供RedisScript封装自动复用脚本EVALSHA 优化性能。示例 1原子扣减库存库存不足不扣减防超卖Component public class StockService { Resource private StringRedisTemplate stringRedisTemplate; /** * Lua脚本库存校验扣减 * KEYS[1] 库存key * ARGV[1] 扣减数量 * 返回1扣减成功0库存不足 */ private static final String DEDUCT_SCRIPT local stock redis.call(GET, KEYS[1]) if not stock or tonumber(stock) tonumber(ARGV[1]) then return 0 end redis.call(DECRBY, KEYS[1], ARGV[1]) return 1; // 预编译脚本指定返回类型 private final RedisScriptLong deductScript new DefaultRedisScript(DEDUCT_SCRIPT, Long.class); /** * 原子扣减库存 */ public boolean deductStock(Long goodsId, int buyNum) { String stockKey goods: goodsId :stock; Long res stringRedisTemplate.execute( deductScript, Collections.singletonList(stockKey), String.valueOf(buyNum) ); return res ! null res 1; } }示例 2原子释放分布式锁仅持有者可删除Component public class RedisDistributedLock { Resource private StringRedisTemplate stringRedisTemplate; /** * Lua脚本校验锁持有者后删除 * KEYS[1] 锁key * ARGV[1] 锁唯一标识请求ID * 返回1释放成功0非持有者 */ private static final String UNLOCK_SCRIPT if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) end return 0; private final RedisScriptLong unlockScript new DefaultRedisScript(UNLOCK_SCRIPT, Long.class); /** * 尝试加锁 */ public boolean tryLock(String lockKey, String requestId, long expireSeconds) { Boolean flag stringRedisTemplate.opsForValue() .setIfAbsent(lockKey, requestId, expireSeconds, TimeUnit.SECONDS); return Boolean.TRUE.equals(flag); } /** * 原子解锁 */ public boolean unlock(String lockKey, String requestId) { Long res stringRedisTemplate.execute( unlockScript, Collections.singletonList(lockKey), requestId ); return res ! null res 1; } }使用方式String lockKey lock:order:create; String requestId UUID.randomUUID().toString(); try { if (redisDistributedLock.tryLock(lockKey, requestId, 30)) { // 执行业务逻辑 createOrder(); } else { throw new BusinessException(系统繁忙请稍后重试); } } finally { // 必须在 finally 中释放锁避免异常导致锁未释放 redisDistributedLock.unlock(lockKey, requestId); }四、经典业务场景完整实现场景 1固定窗口接口限流/** * 限流规则单个用户1分钟内最多访问 limit 次 * return true允许访问false触发限流 */ public boolean rateLimit(Long userId, int limit) { String limitKey user: userId :api:limit; Long count stringRedisTemplate.opsForValue().increment(limitKey); // 首次访问设置过期时间避免重置有效期 if (count ! null count 1) { stringRedisTemplate.expire(limitKey, 60, TimeUnit.SECONDS); } return count ! null count limit; }场景 2可靠任务队列防消息丢失/** * 消费者领取任务原子从待处理队列移至处理中队列 * return 任务内容null无待处理任务 */ public String pickTask() { return stringRedisTemplate.opsForList() .rightPopAndLeftPush(task:pending, task:processing); } /** * 任务完成后从处理中队列移除 */ public void finishTask(String taskId) { stringRedisTemplate.opsForList().remove(task:processing, 0, taskId); }关键注意事项连接一致性事务 WATCH 必须绑定同一个连接否则 WATCH 失效单命令、Lua 脚本天然满足同连接要求。Lua 脚本规范禁止在脚本中执行耗时操作如全量遍历、大量循环否则会阻塞 Redis 主线程影响所有请求。分布式锁规范加锁必须同时设置过期时间解锁必须校验持有者禁止直接 DEL过期时间需大于业务执行时长。事务局限性Redis 事务无回滚机制仅语法错误会整体失败运行时错误会继续执行后续命令复杂逻辑优先用 Lua。Redisson 分布式锁完整实现Spring Boot 生产级Redisson 是 Redis 官方推荐的 Java 级分布式工具集底层全部基于Lua 脚本保证原子性内置可重入、自动续期看门狗、故障容错等能力是生产环境分布式锁的首选方案远比手动实现SETNX Lua更健壮。以下示例基于Spring Boot 2.x/3.x Redisson 3.25.x所有代码可直接复用。一、Spring Boot 整合 Redisson1. 引入 Maven 依赖dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.25.0/version /dependency2. 基础配置单机 Redis 为例在application.yml中配置支持单机、哨兵、集群多种模式spring: redis: # 基础连接信息 host: 127.0.0.1 port: 6379 password: your-password database: 0 # Redisson 专属配置可选也可通过配置类自定义 redisson: config: | singleServerConfig: connectTimeout: 3000 idleConnectionTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500配置类方式更灵活推荐生产使用Configuration public class RedissonConfig { Bean public RedissonClient redissonClient() { Config config new Config(); // 单机模式 config.useSingleServer() .setAddress(redis://127.0.0.1:6379) .setPassword(your-password) .setDatabase(0) .setConnectionMinimumIdleSize(5); return Redisson.create(config); } }使用时直接注入Resource private RedissonClient redissonClient;二、7 种常用分布式锁实现与适用场景1. 可重入锁RLock最核心、最常用完全替代手写SETNX Lua锁支持原子加锁解锁可重入同一线程可多次加锁看门狗自动续期业务没执行完锁不会过期阻塞等待 / 尝试加锁两种模式代码示例/** * 方式1阻塞式加锁一直等到获取锁为止 * 看门狗默认30秒过期每10秒自动续期业务执行完才会释放 */ public void reentrantLockDemo() { String lockKey lock:order:create:1001; RLock lock redissonClient.getLock(lockKey); try { // 加锁默认看门狗机制生效自动续期 lock.lock(); // 执行业务逻辑 doBusiness(); } finally { // 必须在 finally 释放防止异常导致死锁 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } /** * 方式2尝试加锁带超时最推荐生产使用 * waitTime最大等待时间拿不到锁就放弃 * leaseTime锁持有超时时间超过自动释放指定后看门狗不生效 */ public boolean tryLockDemo() { String lockKey lock:stock:deduct:2001; RLock lock redissonClient.getLock(lockKey); try { // 尝试3秒内获取锁拿到后持有10秒自动释放 boolean getLock lock.tryLock(3, 10, TimeUnit.SECONDS); if (!getLock) { // 加锁失败返回业务提示 return false; } // 执行业务扣减库存、生成订单 deductStock(); return true; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }看门狗机制说明 不指定leaseTime时锁默认 30 秒过期Redisson 会每 10 秒自动给锁续期到 30 秒直到业务执行完成调用unlock。彻底避免「业务没执行完、锁提前过期」的问题。2. 公平锁RFairLock特性严格按照请求加锁的顺序分配锁先到先得避免线程饥饿适用场景对公平性要求高不允许某些请求一直抢不到锁的场景public void fairLockDemo() { String lockKey lock:fair:task; RFairLock fairLock redissonClient.getFairLock(lockKey); try { fairLock.lock(); // 执行业务 } finally { if (fairLock.isHeldByCurrentThread()) { fairLock.unlock(); } } }3. 读写锁RReadWriteLock特性读锁共享、写锁互斥读读不阻塞读写、写写阻塞适用场景读多写少场景大幅提升并发性能比如商品详情、配置信息更新public void readWriteLockDemo() { String lockKey lock:goods:info:1001; RReadWriteLock rwLock redissonClient.getReadWriteLock(lockKey); // 读锁多个线程可同时持有共享锁 RLock readLock rwLock.readLock(); // 写锁独占锁写的时候所有读/写都阻塞 RLock writeLock rwLock.writeLock(); // 读数据用读锁 try { readLock.lock(); queryGoodsInfo(); } finally { readLock.unlock(); } // 写数据用写锁 try { writeLock.lock(); updateGoodsInfo(); } finally { writeLock.unlock(); } }4. 联锁RMultiLock特性同时对多个锁进行加锁全部加锁成功才算成功任意一个失败则整体失败适用场景需要同时操作多个独立资源必须全部锁定才能执行业务比如同时扣减多个商品库存public void multiLockDemo() { RLock lock1 redissonClient.getLock(lock:stock:1001); RLock lock2 redissonClient.getLock(lock:stock:1002); RLock lock3 redissonClient.getLock(lock:stock:1003); // 组装联锁 RMultiLock multiLock redissonClient.getMultiLock(lock1, lock2, lock3); try { // 三个锁全部加锁成功才继续 boolean getLock multiLock.tryLock(5, 15, TimeUnit.SECONDS); if (getLock) { // 同时操作三个商品库存 batchDeductStock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { multiLock.unlock(); } }5. 红锁RRedLock特性基于多个独立 Redis 节点过半节点加锁成功才算成功解决单节点宕机导致锁失效的问题适用场景对锁可靠性要求极高、Redis 多实例部署的核心业务public void redLockDemo() { // 需要3个独立Redis节点的RedissonClient实例 RLock lock1 redissonClient1.getLock(lock:order:pay); RLock lock2 redissonClient2.getLock(lock:order:pay); RLock lock3 redissonClient3.getLock(lock:order:pay); RRedLock redLock redissonClient.getRedLock(lock1, lock2, lock3); try { // 过半节点加锁成功才算成功 boolean getLock redLock.tryLock(3, 10, TimeUnit.SECONDS); if (getLock) { doPayBusiness(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { redLock.unlock(); } }6. 信号量RSemaphore特性控制同时访问资源的并发线程数本质是分布式计数器适用场景接口限流、资源池数量控制/** * 限流同一时间最多允许10个线程访问 */ public boolean semaphoreLimit() { RSemaphore semaphore redissonClient.getSemaphore(limit:api:query); // 初始化总许可数只初始化一次 semaphore.trySetPermits(10); try { // 获取1个许可获取不到则阻塞 semaphore.acquire(); // 执行业务 doQuery(); return true; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } finally { // 释放许可 semaphore.release(); } }三、完整业务实战订单创建防重复提交生产级示例结合tryLock实现接口幂等防止用户重复点击导致重复下单Service public class OrderService { Resource private RedissonClient redissonClient; /** * 创建订单 * param userId 用户ID * param goodsId 商品ID * param buyNum 购买数量 */ public String createOrder(Long userId, Long goodsId, int buyNum) { // 锁粒度按用户商品维度避免锁太粗影响其他用户 String lockKey lock:order:create: userId : goodsId; RLock lock redissonClient.getLock(lockKey); try { // 等待2秒锁持有20秒自动释放 boolean getLock lock.tryLock(2, 20, TimeUnit.SECONDS); if (!getLock) { throw new BusinessException(订单处理中请勿重复提交); } // 1. 校验库存 checkStock(goodsId, buyNum); // 2. 扣减库存 deductStock(goodsId, buyNum); // 3. 生成订单 String orderNo generateOrderNo(); saveOrder(orderNo, userId, goodsId, buyNum); return orderNo; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BusinessException(系统繁忙请稍后重试); } finally { // 仅当前线程持有锁时才释放避免误删他人锁 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } // 以下为业务方法省略 private void checkStock(Long goodsId, int buyNum) {} private void deductStock(Long goodsId, int buyNum) {} private String generateOrderNo() { return UUID.randomUUID().toString(); } private void saveOrder(String orderNo, Long userId, Long goodsId, int buyNum) {} }四、核心原理与生产避坑1. 原子性保障Redisson 所有加锁、解锁、续期操作底层全部通过Lua 脚本在 Redis 端原子执行和手动写 Lua 原理一致但边界处理更完善。2. 可重入实现原理使用 Redis Hash 结构存储锁信息field线程唯一标识value重入次数 每次加锁重入次数 1解锁重入次数 - 1减到 0 时删除锁。3. 生产避坑点锁粒度要适中不要全业务共用一把锁也不要锁粒度过细导致管理混乱按业务资源维度拆分。必须在 finally 释放锁防止业务异常导致锁无法释放。优先使用 tryLock避免无限阻塞设置合理的等待时间和持有超时。不要在异步线程释放锁锁和线程绑定跨线程解锁会抛出异常。长业务建议指定 leaseTime防止服务宕机后锁长期占用设置大于业务预估时长的过期时间。读写锁慎用写锁写锁是独占锁频繁写会导致读性能大幅下降。