SpringBoot強(qiáng)大的分布式鎖組件Lock4j,支持多種實(shí)現(xiàn)
環(huán)境:SpringBoot3.2.5
1. 簡介
lock4j是一個(gè)分布式鎖組件,其提供了多種不同的支持以滿足不同性能和環(huán)境的需求。底層通過Spring AOP技術(shù)實(shí)現(xiàn),而該切面的優(yōu)先級是最高的,也就是說當(dāng)你的環(huán)境中有多個(gè)切面時(shí)(如:聲明式事務(wù)),也不會(huì)導(dǎo)致失效問題。該組件具有如下2個(gè)特性:
- 簡單易用,功能強(qiáng)大,擴(kuò)展性強(qiáng)。
- 支持redission,redisTemplate,zookeeper??苫煊?,支持?jǐn)U展。
接下來將詳細(xì)介紹基于Redis的Lock4J使用。
2. 實(shí)戰(zhàn)案例
2.1 引入依賴
<properties>
<lock4j.version>2.2.7</lock4j.version>
</properties>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
<version>${lock4j.version}</version>
</dependency>
如果你想基于redisson或者是zookeeper實(shí)現(xiàn),那么你只需要引入對應(yīng)的包即可。
<!-- redisson -->
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<!-- zookeeper -->
<artifactId>lock4j-zookeeper-spring-boot-starter</artifactId>
spring:
data:
redis:
timeout: 10000
connectTimeout: 20000
host: 127.0.0.1
password: xxxooo
#如果你是基于zookeeper實(shí)現(xiàn),那么做如下配置
coordinate:
zookeeper:
zkServers: 127.0.0.1:2181,...
進(jìn)過以上的配置后接下來你就可以通過注解的方式使用分布式鎖了。
2.2 基本使用
@Service
public class StorageService {
private final StorageRepository storageRepository ;
public StorageService(StorageRepository storageRepository) {
this.storageRepository = storageRepository ;
}
@Lock4j
public void deductStorage(Long storageId, int count) {
// TODO
}
}
使用非常簡單,只需要在你需要上鎖的方法上添加@Lock4j注解即可。而這里將使用默認(rèn)行為:默認(rèn)獲取鎖超時(shí)3秒,30秒鎖過期。
自定義鎖key
在上面示例中沒有自定義@Lock4j注解的任何屬性;那么,將會(huì)使用默認(rèn)的key生成方式,上面的代碼將生成如下key。
# 前綴 + ":" + 完全限定類名+方法名+# (這里的#是固定的,如果你自定義了key,會(huì)在后面繼續(xù)拼接)
lock4j:com.pack.test.lock4j.StorageServicedeductStorage#
當(dāng)我們自定義了key后如下:
@Lock4j(key = "#storageId")
public void deductStorage(Long storageId, int count)
key支持Spring SpEL表達(dá)式,如上將生成如下的key
# 方法調(diào)用storageService.deductStorage(1, 2) ;
lock4j:com.pack.test.lock4j.StorageServicedeductStorage#1
你也可以不使用SpEL表達(dá)式。不使用SpEL表達(dá)式那么就需要使用如下語法
@Lock4j(keys = "'storageId'")
public void deductStorage(...) {}
沒有使用單引號程序?qū)?huì)報(bào)錯(cuò)。
設(shè)置過期時(shí)間
默認(rèn)獲取鎖超時(shí)3秒,30秒鎖過期,可以通過如下屬性配置過期時(shí)間
@Lock4j(keys = {"#storageId"}, expire = 3000, acquireTimeout = 3000)
public void deductStorage(Long storageId, int count) {}
expire: 鎖過期時(shí)間(毫秒);注:鎖過期時(shí)間必須要大于業(yè)務(wù)處理時(shí)間。acquireTimeout: 獲取鎖超時(shí)時(shí)間(毫秒)
以上是Lock4j的基本用法;下面將介紹Lock4j其它高級用法。
2.3 高級用法
全局統(tǒng)一配置
lock4j:
acquire-timeout: 3000
expire: 30000
primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor
lock-key-prefix: lock4j
primary-executor:配置加鎖的實(shí)現(xiàn)方式;默認(rèn)順序是:
redisson > redisTemplate > zookeeper;如果你的環(huán)境中這3個(gè)都引入了,那么就是按照這個(gè)順序,因?yàn)槎x他們時(shí)使用了@Order注解聲明順序。lock-key-prefix:#鎖前綴,如上面示例看到的lock4j為默認(rèn)值。
自定義執(zhí)行器(加鎖)
我們可以通過實(shí)現(xiàn)LockExecutor接口定義自己的加鎖實(shí)現(xiàn),比如基于MySQL實(shí)現(xiàn)。如下:
@Component
public class JdbcLockExecutor implements LockExecutor<String> {
private final JdbcTemplate jdbcTemplate ;
public JdbcLockExecutor(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate ;
}
@Override
public String acquire(String lockKey, String lockValue, long expire, long acquireTimeout) {
// TODO; 獲取鎖
return null ;
}
@Override
public boolean releaseLock(String key, String value, String lockInstance) {
// TODO; 釋放鎖
return false;
}
}
注:這里必須要注冊為Spring Bean。否則將無法獲取。使用如下:
@Lock4j(executor = JdbcLockExecutor.class)
public void deductStorage(Long storageId, int count) {}
加鎖時(shí)會(huì)根據(jù)這里配置的executor Class對象獲取Spring容器對應(yīng)的bean對象。
自定義key生成器
Lock4j默認(rèn)的鎖key生成器為DefaultLockKeyBuilder,我們可以通過LockKeyBuilder接口實(shí)現(xiàn)自己的key生成方式
@Component
public class PackLockKeyBuilder implements LockKeyBuilder {
@Override
public String buildKey(MethodInvocation invocation, String[] definitionKeys) {
// TODO; 生成key
return null;
}
}
注:這里也必須注冊為bean對象。使用如下:
@Lock4j(keyBuilderStrategy = PackLockKeyBuilder.class)
public void deductStorage(Long storageId, int count) {}
內(nèi)部會(huì)通過你這里配置的Class對象獲取對應(yīng)的Spring Bean實(shí)例對象。
自定義獲取鎖失敗策略
當(dāng)獲取鎖失敗時(shí)如何進(jìn)行處理?默認(rèn)實(shí)現(xiàn)是DefaultLockFailureStrategy通過自定義LockFailureStrategy,實(shí)現(xiàn)自己的邏輯。與上面的套路一樣都需要注冊為bean。
@Component
public class PackLockFailureStrategy implements LockFailureStrategy {
@Override
public void onLockFailure(String key, Method method, Object[] arguments) {
// TODO
}
}
使用如下:
@Lock4j(failStrategy = PackLockFailureStrategy.class)
public void deductStorage(Long storageId, int count) {}
注:這里你也可以不指定failStrategy屬性,會(huì)自動(dòng)從容器中查找對應(yīng)的實(shí)現(xiàn)Bean。
非注解實(shí)現(xiàn)方式(手動(dòng)加鎖/釋放鎖)
你也可以通過注入LockTemplate對象,由自己來完成加鎖和釋放鎖的動(dòng)作。
private final LockTemplate lockTemplate ;
public StorageService(LockTemplate lockTemplate) {
this.lockTemplate = lockTemplate ;
}
public void deductStorage(Long storageId, int count) {
String key = "xxxx" ;
final LockInfo lockInfo = lockTemplate.lock(key, 30000L, 5000L, RedisTemplateLockExecutor.class);
if (null == lockInfo) {
throw new RuntimeException("業(yè)務(wù)處理中,請稍后再試") ;
}
// 獲取鎖成功,處理業(yè)務(wù)
try {
// TODO
} finally {
// 釋放鎖
lockTemplate.releaseLock(lockInfo) ;
}
}
}
手動(dòng)加鎖方式,一般適用于需要更細(xì)粒度控制鎖邊界,否則沒必要。