Redisson 实战:Spring Boot 项目中如何优雅实现分布式锁

388 阅读4分钟

Redisson 实战:Spring Boot 项目中如何优雅实现分布式锁

在并发场景下,为了保证多个线程、多个服务节点间对共享资源的互斥访问,我们常常需要用到“分布式锁”。而 Redisson 正是 Redis 官方推荐的 Java 客户端中,对分布式锁封装最优雅、最稳定的实现之一。

本篇文章将基于 Spring Boot + Redisson 实现一个通用的分布式锁组件封装,并提供实际接口测试验证使用效果。

完整示例项目地址:

gitee.com/wangwei5211…

一、使用背景

常见的并发场景如下:

  • 限制用户同时只能执行一次下单请求

  • 避免重复扣减库存

  • 防止定时任务并发执行重复逻辑

这些场景都要求我们实现可靠的“互斥执行”机制,Redisson 提供的 RLock 就能很好地满足这些需求。

二、项目结构概览

redisson-lock-demo/
├── config/        # Redisson 配置类
├── service/       # 分布式锁抽象 + 实现 + 回调接口
├── controller/    # 接口演示
└── Application.java

三、Redisson 配置

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        return Redisson.create(config);
    }
}

四、通用锁服务设计

我们封装了一个统一的 DistributedLockService 接口:

public interface DistributedLockService {

    /**
     * 获取分布式锁(看门狗机制)
     *
     * @param key      锁的key
     * @param callback 业务逻辑
     * @return 业务逻辑执行结果
     */
    <T> T lock(String key, LockCallback<T> callback);

    /**
     * 尝试获取分布式锁
     *
     * @param key       锁的key
     * @param waitTime  等待获取锁的时间
     * @param leaseTime 锁的持有时间
     * @param unit      时间单位
     * @param callback  业务逻辑
     * @return 业务逻辑执行结果
     */
    <T> T tryLock(String key, long waitTime, long leaseTime, TimeUnit unit, LockCallback<T> callback);

    /**
     * 解锁
     *
     * @param key 锁的key
     */
    void unlock(String key);

}

回调接口定义:

@FunctionalInterface
public interface LockCallback<T> {

    T doInLock() throws Exception;

}

实现类:RedissonDistributedLockService

@Slf4j
@Service
@RequiredArgsConstructor
public class RedissonDistributedLockService implements DistributedLockService {

    private final RedissonClient redissonClient;

    @Override
    public <T> T lock(String key, LockCallback<T> callback) {
        RLock lock = redissonClient.getLock(key);
        try {
            lock.lock();
            return callback.doInLock();
        } catch (Exception e) {
            throw new RuntimeException("执行加锁逻辑时失败", e);
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    public <T> T tryLock(String key, long waitTime, long leaseTime, TimeUnit unit, LockCallback<T> callback) {
        RLock lock = redissonClient.getLock(key);
        boolean locked = false;
        try {
            locked = lock.tryLock(waitTime, leaseTime, unit);
            if (!locked) {
                throw new RuntimeException("未能成功获取分布式锁:" + key);
            }
            return callback.doInLock();
        } catch (Exception e) {
            throw new RuntimeException("执行 tryLock 逻辑时失败", e);
        } finally {
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    public void unlock(String key) {
        RLock lock = redissonClient.getLock(key);
        if (lock.isLocked() && lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }

}

五、接口演示

@RestController
@RequiredArgsConstructor
public class TestController {

    private final DistributedLockService lockService;

    @GetMapping("/test/lock")
    public String testLock() {
        return lockService.lock("test-lock", () -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "加锁执行成功";
        });
    }
}

可以使用浏览器或并发工具(如 ApacheBench、JMeter)测试多线程访问 /test/lock,观察是否串行执行。

六、使用建议与常见坑

  • 建议设置锁的超时时间(leaseTime),避免异常情况下造成死锁

  • 注意:Redisson 分布式锁默认不是可重入的,避免多线程内重复加锁

  • 切勿将 lock() 用于无法保证释放的逻辑中,推荐使用 tryLock() 尝试型加锁

七、总结

本文实现了基于 Redisson 的通用分布式锁服务,具备:

  • 封装清晰(接口 + 回调)

  • 使用方便(只需一行 lockService.lock(...))

  • 支持 tryLock、自动释放等能力

适用于绝大多数中后台业务并发控制场景,欢迎收藏使用。

八、补充说明:Redisson 的锁续期机制与看门狗

当我们使用 lock() 方法加锁时,如果没有显式设置 leaseTime,Redisson 会自动使用一个默认的“看门狗”机制来续期锁,避免因业务执行时间过长而提前释放。

🚨 自动续期机制说明

  • 默认锁有效期为 30 秒

  • 若业务执行时间超过 30 秒,Redisson 会每隔 10 秒自动续期

  • 前提是锁还在被线程持有,且客户端未宕机

这一机制被称为 Redisson WatchDog,源码位于 org.redisson.RedissonLock.renewExpiration。

✅ 推荐做法

  • 如果能预估业务时间,建议使用 tryLock(..., leaseTime) 显式指定锁时长,(比如常在 3 秒内完成),建议设置 leaseTime = 5 ~ 10 秒,避免依赖 WatchDog 续期

  • 如果业务不确定耗时,且服务器稳定可靠,则可使用默认机制简化使用