高併發核心技術 - 冪等性 與 分散式鎖

来源:https://www.cnblogs.com/ourtest/archive/2019/07/09/11156906.html
-Advertisement-
Play Games

1. 什麼是冪等性 冪等性就是指:一個冪等操作任其執行多次所產生的影響均與一次執行的影響相同。用數學的概念表達是這樣的: f(f(x)) = f(x).就像 nx1 = n 一樣, x1 就是一個冪等操作。無論是乘以多少次結果都一樣。 2. 常見的冪等性問題 冪等性問題經常會是由網路問題引起的,還有 ...


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);
    }
}

 

看上去好像沒有什麼問題,保存點贊之前已經檢查過是否點贊了,理論上同一個人不會對同一篇文章重覆點贊。但實際不是這樣的。因為網路請求不是排隊進來的,而是一窩蜂涌進來的。

某些時候,用戶網路不好,可能很短的時間內點擊了多次,由於網路傳輸問題,這些請求可能會同時來到我們的伺服器。

  • 第一個請求 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);
    }
}

 

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);
}

 

利用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);
}

 

key 的設計也很講究:
數據不衝突的兩個業務場景,key不能衝突,不同人的key也不一樣,不同的文章Key也不一樣。
根據場景業務設定。
一個原則: 儘可能的縮小key的範圍。 這樣才能增強我們的併發。
首先我們先獲取鎖,獲取鎖成功 執行完操作,保存數據 ,刪除鎖。獲取不到鎖返回失敗。設置過期時間是為了防止‘死鎖’,比如機器獲取到了 鎖,沒有設置過期時間,但是他死機了,沒有刪除釋放鎖。

  • 版本號控制CAS 演算法: CAS有3個操作數,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。這個比較繁雜,有興趣的大家可以去看看。

大家可以加我的程式員交流群:790047143,群內有阿裡,京東等技術大牛講解的最新Java架構技術。作為給廣大朋友的加群福利——分散式(Dubbo、Redis、RabbitMQ、Netty、RPC、Zookeeper、高併發、高可用架構)/微服務(Spring Boot、Spring Cloud)/源碼(Spring、Mybatis)/性能優化(JVM、TomCat、MySQL)【加群備註好消息領取最新面試資料】


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 事件 事件綁定方式 常用事件 事件冒泡 阻止後續事件發生 事件委托 頁面載入和window.onload ...
  • 近幾年來,隨著 HTML5、JS 的流行,前端這個職業火熱了起來!不少人發出疑惑,前端以後還會更有前途嗎? 我只能告訴你:前端不滅 現在都明白了用戶體驗至上,還要用著舒服 後端提供床,前端提供顏值高的妹,你說重要嗎? 我來簡單的介紹下web前端開發的3個疑問 {“ web前端開發 ”是什麼? } { ...
  • 1、如果想保持容器能夠滾動,同時不想看到醜陋的滾動條,chrome、firefox和移動端上不考慮相容性直接 element::-webkit-scrollbar{ display:none } 2、移動端卡頓,加一個屬性就能解決了 element{ -webkit-overflow-scrolli ...
  • 1.核心功能 此組件功能包含: 圖片裁剪(裁剪框拖動,裁剪框改變大小); 圖片馬賽克(繪製馬賽克,清除馬賽克); 圖片預覽、圖片還原(返回原圖、返回處理圖); 圖片上傳(獲取簽名、上傳圖片)。 2.核心邏輯 2.1圖片裁剪 獲取裁剪框(矩形)相對於畫布的位置(左上)和裁剪框的height、width ...
  • 工廠模式是使用頻率很高的一種設計模式,在面試中也經常問到,今天我們就來學習它。 為什麼要用工廠模式? 解答這個問題前,我們先來瞭解什麼是工廠模式。 工廠模式其實也稱創建模式,是用於創建對象的一種方式。本質上就是用工廠方法來代替new實例化對象。 舉個例子:我們在編寫代碼的時候,在一個A類中通過new ...
  • SpringCloud系列教程 | 第十篇:服務網關Zuul高級篇 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如無特殊說明,本系列教程全採用以上版本 上一篇我們主要聊到了Zuul的使用方式,以及自動轉發機制,其實Zuul還有更多的使用姿 ...
  • 架構雜談《一》 從傳統單體架構到服務化架構的發展歷程 典型的單體架構分為三個層級,Web層、業務邏輯層和數據存儲層,每個層的指責分別如下: Web 層:負責與用戶交互或者對外提供介面 業務邏輯層:為了實現業務邏輯而設計的流程處理模塊 數據存儲層:將業務邏輯層處理的結果持久化 將不同的模塊化組件聚合後 ...
  • (詳細)高校宿舍管理系統需求分析說明書(文末-->獲取原文檔) (詳細)高校宿舍管理系統需求分析說明書(文末-->獲取原文檔) 版本狀態 版本 作者 參與者 起止日期 註釋 審閱者 團隊 版本 日期 簽名 教學管理委員會 V1.1 2019.06.13 胡桂虹 教學管理委員會 V1.2 2019.0 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...