SpringBoot集成Redisson實(shí)現(xiàn)分布式鎖的示例代碼
1、集成 Redisson
Redisson 是 Redis 官方推薦的 Java 客戶端,實(shí)現(xiàn)了 可靠的分布式鎖、可重入鎖、讀寫鎖 等,封裝了底層復(fù)雜性。
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自動(dòng)配置,無需額外配置2、分布式鎖解決并發(fā)預(yù)約號(hào)源超賣問題
2.1 預(yù)約服務(wù)中,增加分布式鎖
修改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;
/*
//預(yù)約操作后清除該用戶的預(yù)約緩存
@CacheEvict(value = "appointments", key = "'user:' + #userId + '_page:*'",allEntries = true)
//Spring Cache 原生不支持通配符 * 在 key 中的模糊匹配。
//allEntries = true會(huì)清空整個(gè)緩存名下的所有條目
@Override
@Transactional(rollbackFor = Exception.class) //任何異常都回滾
public boolean bookAppointment(String appointmentNo,Long userId, Long departmentId, String appointmentDate) {
//1\插入預(yù)約記錄
Appointment appointment = new Appointment();
appointment.setAppointmentNo(appointmentNo);
appointment.setUserId(userId);
appointment.setDepartmentId(departmentId);
//將字符串轉(zhuǎn)為Date(格式:yyyy-mm-dd)
try {
//使用SimpleDateFormat 定義日期格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");
Date date = sdf.parse(appointmentDate); //字符串轉(zhuǎn)為Date
appointment.setAppointmentDate(date);
} catch (Exception e) {
throw new RuntimeException("日期格式錯(cuò)誤",e);
}
appointment.setStatus("PENDING"); //狀態(tài):待就診
int rows = appointmentMapper.insert(appointment);
if(rows == 0){
throw new RuntimeException("插入預(yù)約失敗");
}
//2\扣減號(hào)源,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("號(hào)源不足或科室不存在");
}
return true;
}
*/
@Override
@Transactional(rollbackFor = Exception.class) //事務(wù)管理,任何異常都回滾
public boolean bookAppointment(String appointmentNo, Long userId, Long departmentId, String appointmentDate) {
//構(gòu)建分布式鎖的key(科室Id + 預(yù)約日期)
String lockKey = "lock:dept:" + departmentId + ":date:" + appointmentDate;
//創(chuàng)建對(duì)象,Redisson 根據(jù) key 獲取一個(gè)可重入鎖對(duì)象。
RLock lock = redissonClient.getLock(lockKey);
boolean locked = false;
try {
// 嘗試加鎖,最多等待3秒,上鎖后10秒自動(dòng)釋放(防止死鎖)
locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if(!locked){
throw new RuntimeException("系統(tǒng)繁忙,請(qǐng)稍后重試");
}
//1 檢查號(hào)源
Department dept = departmentMapper.selectById(departmentId);
if(dept == null || dept.getRemaining() == null || dept.getRemaining() <= 0){
throw new RuntimeException("號(hào)源不足");
}
//2 插入預(yù)約記錄
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("插入預(yù)約失敗");
}
//3 扣減號(hào)源
//使用自定義扣減號(hào)源方法,SQL條件更新號(hào)源數(shù)量
int updateRows = departmentMapper.decreaseRemaining(departmentId);
if(updateRows == 0){
throw new RuntimeException("扣減號(hào)源失敗,可能已被搶走");
}
return true;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(),e);
} finally {
//釋放鎖,只釋放當(dāng)前線程持有的鎖
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 自定義扣減號(hào)源方法
在DepartmentMapper中添加自定義扣減方法:
// DepartmentMapper.java
@Mapper
public interface DepartmentMapper extends BaseMapper<Department> {
// 自定義扣減號(hào)源(使用樂觀鎖防止超賣)
@Update("UPDATE department SET order_num = order_num - 1 where id = #{departmentId}")
int decreaseRemaining(@Param("departmentId") Long departmentId);
}
3、分布式鎖原理總結(jié)
- 鎖粒度:應(yīng)該細(xì)化到 科室+日期,而不是整個(gè)系統(tǒng)
- 鎖超時(shí):設(shè)置合理超時(shí)時(shí)間,避免業(yè)務(wù)執(zhí)行過長(zhǎng)導(dǎo)致鎖自動(dòng)釋放
- 可重入性:Redisson支持可重入,同一線程可重復(fù)獲取
- Watch Dog機(jī)制:Redisson的鎖會(huì)自動(dòng)續(xù)期(默認(rèn)30秒),防止業(yè)務(wù)未完成鎖過期
以上就是SpringBoot集成Redisson實(shí)現(xiàn)分布式鎖的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Redisson分布式鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Springboot使用redisson實(shí)現(xiàn)分布式鎖的代碼示例
- SpringBoot整合分布式鎖redisson的示例代碼
- 基于Redis分布式鎖Redisson及SpringBoot集成Redisson
- SpringBoot整合Redisson實(shí)現(xiàn)分布式鎖
- Springboot中如何使用Redisson實(shí)現(xiàn)分布式鎖淺析
- SpringBoot使用Redisson實(shí)現(xiàn)分布式鎖(秒殺系統(tǒng))
- SpringBoot集成Redisson實(shí)現(xiàn)分布式鎖的方法示例
- Spring?Task實(shí)現(xiàn)定時(shí)任務(wù)的示例
相關(guān)文章
Java程序的初始化順序,static{}靜態(tài)代碼塊和實(shí)例語句塊的使用方式
這篇文章主要介紹了Java程序的初始化順序,static{}靜態(tài)代碼塊和實(shí)例語句塊的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
在Spring?Boot使用Undertow服務(wù)的方法
Undertow是RedHAT紅帽公司開源的產(chǎn)品,采用JAVA開發(fā),是一款靈活,高性能的web服務(wù)器,提供了NIO的阻塞/非阻塞API,也是Wildfly的默認(rèn)Web容器,這篇文章給大家介紹了在Spring?Boot使用Undertow服務(wù)的方法,感興趣的朋友跟隨小編一起看看吧2023-05-05
通過Java實(shí)現(xiàn)zip文件與rar文件解壓縮的詳細(xì)步驟
這篇文章主要給大家介紹了如何通過?Java?來完成?zip?文件與?rar?文件的解壓縮,文中通過代碼示例講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-07-07
解決SpringMVC接收不到ajaxPOST參數(shù)的問題
今天小編就為大家分享一篇解決SpringMVC接收不到ajaxPOST參數(shù)的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-08-08
Maven中optional和scope元素的使用弄明白了嗎
這篇文章主要介紹了Maven中optional和scope元素的使用弄明白了嗎,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
關(guān)于feign接口動(dòng)態(tài)代理源碼解析
這篇文章主要介紹了關(guān)于feign接口動(dòng)態(tài)代理源碼解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03

