国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

使用Redis實(shí)現(xiàn)鎖(支持分布式應(yīng)用)(整理網(wǎng)絡(luò)資料)

2019-11-15 00:21:09
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友
使用Redis實(shí)現(xiàn)鎖(支持分布式應(yīng)用)(整理網(wǎng)絡(luò)資料)使用Redis實(shí)現(xiàn)鎖(支持分布式應(yīng)用)1. 簡(jiǎn)介

使用Redis指令setnx、expire、getset等操作實(shí)現(xiàn)互斥資源的訪問(wèn)

本文內(nèi)容來(lái)著網(wǎng)絡(luò)整理,參考:

http://www.linuxidc.com/Linux/2014-12/110958.htm

http://www.jeffkit.info/2011/07/1000/

http://blog.csdn.net/java2000_wl/article/details/8740911

2. 背景

  在特殊業(yè)務(wù)邏輯中,需要保證莫一個(gè)操作同時(shí)只有一個(gè)線程在操作,保證數(shù)據(jù)一致性。防止數(shù)據(jù)被多次改寫或產(chǎn)生多條重復(fù)數(shù)據(jù)。

3. 思路通過(guò)get 和set 命令實(shí)現(xiàn)

  這種方式很容易想到,就是當(dāng)每次請(qǐng)求到來(lái)時(shí)通過(guò)get判斷這個(gè)鎖是否存在,如果不存在則set創(chuàng)建。這種方法有一個(gè)弊端,由于get和set是兩次Redis請(qǐng)求,二者之間有延時(shí),在高并發(fā)的環(huán)境下,有可能在get檢測(cè)到鎖不存之后在set之前已經(jīng)被其他線程set,這時(shí)當(dāng)前線程再set,這樣鎖就失效了。所以這種方法只能應(yīng)對(duì)并發(fā)量不是很高的情況

通過(guò)setnx 和 expire命令實(shí)現(xiàn)

  在訪問(wèn)需要互斥訪問(wèn)的資源時(shí),通過(guò)setnx命令去設(shè)置一個(gè)lock 鍵,setnx的作用是判斷鎖是否存在,如果不存在則創(chuàng)建,返回成功,如果存在則返回失敗,服務(wù)器返回給客戶端,指示客戶端稍后重試。expire命令用于給該鎖設(shè)定一個(gè)過(guò)期時(shí)間,用于防止線程crash,導(dǎo)致鎖一直有效,從而導(dǎo)致死鎖。例如:設(shè)定鎖的有效期為100秒,那么即使線程奔潰,在100秒后鎖會(huì)自動(dòng)失效。(實(shí)際上,這個(gè)地方也有問(wèn)題,高并發(fā)下在執(zhí)行expire命令時(shí)偶爾會(huì)失敗(Redis socket鏈接問(wèn)題),失敗后這個(gè)lock就不會(huì)自動(dòng)過(guò)期,值會(huì)會(huì)一直存在,出現(xiàn)死鎖導(dǎo)致后續(xù)的重試操作就永遠(yuǎn)不會(huì)成功! 為保證執(zhí)行成功需要考慮失敗時(shí)多次執(zhí)行expire

setnx lock "lock"

expire lock 100 //如果鎖定成功,則設(shè)置過(guò)期時(shí)間

do work code //工作邏輯代碼

del lock //訪問(wèn)互斥資源結(jié)束后,刪除鎖

通過(guò)setnx和 getset命令加 timespan+timeout (推薦)

如何解決setnx + expire 的死鎖問(wèn)題?可以通過(guò)鎖的鍵對(duì)應(yīng)的時(shí)間戳來(lái)判斷這種情況是否發(fā)生了,如果當(dāng)前的時(shí)間已經(jīng)大于lock的值,說(shuō)明該鎖已失效,可以被重新使用。

發(fā)生這種情況時(shí),可不能簡(jiǎn)單的通過(guò)DEL來(lái)刪除鎖,然后再SETNX一次,當(dāng)多個(gè)客戶端檢測(cè)到鎖超時(shí)后都會(huì)嘗試去釋放它,這里就可能出現(xiàn)一個(gè)競(jìng)態(tài)條件,讓我們模擬一下這個(gè)場(chǎng)景:

C0操作超時(shí)了,但它還持有著鎖,C1和C2讀取lock檢查時(shí)間戳,先后發(fā)現(xiàn)超時(shí)了。

C1 發(fā)送DEL lock

C1 發(fā)送SETNX lock并且成功了。

C2 發(fā)送DEL lock

C2 發(fā)送SETNX lock并且成功了。

這樣一來(lái),C1,C2都拿到了鎖!問(wèn)題大了!

幸好這種問(wèn)題是可以避免的,讓我們來(lái)看看C3這個(gè)客戶端是怎樣做的:

C3發(fā)送SETNX lock想要獲得鎖,由于C0還持有鎖,所以Redis返回給C3一個(gè)0

C3發(fā)送GET lock以檢查鎖是否超時(shí)了,如果沒(méi)超時(shí),則等待或重試。

反之,如果已超時(shí),C3通過(guò)下面的操作來(lái)嘗試獲得鎖:

GETSET lock <current Unix time + lock timeout + 1>

通過(guò)GETSET,C3拿到的時(shí)間戳如果仍然是超時(shí)的,那就說(shuō)明,C3如愿以償拿到鎖了。

如果在C3之前,有個(gè)叫C4的客戶端比C3快一步執(zhí)行了上面的操作,那么C3拿到的時(shí)間戳是個(gè)未超時(shí)的值,這時(shí),C3沒(méi)有如期獲得鎖,需要再次等待或重試。留意一下,盡管C3沒(méi)拿到鎖,但它改寫了C4設(shè)置的鎖的超時(shí)值,不過(guò)這一點(diǎn)非常微小的誤差帶來(lái)的影響可以忽略不計(jì)。

注意:為了讓分布式鎖的算法更穩(wěn)鍵些,持有鎖的客戶端在解鎖之前應(yīng)該再檢查一次自己的鎖是否已經(jīng)超時(shí),再去做DEL操作,因?yàn)榭赡芸蛻舳艘驗(yàn)槟硞€(gè)耗時(shí)的操作而掛起,操作完的時(shí)候鎖因?yàn)槌瑫r(shí)已經(jīng)被別人獲得,這時(shí)就不必解鎖了

附偽代碼:

# get lock

lock = 0

while lock != 1:

timestamp = current Unix time + lock timeout + 1

lock = SETNX lock.foo timestamp

if lock == 1 or (now() > (GET lock.foo) and now() > (GETSET lock.foo timestamp)):

break;

else:

sleep(10ms)

# do your job

do_job()

# release

if now() < GET lock.foo:

DEL lock.foo

4. 代碼通過(guò)setnx 和 expire命令實(shí)現(xiàn)代碼
 1 public boolean tryLock(String key, int timeout, int expiretime, int sleeptime) throws Exception { 2  3   4  5         Jedis redis = jedisPool.getResource(); 6  7         try { 8  9             long nano = System.nanoTime();10 11             do {12 13                 Long i = redis.setnx(key, "key");14 15                 jedisPool.returnResource(redis);16 17                 if (i == 1) {18 19                     redis.expire(key, expiretime);20 21                     return Boolean.TRUE;22 23                 }24 25                 if (timeout == 0) {26 27                     break;28 29                 }30 31                 Thread.sleep(sleeptime);32 33             } while ((System.nanoTime() - nano) < TimeUnit.SECONDS.toNanos(timeout));34 35             return Boolean.FALSE;36 37         } catch (RuntimeException | InterruptedException e) {38 39             if (redis != null) {40 41                 jedisPool.returnBrokenResource(redis);42 43             }44 45             throw e;46 47         }48 49     }50 51  

通過(guò)setnx和 getset命令加 timespan+timeout (推薦代碼)

 1 public boolean tryLock(String key, int timeout, int expiretime, int sleeptime) throws Exception { 2  3   4  5         Jedis redis = jedisPool.getResource(); 6  7         try { 8  9             long nano = System.nanoTime();10 11  12 13             do {14 15                 long timestamp = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiretime) + 1;16 17                 Long i = redis.setnx(key, String.valueOf(timestamp));18 19                 jedisPool.returnResource(redis);20 21                 if (i == 1) {22 23                     return Boolean.TRUE;24 25                 }26 27                 String lockVal = getString(key);28 29                 if (StringUtils.isBlank(lockVal) || !StringUtils.isNumeric(lockVal)) {30 31                     lockVal = "0";32 33                 }34 35                 if (System.currentTimeMillis() > Long.valueOf(lockVal)) {36 37                     lockVal = getAndset(key, String.valueOf(timestamp));38 39                     if (StringUtils.isBlank(lockVal) || !StringUtils.isNumeric(lockVal)) {40 41                         lockVal = "0";42 43                     }44 45                     if (System.currentTimeMillis() > Long.valueOf(lockVal)) {46 47                         return Boolean.TRUE;48 49                     }50 51                 }52 53                 if (timeout == 0) {54 55                     break;56 57                 }58 59                 Thread.sleep(sleeptime);60 61             } while ((System.nanoTime() - nano) < TimeUnit.SECONDS.toNanos(timeout));62 63             return Boolean.FALSE;64 65         } catch (RuntimeException | InterruptedException e) {66 67             if (redis != null) {68 69                 jedisPool.returnBrokenResource(redis);70 71             }72 73             throw e;74 75         }76 77     }


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 旬阳县| 沿河| 旬邑县| 江油市| 余庆县| 博罗县| 桐梓县| 怀柔区| 石河子市| 寿宁县| 蓬溪县| 平湖市| 临澧县| 武冈市| 新巴尔虎右旗| 崇阳县| 奉新县| 泽州县| 连州市| 朝阳区| 南昌市| 眉山市| 贺州市| 崇仁县| 抚顺县| 祁门县| 房产| 瓦房店市| 邓州市| 正蓝旗| 东莞市| 五台县| 合江县| 澄江县| 忻城县| 五莲县| 赤壁市| 松原市| 裕民县| 安宁市| 思茅市|