前段時(shí)間面試了一個(gè)高級(jí)程序員,問(wèn)他了一些分布式鎖的知識(shí),回答的還不錯(cuò)。后來(lái)進(jìn)了我們的團(tuán)隊(duì),剛好讓他接手一個(gè)新項(xiàng)目,需要用到分布式鎖,我說(shuō)這是你的強(qiáng)項(xiàng),你來(lái)實(shí)現(xiàn)。
誰(shuí)知,項(xiàng)目上線后,各種問(wèn)題接連爆發(fā)出來(lái)。后來(lái)我找他談話,主要是說(shuō),千萬(wàn)不要重復(fù)造輪子,直接吧 A 項(xiàng)目中的 Redisson 用法實(shí)現(xiàn)復(fù)制過(guò)來(lái)一份不就行了,干嘛非要自己搞一套,還搞出問(wèn)題。。。
今天,我們一起來(lái)手把手來(lái)實(shí)現(xiàn)一個(gè)高效的 SpringBoot 分布式鎖!本文通過(guò) Spring Boot 整合 redisson 來(lái)實(shí)現(xiàn)分布式鎖,并結(jié)合 demo 測(cè)試結(jié)果。
分析設(shè)計(jì)要點(diǎn)
當(dāng)我們?cè)谠O(shè)計(jì)分布式鎖的時(shí)候,我們應(yīng)該考慮分布式鎖至少要滿足的一些條件,同時(shí)考慮如何高效的設(shè)計(jì)分布式鎖,這里我認(rèn)為以下幾點(diǎn)是必須要考慮的。
1、互斥
在分布式高并發(fā)的條件下,我們最需要保證,同一時(shí)刻只能有一個(gè)線程獲得鎖,這是最基本的一點(diǎn)。
2、防止死鎖
在分布式高并發(fā)的條件下,比如有個(gè)線程獲得鎖的同時(shí),還沒(méi)有來(lái)得及去釋放鎖,就因?yàn)橄到y(tǒng)故障或者其它原因使它無(wú)法執(zhí)行釋放鎖的命令,導(dǎo)致其它線程都無(wú)法獲得鎖,造成死鎖。
所以分布式非常有必要設(shè)置鎖的有效時(shí)間,確保系統(tǒng)出現(xiàn)故障后,在一定時(shí)間內(nèi)能夠主動(dòng)去釋放鎖,避免造成死鎖的情況。
3、性能
對(duì)于訪問(wèn)量大的共享資源,需要考慮減少鎖等待的時(shí)間,避免導(dǎo)致大量線程阻塞。
所以在鎖的設(shè)計(jì)時(shí),需要考慮兩點(diǎn)。
1、鎖的顆粒度要盡量小。比如你要通過(guò)鎖來(lái)減庫(kù)存,那這個(gè)鎖的名稱你可以設(shè)置成是商品的ID,而不是任取名稱。這樣這個(gè)鎖只對(duì)當(dāng)前商品有效,鎖的顆粒度小。
2、鎖的范圍盡量要小。比如只要鎖2行代碼就可以解決問(wèn)題的,那就不要去鎖10行代碼了。
4、重入
我們知道ReentrantLock是可重入鎖,那它的特點(diǎn)就是:同一個(gè)線程可以重復(fù)拿到同一個(gè)資源的鎖。重入鎖非常有利于資源的高效利用。關(guān)于這點(diǎn)之后會(huì)做演示。
針對(duì)以上Redisson都能很好的滿足,下面就來(lái)使用它。
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.10.6</version>
</dependency>
配置信息
spring:
# redis
redis:
host: 47.103.5.190
port: 6379
jedis:
pool:
# 連接池最大連接數(shù)(使用負(fù)值表示沒(méi)有限制)
max-active: 100
# 連接池中的最小空閑連接
max-idle: 10
# 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒(méi)有限制)
max-wait: -1
# 連接超時(shí)時(shí)間(毫秒)
timeout: 5000
#默認(rèn)是索引為0的數(shù)據(jù)庫(kù)
database: 0
配置類
/**
* redisson 配置,下面是單節(jié)點(diǎn)配置:
*
* @author gourd
*/
@Configuration
publicclassRedissonConfig{
@Value('${spring.redis.host}')
privateString host;
@Value('${spring.redis.port}')
privateString port;
@Value('${spring.redis.password:}')
privateString password;
@Bean
publicRedissonClient redissonClient() {
Config config = newConfig();
//單節(jié)點(diǎn)
config.useSingleServer().setAddress('redis://'+ host + ':'+ port);
if(StringUtils.isEmpty(password)) {
config.useSingleServer().setPassword(null);
} else{
config.useSingleServer().setPassword(password);
}
//添加主從配置
// config.useMasterSlaveServers().setMasterAddress('').setPassword('').addSlaveAddress(new String[]{'',''});
// 集群模式配置 setScanInterval()掃描間隔時(shí)間,單位是毫秒, //可以用'rediss://'來(lái)啟用SSL連接
// config.useClusterServers().setScanInterval(2000).addNodeAddress('redis://127.0.0.1:7000', 'redis://127.0.0.1:7001').addNodeAddress('redis://127.0.0.1:7002');
returnRedisson.create(config);
}
}
Redisson 工具類
/**
* redis分布式鎖幫助類
*
* @author gourd
*
*/
publicclassRedisLockUtil{
privatestaticDistributedLocker distributedLocker = SpringContextHolder.getBean('distributedLocker',DistributedLocker.class);
/**
* 加鎖
* @param lockKey
* @return
*/
publicstaticRLocklock(String lockKey) {
return distributedLocker.lock(lockKey);
}
/**
* 釋放鎖
* @param lockKey
*/
publicstaticvoid unlock(String lockKey) {
distributedLocker.unlock(lockKey);
}
/**
* 釋放鎖
* @param lock
*/
publicstaticvoid unlock(RLocklock) {
distributedLocker.unlock(lock);
}
/**
* 帶超時(shí)的鎖
* @param lockKey
* @param timeout 超時(shí)時(shí)間 單位:秒
*/
publicstaticRLocklock(String lockKey, int timeout) {
return distributedLocker.lock(lockKey, timeout);
}
/**
* 帶超時(shí)的鎖
* @param lockKey
* @param unit 時(shí)間單位
* @param timeout 超時(shí)時(shí)間
*/
publicstaticRLocklock(String lockKey, int timeout,TimeUnit unit ) {
return distributedLocker.lock(lockKey, unit, timeout);
}
/**
* 嘗試獲取鎖
* @param lockKey
* @param waitTime 最多等待時(shí)間
* @param leaseTime 上鎖后自動(dòng)釋放鎖時(shí)間
* @return
*/
publicstaticboolean tryLock(String lockKey, int waitTime, int leaseTime) {
return distributedLocker.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);
}
/**
* 嘗試獲取鎖
* @param lockKey
* @param unit 時(shí)間單位
* @param waitTime 最多等待時(shí)間
* @param leaseTime 上鎖后自動(dòng)釋放鎖時(shí)間
* @return
*/
publicstaticboolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
return distributedLocker.tryLock(lockKey, unit, waitTime, leaseTime);
}
/**
* 獲取計(jì)數(shù)器
*
* @param name
* @return
*/
publicstaticRCountDownLatch getCountDownLatch(String name){
return distributedLocker.getCountDownLatch(name);
}
/**
* 獲取信號(hào)量
*
* @param name
* @return
*/
publicstaticRSemaphore getSemaphore(String name){
return distributedLocker.getSemaphore(name);
}
}
底層封裝
/**
* @author gourd
*/
publicinterfaceDistributedLocker{
RLocklock(String lockKey);
RLocklock(String lockKey, int timeout);
RLocklock(String lockKey, TimeUnit unit, int timeout);
boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime);
void unlock(String lockKey);
void unlock(RLocklock);
}
/**
* @author gourd
*/
@Component
publicclassRedisDistributedLockerimplementsDistributedLocker{
@Autowired
privateRedissonClient redissonClient;
@Override
publicRLocklock(String lockKey) {
RLocklock= redissonClient.getLock(lockKey);
lock.lock();
returnlock;
}
@Override
publicRLocklock(String lockKey, int leaseTime) {
RLocklock= redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
returnlock;
}
@Override
publicRLocklock(String lockKey, TimeUnit unit ,int timeout) {
RLocklock= redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
returnlock;
}
@Override
publicboolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLocklock= redissonClient.getLock(lockKey);
try{
returnlock.tryLock(waitTime, leaseTime, unit);
} catch(InterruptedException e) {
returnfalse;
}
}
@Override
publicvoid unlock(String lockKey) {
RLocklock= redissonClient.getLock(lockKey);
lock.unlock();
}
@Override
publicvoid unlock(RLocklock) {
lock.unlock();
}
}
/**
* redis分布式鎖控制器
* @author gourd
* @since 2019-07-30
*/
@RestController
@Api(tags = 'redisson', description = 'redis分布式鎖控制器')
@RequestMapping('/redisson')
@Slf4j
publicclassRedissonLockController{
/**
* 鎖測(cè)試共享變量
*/
privateInteger lockCount = 10;
/**
* 無(wú)鎖測(cè)試共享變量
*/
privateInteger count = 10;
/**
* 模擬線程數(shù)
*/
privatestaticint threadNum = 10;
/**
* 模擬并發(fā)測(cè)試加鎖和不加鎖
* @return
*/
@GetMapping('/test')
@ApiOperation(value = '模擬并發(fā)測(cè)試加鎖和不加鎖')
publicvoidlock(){
// 計(jì)數(shù)器
finalCountDownLatch countDownLatch = newCountDownLatch(1);
for(int i = 0; i < threadNum; i ++) {
MyRunnable myRunnable = newMyRunnable(countDownLatch);
Thread myThread = newThread(myRunnable);
myThread.start();
}
// 釋放所有線程
countDownLatch.countDown();
}
/**
* 加鎖測(cè)試
*/
privatevoid testLockCount() {
String lockKey = 'lock-test';
try{
// 加鎖,設(shè)置超時(shí)時(shí)間2s
RedisLockUtil.lock(lockKey,2, TimeUnit.SECONDS);
lockCount--;
log.info('lockCount值:'+lockCount);
}catch(Exception e){
log.error(e.getMessage(),e);
}finally{
// 釋放鎖
RedisLockUtil.unlock(lockKey);
}
}
/**
* 無(wú)鎖測(cè)試
*/
privatevoid testCount() {
count--;
log.info('count值:'+count);
}
publicclassMyRunnableimplementsRunnable{
/**
* 計(jì)數(shù)器
*/
finalCountDownLatch countDownLatch;
publicMyRunnable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
publicvoid run() {
try{
// 阻塞當(dāng)前線程,直到計(jì)時(shí)器的值為0
countDownLatch.await();
} catch(InterruptedException e) {
log.error(e.getMessage(),e);
}
// 無(wú)鎖操作
testCount();
// 加鎖操作
testLockCount();
}
}
}
調(diào)用接口后打印值:
聯(lián)系客服