Posted on 2021-03-24 20:11
為自己代言 閱讀(214)
評(píng)論(0) 編輯 收藏 所屬分類:
java/J2EE
1:分布鎖有好多實(shí)現(xiàn)方式
- 基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)
這個(gè)實(shí)現(xiàn)方式比較復(fù)雜,考慮因素比較多,比如:超時(shí),非公平鎖,非重入等會(huì)有各種各樣的問(wèn)題,在解決問(wèn)題的過(guò)程中會(huì)使整個(gè)方案變得越來(lái)越復(fù)雜。操作數(shù)據(jù)庫(kù)需要一定的開(kāi)銷,性能問(wèn)題需要考慮
- 基于redis實(shí)現(xiàn)(這個(gè)對(duì)于不太敏感的場(chǎng)景可以使用,由于redis集群和單機(jī),還有客戶端,版本等多方面因素考慮情況比較多)
性能好。使用緩存實(shí)現(xiàn)分布式鎖的缺點(diǎn) 其數(shù)據(jù)庫(kù)一樣
- 基于zookeeper實(shí)現(xiàn)(這個(gè)是最終也是最好最可靠的)
創(chuàng)建臨時(shí)節(jié)點(diǎn),可以解決單機(jī),鎖無(wú)法釋放,非阻塞,不可沖入,非公平的問(wèn)題
總結(jié)
從理解的難易程度角度(從低到高)
數(shù)據(jù)庫(kù) > 緩存 > Zookeeper
從實(shí)現(xiàn)的復(fù)雜性角度(從低到高)
Zookeeper > 緩存 > 數(shù)據(jù)庫(kù)
從性能角度(從高到低)
緩存 > Zookeeper >= 數(shù)據(jù)庫(kù)
從可靠性角度(從高到低)
Zookeeper > 緩存 > 數(shù)據(jù)庫(kù)
下面講基于redis實(shí)現(xiàn)分布鎖代碼:RedisTemplate 客戶端
lettuce
@Service
public class RedisDistributedLockUtils {
@Autowired
private RedisTemplate redisTemplate;
private static final Long RELEASE_SUCCESS = 1L;
private static final long DEFAULT_TIMEOUT = 1000 * 10;
//因?yàn)橐褂胠ua 腳本是因?yàn)?nbsp;redis 執(zhí)行l(wèi)ua腳本是原子操作
private static final String UNLOCK_LUA= "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
/**
* 實(shí)時(shí)獲取鎖
*
* 嘗試獲取分布式鎖 將redis版本升級(jí)到2.1以上(spring-boot-starter-data-redis 版本 2.X以上),然后使用setIfAbsent 不存在
* 當(dāng)setIfAbsent成功之后斷開(kāi)連接,下面設(shè)置過(guò)期時(shí)間的代碼 stringRedisTemplate.expire(key,timeout);是無(wú)法執(zhí)行的,這時(shí)候就會(huì)有大量沒(méi)有過(guò)期時(shí)間的數(shù)據(jù)存在數(shù)據(jù)庫(kù)
* @param lockKey 鎖
* @param requestId 請(qǐng)求標(biāo)識(shí)
* @param expireTime 超期時(shí)間
* @return 是否獲取成功
*/
public boolean trySetDistributedLock(String lockKey, String requestId, long expireTime) {
return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId,0 == expireTime ? DEFAULT_TIMEOUT : expireTime, TimeUnit.MILLISECONDS);
}
/**
* 以阻塞方式的獲取鎖
* @param key
* @param value
* @param timeout
* @return
*/
public boolean setDistributedLock(String key, String value, long timeout) {
Boolean lock = false;
long start = System.currentTimeMillis();
while (!lock && (System.currentTimeMillis() - start < timeout)) {
//執(zhí)行set命令
lock = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.MILLISECONDS);
//不頻繁去獲取鎖
try {
if (!lock) {
Thread.sleep(60);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return lock;
}
public boolean releaseLock(String key, String value) {
// 使用Lua腳本:先判斷是否是自己設(shè)置的鎖,再執(zhí)行刪除
// 使用lua腳本刪除redis中匹配value的key,可以避免由于方法執(zhí)行時(shí)間過(guò)長(zhǎng)而redis鎖自動(dòng)過(guò)期失效的時(shí)候誤刪其他線程的鎖
// spring自帶的執(zhí)行腳本方法中,集群模式直接拋出不支持執(zhí)行腳本的異常EvalSha is not supported in cluster environment
// 所以只能拿到原redis的connection來(lái)執(zhí)行腳本
List<String> keys = new ArrayList<>();
keys.add(key);
List<String> args = new ArrayList<>();
args.add(value);
Long result = (Long)redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
// 集群模式和單機(jī)模式雖然執(zhí)行腳本的方法一樣,但是沒(méi)有共同的接口,所以只能分開(kāi)執(zhí)行
// 集群模式
if (nativeConnection instanceof JedisCluster) {
return (Long)((JedisCluster)nativeConnection).eval(UNLOCK_LUA, keys, args);
}
//客戶端是Jedis時(shí)候(單機(jī)模式)
else if (nativeConnection instanceof Jedis) {
return (Long)((Jedis)nativeConnection).eval(UNLOCK_LUA, keys, args);
}
//這里使用 redisTemplate 中l(wèi)ettuce 客戶端
else{
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(UNLOCK_LUA);
redisScript.setResultType(Long.class);
return (Long)redisTemplate.execute(redisScript, keys, value);
}
}
});
//返回最終結(jié)果
return RELEASE_SUCCESS.equals(result);
}
}
基于zookeeper實(shí)現(xiàn)下期補(bǔ)上:
介紹分布式鎖文章寫(xiě)的比較詳細(xì):
https://blog.csdn.net/u010963948/article/details/79006572