SpringBoot集成Redisson實現(xiàn)分布式鎖的示例代碼
更新時間:2026年04月08日 08:33:44 作者:Zzxy
本文介紹了使用Redisson實現(xiàn)分布式鎖以解決并發(fā)預約號源超賣問題,首先添加Redisson依賴并配置RedissonClient,接著在預約服務中引入分布式鎖,并自定義扣減號源方法,最后總結了分布式鎖的實現(xiàn)原理,包括鎖粒度、鎖超時、可重入性和WatchDog機制,需要的朋友可以參考下
1、集成 Redisson
Redisson 是 Redis 官方推薦的 Java 客戶端,實現(xiàn)了 可靠的分布式鎖、可重入鎖、讀寫鎖 等,封裝了底層復雜性。
1.1 添加 Redisson 依賴
<!-- Redisson(Redis分布式鎖客戶端) -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.4</version>
</dependency>1.2 配置 RedissonClient
在application.yml中添加:
spring:
redis:
host: localhost
port: 6379
# Redisson自動配置,無需額外配置2、分布式鎖解決并發(fā)預約號源超賣問題
2.1 預約服務中,增加分布式鎖
修改AppointmentServiceImpl.bookAppointment方法:
package com.example.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.springBoot.hospital.entity.Appointment;
import com.springBoot.hospital.entity.Department;
import com.springBoot.hospital.mapper.AppointmentMapper;
import com.springBoot.hospital.mapper.DepartmentMapper;
import com.springBoot.hospital.service.AppointmentService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.text.SimpleDateFormat;
import java.util.concurrent.TimeUnit;
@Service
public class AppointmentServiceImpl implements AppointmentService {
@Autowired
private DepartmentMapper departmentMapper;
@Autowired
private AppointmentMapper appointmentMapper;
@Autowired
private RedissonClient redissonClient;
/*
//預約操作后清除該用戶的預約緩存
@CacheEvict(value = "appointments", key = "'user:' + #userId + '_page:*'",allEntries = true)
//Spring Cache 原生不支持通配符 * 在 key 中的模糊匹配。
//allEntries = true會清空整個緩存名下的所有條目
@Override
@Transactional(rollbackFor = Exception.class) //任何異常都回滾
public boolean bookAppointment(String appointmentNo,Long userId, Long departmentId, String appointmentDate) {
//1\插入預約記錄
Appointment appointment = new Appointment();
appointment.setAppointmentNo(appointmentNo);
appointment.setUserId(userId);
appointment.setDepartmentId(departmentId);
//將字符串轉為Date(格式:yyyy-mm-dd)
try {
//使用SimpleDateFormat 定義日期格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");
Date date = sdf.parse(appointmentDate); //字符串轉為Date
appointment.setAppointmentDate(date);
} catch (Exception e) {
throw new RuntimeException("日期格式錯誤",e);
}
appointment.setStatus("PENDING"); //狀態(tài):待就診
int rows = appointmentMapper.insert(appointment);
if(rows == 0){
throw new RuntimeException("插入預約失敗");
}
//2\扣減號源,department中
//使用條件更新,避免并發(fā)
LambdaUpdateWrapper<Department> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Department::getId,departmentId)
.gt(Department::getRemaining,0)
.setSql("order_num = order_num - 1");
int updateRows = departmentMapper.update(null,updateWrapper);
if(updateRows == 0){
throw new RuntimeException("號源不足或科室不存在");
}
return true;
}
*/
@Override
@Transactional(rollbackFor = Exception.class) //事務管理,任何異常都回滾
public boolean bookAppointment(String appointmentNo, Long userId, Long departmentId, String appointmentDate) {
//構建分布式鎖的key(科室Id + 預約日期)
String lockKey = "lock:dept:" + departmentId + ":date:" + appointmentDate;
//創(chuàng)建對象,Redisson 根據 key 獲取一個可重入鎖對象。
RLock lock = redissonClient.getLock(lockKey);
boolean locked = false;
try {
// 嘗試加鎖,最多等待3秒,上鎖后10秒自動釋放(防止死鎖)
locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if(!locked){
throw new RuntimeException("系統(tǒng)繁忙,請稍后重試");
}
//1 檢查號源
Department dept = departmentMapper.selectById(departmentId);
if(dept == null || dept.getRemaining() == null || dept.getRemaining() <= 0){
throw new RuntimeException("號源不足");
}
//2 插入預約記錄
Appointment appointment = new Appointment();
appointment.setAppointmentNo(appointmentNo);
appointment.setUserId(userId);
appointment.setDepartmentId(departmentId);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
appointment.setAppointmentDate(sdf.parse(appointmentDate));
appointment.setStatus("PENDING");
int insertRows = appointmentMapper.insert(appointment);
if(insertRows == 0){
throw new RuntimeException("插入預約失敗");
}
//3 扣減號源
//使用自定義扣減號源方法,SQL條件更新號源數(shù)量
int updateRows = departmentMapper.decreaseRemaining(departmentId);
if(updateRows == 0){
throw new RuntimeException("扣減號源失敗,可能已被搶走");
}
return true;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(),e);
} finally {
//釋放鎖,只釋放當前線程持有的鎖
if(locked && lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
//分頁查詢緩存,分頁參數(shù)影響key
@Cacheable(value = "appointments", key = "'user:' + #userId + '_page:' + #pageNum + '_size:' + #pageSize")
@Override
public IPage<Appointment> findAppointmentsByUserId(Long userId, Integer pageNum, Integer pageSize) {
Page<Appointment> page = new Page<>(pageNum,pageSize);
LambdaQueryWrapper<Appointment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Appointment::getUserId,userId)
.orderByDesc(Appointment::getAppointmentDate);
return appointmentMapper.selectPage(page,queryWrapper);
}
}2.2 自定義扣減號源方法
在DepartmentMapper中添加自定義扣減方法:
// DepartmentMapper.java
@Mapper
public interface DepartmentMapper extends BaseMapper<Department> {
// 自定義扣減號源(使用樂觀鎖防止超賣)
@Update("UPDATE department SET order_num = order_num - 1 where id = #{departmentId}")
int decreaseRemaining(@Param("departmentId") Long departmentId);
}
3、分布式鎖原理總結
- 鎖粒度:應該細化到 科室+日期,而不是整個系統(tǒng)
- 鎖超時:設置合理超時時間,避免業(yè)務執(zhí)行過長導致鎖自動釋放
- 可重入性:Redisson支持可重入,同一線程可重復獲取
- Watch Dog機制:Redisson的鎖會自動續(xù)期(默認30秒),防止業(yè)務未完成鎖過期
以上就是SpringBoot集成Redisson實現(xiàn)分布式鎖的示例代碼的詳細內容,更多關于SpringBoot Redisson分布式鎖的資料請關注腳本之家其它相關文章!
相關文章
Java程序的初始化順序,static{}靜態(tài)代碼塊和實例語句塊的使用方式
這篇文章主要介紹了Java程序的初始化順序,static{}靜態(tài)代碼塊和實例語句塊的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
通過Java實現(xiàn)zip文件與rar文件解壓縮的詳細步驟
這篇文章主要給大家介紹了如何通過?Java?來完成?zip?文件與?rar?文件的解壓縮,文中通過代碼示例講解的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2024-07-07
解決SpringMVC接收不到ajaxPOST參數(shù)的問題
今天小編就為大家分享一篇解決SpringMVC接收不到ajaxPOST參數(shù)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
Maven中optional和scope元素的使用弄明白了嗎
這篇文章主要介紹了Maven中optional和scope元素的使用弄明白了嗎,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12

