www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]作者:快應(yīng)用服務(wù)器研發(fā)團(tuán)隊(duì)-LinYupan一、業(yè)務(wù)背景許多面向用戶(hù)的互聯(lián)網(wǎng)業(yè)務(wù)都會(huì)在系統(tǒng)后端維護(hù)一份用戶(hù)數(shù)據(jù),快應(yīng)用中心業(yè)務(wù)也同樣做了這件事??鞈?yīng)用中心允許用戶(hù)對(duì)快應(yīng)用進(jìn)行收藏,并在服務(wù)端記錄了用戶(hù)的收藏列表,通過(guò)用戶(hù)賬號(hào)標(biāo)識(shí)OpenID來(lái)關(guān)聯(lián)收藏的快應(yīng)用包名。為了使用戶(hù)在快應(yīng)...

作者:快應(yīng)用服務(wù)器研發(fā)團(tuán)隊(duì)-Lin Yupan

一、業(yè)務(wù)背景


許多面向用戶(hù)的互聯(lián)網(wǎng)業(yè)務(wù)都會(huì)在系統(tǒng)后端維護(hù)一份用戶(hù)數(shù)據(jù),快應(yīng)用中心業(yè)務(wù)也同樣做了這件事。快應(yīng)用中心允許用戶(hù)對(duì)快應(yīng)用進(jìn)行收藏,并在服務(wù)端記錄了用戶(hù)的收藏列表,通過(guò)用戶(hù)賬號(hào)標(biāo)識(shí)OpenID來(lái)關(guān)聯(lián)收藏的快應(yīng)用包名。


為了使用戶(hù)在快應(yīng)用中心的收藏列表能夠與快應(yīng)用Menubar的收藏狀態(tài)打通,我們同時(shí)也記錄了用戶(hù)賬號(hào)標(biāo)識(shí)OpenID與客戶(hù)端本地標(biāo)識(shí)local_identifier的綁定關(guān)系。因?yàn)榭鞈?yīng)用Manubar由快應(yīng)用引擎持有,獨(dú)立于快應(yīng)用中心外,無(wú)法通過(guò)賬號(hào)體系獲取到用戶(hù)賬號(hào)標(biāo)識(shí),只能獲取到客戶(hù)端本地標(biāo)識(shí)local_identifier,所以我們只能通過(guò)二者的映射關(guān)系來(lái)保持狀態(tài)同步。


靈活運(yùn)用分布式鎖解決數(shù)據(jù)重復(fù)插入問(wèn)題


在具體實(shí)現(xiàn)上,我們是在用戶(hù)啟動(dòng)快應(yīng)用中心的時(shí)候觸發(fā)一次同步操作,由客戶(hù)端將OpenID和客戶(hù)端本地標(biāo)識(shí)提交到服務(wù)端進(jìn)行綁定。服務(wù)端的綁定邏輯是:判斷OpenID是否已經(jīng)存在,如果不存在則插入數(shù)據(jù)庫(kù),否則更新對(duì)應(yīng)數(shù)據(jù)行的local_identifier字段(因?yàn)橛脩?hù)可能先后在兩個(gè)不同的手機(jī)上登錄同一個(gè)vivo賬號(hào))。在后續(xù)的業(yè)務(wù)流程中,我們就可以根據(jù)OpenID查詢(xún)對(duì)應(yīng)的local_identifier,反之亦可。


靈活運(yùn)用分布式鎖解決數(shù)據(jù)重復(fù)插入問(wèn)題

靈活運(yùn)用分布式鎖解決數(shù)據(jù)重復(fù)插入問(wèn)題


但是代碼上線(xiàn)一段時(shí)間后,我們發(fā)現(xiàn)t_account數(shù)據(jù)表中居然存在許多重復(fù)的OpenID記錄。根據(jù)如上所述的綁定邏輯,這種情況理論上是不應(yīng)該發(fā)生的。所幸這些重復(fù)數(shù)據(jù)并沒(méi)有對(duì)更新和查詢(xún)的場(chǎng)景造成影響,因?yàn)樵诓樵?xún)的SQL中我們加入了LIMIT 1的限制,因此針對(duì)一個(gè)OpenID的更新和查詢(xún)操作實(shí)際上都只作用于ID最小的那條記錄。


二、問(wèn)題分析與定位


雖然冗余數(shù)據(jù)沒(méi)有對(duì)實(shí)際業(yè)務(wù)造成影響,但是這種明顯的數(shù)據(jù)問(wèn)題也肯定是不能容忍的。因此我們開(kāi)始著手排查問(wèn)題。


首先想到的就是從數(shù)據(jù)本身入手。先通過(guò)對(duì)t_account表數(shù)據(jù)進(jìn)行粗略觀(guān)察,發(fā)現(xiàn)大約有3%的OpenID會(huì)存在重復(fù)的情況。也就是說(shuō)重復(fù)插入的情況是偶現(xiàn)的,大多數(shù)請(qǐng)求的處理都是按照預(yù)期被正確處理了。我們對(duì)代碼重新進(jìn)行了走讀,確認(rèn)了代碼在實(shí)現(xiàn)上確實(shí)不存在什么明顯的邏輯錯(cuò)誤。


我們進(jìn)一步對(duì)數(shù)據(jù)進(jìn)行細(xì)致觀(guān)察。我們挑選了幾個(gè)出現(xiàn)重復(fù)情況的OpenID,將相關(guān)的數(shù)據(jù)記錄查詢(xún)出來(lái),發(fā)現(xiàn)這些OpenID重復(fù)的次數(shù)也不盡相同,有的只重復(fù)一次,有的則更多。但是,這時(shí)候我們發(fā)現(xiàn)了一個(gè)更有價(jià)值的信息——這些相同OpenID的數(shù)據(jù)行的創(chuàng)建時(shí)間都是完全相同的,而且自增ID是連續(xù)的。


靈活運(yùn)用分布式鎖解決數(shù)據(jù)重復(fù)插入問(wèn)題


于是,我們猜測(cè)問(wèn)題的產(chǎn)生應(yīng)該是由于并發(fā)請(qǐng)求造成的!我們模擬了客戶(hù)端對(duì)接口的并發(fā)調(diào)用,確實(shí)出現(xiàn)了重復(fù)插入數(shù)據(jù)的現(xiàn)象,進(jìn)一步證實(shí)了這個(gè)猜測(cè)的合理性。但是,明明客戶(hù)端的邏輯是每個(gè)用戶(hù)在啟動(dòng)的時(shí)候進(jìn)行一次同步,為什么會(huì)出現(xiàn)同一個(gè)OpenID并發(fā)請(qǐng)求呢?


事實(shí)上,代碼的實(shí)際運(yùn)行并不如我們想象中的那么理想,計(jì)算機(jī)的運(yùn)行過(guò)程中往往存在一些不穩(wěn)定的因素,比如網(wǎng)絡(luò)環(huán)境、服務(wù)器的負(fù)載情況。而這些不穩(wěn)定因素就可能導(dǎo)致客戶(hù)端發(fā)送請(qǐng)求失敗,這里的“失敗”可能并不意味著真正的失敗,而是可能整個(gè)請(qǐng)求時(shí)間過(guò)長(zhǎng),超過(guò)了客戶(hù)端設(shè)定的超時(shí)時(shí)間,從而被人為地判定為失敗,于是通過(guò)重試機(jī)制再次發(fā)送請(qǐng)求。那么最終就可能導(dǎo)致同樣的請(qǐng)求被提交了多次,而且這些請(qǐng)求也許在中間某個(gè)環(huán)節(jié)被阻塞了(比如當(dāng)服務(wù)器的處理線(xiàn)程負(fù)載過(guò)大,來(lái)不及處理請(qǐng)求,請(qǐng)求進(jìn)入了緩沖隊(duì)列),當(dāng)阻塞緩解后這幾個(gè)請(qǐng)求就可能在很短的時(shí)間內(nèi)被并發(fā)處理了。


這其實(shí)是一個(gè)典型的并發(fā)沖突問(wèn)題,可以把這個(gè)問(wèn)題簡(jiǎn)單抽象為:如何避免并發(fā)情況下寫(xiě)入重復(fù)數(shù)據(jù)。事實(shí)上,有很多常見(jiàn)的業(yè)務(wù)場(chǎng)景都可能面臨這個(gè)問(wèn)題,比如用戶(hù)注冊(cè)時(shí)不允許使用相同的用戶(hù)名。


一般來(lái)說(shuō),我們?cè)谔幚磉@類(lèi)問(wèn)題時(shí),最直觀(guān)的方式就是先進(jìn)行一次查詢(xún),當(dāng)判斷數(shù)據(jù)庫(kù)中不存在當(dāng)前數(shù)據(jù)時(shí)才允許插入。


靈活運(yùn)用分布式鎖解決數(shù)據(jù)重復(fù)插入問(wèn)題


顯然,這個(gè)流程從單個(gè)請(qǐng)求的角度來(lái)看是沒(méi)有問(wèn)題的。但是當(dāng)多個(gè)請(qǐng)求并發(fā)時(shí),請(qǐng)求A和請(qǐng)求B都先發(fā)起一次查詢(xún),并且都得到結(jié)果是不存在,于是兩者都又執(zhí)行了數(shù)據(jù)插入,最終導(dǎo)致并發(fā)沖突。


靈活運(yùn)用分布式鎖解決數(shù)據(jù)重復(fù)插入問(wèn)題


三、探索可行的方案


既然問(wèn)題定位到了,接下來(lái)就要開(kāi)始尋求解決方案了。面對(duì)這種情況,我們通常有兩種選擇,一種是讓數(shù)據(jù)庫(kù)來(lái)解決,另一種是由應(yīng)用程序來(lái)解決。


3.1 數(shù)據(jù)庫(kù)層面處理——唯一索引


當(dāng)使用MySQL數(shù)據(jù)庫(kù)及InnoDB存儲(chǔ)引擎時(shí),我們可以利用唯一索引來(lái)保障同一個(gè)列的值具有唯一性。顯然,在t_account這張表中,我們最開(kāi)始是沒(méi)有為open_id列創(chuàng)建唯一索引的。如果我們想要此時(shí)加上唯一索引的話(huà),可以利用下列的ALTER TABLE語(yǔ)句。

ALTER TABLE t_account ADD UNIQUE uk_open_id( open_id );

一旦為open_id列加上唯一索引后,當(dāng)上述并發(fā)情況發(fā)生時(shí),請(qǐng)求A和請(qǐng)求B中必然有一者會(huì)優(yōu)先完成數(shù)據(jù)的插入操作,而另一者則會(huì)得到類(lèi)似錯(cuò)誤。因此,最終保證t_account表中只有一條openid=xxx的記錄存在。

Error Code: 1062. Duplicate entry 'xxx' for key 'uk_open_id'

3.2 應(yīng)用程序?qū)用嫣幚怼?a href="/tags/分布式" target="_blank">分布式鎖


另一種解決的思路是我們不依賴(lài)底層的數(shù)據(jù)庫(kù)來(lái)為我們提供唯一性的保障,而是靠應(yīng)用程序自身的代碼邏輯來(lái)避免并發(fā)沖突。應(yīng)用層的保障其實(shí)是一種更具通用性的方案,畢竟我們不能假設(shè)所有系統(tǒng)使用的數(shù)據(jù)持久化組件都具備數(shù)據(jù)唯一性檢測(cè)的能力。


那具體怎么做呢?簡(jiǎn)單來(lái)說(shuō),就是化并行為串行。之所以我們會(huì)遇到重復(fù)插入數(shù)據(jù)的問(wèn)題,是因?yàn)椤皺z測(cè)數(shù)據(jù)是否已經(jīng)存在”和“插入數(shù)據(jù)”兩個(gè)動(dòng)作被分割開(kāi)來(lái)。由于這兩個(gè)步驟不具備原子性,才導(dǎo)致兩個(gè)不同的請(qǐng)求可以同時(shí)通過(guò)第一步的檢測(cè)。如果我們能夠把這兩個(gè)動(dòng)作合并為一個(gè)原子操作,就可以避免數(shù)據(jù)沖突了。這時(shí)候我們就需要通過(guò)加鎖,來(lái)實(shí)現(xiàn)這個(gè)代碼塊的原子性。


靈活運(yùn)用分布式鎖解決數(shù)據(jù)重復(fù)插入問(wèn)題


對(duì)于Java語(yǔ)言,大家最熟悉的鎖機(jī)制就是synchronized關(guān)鍵字了。

public synchronized void submit(String openId, String localIdentifier){ Account account = accountDao.find(openId); if (account == null) { // insert } else { // update }}

但是,事情可沒(méi)這么簡(jiǎn)單。要知道,我們的程序可不是只部署在一臺(tái)服務(wù)器上,而是部署了多個(gè)節(jié)點(diǎn)。也就是說(shuō)這里的并發(fā)不僅僅是線(xiàn)程間的并發(fā),而是進(jìn)程間的并發(fā)。因此,我們無(wú)法通過(guò)java語(yǔ)言層面的鎖機(jī)制來(lái)解決這個(gè)同步問(wèn)題,我們這里需要的應(yīng)該是分布式鎖。


3.3 兩種解決方案的權(quán)衡


基于以上的分析,看上去兩種方案都是可行的,但最終我們選擇了分布式鎖的方案。為什么明明第一種方案只需要簡(jiǎn)單地加個(gè)索引,我們卻不采用呢?


因?yàn)楝F(xiàn)有的線(xiàn)上數(shù)據(jù)已然在open_id列上存在重復(fù)數(shù)據(jù),如果此時(shí)直接去加唯一索引是無(wú)法成功的。為了加上唯一索引,我們必須首先將已有的重復(fù)數(shù)據(jù)先進(jìn)行清理。但是問(wèn)題又來(lái)了,線(xiàn)上的程序一直持續(xù)運(yùn)行著,重復(fù)數(shù)據(jù)可能會(huì)源源不斷地產(chǎn)生。那我們能不能找一個(gè)用戶(hù)請(qǐng)求不活躍的時(shí)間段去進(jìn)行清理,并在新的重復(fù)數(shù)據(jù)插入之前完成唯一索引的建立?答案當(dāng)然是肯定的,只不過(guò)這種方案需要運(yùn)維、DBA、開(kāi)發(fā)多方協(xié)同處理,而且由于業(yè)務(wù)特性,最合適的處理時(shí)間段應(yīng)該是凌晨這種夜深人靜的時(shí)候。即便是采取這么苛刻的修復(fù)措施,也不能百分之百完全保證數(shù)據(jù)清理完成到索引建立之間不會(huì)有新的重復(fù)數(shù)據(jù)插入。因此,基于唯一索引的修復(fù)方案乍看之下非常合適,但是具體操作起來(lái)還是略為麻煩。


事實(shí)上,建立唯一索引最合適的契機(jī)應(yīng)該是在系統(tǒng)最初的設(shè)計(jì)階段,這樣就能有效避免重復(fù)數(shù)據(jù)的問(wèn)題。然而木已成舟,在當(dāng)前這個(gè)情景下,我們還是選擇了可操作性更強(qiáng)的分布式鎖方案。因?yàn)檫x擇這個(gè)方案的話(huà),我們可以先上線(xiàn)加入了分布式鎖修復(fù)的新代碼,阻斷新的重復(fù)數(shù)據(jù)插入,然后再對(duì)原有的重復(fù)數(shù)據(jù)執(zhí)行清理操作,這樣一來(lái)只需要修改代碼并一次上線(xiàn)即可。當(dāng)然,待問(wèn)題徹底解決之后,我們可以重新再考慮為數(shù)據(jù)表加上唯一索引。


那么接下來(lái),我們就來(lái)看看基于分布式鎖的方案如何實(shí)現(xiàn)。首先我們先來(lái)回顧一下分布式鎖的相關(guān)知識(shí)。


四、分布式鎖概述


4.1 分布式鎖需要具備哪些特性?


  • 在分布式系統(tǒng)環(huán)境下,同一時(shí)間只有一臺(tái)機(jī)器的一個(gè)線(xiàn)程可以獲取到鎖;

  • 高可用的獲取鎖與釋放鎖;

  • 高性能的獲取鎖與釋放鎖;

  • 具備可重入特性;

  • 具備鎖失效機(jī)制,防止死鎖;

  • 具備阻塞/非阻塞鎖特性。


4.2 分布式鎖有哪些實(shí)現(xiàn)方式?


分布式鎖實(shí)現(xiàn)主要有如下三種:

  • 基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖;

  • 基于Zookeeper實(shí)現(xiàn)分布式鎖;

  • 基于Redis實(shí)現(xiàn)分布式鎖;


4.2.1 基于數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方式


基于數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方式就是直接創(chuàng)建一張鎖表,通過(guò)操作表數(shù)據(jù)來(lái)實(shí)現(xiàn)加鎖、解鎖。以MySQL數(shù)據(jù)庫(kù)為例,我們可以創(chuàng)建這樣一張表,并且對(duì)method_name進(jìn)行加上唯一索引的約束:

CREATE TABLE `myLock` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `method_name` varchar(100) NOT NULL DEFAULT '' COMMENT '鎖定的方法名', `value` varchar(1024) NOT NULL DEFAULT '鎖信息', PRIMARY KEY (`id`), UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='鎖定中的方法';

然后,我們就可以通過(guò)插入數(shù)據(jù)和刪除數(shù)據(jù)的方式來(lái)實(shí)現(xiàn)加鎖和解鎖:

#加鎖insert into myLock(method_name, value) values ('m1', '1'); #解鎖delete from myLock where method_name ='m1';

基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)的方式雖然簡(jiǎn)單,但是存在一些明顯的問(wèn)題:

  • 沒(méi)有鎖失效時(shí)間,如果解鎖失敗,就會(huì)導(dǎo)致鎖記錄永遠(yuǎn)留在數(shù)據(jù)庫(kù)中,造成死鎖。

  • 該鎖不可重入,因?yàn)樗徽J(rèn)識(shí)請(qǐng)求方是不是當(dāng)前占用鎖的線(xiàn)程。

  • 當(dāng)前數(shù)據(jù)庫(kù)是單點(diǎn),一旦宕機(jī),鎖機(jī)制就會(huì)完全崩壞。


4.2.2 基于Zookeeper的實(shí)現(xiàn)方式


ZooKeeper是一個(gè)為分布式應(yīng)用提供一致性服務(wù)的開(kāi)源組件,它內(nèi)部是一個(gè)分層的文件系統(tǒng)目錄樹(shù)結(jié)構(gòu),規(guī)定同一個(gè)目錄下的節(jié)點(diǎn)名稱(chēng)都是唯一的。


ZooKeeper的節(jié)點(diǎn)(Znode)有4種類(lèi)型:

  • 持久化節(jié)點(diǎn)(會(huì)話(huà)斷開(kāi)后節(jié)點(diǎn)還存在)

  • 持久化順序節(jié)點(diǎn)

  • 臨時(shí)節(jié)點(diǎn)(會(huì)話(huà)斷開(kāi)后節(jié)點(diǎn)就刪除了)

  • 臨時(shí)順序節(jié)點(diǎn)


當(dāng)一個(gè)新的Znode被創(chuàng)建為一個(gè)順序節(jié)點(diǎn)時(shí),ZooKeeper通過(guò)將10位的序列號(hào)附加到原始名稱(chēng)來(lái)設(shè)置Znode的路徑。例如,如果將具有路徑/mynode的Znode創(chuàng)建為順序節(jié)點(diǎn),則ZooKeeper會(huì)將路徑更改為/mynode0000000001,并將下一個(gè)序列號(hào)設(shè)置為0000000002,這個(gè)序列號(hào)由父節(jié)點(diǎn)維護(hù)。如果兩個(gè)順序節(jié)點(diǎn)是同時(shí)創(chuàng)建的,那么ZooKeeper不會(huì)對(duì)每個(gè)Znode使用相同的數(shù)字。


基于ZooKeeper的特性,可以按照如下方式來(lái)實(shí)現(xiàn)分布式鎖:


  • 創(chuàng)建一個(gè)目錄mylock;

  • 線(xiàn)程A想獲取鎖就在mylock目錄下創(chuàng)建臨時(shí)順序節(jié)點(diǎn);

  • 獲取mylock目錄下所有的子節(jié)點(diǎn),然后獲取比自己小的兄弟節(jié)點(diǎn),如果不存在,則說(shuō)明當(dāng)前線(xiàn)程順序號(hào)最小,獲得鎖;

  • 線(xiàn)程B獲取所有節(jié)點(diǎn),判斷自己不是最小節(jié)點(diǎn),設(shè)置監(jiān)聽(tīng)比自己次小的節(jié)點(diǎn);

  • 線(xiàn)程A處理完,刪除自己的節(jié)點(diǎn),線(xiàn)程B監(jiān)聽(tīng)到變更事件,判斷自己是不是最小的節(jié)點(diǎn),如果是則獲得鎖。


由于創(chuàng)建的是臨時(shí)節(jié)點(diǎn),當(dāng)持有鎖的線(xiàn)程意外宕機(jī)時(shí),鎖依然可以得到釋放,因此可以避免死鎖的問(wèn)題。另外,我們也可以通過(guò)節(jié)點(diǎn)排隊(duì)監(jiān)聽(tīng)機(jī)制實(shí)現(xiàn)阻塞特性,也可以通過(guò)在Znode中攜帶線(xiàn)程標(biāo)識(shí)來(lái)實(shí)現(xiàn)可重入鎖。同時(shí),由于ZooKeeper集群的高可用特性,分布式鎖的可用性也能夠得到保障。不過(guò),因?yàn)樾枰l繁的創(chuàng)建和刪除節(jié)點(diǎn),Zookeeper方式在性能上不如Redis方式。


4.2.3 基于Redis的實(shí)現(xiàn)方式


Redis是一個(gè)開(kāi)源的鍵值對(duì)(Key-Value)存儲(chǔ)數(shù)據(jù)庫(kù),其基于內(nèi)存實(shí)現(xiàn),性能非常高,常常被用作緩存。


基于Redis實(shí)現(xiàn)分布式鎖的核心原理是:嘗試對(duì)特定key進(jìn)行set操作,如果設(shè)置成功(key之前不存在)了,則相當(dāng)于獲取到鎖,同時(shí)對(duì)該key設(shè)置一個(gè)過(guò)期時(shí)間,避免線(xiàn)程在釋放鎖之前退出造成死鎖。線(xiàn)程執(zhí)行完同步任務(wù)后主動(dòng)釋放鎖則通過(guò)delete命令來(lái)完成。


這里需要特別注意的一點(diǎn)是如何加鎖并設(shè)置過(guò)期時(shí)間。有的人會(huì)使用setnx expire這兩個(gè)命令來(lái)實(shí)現(xiàn),但這是有問(wèn)題的。假設(shè)當(dāng)前線(xiàn)程執(zhí)行setnx獲得了鎖,但是在執(zhí)行expire之前宕機(jī)了,就會(huì)造成鎖無(wú)法被釋放。當(dāng)然,我們可以將兩個(gè)命令合并在一段lua腳本里,實(shí)現(xiàn)兩條命令的原子提交。


其實(shí),我們簡(jiǎn)單利用set命令可以直接在一條命令中實(shí)現(xiàn)setnx和設(shè)置過(guò)期時(shí)間,從而完成加鎖操作:

SET key value [EX seconds] [PX milliseconds] NX

解鎖操作只需要:

DEL key

五、基于Redis分布式鎖的解決方案


在本案例中,我們采用了基于Redis實(shí)現(xiàn)分布式鎖的方式。


5.1 分布式鎖的Java實(shí)現(xiàn)


由于項(xiàng)目采用了Jedis框架,而且線(xiàn)上Redis部署為集群模式,因此我們基于redis.clients.jedis.JedisCluster封裝了一個(gè)RedisLock類(lèi),提供加鎖與解鎖接口。

public?class?RedisLock?{ private static final String LOCK_SUCCESS = "OK"; private static final String LOCK_VALUE = "lock"; private static final int EXPIRE_SECONDS = 3; @Autowired protected JedisCluster jedisCluster; public boolean lock(String openId) { String redisKey = this.formatRedisKey(openId); String ok = jedisCluster.set(redisKey, LOCK_VALUE, "NX", "EX", EXPIRE_SECONDS); return LOCK_SUCCESS.equals(ok); } public void unlock(String openId) { String redisKey = this.formatRedisKey(openId); jedisCluster.del(redisKey); } private String formatRedisKey(String openId){ return "keyPrefix:" openId; }}

在具體實(shí)現(xiàn)上,我們?cè)O(shè)置了3秒鐘的過(guò)期時(shí)間,因?yàn)楸患渔i的任務(wù)是簡(jiǎn)單的數(shù)據(jù)庫(kù)查詢(xún)和插入,而且服務(wù)器與數(shù)據(jù)庫(kù)部署在同個(gè)機(jī)房,正常情況下3秒鐘已經(jīng)完全能夠足夠滿(mǎn)足代碼的執(zhí)行。


事實(shí)上,以上的實(shí)現(xiàn)是一個(gè)簡(jiǎn)陋版本的Redis分布式鎖,我們?cè)趯?shí)現(xiàn)中并沒(méi)有考慮線(xiàn)程的可重入性,也沒(méi)有考慮鎖被其他進(jìn)程誤釋放的問(wèn)題,但是它在這個(gè)業(yè)務(wù)場(chǎng)景下已經(jīng)能夠滿(mǎn)足我們的需求了。假設(shè)推廣到更為通用的業(yè)務(wù)場(chǎng)景,我們可以考慮在value中加入當(dāng)前進(jìn)程的特定標(biāo)識(shí),并在上鎖和釋放鎖的階段做相對(duì)應(yīng)的匹配檢測(cè),就可以得到一個(gè)更為安全可靠的Redis分布式鎖的實(shí)現(xiàn)了。


當(dāng)然,像Redission之類(lèi)的框架也提供了相當(dāng)完備的Redis分布式鎖的封裝實(shí)現(xiàn),在一些要求相對(duì)嚴(yán)苛的業(yè)務(wù)場(chǎng)景下,我建議直接使用這類(lèi)框架。由于本文側(cè)重于介紹排查及解決問(wèn)題的思路,因此沒(méi)有對(duì)Redisson分布式的具體實(shí)現(xiàn)原理做更多介紹,感興趣的小伙伴可以在網(wǎng)上找到非常豐富的資料。


5.2 改進(jìn)后的代碼邏輯


現(xiàn)在,我們可以利用封裝好的RedisLock來(lái)改進(jìn)原來(lái)的代碼了。

public?class?AccountService?{ @Autowired private RedisLock redisLock; public void submit(String openId, String localIdentifier) { if (!redisLock.lock(openId)) { // 如果相同openId并發(fā)情況下,線(xiàn)程沒(méi)有搶到鎖,則直接丟棄請(qǐng)求 return; } // 獲取到鎖,開(kāi)始執(zhí)行用戶(hù)數(shù)據(jù)同步邏輯 try { Account account = accountDao.find(openId); if (account == null) { // insert } else { // update } } finally { // 釋放鎖 redisLock.unlock(openId); } }}

5.3 數(shù)據(jù)清理


最后再簡(jiǎn)單說(shuō)一下收尾工作。由于重復(fù)數(shù)據(jù)的數(shù)據(jù)量較大,不太可能手工去慢慢處理。于是我們編寫(xiě)了一個(gè)定時(shí)任務(wù)類(lèi),每隔一分鐘執(zhí)行一次清理操作,每次清理1000個(gè)重復(fù)的OpenID,避免短時(shí)間內(nèi)大量查詢(xún)和刪除操作對(duì)數(shù)據(jù)庫(kù)性能造成影響。當(dāng)確認(rèn)重復(fù)數(shù)據(jù)已經(jīng)完全清理完畢后就停掉定時(shí)任務(wù)的調(diào)度,并在下一次版本迭代中將此代碼移除。


六、總結(jié)


在日常開(kāi)發(fā)過(guò)程中難免會(huì)各種各樣的問(wèn)題,我們要學(xué)會(huì)順藤摸瓜逐步分析,找到問(wèn)題的根因;然后在自己的認(rèn)知范圍內(nèi)盡量去尋找可行的解決方案,并且仔細(xì)權(quán)衡各種方案的利弊,才能最終高效地解決問(wèn)題。

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀(guān)點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專(zhuān)欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車(chē)的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車(chē)技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車(chē)工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車(chē)。 SODA V工具的開(kāi)發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車(chē) 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來(lái)越多用戶(hù)希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來(lái)越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開(kāi)幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱(chēng),數(shù)字世界的話(huà)語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱(chēng)"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉