Redisson 是一種基于 Redis 的 Java 駐留集群的分布式對(duì)象和服務(wù)庫(kù)嫂粟,可以為我們提供豐富的分布式鎖和線(xiàn)程安全集合的實(shí)現(xiàn)。在 Spring Boot 應(yīng)用程序中使用 Redisson 可以方便地實(shí)現(xiàn)分布式應(yīng)用程序的某些方面墨缘,例如分布式鎖星虹、分布式集合零抬、分布式事件發(fā)布和訂閱等。本篇是一個(gè)使用 Redisson 實(shí)現(xiàn)分布式鎖的詳細(xì)示例宽涌,在這個(gè)示例中平夜,我們定義了DistributedLock注解,它可以標(biāo)注在方法上卸亮,配合DistributedLockAspect切面以及IDistributedLock分布式鎖封裝的接口忽妒,來(lái)實(shí)現(xiàn)redisson 分布式鎖的 API 調(diào)用。
一兼贸、Spring Boot 集成 Redisson
1段直、在 pom.xml 文件中添加 Redisson 的相關(guān)依賴(lài)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.30.0</version>
</dependency>
<!-- AspectJ依賴(lài) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2、在 application.yml 中配置 Redisson單機(jī)模式 的連接信息和相關(guān)參數(shù)
spring:
redis:
host: localhost
port: 6379
password: null
redisson:
codec: org.redisson.codec.JsonJacksonCodec
threads: 4
netty:
threads: 4
single-server-config:
address: "redis://localhost:6379"
password: null
database: 0
3溶诞、Redission還支持主從坷牛、集群、哨兵配置
//主從模式
spring:
redis:
sentinel:
master: my-master
nodes: localhost:26379,localhost:26389
password: your_password
redisson:
master-slave-config:
master-address: "redis://localhost:6379"
slave-addresses: "redis://localhost:6380,redis://localhost:6381"
password: ${spring.redis.password}
// 集群模式
spring:
data:
redis:
cluster:
nodes: localhost:6379,localhost:6380,localhost:6381,localhost:6382,localhost:6383,localhost:6384
lettuce:
pool:
max-active: 10 # 允許最大連接數(shù)很澄,默認(rèn)8(負(fù)值表示沒(méi)有限制)京闰,推薦值:大于cpu * 2,通常為(cpu * 2) + 2
max-idle: 8 # 最大空閑連接數(shù)甩苛,默認(rèn)8蹂楣,推薦值:cpu * 2
min-idle: 0 # 最小空閑連接數(shù)痊土,默認(rèn)0
max-wait: 5000 # 連接用完時(shí),新的請(qǐng)求等待時(shí)間(s秒、ms毫秒),超過(guò)該時(shí)間拋出異常官辈,默認(rèn)-1(負(fù)值表示沒(méi)有限制)
timeout: 60000
password:
redisson:
cluster-config:
node-addresses: "redis://localhost:6379,redis://localhost:6380,redis://localhost:6381,redis://localhost:6382,redis://localhost:6383,redis://localhost:6384"
password: ${spring.redis.password}
// 哨兵模式
spring:
redis:
sentinel:
master: my-master
nodes: localhost:26379,localhost:26389
password: your_password
redisson:
sentinel-config:
master-name: my-master
sentinel-addresses: "redis://localhost:26379,redis://localhost:26380,redis://localhost:26381"
password: ${spring.redis.password}
4风瘦、構(gòu)建Redission配置
@Component
@ConfigurationProperties(prefix = "spring.data.redis")
@Data
public class RedisConfigProperties {
private Integer timeout;
private String password;
private cluster cluster;
@Data
public static class cluster {
private String nodes;
}
}
@Configuration
public class RedissonConfig {
@Resource
private RedisConfigProperties redisConfigProperties;
//添加redisson的bean
@Bean
public Redisson redisson() {
//redisson版本是3.5昧谊,集群的ip前面要加上“redis://”,不然會(huì)報(bào)錯(cuò)涡真,3.2版本可不加
String[] nodes = redisConfigProperties.getCluster().getNodes().split(",");
List<String> clusterNodes = new ArrayList<>(nodes.length);
Arrays.stream(nodes).forEach((node) -> clusterNodes.add(
node.startsWith("redis://") ? node : "redis://" + node)
);
Config config = new Config();
ClusterServersConfig serverConfig = config.useClusterServers()
.addNodeAddress(clusterNodes.toArray(new String[0]))
.setTimeout(redisConfigProperties.getTimeout());
if (StringUtils.hasText(redisConfigProperties.getPassword())) {
serverConfig.setPassword(redisConfigProperties.getPassword());
}
return (Redisson) Redisson.create(config);
}
}
二、本地封裝Redisson 分布式鎖
1东亦、定義ILock鎖對(duì)象
/**
* @Author: huangyibo
* @Date: 2024/5/26 2:12
* @Description: RedissonLock 包裝的鎖對(duì)象 實(shí)現(xiàn)AutoCloseable接口橄登,在java7的try(with resource)語(yǔ)法,不用顯示調(diào)用close方法
*/
@AllArgsConstructor
public class ILock implements AutoCloseable {
/**
* 持有的鎖對(duì)象
*/
@Getter
private Object lock;
/**
* 分布式鎖接口
*/
@Getter
private IDistributedLock distributedLock;
@Override
public void close() throws Exception {
if(Objects.nonNull(lock)){
distributedLock.unLock(lock);
}
}
}
2讥此、定義IDistributedLock分布式鎖接口
public interface IDistributedLock {
/**
* 獲取鎖拢锹,默認(rèn)30秒失效,失敗一直等待直到獲取鎖
*
* @param key 鎖的key
* @return 鎖對(duì)象
*/
ILock lock(String key);
/**
* 獲取鎖,失敗一直等待直到獲取鎖
*
* @param key 鎖的key
* @param lockTime 加鎖的時(shí)間萄喳,超過(guò)這個(gè)時(shí)間后鎖便自動(dòng)解鎖卒稳; 如果lockTime為-1,則保持鎖定直到顯式解鎖
* @param unit {@code lockTime} 參數(shù)的時(shí)間單位
* @param fair 是否公平鎖
* @return 鎖對(duì)象
*/
ILock lock(String key, long lockTime, TimeUnit unit, boolean fair);
/**
* 嘗試獲取鎖他巨,30秒獲取不到超時(shí)異常充坑,鎖默認(rèn)30秒失效
*
* @param key 鎖的key
* @param tryTime 獲取鎖的最大嘗試時(shí)間
* @return
* @throws Exception
*/
ILock tryLock(String key, long tryTime) throws Exception;
/**
* 嘗試獲取鎖减江,獲取不到超時(shí)異常
*
* @param key 鎖的key
* @param tryTime 獲取鎖的最大嘗試時(shí)間
* @param lockTime 加鎖的時(shí)間
* @param unit {@code tryTime @code lockTime} 參數(shù)的時(shí)間單位
* @param fair 是否公平鎖
* @return
* @throws Exception
*/
ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair) throws Exception;
/**
* 解鎖
*
* @param lock
* @throws Exception
*/
void unLock(Object lock);
/**
* 釋放鎖
*
* @param lock
* @throws Exception
*/
default void unLock(ILock lock) {
if (lock != null) {
unLock(lock.getLock());
}
}
}
3、IDistributedLock實(shí)現(xiàn)類(lèi)
@Slf4j
@Component
public class RedissonDistributedLock implements IDistributedLock {
@Resource
private RedissonClient redissonClient;
/**
* 統(tǒng)一前綴
*/
@Value("${redisson.lock.prefix:bi:distributed:lock}")
private String prefix;
@Override
public ILock lock(String key) {
return this.lock(key, 0L, TimeUnit.SECONDS, false);
}
@Override
public ILock lock(String key, long lockTime, TimeUnit unit, boolean fair) {
RLock lock = getLock(key, fair);
// 獲取鎖,失敗一直等待,直到獲取鎖,不支持自動(dòng)續(xù)期
if (lockTime > 0L) {
lock.lock(lockTime, unit);
} else {
// 具有Watch Dog 自動(dòng)延期機(jī)制 默認(rèn)續(xù)30s 每隔30/3=10 秒續(xù)到30s
lock.lock();
}
return new ILock(lock, this);
}
@Override
public ILock tryLock(String key, long tryTime) throws Exception {
return this.tryLock(key, tryTime, 0L, TimeUnit.SECONDS, false);
}
@Override
public ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair)
throws Exception {
RLock lock = getLock(key, fair);
boolean lockAcquired;
// 嘗試獲取鎖捻爷,獲取不到超時(shí)異常,不支持自動(dòng)續(xù)期
if (lockTime > 0L) {
lockAcquired = lock.tryLock(tryTime, lockTime, unit);
} else {
// 具有Watch Dog 自動(dòng)延期機(jī)制 默認(rèn)續(xù)30s 每隔30/3=10 秒續(xù)到30s
lockAcquired = lock.tryLock(tryTime, unit);
}
if (lockAcquired) {
return new ILock(lock, this);
}
return null;
}
/**
* 獲取鎖
*
* @param key
* @param fair
* @return
*/
private RLock getLock(String key, boolean fair) {
RLock lock;
String lockKey = prefix + ":" + key;
if (fair) {
// 獲取公平鎖
lock = redissonClient.getFairLock(lockKey);
} else {
// 獲取普通鎖
lock = redissonClient.getLock(lockKey);
}
return lock;
}
@Override
public void unLock(Object lock) {
if (!(lock instanceof RLock)) {
throw new IllegalArgumentException("Invalid lock object");
}
RLock rLock = (RLock) lock;
if (rLock.isLocked()) {
try {
rLock.unlock();
} catch (IllegalMonitorStateException e) {
log.error("釋放分布式鎖異常", e);
}
}
}
}
4辈灼、注入IDistributedLock接口使用示例
public interface IProductSkuSupplierMeasureService {
/**
* 保存SKU供應(yīng)商供貨信息
*
* @param dto
* @return
*/
Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto);
/**
* 編輯SKU供應(yīng)商供貨信息
*
* @param dto
* @return
*/
Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto);
}
手動(dòng)釋放鎖示例
@Service
@Slf4j
public class ProductSkuSupplierMeasureServiceImpl implements IProductSkuSupplierMeasureService {
@Resource
private IDistributedLock distributedLock;
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto) {
// 手動(dòng)釋放鎖
String sku = dto.getSku();
ILock lock = null;
try {
lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false);
if (Objects.isNull(lock)) {
throw new BusinessException("Duplicate request for method still in process");
}
// 業(yè)務(wù)代碼
}catch (BusinessException e) {
throw new BusinessException(e.getMessage());
} catch (Exception e) {
log.error("保存異常", e);
throw new BusinessException (e.getMessage());
} finally {
if (Objects.nonNull(lock)) {
distributedLock.unLock(lock);
}
}
return Boolean.TRUE;
}
}
使用try-with-resources 語(yǔ)法糖自動(dòng)釋放鎖
@Service
@Slf4j
public class ProductSkuSupplierMeasureServiceImpl implements IProductSkuSupplierMeasureService {
@Resource
private IDistributedLock distributedLock;
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto) {
String sku = dto.getSku();
// try-with-resources 語(yǔ)法糖自動(dòng)釋放鎖
try(ILock lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false)) {
if(Objects.isNull(lock)){
throw new BusinessException ("Duplicate request for method still in process");
}
// 業(yè)務(wù)代碼
}catch (BusinessException e) {
throw new BusinessException (e.getMessage());
} catch (Exception e) {
log.error("修改異常", e);
throw new BusinessException ("修改異常");
}
return Boolean.TRUE;
}
}
5、使用AOP切面實(shí)現(xiàn)分布式鎖的綁定
定義DistributedLock注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {
/**
* 保證業(yè)務(wù)接口的key的唯一性也榄,否則失去了分布式鎖的意義 鎖key
* 支持使用spEl表達(dá)式
*/
String key();
/**
* 保證業(yè)務(wù)接口的key的唯一性巡莹,否則失去了分布式鎖的意義 鎖key 前綴
*/
String keyPrefix() default "";
/**
* 是否在等待時(shí)間內(nèi)獲取鎖,如果在等待時(shí)間內(nèi)無(wú)法獲取到鎖甜紫,則返回失敗
*/
boolean tryLok() default false;
/**
* 獲取鎖的最大嘗試時(shí)間 降宅,會(huì)嘗試tryTime時(shí)間獲取鎖,在該時(shí)間內(nèi)獲取成功則返回囚霸,否則拋出獲取鎖超時(shí)異常腰根,tryLok=true時(shí),該值必須大于0拓型。
*
*/
long tryTime() default 0;
/**
* 加鎖的時(shí)間额嘿,超過(guò)這個(gè)時(shí)間后鎖便自動(dòng)解鎖
*/
long lockTime() default 30;
/**
* tryTime 和 lockTime的時(shí)間單位
*/
TimeUnit unit() default TimeUnit.SECONDS;
/**
* 是否公平鎖,false:非公平鎖劣挫,true:公平鎖
*/
boolean fair() default false;
}
定義DistributedLockAspect Lock切面
@Aspect
@Slf4j
public class DistributedLockAspect {
@Resource
private IDistributedLock distributedLock;
/**
* SpEL表達(dá)式解析
*/
private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
/**
* 用于獲取方法參數(shù)名字
*/
private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
@Pointcut("@annotation(com.yibo.redissonannodemo.anno.DistributedLock)")
public void distributorLock() {
}
@Around("distributorLock()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 獲取DistributedLock
DistributedLock distributedLock = this.getDistributedLock(pjp);
// 獲取 lockKey
String lockKey = this.getLockKey(pjp, distributedLock);
ILock lockObj = null;
try {
// 加鎖册养,tryLok = true,并且tryTime > 0時(shí),嘗試獲取鎖揣云,獲取不到超時(shí)異常
if (distributedLock.tryLok()) {
if(distributedLock.tryTime() <= 0){
throw new RuntimeException("tryTime must be greater than 0");
}
lockObj = this.distributedLock.tryLock(lockKey, distributedLock.tryTime(), distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
} else {
lockObj = this.distributedLock.lock(lockKey, distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
}
if (Objects.isNull(lockObj)) {
throw new RuntimeException("Duplicate request for method still in process");
}
return pjp.proceed();
} catch (Exception e) {
throw e;
} finally {
// 解鎖
this.unLock(lockObj);
}
}
/**
* @param pjp
* @return
* @throws NoSuchMethodException
*/
private DistributedLock getDistributedLock(ProceedingJoinPoint pjp) throws NoSuchMethodException {
String methodName = pjp.getSignature().getName();
Class clazz = pjp.getTarget().getClass();
Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
Method lockMethod = clazz.getMethod(methodName, par);
DistributedLock distributedLock = lockMethod.getAnnotation(DistributedLock.class);
return distributedLock;
}
/**
* 解鎖
*
* @param lockObj
*/
private void unLock(ILock lockObj) {
if (Objects.isNull(lockObj)) {
return;
}
try {
this.distributedLock.unLock(lockObj);
} catch (Exception e) {
log.error("分布式鎖解鎖異常", e);
}
}
/**
* 獲取 lockKey
*
* @param pjp
* @param distributedLock
* @return
*/
private String getLockKey(ProceedingJoinPoint pjp, DistributedLock distributedLock) {
String lockKey = distributedLock.key();
String keyPrefix = distributedLock.keyPrefix();
if (StringUtils.hasText(lockKey)) {
throw new RuntimeException("Lok key cannot be empty");
}
if (lockKey.contains("#")) {
this.checkSpEL(lockKey);
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
// 獲取方法參數(shù)值
Object[] args = pjp.getArgs();
lockKey = getValBySpEL(lockKey, methodSignature, args);
}
lockKey = StringUtils.hasText(keyPrefix) ? lockKey : keyPrefix + lockKey;
return lockKey;
}
/**
* 解析spEL表達(dá)式
*
* @param spEL
* @param methodSignature
* @param args
* @return
*/
private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
// 獲取方法形參名數(shù)組
String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
if (paramNames == null || paramNames.length < 1) {
throw new RuntimeException("Lok key cannot be empty");
}
Expression expression = spelExpressionParser.parseExpression(spEL);
// spring的表達(dá)式上下文對(duì)象
EvaluationContext context = new StandardEvaluationContext();
// 給上下文賦值
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
Object value = expression.getValue(context);
if (value == null) {
throw new RuntimeException("The parameter value cannot be null");
}
return value.toString();
}
/**
* SpEL 表達(dá)式校驗(yàn)
*
* @param spEL
* @return
*/
private void checkSpEL(String spEL) {
try {
ExpressionParser parser = new SpelExpressionParser();
parser.parseExpression(spEL, new TemplateParserContext());
} catch (Exception e) {
log.error("spEL表達(dá)式解析異常", e);
throw new RuntimeException("Invalid SpEL expression [" + spEL + "]");
}
}
}
定義分布式鎖注解版啟動(dòng)元注解
/**
* @Author: huangyibo
* @Date: 2024/5/26 2:34
* @Description: 定義分布式鎖注解版啟動(dòng)元注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({DistributedLockAspect.class})
public @interface EnableDistributedLock {
}
6捕儒、AOP切面實(shí)現(xiàn)分布式鎖的綁定使用示例
啟動(dòng)類(lèi)添加@EnableDistributedLock啟用注解支持
@SpringBootApplication
@EnableDistributedLock
public class RedissonAnnoDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RedissonAnnoDemoApplication.class, args);
}
}
@DistributedLock標(biāo)注需要使用分布式鎖的方法
@Service
@Slf4j
public class ProductSkuSupplierMeasureServiceImpl implements IProductSkuSupplierMeasureService {
//@DistributedLock(key = "#dto.sku + '-' + #dto.skuId", lockTime = 10L, keyPrefix = "sku-")
@DistributedLock(key = "#dto.sku", lockTime = 10L, keyPrefix = "sku-")
@Override
public Boolean addSupplierOrder(ProductSkuSupplierInfoDTO dto) {
//執(zhí)行創(chuàng)建供應(yīng)商訂單業(yè)務(wù)
return Boolean.TRUE;
}
}
參考:
https://blog.csdn.net/Ascend1977/article/details/131126047