場景描述:
更新一個頁面信息寡润,首先更新頁面在導(dǎo)航中的信息,然后刪除頁面下所有圖表的組件以及配置等詳細(xì)信息并保存新頁面的所有圖表信息饵较。
注:下列方法存在于在前篇:Redis+Lua實現(xiàn)分布式鎖
類:LockSerice.java
1.方法lock(String lock, int expire)?:獲取鎖
2.方法softUnlock(String lock):解鎖树叽,只有當(dāng)鎖過期時才會解鎖
3.方法hardUnlock(String lock):解鎖靴跛,不關(guān)心鎖的是否過期
4.方法getLockStatus(String lock):獲取鎖的狀態(tài)
5.方法autoIncrLock(String lock):鎖的值自加1诊霹,如果鎖本身不存在羞延,默認(rèn)從0開始
6.方法autoDecrLock(String lock):鎖的值自減1,如果鎖本身不存在脾还,默認(rèn)從0開始
7.方法softUnlockAuto(String lock):解鎖伴箩,只有當(dāng)鎖過期時才會解鎖
8.方法hardUnlockAuto(String lock):解鎖,不關(guān)心當(dāng)前鎖的狀態(tài)
9.方法getLockStatusAuto(String lock):獲取鎖的狀態(tài)
說明:下面提到的對線程的鎖和頁面的鎖使用了相同的lock鄙漏,但是在放入緩存前會加前綴嗤谚,所有,盡管線程的鎖和頁面的鎖顯示的lock一樣泥张,但是實際上不同呵恢,后續(xù)不在對此做贅述鞠值。以下代碼只是上述問題的邏輯展現(xiàn)媚创,沒有實現(xiàn)細(xì)節(jié)以及異常問題處理,參考時請注意彤恶!
一钞钙、getPageInfo(?Long pageId)
獲取頁面信息:現(xiàn)獲取頁面鎖,然后再獲取線程鎖(若拿不到稍微等一會)声离,如果實在等不到(很可能已經(jīng)掛了)芒炼,就從緩存中讀取備份的數(shù)據(jù)。
PageInfo getPageInfo ( Long pageId) throws Exception {
? ? Assert.isTrue(getLockForWait(lock, 2), "頁面正在更新术徊,請稍后重試...");? ?
? ? PageInfo pageInfo;
? ? try {
? ? ? ? if(!getThreadLockForWait(lock, 2)){
? ??????????pageInfo = ##從Redis中獲取備份的數(shù)據(jù)##;
? ??????} else {
? ? ? ??? ??pageInfo =?##從數(shù)據(jù)庫中獲取數(shù)據(jù)##;
? ? ? ? }
? ? } finally {
? ??????lockService.hardUnlock(lock);
????}
? ? return pageInfo;
}
二本刽、getLockForWait(String lock, int expire)
獲取頁面的鎖,如果拿不到鎖赠涮,就稍微等一會子寓,如果實在等不到就算了。
private boolean getLockForWait(String lock, int expire) throws Exception {
??? ????boolean status;
? ?? ???int count =5 * expire;
? ??? ??while (!(status = getLock(lock)) && count >0) {
? ??????????????Thread.sleep(200);
? ? ? ??? ??????count--;
??? ??? }
? ??????return status;
}
三笋除、getThreadLockForWait(String?threadLock, int expire)
獲取線程的鎖斜友,如果拿到了,就清除上次的備份數(shù)據(jù)垃它。返回true
如果拿不到鲜屏,稍微等一會,實在等不到国拇,強制解鎖洛史,返回false。這種情況還需要在進(jìn)步一處理酱吝,目前還沒有想好方案虹菲。
public boolean getThreadLockForWait(String threadLock, int expire) throws Exception {
? ? int count =5 * expire;
? ? while (!lockService.getLockStatusAuto(threadLock) && count >0) {
????????count--;
? ? ? ? Thread.sleep(200);
? ? }
????if (lockService.softUnlockAuto(threadLock)) {
? ? ? ? ? ? ##do:刪除緩存中備份的數(shù)據(jù)##
????????????return true;
? ? } else {
????????????lockService.hardUnlockAuto(lock);
????????????return false;
? ? }
}
三、updatePageInfo(PageInfo pageInfo)
更新頁面掉瞳,先獲取頁面的鎖毕源,拿到之后浪漠,再判斷之前保存該頁面時的線程是否已經(jīng)執(zhí)行完成,如果沒有執(zhí)行完就稍微等一會霎褐,如果實在等不到址愿,則在緩存在備份一份數(shù)據(jù)。
Long updatePageInfo(PageInfo pageInfo) throws Exception {
? ? ? ? Long pageId = pageInfo.getId();
? ? ? ? if(lockService.lock(lock, expire)) {
????????????????updateMenuInfo(pageInfo);
????????????????deletePageInfo(pageId);//刪除頁面配置必須放在更新導(dǎo)航之后冻璃,如有疑問可留言
? ????????? ????updatePageInfo(pageId, pageInfo);?
? ? ? ? ? ? ? ? tryBackups(lock, pageInfo);
? ? ? ? ? ??? ??lockService.hardUnlock(lock);
????????}
}
四响谓、?tryBackups(String lock, PageInfo pageInfo)
嘗試備份,如果線程執(zhí)行完成則直接返回true省艳。若線程尚未執(zhí)行完娘纷,則等待幾秒(這個方法可以拋出一個線程去執(zhí)行),如果線程在規(guī)定等待時間內(nèi)一直未能完成跋炕,則進(jìn)行備份赖晶。
public void tryBackups(String lock, PageInfo pageInfo) {
? ? ? ? int count =100;
? ? ? ? while (!lockService.softUnlockAuto(lock) && count >0) {
????????????count--;
? ? ? ? ? ? Thread.sleep(200);
? ? ? ? }
? ? ? ?if(!lockService.softUnlockAuto(lock)) {
? ? ? ? ? ? do:在緩存中做備份,可設(shè)定過期時間為1天
? ??????}
}
五辐烂、deletePageInfo(final Long pageId)
刪除頁面遏插,以多線程的方式刪除,并記錄線程的狀態(tài)
void deletePageInfo(final Long pageId) throws Exception {
????final String lock = String.valueOf(pageId);
????List<ChartInfo> chartInfos = getChartInfos(pageId);
????for (ChartInfo chartInfo : chartInfos) {
????????fixedThreadPool.execute(new Runnable() {
????????????@Override
????????????public void run() {
????????????????autoIncrLock(lock);?
????????????????deleteChartInfo(pageId);
????????????????autoDecrLock(lock);
? ? ? ?}}}}
? ? deletePageInfo(pageId);
}
六纠修、updatePageInfo(final Long pageId, PageInfo pageInfo)
保存頁面中的圖表信息胳嘲,以多線程的方式執(zhí)行,并記錄線程的狀態(tài)
void updatePageInfo(final Long pageId, PageInfo pageInfo) throws Exception{
? ? if(pageId != null){
? ? ? ? final String lock = String.valueOf(pageId);
? ? ? ? if(CollectionUtils.isNotEmpty(pageInfo)){
? ? ? ? ? ? for(final CharInfo chartInfo : pageInfo){
????????????????fixedThreadPool.execute(new Runnable() {? ??????????
????????????????????@Override
? ? ????????????????public void run() {
????????????????????????????????autoIncrLock(lock);?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? updateChartInfo(pageId);?
????????????????????????????????autoDecrLock(lock);? ? ? ? ? ? ? ? ? ? ? ? ??
? ??}}}}}}
問題總結(jié):
分布式鎖在使用過程中需要特別注意不同鎖之前的嵌套扣草,防止產(chǎn)生死鎖了牛。
多線程在使用過程中,要注意并發(fā)是否對線程執(zhí)行狀態(tài)有影響辰妙。
上述提到的實例中鹰祸,鎖的粒度一共有3種。
第一種為站點級別的鎖上岗,防止導(dǎo)航菜單并發(fā)帶來的問題福荸。
第二種為頁面級別的鎖,防止頁面并發(fā)帶來的問題肴掷。
第三類是線程級別的鎖敬锐,主要是為了多線程的情況下,存在未完成線程帶來的問題呆瞻。
其中台夺,線程的鎖最容易產(chǎn)生問題,因此增加了簡單的容錯機制痴脾,當(dāng)線程的鎖不能釋放時颤介,做了相應(yīng)的等待以及備份處理。頁面更新時,頁面被鎖滚朵,其他任何進(jìn)程均不允許修改以及獲取該頁面的信息冤灾,以免獲取的信息不完整以及相互覆蓋的問題。當(dāng)頁面被讀取時(此處應(yīng)該做一下調(diào)整辕近,頁面讀取時韵吨,應(yīng)該允許其他線程讀取,但是不允許更新)