分布式鎖與實現(xiàn)(一)基于Redis實現(xiàn)

目前幾乎很多大型網(wǎng)站及應(yīng)用都是分布式部署的,分布式場景中的數(shù)據(jù)一致性問題一直是一個比較重要的話題。分布式的CAP理論告訴我們“任何一個分布式系統(tǒng)都無法同時滿足一致性(Consistency)娱颊、可用性(Availability)和分區(qū)容錯性(Partition tolerance)军熏,最多只能同時滿足兩項涨颜。”所以别垮,很多系統(tǒng)在設(shè)計之初就要對這三者做出取舍。在互聯(lián)網(wǎng)領(lǐng)域的絕大多數(shù)的場景中扎谎,都需要犧牲強(qiáng)一致性來換取系統(tǒng)的高可用性碳想,系統(tǒng)往往只需要保證“最終一致性”,只要這個最終時間是在用戶可以接受的范圍內(nèi)即可毁靶。

在很多場景中胧奔,我們?yōu)榱吮WC數(shù)據(jù)的最終一致性,需要很多的技術(shù)方案來支持预吆,比如分布式事務(wù)龙填、分布式鎖等。

選用Redis實現(xiàn)分布式鎖原因

Redis有很高的性能

Redis命令對此支持較好啡浊,實現(xiàn)起來比較方便


使用命令介紹

SETNX

SETNX key val

當(dāng)且僅當(dāng)key不存在時觅够,set一個key為val的字符串,返回1巷嚣;若key存在喘先,則什么都不做,返回0廷粒。

expire

expire key timeout

為key設(shè)置一個超時時間窘拯,單位為second,超過這個時間鎖會自動釋放坝茎,避免死鎖涤姊。

delete

delete key

刪除key

在使用Redis實現(xiàn)分布式鎖的時候,主要就會使用到這三個命令嗤放。

實現(xiàn)

使用的是jedis來連接Redis思喊。

實現(xiàn)思想

獲取鎖的時候,使用setnx加鎖次酌,并使用expire命令為鎖添加一個超時時間恨课,超過該時間則自動釋放鎖舆乔,鎖的value值為一個隨機(jī)生成的UUID,通過此在釋放鎖的時候進(jìn)行判斷剂公。

獲取鎖的時候還設(shè)置一個獲取的超時時間希俩,若超過這個時間則放棄獲取鎖。

釋放鎖的時候纲辽,通過UUID判斷是不是該鎖颜武,若是該鎖,則執(zhí)行delete進(jìn)行鎖釋放拖吼。

分布式鎖的核心代碼如下:

import?redis.clients.jedis.Jedis;import?redis.clients.jedis.JedisPool;import?redis.clients.jedis.Transaction;import?redis.clients.jedis.exceptions.JedisException;import?java.util.List;import?java.util.UUID;/**

*?Created?by?liuyang?on?2017/4/20.

*/public?class?DistributedLock?{????private?final?JedisPool?jedisPool;????public?DistributedLock(JedisPool?jedisPool)?{????????this.jedisPool?=?jedisPool;

}????/**

*?加鎖

*?@param?locaName??鎖的key

*?@param?acquireTimeout??獲取超時時間

*?@param?timeout???鎖的超時時間

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

*/

public?String?lockWithTimeout(String?locaName,??????????????????????????????????long?acquireTimeout,?long?timeout)?{

Jedis?conn?=?null;

String?retIdentifier?=?null;????????try?{????????????//?獲取連接

conn?=?jedisPool.getResource();????????????//?隨機(jī)生成一個value

String?identifier?=?UUID.randomUUID().toString();????????????//?鎖名鳞上,即key值

String?lockKey?=?"lock:"?+?locaName;????????????//?超時時間,上鎖后超過此時間則自動釋放鎖

int?lockExpire?=?(int)(timeout?/?1000);????????????//?獲取鎖的超時時間吊档,超過這個時間則放棄獲取鎖

long?end?=?System.currentTimeMillis()?+?acquireTimeout;????????????while?(System.currentTimeMillis()?<?end)?{????????????????if?(conn.setnx(lockKey,?identifier)?==?1)?{

conn.expire(lockKey,?lockExpire);????????????????????//?返回value值因块,用于釋放鎖時間確認(rèn)

retIdentifier?=?identifier;????????????????????return?retIdentifier;

}????????????????//?返回-1代表key沒有設(shè)置超時時間,為key設(shè)置一個超時時間

想學(xué)習(xí)更多java知識的朋友可以進(jìn)群:874811168?一起學(xué)習(xí)?還有全套的免費(fèi)資料領(lǐng)取

if?(conn.ttl(lockKey)?==?-1)?{

conn.expire(lockKey,?lockExpire);

}????????????????try?{

Thread.sleep(10);

}?catch?(InterruptedException?e)?{

Thread.currentThread().interrupt();

}

}

}?catch?(JedisException?e)?{

e.printStackTrace();

}?finally?{????????????if?(conn?!=?null)?{

conn.close();

}

}????????return?retIdentifier;

}????/**

*?釋放鎖

*?@param?lockName?鎖的key

*?@param?identifier????釋放鎖的標(biāo)識

*?@return

*/

public?boolean?releaseLock(String?lockName,?String?identifier)?{

Jedis?conn?=?null;

String?lockKey?=?"lock:"?+?lockName;????????boolean?retFlag?=?false;????????try?{

conn?=?jedisPool.getResource();????????????while?(true)?{????????????????//?監(jiān)視lock籍铁,準(zhǔn)備開始事務(wù)

conn.watch(lockKey);????????????????//?通過前面返回的value值判斷是不是該鎖涡上,若是該鎖,則刪除拒名,釋放鎖

if?(identifier.equals(conn.get(lockKey)))?{

Transaction?transaction?=?conn.multi();

transaction.del(lockKey);

List?results?=?transaction.exec();????????????????????if?(results?==?null)?{????????????????????????continue;

}

retFlag?=?true;

}

conn.unwatch();????????????????break;

}

}?catch?(JedisException?e)?{

e.printStackTrace();

}?finally?{????????????if?(conn?!=?null)?{

conn.close();

}

}????????return?retFlag;

}

}

測試

下面就用一個簡單的例子測試剛才實現(xiàn)的分布式鎖吩愧。

例子中使用50個線程模擬秒殺一個商品,使用--運(yùn)算符來實現(xiàn)商品減少增显,從結(jié)果有序性就可以看出是否為加鎖狀態(tài)雁佳。

模擬秒殺服務(wù),在其中配置了jedis線程池同云,在初始化的時候傳給分布式鎖糖权,供其使用。

import?redis.clients.jedis.JedisPool;

import?redis.clients.jedis.JedisPoolConfig;/**

*?Created?by?liuyang?on?2017/4/20.

*/public?class?Service?{????private?static?JedisPool?pool?=?null;????static?{

JedisPoolConfig?config?=?new?JedisPoolConfig();????????//?設(shè)置最大連接數(shù)

config.setMaxTotal(200);????????//?設(shè)置最大空閑數(shù)

config.setMaxIdle(8);????????//?設(shè)置最大等待時間

config.setMaxWaitMillis(1000?*?100);????????//?在borrow一個jedis實例時炸站,是否需要驗證星澳,若為true,則所有jedis實例均是可用的

config.setTestOnBorrow(true);

想學(xué)習(xí)更多java知識的朋友可以進(jìn)群:874811168 一起學(xué)習(xí) 還有全套的免費(fèi)資料領(lǐng)取

pool?=?new?JedisPool(config,?"127.0.0.1",?6379,?3000);

}

?

DistributedLock?lock?=?new?DistributedLock(pool);????int?n?=?500;????public?void?seckill()?{????????//?返回鎖的value值旱易,供釋放鎖時候進(jìn)行判斷

String?indentifier?=?lock.lockWithTimeout("resource",?5000,?1000);

System.out.println(Thread.currentThread().getName()?+?"獲得了鎖");

System.out.println(--n);????????lock.releaseLock("resource",?indentifier);

}

}

// 模擬線程進(jìn)行秒殺服務(wù)

public?class?ThreadA?extends?Thread?{????private?Service?service;????public?ThreadA(Service?service)?{????????this.service?=?service;

}????@Override

public?void?run()?{

service.seckill();

}

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

Service?service?=?new?Service();????????for?(int?i?=?0;?i?<?50;?i++)?{

ThreadA?threadA?=?new?ThreadA(service);

threadA.start();

}

}

}

結(jié)果如下禁偎,結(jié)果為有序的。

若注釋掉使用鎖的部分

public?void?seckill()?{????//?返回鎖的value值阀坏,供釋放鎖時候進(jìn)行判斷

//String?indentifier?=?lock.lockWithTimeout("resource",?5000,?1000);

System.out.println(Thread.currentThread().getName()?+?"獲得了鎖");

想學(xué)習(xí)更多java知識的朋友可以進(jìn)群:874811168?一起學(xué)習(xí)?還有全套的免費(fèi)資料領(lǐng)取

System.out.println(--n);????//lock.releaseLock("resource",?indentifier);}

從結(jié)果可以看出如暖,有一些是異步進(jìn)行的。

在分布式環(huán)境中忌堂,對資源進(jìn)行上鎖有時候是很重要的盒至,比如搶購某一資源,這時候使用分布式鎖就可以很好地控制資源。

當(dāng)然枷遂,在具體使用中寝蹈,還需要考慮很多因素,比如超時時間的選取登淘,獲取鎖時間的選取對并發(fā)量都有很大的影響,上述實現(xiàn)的分布式鎖也只是一種簡單的實現(xiàn)封字,主要是一種思想黔州。

下一次我會使用zookeeper實現(xiàn)分布式鎖,使用zookeeper的可靠性是要大于使用redis實現(xiàn)的分布式鎖的阔籽,但是相比而言流妻,redis的性能更好。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笆制,一起剝皮案震驚了整個濱河市绅这,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌在辆,老刑警劉巖证薇,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匆篓,居然都是意外死亡浑度,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門鸦概,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箩张,“玉大人,你說我怎么就攤上這事窗市∠瓤叮” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵咨察,是天一觀的道長论熙。 經(jīng)常有香客問我,道長摄狱,這世上最難降的妖魔是什么赴肚? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮二蓝,結(jié)果婚禮上誉券,老公的妹妹穿的比我還像新娘。我一直安慰自己刊愚,他們只是感情好踊跟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般商玫。 火紅的嫁衣襯著肌膚如雪箕憾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天拳昌,我揣著相機(jī)與錄音袭异,去河邊找鬼。 笑死炬藤,一個胖子當(dāng)著我的面吹牛御铃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沈矿,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼上真,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了羹膳?” 一聲冷哼從身側(cè)響起睡互,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎陵像,沒想到半個月后就珠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡醒颖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年嗓违,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片图贸。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡蹂季,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疏日,到底是詐尸還是另有隱情偿洁,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布沟优,位于F島的核電站涕滋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏挠阁。R本人自食惡果不足惜宾肺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望侵俗。 院中可真熱鬧锨用,春花似錦、人聲如沸隘谣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掌栅,卻和暖如春秩仆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背猾封。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工澄耍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晌缘。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓齐莲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親枚钓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

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