高併發核心技術 - 冪等性 與 分散式鎖
1. 什麼是冪等性
冪等性就是指:一個冪等操作任其執行多次所產生的影響均與一次執行的影響相同。用數學的概念表達是這樣的: f(f(x)) = f(x).就像 nx1 = n 一樣, x1 就是一個冪等操作。無論是乘以多少次結果都一樣。
2. 常見的冪等性問題
冪等性問題經常會是由網路問題引起的,還有重複操作引起的。
場景一:比如點贊功能,一個使用者只能對同一片文章點贊一次,重複點贊提示已經點過讚了。
示例程式碼:
public void like(Article article,User user) { //檢查是否點過贊 if (checkIsLike(article,user)) { //點過讚了 throw new ApiException(CodeEnums.SYSTEM_ERR); } else { //儲存點贊 saveLike(article,user); } } </pre>
看上去好像沒有什麼問題,儲存點贊之前已經檢查過是否點讚了,理論上同一個人不會對同一篇文章重複點贊。但實際不是這樣的。因為網路請求不是排隊進來的,而是一窩蜂湧進來的。
某些時候,使用者網路不好,可能很短的時間內點選了多次,由於網路傳輸問題,這些請求可能會同時來到我們的伺服器。
-
第一個請求 checkIsLike() 返回 false , 正在執行 saveLike() 操作,還沒來的及提交事務
-
第二個請求過來了 ,checkIsLike() 返回 也是 false , 並去 執行了 saveLike() 操作
這樣子,就造成了一個使用者同時對一篇文章進行了多次點贊操作。
這就是典型的冪等性問題, 操作了一次和操作了兩次結果不一樣,因為你多點了一次贊,按照冪等性原則 不管你點選了多少次結果都一樣,只點了一次贊。
很多場景都是這樣造成的,比如使用者重複下單,重複評論,重複提交表單等。
那怎麼解決呢?假設網路的請求是排隊進來的就不會出現這個問題了。
於是我們可以改成這樣:
public synchronized void like(Article article,User user) { //檢查是否點過贊 if (checkIsLike(article,user)) { //點過讚了 throw new ApiException(CodeEnums.SYSTEM_ERR); } else { //儲存點贊 saveLike(article,user); } } </pre>
synchronized 同步鎖 這樣我們的請求就會乖乖的排隊進來了。
PS :這樣做是效率比較低的做法,不建議這麼做,只是舉例子,synchronized 也不適合分散式叢集場景。
場景二 : 第三方回撥
我們系統經常需要和第三方系統打交道,比如微信充值,支付寶充值什麼的,微信和支付寶常常會以回撥你的介面通知你支付結果。為了保證你能收到回撥,往往可能會回撥多次。
有時候我們也為了保證資料的準確性會有個定時器去查詢支付結果未知的流水,並執行響應的處理。
如果定時器的輪訓和回撥剛好是在同時進行,這可能又出BUG了,又進行了兩次重複操作。
那麼問題來了:假設我是一個充值操作, 回撥回來的時候 ,會做業務處理,成功了給使用者賬戶加錢。這是後就要保證冪等性了, 假設微信同一筆交易給你回撥了兩次,如果你給使用者充值了兩次,這顯然不合理(我是老闆肯定扣你工資),所以要保證 不管微信回撥你多少次 ,同一筆交易你只能給使用者充一次錢。這就冪等性。
解決冪等性問題方案
-
synchronized 適合單機應用,不追求效能 ,不追求併發。
-
分散式鎖 但是往往我們的應用是分散式的叢集,並且很講究效能,併發,所以我們需要用到 分散式鎖 來解決這個問題。
Redis 分散式鎖:
/** * setNx * * @param key * @param value * @return*/public Boolean setNx(String key,Object value) { return redisTemplate.opsForValue().setIfAbsent(key,value); }/** * @param key 鎖 * @param waitTime 等待時間 毫秒 * @param expireTime 超時時間 毫秒 * @return*/public Boolean lock(String key,long waitTime,long expireTime) { String vlaue = UUIDUtil.mongoObjectId(); Boolean flag = setNx(key,vlaue); //嘗試獲取鎖 成功返回 if (flag) { redisTemplate.expire(key,expireTime,TimeUnit.MILLISECONDS); return flag; } else { //失敗 //現在時間 long newTime = System.currentTimeMillis(); //等待過期時間 long loseTime = newTime + waitTime; //不斷嘗試獲取鎖成功返回 while (System.currentTimeMillis() < loseTime) { Boolean testFlag = setNx(key,vlaue); if (testFlag) { redisTemplate.expire(key,expireTime,TimeUnit.MILLISECONDS); return testFlag; } //休眠100毫秒 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } return false; }/** * @param key * @return*/public Boolean lock(String key) { return lock(key,1000L,60 * 1000L); }/** * @param key */public void unLock(String key) { remove(key); } </pre>
利用Redis 分散式鎖 我們的程式碼可以改成這樣:
public void like(Article article,User user) { String key = "key:like" + article.getId() + ":" + user.getUserId(); // 等待鎖的時間 0 , 過期時間 一分鐘防止死鎖 Boolean flag = redisService.lock(key,0,60 * 1000L); if(!flag) { //獲取鎖失敗 說明前面的請求已經獲取了鎖 throw new ApiException(CodeEnums.SYSTEM_ERR); } //檢查是否點過贊 if (checkIsLike(article,user)) { //點過讚了 throw new ApiException(CodeEnums.SYSTEM_ERR); } else { //儲存點贊 saveLike(article,user); } //刪除鎖 redisService.unLock(key); } </pre>
key 的設計也很講究:
資料不衝突的兩個業務場景,key不能衝突,不同人的key也不一樣,不同的文章Key也不一樣。
根據場景業務設定。
一個原則: 儘可能的縮小key的範圍
。 這樣才能增強我們的併發。
首先我們先獲取鎖,獲取鎖成功 執行完操作,儲存資料 ,刪除鎖。獲取不到鎖返回失敗。設定過期時間是為了防止‘死鎖’,比如機器獲取到了 鎖,沒有設定過期時間,但是他當機了,沒有刪除釋放鎖。
-
版本號控制CAS 演算法: CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。這個比較繁雜,有興趣的大家可以去看看。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69912582/viewspace-2649908/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 高併發的核心技術-冪等的實現方案
- 分散式鎖不是控制併發冪等的方式分散式
- 關於高併發和分散式中的冪等處理分散式
- 分散式之介面冪等性分散式
- 架構與思維:高併發下冪等性解決方案架構
- 高併發下如何保證介面的冪等性?
- 高併發下的介面冪等性解決方案!
- 我們來談下高併發和分散式中的冪等處理分散式
- 騰訊二面:如何保證介面冪等性?高併發下的介面冪等性如何實現?
- 分散式系統中介面的冪等性分散式
- 高併發實戰之冪等處理
- 「分散式技術專題」併發系列一:基於加鎖的併發控制分散式
- [分散式][高併發]高併發架構分散式架構
- 分散式鎖--高併發優化實踐(分段加鎖思想)!分散式優化
- 【高併發】之分散式全域性唯一 ID分散式
- 什麼是分散式系統中的冪等性分散式
- 面試集錦(八)分散式與高併發面試分散式
- 高併發技術
- 什麼是qps,tps,併發量,pv,uv、介面冪等性、悲觀鎖樂觀鎖
- 深入理解冪等技術
- 技術分享 | Redis 之分散式鎖Redis分散式
- 多執行緒與併發-----Lock鎖技術執行緒
- 開源:Taurus.Idempotent 分散式冪等性鎖框架,支援 .Net 和 .Net Core 雙系列版本IDE分散式框架
- 「分散式技術專題」資料切分與合併分散式
- 分散式叢集與多執行緒高併發分散式執行緒
- Mysql核心技術:用NOSql給高併發系統加速MySql
- 高併發的核心技術 - 訊息中介軟體(MQ)MQ
- 從零開始的高併發(二)--- Zookeeper實現分散式鎖分散式
- 設計一個支援高併發的分散式鎖,商品維度分散式
- 高併發架構系列:分散式鎖的由來、特點及Redis分散式鎖的實現詳解架構分散式Redis
- 高併發(鎖)
- 「分散式技術專題」併發系列三:樂觀併發控制之原型系統(分散式驗證)分散式原型
- 併發技術4:讀寫鎖
- 分散式鎖(1):Java 常用技術方案分散式Java
- 用分散式鎖解決併發問題分散式
- 「分散式技術專題」併發系列二:基於時間的併發控制分散式
- 「分散式技術專題」併發系列三:樂觀併發控制之理論研究分散式
- ZooKeeper 分散式鎖 Curator 原始碼 03:可重入鎖併發加鎖分散式原始碼