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

来源: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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...