Redisson 分布式锁

在我的智慧校园项目中,多个用户同时预约同一时间段这是不可避免的一个高并发场景,本文介绍如何使用Redisson分布式锁来解决这个问题。

什么是分布式锁?

分布式锁是控制分布式系统间同步访问共享资源的技术手段,用于协调不同系统或主机对资源的互斥访问,防止数据冲突并保证一致性在分布式系统中。如何保证同一时刻只有一个进程(或服务器)能操作某个共享资源——分布式锁解决了这个问题。————百度百科

听着很高大上,其实目的就是为了解决并发问题,不过环境是在集群模式下。

为什么要使用Redisson分布式锁?

在一般的项目当中,并发问题是不可避免的,比如当多个学生同时预约同一资源的同一时间段时,就会出现超卖等并发问题。

并发冲突演示:多个用户同时预约同一资源

如果没有分布式锁保护,当极短时间内发生并发请求时,数据库的常规查询将失效,导致严重的数据不一致问题(即“超卖”)。

👨‍🎓
用户 A
点击预约
🔬
深度学习实验室
明天 14:00 – 16:00
👩‍🎓
用户 B
点击预约
T1: 10:00:00.001

请求到达服务器。
执行 SELECT count 检查时间是否被占用。

T2: 10:00:00.002

请求几乎同时到达服务器。
执行 SELECT count 检查时间是否被占用。

T3: 10:00:00.010

查询结果返回:该时段空闲
准备执行 INSERT 插入预约记录。

T4: 10:00:00.011

查询结果返回:该时段空闲(因为A的记录还未插入)。
准备执行 INSERT 插入预约记录。

T5: 10:00:00.020

成功写入数据库,预约成功!🎉

T6: 10:00:00.021

成功写入数据库,预约成功!🎉

⚠️ 灾难发生:同一个实验室的同一个时间段,被同时分配给了用户 A 和用户 B,发生了“超卖”冲突!

为了解决这个问题,可以引入锁,在单机部署下,我们可以使用 Java 自带的 synchronized来解决。但为了应对高并发,项目通常是集群部署的。此时,本地锁将失效,我们必须引入分布式锁。

为什么要用Redisson来实现分布式锁?可以看一下几种分布式锁方式的对比。

特性RedissonSET NX + LuaZooKeeper
可重入
锁续期✅ 自动续期需手动实现
等待锁需循环实现
性能最高
集群支持需额外处理

可见Redisson实现的分布式锁对比其他例如ZooKeeper,Redis原生实现的分布式锁的优势很大,所以我们一般选择Redisson来实现分布式锁。

分布式锁功能实现

分布式锁基本实现

引入Redisson的依赖

<!-- pom.xml -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.17.6</version>
</dependency>

核心代码实现

@Service
@Slf4j
public class UserAppointmentServiceImpl implements UserAppointmentService {

    @Autowired
    private RedissonClient redissonClient;//注入Redisson

    @Autowired
    private UserAppointmentMapper userAppointmentMapper;

    // 其他注入...

    @Override
    public void submitAppointment(AppointmentDTO dto) {
        // 1. 构造锁的 key(粒度:资源 + 日期 + 开始时间 + 结束时间)
        String lockKey = "lock:appointment:" + dto.getResourceId()
            + ":" + dto.getAppointDate()
            + ":" + dto.getStartTime()
            + ":" + dto.getEndTime();

        // 2. 获取 Redisson 锁
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 3. 尝试获取锁
            // 参数1:等待时间 5秒 - 超过5秒放弃获取
            // 参数2:锁自动释放时间 10秒 - 防止死锁
            // 参数3:时间单位
            if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
                try {
                    // 4. 检查时间冲突
                    Integer count = userAppointmentMapper.countConflict(
                        dto.getResourceId(),
                        dto.getAppointDate(),
                        dto.getStartTime(),
                        dto.getEndTime()
                    );

                    if (count > 0) {
                        throw new RuntimeException(
                            "手慢了!该时间段刚刚已被抢占,请刷新后重试。");
                    }

                    // 5. 提交预约
                    Appointment appointment = new Appointment();
                    BeanUtils.copyProperties(dto, appointment);
                    appointment.setUserId(BaseContext.getCurrentId());
                    appointment.setStatus(0);
                    userAppointmentMapper.insert(appointment);

                } finally {
                    // 6. 释放锁
                    lock.unlock();
                }
            } else {
                // 获取锁失败
                throw new RuntimeException("系统繁忙,请稍后再试");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("系统繁忙,请稍后再试");
        }
    }
}

锁的 Key 设计

  • 锁的粒度要足够细,避免不必要的锁竞争
  • 包含所有唯一标识:资源ID、日期、开始时间、结束时间
  • 使用冒号分隔,便于 Redis 客户端可视化查看

核心机制解析

tryLock 参数说明

lock.tryLock(5, 10, TimeUnit.SECONDS)
参数含义作用
5等待时间5秒内持续尝试获取锁,超过则放弃
10锁持有时间锁自动释放时间,防止业务未完成锁过期
TimeUnit.SECONDS时间单位

Redisson实现的分布式锁有其独特的看门狗机制

锁获取成功 → 启动看门狗线程 → 每 10 秒检查一次→ 锁还持有?续期 30 秒

释放锁

释放锁要放在finally块中保证能够成功释放,并且Redisson 会校验线程 ID,防止误释放。

避坑

我在通过分布式锁进行并发处理的时候往方法加了Transactional注解,结果我在Jmeter压测的时候发现分布式锁没有成功实现,我debug了很久,最后才发现是因为事务的原因——锁在方法内部释放了,但事务还没提交,这就导致数据库没有改变,也就不能解决并发的问题了。

如何解决呢?

一是可以直接在Controller层加锁然后Service层加事务。 二是在 Service 层的无事务方法中加锁,锁内部调用另一个有 @Transactional 的方法,可以通过注入自身AopContext.currentProxy()获取代理对象来解决。

总之,锁的边界必须大于事务的边界,这样锁才不会被事务影响。

总结

通过引入 Redisson 分布式锁,我们优雅地解决了智慧校园中的预约冲突问题。这也是并发问题在集群模式下的常规解决的最佳实践。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇