Redisson 鎖的加鎖機(jī)制
自定義redis分布式鎖無(wú)法自動(dòng)續(xù)期,比如,一個(gè)鎖設(shè)置了1分鐘超時(shí)釋放,如果拿到這個(gè)鎖的線程在一分鐘內(nèi)沒(méi)有執(zhí)行完畢,那么這個(gè)鎖就會(huì)被其他線程拿到,可能會(huì)導(dǎo)致嚴(yán)重的線上問(wèn)題,在秒殺場(chǎng)景下,很容易因?yàn)檫@個(gè)缺陷導(dǎo)致的超賣(mài)了。
Redisson 鎖加鎖流程:線程去獲取鎖,獲取成功則執(zhí)行l(wèi)ua腳本,保存數(shù)據(jù)到redis數(shù)據(jù)庫(kù)。如果獲取失敗: 一直通過(guò)while循環(huán)嘗試獲取鎖(可自定義等待時(shí)間,超時(shí)后返回失敗)。Redisson提供的分布式鎖是支持鎖自動(dòng)續(xù)期的,也就是說(shuō),如果線程仍舊沒(méi)有執(zhí)行完,那么redisson會(huì)自動(dòng)給redis中的目標(biāo)key延長(zhǎng)超時(shí)時(shí)間,這在Redisson中稱(chēng)之為 Watch Dog 機(jī)制。

Redisson 加解鎖API
public void test() throws Exception{
RLock lock = redissonClient.getLock("guodong"); // 拿鎖失敗時(shí)會(huì)不停的重試
// 具有Watch Dog 自動(dòng)延期機(jī)制 默認(rèn)續(xù)30s 每隔30/3=10 秒續(xù)到30s
lock.lock();
// 具有Watch Dog 自動(dòng)延期機(jī)制 默認(rèn)續(xù)30s
// 嘗試拿鎖10s后停止重試,返回false 具有Watch Dog 自動(dòng)延期機(jī)制 默認(rèn)續(xù)30s
boolean res1 = lock.tryLock(10, TimeUnit.SECONDS);
// 沒(méi)有Watch Dog
// 嘗試獲取鎖10秒,如果獲取不到則放棄
lock.lock(10, TimeUnit.SECONDS);
// 沒(méi)有Watch Dog
// 嘗試獲取鎖,等待100秒,持有鎖10秒鐘
boolean res2 = lock.tryLock(100, 10, TimeUnit.SECONDS);
Thread.sleep(40000L);
lock.unlock();
}
lock() 方法是阻塞獲取鎖的方式,如果當(dāng)前鎖被其他線程持有,則當(dāng)前線程會(huì)一直阻塞等待獲取鎖,直到獲取到鎖或者發(fā)生超時(shí)或中斷等情況才會(huì)結(jié)束等待。該方法獲取到鎖之后可以保證線程對(duì)共享資源的訪問(wèn)是互斥的,適用于需要確保共享資源只能被一個(gè)線程訪問(wèn)的場(chǎng)景。Redisson 的 lock() 方法支持可重入鎖和公平鎖等特性,可以更好地滿足多線程并發(fā)訪問(wèn)的需求。
而 tryLock() 方法是一種非阻塞獲取鎖的方式,在嘗試獲取鎖時(shí)不會(huì)阻塞當(dāng)前線程,而是立即返回獲取鎖的結(jié)果,如果獲取成功則返回 true,否則返回 false。Redisson 的 tryLock() 方法支持加鎖時(shí)間限制、等待時(shí)間限制以及可重入等特性,可以更好地控制獲取鎖的過(guò)程和等待時(shí)間,避免程序出現(xiàn)長(zhǎng)時(shí)間無(wú)法響應(yīng)等問(wèn)題。
默認(rèn)情況下,看門(mén)狗的續(xù)期時(shí)間是30s,也可以通過(guò)修改Config.lockWatchdogTimeout來(lái)另行指定。另外Redisson 還提供了可以指定leaseTime參數(shù)的加鎖方法來(lái)指定加鎖的時(shí)間。超過(guò)這個(gè)時(shí)間后鎖便自動(dòng)解開(kāi)了,不會(huì)延長(zhǎng)鎖的有效期。
watch dog缺點(diǎn)
redisson看門(mén)狗雖然能保證在線程沒(méi)有執(zhí)行完畢時(shí),鎖不會(huì)釋放,對(duì)于秒殺這種強(qiáng)一致性的場(chǎng)景是適用的,但是對(duì)于防重這種場(chǎng)景,是不適用的,在高并發(fā)情況下,會(huì)導(dǎo)致接口性能下降。
高并發(fā)防重時(shí),如果加鎖失敗就快速失敗,這時(shí)候可以使用自定義鎖,或者tryLock,如下
方式一:自定義redis分布式鎖
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
//NX|XX, NX -- Only set the key if it does not already exist;
// XX -- Only set the key if it already exist.
private static final String SET_IF_NOT_EXIST = "NX";
//EX|PX, expire time units: EX = seconds; PX = milliseconds
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static volatile JedisPool jedisPool = null;
public static JedisPool getRedisPoolUtil() {
if(null == jedisPool ){
synchronized (RedisTool.class){
if(null == jedisPool){
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(10);
poolConfig.setMaxWaitMillis(100*1000);
poolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(poolConfig,"192.168.10.151",6379);
}
}
}
return jedisPool;
}
/**
* 嘗試獲取分布式鎖
* @param lockKey 鎖
* @param requestId 請(qǐng)求標(biāo)識(shí)
* @param expireTime 超期時(shí)間
* @return 是否獲取成功
*/
public static boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) {
Jedis jedis = jedisPool.getResource();
try {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}catch (Exception e){
return false;
}finally {
jedisPool.returnResource(jedis);
}
}
private static final Long RELEASE_SUCCESS = 1L;
/**
* 釋放分布式鎖
* @param lockKey 鎖
* @param requestId 請(qǐng)求標(biāo)識(shí)
* @return 是否釋放成功
*/
public static boolean releaseDistributedLock(String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//如果使用的是切片shardedJedis,那么需要先獲取到j(luò)edis,
//Jedis jedis = shardedJedis.getShard(key);
Jedis jedis = jedisPool.getResource();
try {
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}catch (Exception e){
return false;
}finally {
jedisPool.returnResource(jedis);
}
}
}
方式二:redisson tryLock
RLock lock = redissonClient.getLock("Export:create:" + Context.get().getCorpId());
try {
//嘗試加鎖,最多等待0秒,上鎖以后5秒自動(dòng)解鎖
if (lock.tryLock(0, 5, TimeUnit.SECONDS)) {
//業(yè)務(wù)處理
} else {
Assert.isTrue(false, "排隊(duì)中,請(qǐng)稍后重試!");
}
} catch (InterruptedException e) {
Assert.isTrue(false, "請(qǐng)勿重復(fù)操作!");
} finally {
if (lock.isLocked()) {
lock.unlock();
}
}
總體上來(lái)說(shuō),防重場(chǎng)景下,優(yōu)先使用方式一,邏輯簡(jiǎn)單執(zhí)行性能高。