【分布式緩存系列】Redis實(shí)現(xiàn)分布式鎖的正確姿勢(shì)

一、前言

  在我們?nèi)粘9ぷ髦校薙pring和Mybatis外,用到最多無(wú)外乎分布式緩存框架——Redis但骨。但是很多工作很多年的朋友對(duì)Redis還處于一個(gè)最基礎(chǔ)的使用和認(rèn)識(shí)。所以我就像把自己對(duì)分布式緩存的一些理解和應(yīng)用整理一個(gè)系列玻淑,希望可以幫助到大家加深對(duì)Redis的理解嗽冒。本系列的文章思路先從Redis的應(yīng)用開(kāi)始呀伙。再解析Redis的內(nèi)部實(shí)現(xiàn)原理补履。最后以經(jīng)常會(huì)問(wèn)到Redist相關(guān)的面試題為結(jié)尾。

二剿另、分布式鎖的實(shí)現(xiàn)要點(diǎn)

?為了實(shí)現(xiàn)分布式鎖箫锤,需要確保鎖同時(shí)滿足以下四個(gè)條件:

互斥性贬蛙。在任意時(shí)刻,只有一個(gè)客戶端能持有鎖

不會(huì)發(fā)送死鎖谚攒。即使一個(gè)客戶端持有鎖的期間崩潰而沒(méi)有主動(dòng)釋放鎖阳准,也需要保證后續(xù)其他客戶端能夠加鎖成功

加鎖和解鎖必須是同一個(gè)客戶端,客戶端自己不能把別人加的鎖給釋放了馏臭。

容錯(cuò)性野蝇。只要大部分的Redis節(jié)點(diǎn)正常運(yùn)行,客戶端就可以進(jìn)行加鎖和解鎖操作括儒。

三绕沈、Redis實(shí)現(xiàn)分布式鎖的錯(cuò)誤姿勢(shì)

3.1?加鎖錯(cuò)誤姿勢(shì)

?  在講解使用Redis實(shí)現(xiàn)分布式鎖的正確姿勢(shì)之前,我們有必要來(lái)看下錯(cuò)誤實(shí)現(xiàn)方式帮寻。

首先乍狐,為了保證互斥性和不會(huì)發(fā)送死鎖2個(gè)條件,所以我們?cè)诩渔i操作的時(shí)候固逗,需要使用SETNX指令來(lái)保證互斥性——只有一個(gè)客戶端能夠持有鎖浅蚪。為了保證不會(huì)發(fā)送死鎖,需要給鎖加一個(gè)過(guò)期時(shí)間烫罩,這樣就可以保證即使持有鎖的客戶端期間崩潰了也不會(huì)一直不釋放鎖惜傲。

  為了保證這2個(gè)條件,有些人錯(cuò)誤的實(shí)現(xiàn)會(huì)用如下代碼來(lái)實(shí)現(xiàn)加鎖操作:

/**

? ? * 實(shí)現(xiàn)加鎖的錯(cuò)誤姿勢(shì)

? ? * @param jedis

? ? * @param lockKey

? ? * @param requestId

? ? * @param expireTime

? ? */

? ? public static void wrongGetLock1(Jedis jedis, String lockKey, String requestId, int expireTime) {

? ? ? ? Long result = jedis.setnx(lockKey, requestId);

? ? ? ? if (result == 1) {

? ? ? ? ? ? // 若在這里程序突然崩潰贝攒,則無(wú)法設(shè)置過(guò)期時(shí)間操漠,將發(fā)生死鎖

? ? ? ? ? ? jedis.expire(lockKey, expireTime);

? ? ? ? }

? ? }

可能一些初學(xué)者還沒(méi)看出以上實(shí)現(xiàn)加鎖操作的錯(cuò)誤原因。這樣我們解釋下饿这。setnx?和expire是兩條Redis指令浊伙,不具備原子性,如果程序在執(zhí)行完setnx之后突然崩潰长捧,導(dǎo)致沒(méi)有設(shè)置鎖的過(guò)期時(shí)間嚣鄙,從而就導(dǎo)致死鎖了。因?yàn)檫@個(gè)客戶端持有的所有不會(huì)被其他客戶端釋放串结,持有鎖的客戶端又崩潰了哑子,也不會(huì)主動(dòng)釋放。從而該鎖永遠(yuǎn)不會(huì)釋放肌割,導(dǎo)致其他客戶端也獲得不能鎖卧蜓。從而其他客戶端一直阻塞。所以針對(duì)該代碼正確姿勢(shì)應(yīng)該保證setnx和expire原子性把敞。

  實(shí)現(xiàn)加鎖操作的錯(cuò)誤姿勢(shì)2弥奸。具體實(shí)現(xiàn)如下代碼所示

/**

? ? * 實(shí)現(xiàn)加鎖的錯(cuò)誤姿勢(shì)2

? ? * @param jedis

? ? * @param lockKey

? ? * @param expireTime

? ? * @return

? ? */

? ? public static boolean wrongGetLock2(Jedis jedis, String lockKey, int expireTime) {

? ? ? ? long expires = System.currentTimeMillis() + expireTime;

? ? ? ? String expiresStr = String.valueOf(expires);

? ? ? ? // 如果當(dāng)前鎖不存在,返回加鎖成功

? ? ? ? if (jedis.setnx(lockKey, expiresStr) == 1) {

? ? ? ? ? ? return true;

? ? ? ? }

? ? ? ? // 如果鎖存在奋早,獲取鎖的過(guò)期時(shí)間

? ? ? ? String currentValueStr = jedis.get(lockKey);

? ? ? ? if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {

? ? ? ? ? ? // 鎖已過(guò)期盛霎,獲取上一個(gè)鎖的過(guò)期時(shí)間赠橙,并設(shè)置現(xiàn)在鎖的過(guò)期時(shí)間

? ? ? ? ? ? String oldValueStr = jedis.getSet(lockKey, expiresStr);

? ? ? ? ? ? if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {

? ? ? ? ? ? ? ? // 考慮多線程并發(fā)的情況,只有一個(gè)線程的設(shè)置值和當(dāng)前值相同愤炸,它才有權(quán)利加鎖

? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? // 其他情況期揪,一律返回加鎖失敗

? ? ? ? return false;

? ? }

  這個(gè)加鎖操作咋一看沒(méi)有毛病對(duì)吧。那以上這段代碼的問(wèn)題毛病出在哪里呢规个?

  1.?由于客戶端自己生成過(guò)期時(shí)間凤薛,所以需要強(qiáng)制要求分布式環(huán)境下所有客戶端的時(shí)間必須同步。

2.?當(dāng)鎖過(guò)期的時(shí)候诞仓,如果多個(gè)客戶端同時(shí)執(zhí)行jedis.getSet()方法枉侧,雖然最終只有一個(gè)客戶端加鎖,但是這個(gè)客戶端的鎖的過(guò)期時(shí)間可能被其他客戶端覆蓋狂芋。不具備加鎖和解鎖必須是同一個(gè)客戶端的特性榨馁。解決上面這段代碼的方式就是為每個(gè)客戶端加鎖添加一個(gè)唯一標(biāo)示,已確保加鎖和解鎖操作是來(lái)自同一個(gè)客戶端帜矾。

3.2?解鎖錯(cuò)誤姿勢(shì)

  分布式鎖的實(shí)現(xiàn)無(wú)法就2個(gè)方法翼虫,一個(gè)加鎖,一個(gè)就是解鎖屡萤。下面我們來(lái)看下解鎖的錯(cuò)誤姿勢(shì)珍剑。

  錯(cuò)誤姿勢(shì)1.

/**

? ? * 解鎖錯(cuò)誤姿勢(shì)1

? ? * @param jedis

? ? * @param lockKey

? ? */

? ? public static void wrongReleaseLock1(Jedis jedis, String lockKey) {

? ? ? ? jedis.del(lockKey);

? ? }

  上面實(shí)現(xiàn)是最簡(jiǎn)單直接的解鎖方式,這種不先判斷擁有者而直接解鎖的方式死陆,會(huì)導(dǎo)致任何客戶端都可以隨時(shí)解鎖招拙。即使這把鎖不是它上鎖的。

  錯(cuò)誤姿勢(shì)2:

/**

? ? * 解鎖錯(cuò)誤姿勢(shì)2

? ? * @param jedis

? ? * @param lockKey

? ? * @param requestId

? ? */

? ? public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {

? ? ? ? // 判斷加鎖與解鎖是不是同一個(gè)客戶端

? ? ? ? if (requestId.equals(jedis.get(lockKey))) {

? ? ? ? ? ? // 若在此時(shí)措译,這把鎖突然不是這個(gè)客戶端的别凤,則會(huì)誤解鎖

? ? ? ? ? ? jedis.del(lockKey);

? ? ? ? }

既然錯(cuò)誤姿勢(shì)1中沒(méi)有判斷鎖的擁有者,那姿勢(shì)2中判斷了擁有者领虹,那錯(cuò)誤原因又在哪里呢规哪?答案又是原子性上面。因?yàn)榕袛嗪蛣h除不是一個(gè)原子性操作塌衰。在并發(fā)的時(shí)候很可能發(fā)生解除了別的客戶端加的鎖诉稍。具體場(chǎng)景有:客戶端A加鎖,一段時(shí)間之后客戶端A進(jìn)行解鎖操作時(shí)最疆,在執(zhí)行jedis.del()之前杯巨,鎖突然過(guò)期了,此時(shí)客戶端B嘗試加鎖成功努酸,然后客戶端A再執(zhí)行del方法服爷,則客戶端A將客戶端B的鎖給解除了。從而不也不滿足加鎖和解鎖必須是同一個(gè)客戶端特性。解決思路就是需要保證GET和DEL操作在一個(gè)事務(wù)中進(jìn)行层扶,保證其原子性。

四烙荷、Redis實(shí)現(xiàn)分布式鎖的正確姿勢(shì)

剛剛介紹完了錯(cuò)誤的姿勢(shì)后镜会,從上面錯(cuò)誤姿勢(shì)中,我們可以知道终抽,要使用Redis實(shí)現(xiàn)分布式鎖戳表。加鎖操作的正確姿勢(shì)為:

使用setnx命令保證互斥性

需要設(shè)置鎖的過(guò)期時(shí)間,避免死鎖

setnx和設(shè)置過(guò)期時(shí)間需要保持原子性昼伴,避免在設(shè)置setnx成功之后在設(shè)置過(guò)期時(shí)間客戶端崩潰導(dǎo)致死鎖

加鎖的Value?值為一個(gè)唯一標(biāo)示匾旭。可以采用UUID作為唯一標(biāo)示圃郊。加鎖成功后需要把唯一標(biāo)示返回給客戶端來(lái)用來(lái)客戶端進(jìn)行解鎖操作

解鎖的正確姿勢(shì)為:

  1.?需要拿加鎖成功的唯一標(biāo)示要進(jìn)行解鎖价涝,從而保證加鎖和解鎖的是同一個(gè)客戶端

  2.?解鎖操作需要比較唯一標(biāo)示是否相等,相等再執(zhí)行刪除操作持舆。這2個(gè)操作可以采用Lua腳本方式使2個(gè)命令的原子性色瘩。

  Redis分布式鎖實(shí)現(xiàn)的正確姿勢(shì)的實(shí)現(xiàn)代碼:

public interface DistributedLock {

? ? /**

? ? * 獲取鎖

? ? * @author zhi.li

? ? * @return 鎖標(biāo)識(shí)

? ? */

? ? String acquire();

? ? /**

? ? * 釋放鎖

? ? * @author zhi.li

? ? * @param indentifier

? ? * @return

? ? */

? ? boolean release(String indentifier);

}

/**

* @author zhi.li

* @Description

* @created 2019/1/1 20:32

*/

@Slf4j

public class RedisDistributedLock implements DistributedLock{

? ? private static final String LOCK_SUCCESS = "OK";

? ? private static final Long RELEASE_SUCCESS = 1L;

? ? private static final String SET_IF_NOT_EXIST = "NX";

? ? private static final String SET_WITH_EXPIRE_TIME = "PX";

? ? /**

? ? * redis 客戶端

? ? */

? ? private Jedis jedis;

? ? /**

? ? * 分布式鎖的鍵值

? ? */

? ? private String lockKey;

? ? /**

? ? * 鎖的超時(shí)時(shí)間 10s

? ? */

? ? int expireTime = 10 * 1000;

? ? /**

? ? * 鎖等待,防止線程饑餓

? ? */

? ? int acquireTimeout? = 1 * 1000;

? ? /**

? ? * 獲取指定鍵值的鎖

? ? * @param jedis jedis Redis客戶端

? ? * @param lockKey 鎖的鍵值

? ? */

? ? public RedisDistributedLock(Jedis jedis, String lockKey) {

? ? ? ? this.jedis = jedis;

? ? ? ? this.lockKey = lockKey;

? ? }

? ? /**

? ? * 獲取指定鍵值的鎖,同時(shí)設(shè)置獲取鎖超時(shí)時(shí)間

? ? * @param jedis jedis Redis客戶端

? ? * @param lockKey 鎖的鍵值

? ? * @param acquireTimeout 獲取鎖超時(shí)時(shí)間

? ? */

? ? public RedisDistributedLock(Jedis jedis,String lockKey, int acquireTimeout) {

? ? ? ? this.jedis = jedis;

? ? ? ? this.lockKey = lockKey;

? ? ? ? this.acquireTimeout = acquireTimeout;

? ? }

? ? /**

? ? * 獲取指定鍵值的鎖,同時(shí)設(shè)置獲取鎖超時(shí)時(shí)間和鎖過(guò)期時(shí)間

? ? * @param jedis jedis Redis客戶端

? ? * @param lockKey 鎖的鍵值

? ? * @param acquireTimeout 獲取鎖超時(shí)時(shí)間

? ? * @param expireTime 鎖失效時(shí)間

? ? */

? ? public RedisDistributedLock(Jedis jedis, String lockKey, int acquireTimeout, int expireTime) {

? ? ? ? this.jedis = jedis;

? ? ? ? this.lockKey = lockKey;

? ? ? ? this.acquireTimeout = acquireTimeout;

? ? ? ? this.expireTime = expireTime;

? ? }

? ? @Override

? ? public String acquire() {

? ? ? ? try {

? ? ? ? ? ? // 獲取鎖的超時(shí)時(shí)間逸寓,超過(guò)這個(gè)時(shí)間則放棄獲取鎖

? ? ? ? ? ? long end = System.currentTimeMillis() + acquireTimeout;

? ? ? ? ? ? // 隨機(jī)生成一個(gè)value

? ? ? ? ? ? String requireToken = UUID.randomUUID().toString();

? ? ? ? ? ? while (System.currentTimeMillis() < end) {

? ? ? ? ? ? ? ? String result = jedis.set(lockKey, requireToken, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

? ? ? ? ? ? ? ? if (LOCK_SUCCESS.equals(result)) {

? ? ? ? ? ? ? ? ? ? return requireToken;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? Thread.sleep(100);

? ? ? ? ? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? ? ? ? ? Thread.currentThread().interrupt();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? log.error("acquire lock due to error", e);

? ? ? ? }

? ? ? ? return null;

? ? }

? ? @Override

? ? public boolean release(String identify) {

    if(identify == null){

? ? ? ? ? ? return false;

? ? ? ? }

? ? ? ? String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

? ? ? ? Object result = new Object();

? ? ? ? try {

? ? ? ? ? ? result = jedis.eval(script, Collections.singletonList(lockKey),

? ? ? ? ? ? ? ? Collections.singletonList(identify));

? ? ? ? if (RELEASE_SUCCESS.equals(result)) {

? ? ? ? ? ? log.info("release lock success, requestToken:{}", identify);

? ? ? ? ? ? return true;

? ? ? ? }}catch (Exception e){

? ? ? ? ? ? log.error("release lock due to error",e);

? ? ? ? }finally {

? ? ? ? ? ? if(jedis != null){

? ? ? ? ? ? ? ? jedis.close();

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? log.info("release lock failed, requestToken:{}, result:{}", identify, result);

? ? ? ? return false;

? ? }

}

  下面就以秒殺庫(kù)存數(shù)量為場(chǎng)景居兆,測(cè)試下上面實(shí)現(xiàn)的分布式鎖的效果。具體測(cè)試代碼如下:

public class RedisDistributedLockTest {

? ? static int n = 500;

? ? public static void secskill() {

? ? ? ? System.out.println(--n);

? ? }

? ? public static void main(String[] args) {

? ? ? ? Runnable runnable = () -> {

? ? ? ? ? ? RedisDistributedLock lock = null;

? ? ? ? ? ? String unLockIdentify = null;

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? Jedis conn = new Jedis("127.0.0.1",6379);

? ? ? ? ? ? ? ? lock = new RedisDistributedLock(conn, "test1");

? ? ? ? ? ? ? ? unLockIdentify = lock.acquire();

? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "正在運(yùn)行");

? ? ? ? ? ? ? ? secskill();

? ? ? ? ? ? } finally {

? ? ? ? ? ? ? ? if (lock != null) {

? ? ? ? ? ? ? ? ? ? lock.release(unLockIdentify);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? };

? ? ? ? for (int i = 0; i < 10; i++) {

? ? ? ? ? ? Thread t = new Thread(runnable);

? ? ? ? ? ? t.start();

? ? ? ? }

? ? }

}

  運(yùn)行效果如下圖所示竹伸。從圖中可以看出泥栖,同一個(gè)資源在同一個(gè)時(shí)刻只能被一個(gè)線程獲取,從而保證了庫(kù)存數(shù)量N的遞減是順序的勋篓。

五吧享、總結(jié)

  這樣是不是已經(jīng)完美使用Redis實(shí)現(xiàn)了分布式鎖呢?答案是并沒(méi)有結(jié)束譬嚣。上面的實(shí)現(xiàn)代碼只是針對(duì)單機(jī)的Redis沒(méi)問(wèn)題耙蔑。但是現(xiàn)實(shí)生產(chǎn)中大部分都是集群的或者是主備的。但上面的實(shí)現(xiàn)姿勢(shì)在集群或者主備情況下會(huì)有相應(yīng)的問(wèn)題孤荣。這里先買一個(gè)關(guān)子甸陌,在后面一篇文章將詳細(xì)分析集群或者主備環(huán)境下Redis分布式鎖的實(shí)現(xiàn)方式。

在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流圈:830478757 幫助突破瓶頸 提升思維能力

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盐股,一起剝皮案震驚了整個(gè)濱河市钱豁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疯汁,老刑警劉巖牲尺,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡谤碳,警方通過(guò)查閱死者的電腦和手機(jī)溃卡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蜒简,“玉大人瘸羡,你說(shuō)我怎么就攤上這事〈瓴纾” “怎么了犹赖?”我有些...
    開(kāi)封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)卷仑。 經(jīng)常有香客問(wèn)我峻村,道長(zhǎng),這世上最難降的妖魔是什么锡凝? 我笑而不...
    開(kāi)封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任粘昨,我火速辦了婚禮,結(jié)果婚禮上窜锯,老公的妹妹穿的比我還像新娘雾棺。我一直安慰自己,他們只是感情好衬浑,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布捌浩。 她就那樣靜靜地躺著,像睡著了一般工秩。 火紅的嫁衣襯著肌膚如雪尸饺。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天助币,我揣著相機(jī)與錄音浪听,去河邊找鬼。 笑死眉菱,一個(gè)胖子當(dāng)著我的面吹牛迹栓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俭缓,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼克伊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了华坦?” 一聲冷哼從身側(cè)響起愿吹,我...
    開(kāi)封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惜姐,沒(méi)想到半個(gè)月后犁跪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體椿息,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年坷衍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寝优。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枫耳,死狀恐怖乏矾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嘉涌,我是刑警寧澤妻熊,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布夸浅,位于F島的核電站仑最,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏帆喇。R本人自食惡果不足惜警医,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坯钦。 院中可真熱鬧预皇,春花似錦、人聲如沸婉刀。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)突颊。三九已至鲁豪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間律秃,已是汗流浹背爬橡。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棒动,地道東北人糙申。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像船惨,于是被迫代替她去往敵國(guó)和親柜裸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 前言 分布式鎖一般有三種實(shí)現(xiàn)方式:1. 數(shù)據(jù)庫(kù)樂(lè)觀鎖粱锐;2. 基于Redis的分布式鎖粘室;3. 基于ZooKeeper...
    朦朧蜜桃閱讀 484評(píng)論 1 0
  • 前言 分布式鎖一般有三種實(shí)現(xiàn)方式:1. 數(shù)據(jù)庫(kù)樂(lè)觀鎖;2. 基于Redis的分布式鎖卜范;3. 基于ZooKeeper...
    程序員技術(shù)圈閱讀 3,811評(píng)論 4 80
  • 前言 分布式鎖一般有三種實(shí)現(xiàn)方式:1. 數(shù)據(jù)庫(kù)樂(lè)觀鎖衔统;2. 基于Redis的分布式鎖;3. 基于ZooKeeper...
    bbe9e62bc5ba閱讀 292評(píng)論 0 1
  • 這里只做個(gè)人技術(shù)筆錄,不搞一堆的廢話官網(wǎng)中提示:RabbitMQ實(shí)現(xiàn)了多種協(xié)議锦爵。此處測(cè)試使用AMQP 0-9-1協(xié)...
    wingedsnake閱讀 766評(píng)論 0 0
  • 我的母親今年57歲了舱殿,臉上手上有老年斑了,兩鬢也已經(jīng)斑白了险掀。老年發(fā)胖了點(diǎn)沪袭,下巴是雙的了。她孕育了五個(gè)子女樟氢,我是老大...
    埴林閱讀 388評(píng)論 0 0