亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線(xiàn)咨詢(xún)客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

應(yīng)用場(chǎng)景

分布式系統(tǒng)中,面對(duì)高并發(fā)場(chǎng)景,又對(duì)數(shù)據(jù)一致性有一定要求的情況下,使用分布式鎖。例如商城中下單扣庫(kù)存這種情況。

解決方案

基于數(shù)據(jù)庫(kù)

例如:

select * from mall_spu where id=111 for update

例如:專(zhuān)門(mén)建一張表用來(lái)實(shí)現(xiàn)。例如以類(lèi)名、方法名、數(shù)據(jù)ID作為唯一主鍵,org.leo.mall.order.OrderServer.addOrder.skuId.111,方法執(zhí)行的時(shí)候,如果能插入成功,代表拿到鎖,如果報(bào)主鍵沖突,則拿鎖失敗。


基于Zookeeper

以類(lèi)、方法、數(shù)據(jù)ID作為目錄,請(qǐng)求取順序節(jié)點(diǎn)&節(jié)點(diǎn)列表,如果自己的節(jié)點(diǎn)最小,說(shuō)明拿鎖成功。而且還可以通過(guò)watch,在鎖釋放的時(shí)候重新拿鎖。因?yàn)槭桥R時(shí)鎖,所以主動(dòng)釋放,或者session失效都可以釋放鎖,避免死鎖產(chǎn)生。

性能差點(diǎn),因?yàn)閆ookeeper的操作都在主節(jié)點(diǎn)上。


基于redis

本文主要講講應(yīng)用的一些變革。

1、加鎖。

原來(lái)的做法是:

public static boolean getLock(String key,int expireTime){
 Long result=RedisClient.setnx(key,"");
 if(result!=1){return false}
 RedisClient.expire(key,expireTime);
 return true;
}

setnx加鎖,成功后用expire加上超時(shí)時(shí)間。

問(wèn)題在于:sennx和expire不是原子操作,萬(wàn)一expire的時(shí)候崩了,這條命令永遠(yuǎn)不過(guò)期了。

所以后來(lái)基于Redis的升級(jí),有了下面正確的加鎖方法:

public static boolean getLock(String key,String requestId,int expireTime){
 String result=RedisClient.set(key,requestId,"NX","PK",expireTime);
 if(result.equals("OK")){return true;}
 return false;
}

其實(shí)就是用Redis提供的一條set命令,替代了前面的setnx、expire兩條命令,保證了原子性。

NX是指Key不存在就新增。PX是指設(shè)置超時(shí)時(shí)間。

requestId是為了后面解鎖用。

2、解鎖

解鎖看著最簡(jiǎn)單,其實(shí)蠻復(fù)雜。

腦子里第一想法就是:

public static void releaseLock(String key){
 RedisClient.del(key);
}

這個(gè)危險(xiǎn)性在于任何人都可以解鎖!比如A請(qǐng)求加了鎖:spu_id_111。B請(qǐng)求也要對(duì)111進(jìn)行操作,一看鎖被占了,直接del,然后自己拿鎖——雖然在程序開(kāi)發(fā)上講,沒(méi)有哪個(gè)傻子會(huì)這么干!!

所以這才有了第二種做法:

A請(qǐng)求加鎖的時(shí)候,通過(guò)UUID、Random等方法生成隨機(jī)數(shù)requestId。

public static void releaseLock(String key,String requestId){
  String result=RedisClient.get(key);//步驟1
  if(result.equals(requestId)){//步驟2
   //二者相等,說(shuō)明加解鎖的請(qǐng)求是同一個(gè)
   RedisClient.del(key);//步驟3
 }
}

看似很?chē)?yán)謹(jǐn),但是問(wèn)題出在哪呢?還是出在操作不是原子性上。

A請(qǐng)求執(zhí)行步驟1、2完畢,還未執(zhí)行步驟2時(shí),鎖過(guò)期了,自動(dòng)解鎖!這時(shí)B請(qǐng)求加鎖必然成功,而A請(qǐng)求繼續(xù)執(zhí)行步驟3,把B請(qǐng)求的鎖給刪了。

正確的做法如下:

public static boolean releaseLock(String key,String requestId){
 String luaCommand="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
 List<String> keyList=Lists.newArrayList(key);//這里我用的是Guava
List<String> valList=Lists.newArrayList(requestId);
 Object result=RedisClient.eval(luaCommand,keyList,valList);
 if(result.equals(1L)){return true;}
 return false;
}

利用的就是Redis通過(guò)eval命令執(zhí)行LUA腳本是原子性的特性。


再然后就是使用Redisson實(shí)現(xiàn)了,這個(gè)適用于集群部署的Redis。

我在實(shí)際使用Redis分布式鎖的時(shí)候遇到過(guò)一種情況。使用分布式鎖后,要調(diào)用第三方接口,從而導(dǎo)致整個(gè)流程時(shí)間偏長(zhǎng),鎖過(guò)期的情況下還沒(méi)有執(zhí)行完,當(dāng)時(shí)的處理方式是加大了過(guò)期時(shí)間。

如果使用Redisson,因?yàn)橛锌撮T(mén)狗機(jī)制,就很好地解決了這個(gè)問(wèn)題。看門(mén)狗會(huì)定時(shí)去檢查,如果請(qǐng)求實(shí)例還在則自動(dòng)去延長(zhǎng)超時(shí)時(shí)間。不過(guò)這帶來(lái)的問(wèn)題一定是性能的下降,所以當(dāng)時(shí)我們還是采用了粗暴的延長(zhǎng)設(shè)置過(guò)期時(shí)間來(lái)解決此類(lèi)問(wèn)題。

Redisson也是個(gè)可重入鎖,因?yàn)殒i的內(nèi)容除了key、實(shí)例ID之外還有數(shù)字Value,這樣一來(lái)同樣的實(shí)例多次拿鎖,Value+1,釋放鎖,Value-1即可。

分享到:
標(biāo)簽:Redis
用戶(hù)無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定