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原子设置:仅键不存在时生效,同时设置过期时间分布式锁(替代 SETNX+EXPIRE 非原子组合)
GETSET key value原子设置新值并返回旧值数据幂等校验、状态更替

示例:

# 初始化商品1001的库存为100
SET goods:1001:stock 100

# 高并发下原子扣减1件库存
DECR goods:1001:stock

同类命令对比(上面的goods:1001:stock就是key)

  • DECR key:自减 1
  • DECRBY key N:一次性扣减 N,例 DECRBY goods:1001:stock 5 一次性减 5
  • INCR / INCRBY:增加库存

2. Hash 哈希类型

针对哈希表内单个字段做原子增减,适合多维度聚合计数场景。

命令功能说明典型场景
HINCRBY key field increment对哈希指定字段原子增减整数购物车商品数量、用户多维度积分
HINCRBYFLOAT key field increment对哈希指定字段原子增减浮点数用户余额、商品折扣计算

示例:

# 用户1001的购物车中,商品2001的数量+1
HINCRBY cart:1001 goods:2001 1

3. List 列表类型

原子完成列表间的元素转移,保障消息 / 任务的可靠性。

表格

命令功能说明典型场景
RPOPLPUSH source destination原子从源列表右端弹出,推入目标列表左端可靠消息队列、任务队列重试
BRPOPLPUSH source destination timeout阻塞版 RPOPLPUSH,无数据时等待超时异步任务消费,避免轮询空转
BLPOP/BRPOP key timeout阻塞式弹出列表元素简单消息队列消费

示例:

# 原子领取任务:从待处理队列弹出,移入处理中队列
# 防止消费过程中服务宕机导致消息丢失
RPOPLPUSH task:pending task:processing

4. 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
EXEC

2. Lua 脚本(EVAL / EVALSHA)

Redis 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"

若消费者宕机,可定时扫描处理中队列的超时任务,重新放回待处理队列。

常见误区

  1. SETNX + EXPIRE 是原子的:错误,两条命令之间若服务宕机,锁会永久不过期,必须使用 SET ... NX EX 单命令原子设置。
  2. Redis 事务支持回滚:错误,Redis 事务无回滚机制,仅语法错误会整体放弃,运行时错误会继续执行后续命令。
  3. 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) {
    List<Object> result = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
        @Override
        public List<Object> 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 RedisScript<Long> 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 RedisScript<Long> 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);
}

关键注意事项

  1. 连接一致性:事务 + WATCH 必须绑定同一个连接,否则 WATCH 失效;单命令、Lua 脚本天然满足同连接要求。
  2. Lua 脚本规范:禁止在脚本中执行耗时操作(如全量遍历、大量循环),否则会阻塞 Redis 主线程,影响所有请求。
  3. 分布式锁规范:加锁必须同时设置过期时间;解锁必须校验持有者,禁止直接 DEL;过期时间需大于业务执行时长。
  4. 事务局限性:Redis 事务无回滚机制,仅语法错误会整体失败,运行时错误会继续执行后续命令,复杂逻辑优先用 Lua。

Redisson 分布式锁完整实现(Spring Boot 生产级)

Redisson 是 Redis 官方推荐的 Java 级分布式工具集,底层全部基于 Lua 脚本保证原子性,内置可重入、自动续期(看门狗)、故障容错等能力,是生产环境分布式锁的首选方案,远比手动实现 SETNX + Lua 更健壮。

以下示例基于 Spring Boot 2.x/3.x + Redisson 3.25.x,所有代码可直接复用。


一、Spring Boot 整合 Redisson

1. 引入 Maven 依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.25.0</version>
</dependency>

2. 基础配置(单机 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. 生产避坑点

  1. 锁粒度要适中:不要全业务共用一把锁,也不要锁粒度过细导致管理混乱,按业务资源维度拆分。
  2. 必须在 finally 释放锁:防止业务异常导致锁无法释放。
  3. 优先使用 tryLock:避免无限阻塞,设置合理的等待时间和持有超时。
  4. 不要在异步线程释放锁:锁和线程绑定,跨线程解锁会抛出异常。
  5. 长业务建议指定 leaseTime:防止服务宕机后锁长期占用,设置大于业务预估时长的过期时间。
  6. 读写锁慎用写锁:写锁是独占锁,频繁写会导致读性能大幅下降。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐