高并發(fā)核心技術(shù)-冪等性與分布式鎖

高并發(fā)核心技術(shù)之 - 冪等性

1. 什么是冪等性

冪等性就是指:一個冪等操作任其執(zhí)行多次所產(chǎn)生的影響均與一次執(zhí)行的影響相同。
用數(shù)學(xué)的概念表達是這樣的: f(f(x)) = f(x).
就像 nx1 = n 一樣, x1 就是一個冪等操作。無論是乘以多少次結(jié)果都一樣。

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、微信小程序定制開發(fā)、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了琿春免費建站歡迎大家使用!

2. 常見的冪等性問題

冪等性問題經(jīng)常會是由網(wǎng)絡(luò)問題引起的,還有重復(fù)操作引起的。

場景一:比如點贊功能,一個用戶只能對同一片文章點贊一次,重復(fù)點贊提示已經(jīng)點過贊了。

示例代碼:

    public void like(Article article,User user) {
    //檢查是否點過贊
    if (checkIsLike(article,user)) {
    //點過贊了
    throw new ApiException(CodeEnums.SYSTEM_ERR);
}
else {
    //保存點贊
    saveLike(article,user);
}
}
</pre>

看上去好像沒有什么問題,保存點贊之前已經(jīng)檢查過是否點贊了,理論上同一個人不會對同一篇文章重復(fù)點贊。但實際不是這樣的。因為網(wǎng)絡(luò)請求不是排隊進來的,而是一窩蜂涌進來的。

某些時候,用戶網(wǎng)絡(luò)不好,可能很短的時間內(nèi)點擊了多次,由于網(wǎng)絡(luò)傳輸問題,這些請求可能會同時來到我們的服務(wù)器

  • 第一個請求 checkIsLike() 返回 false , 正在執(zhí)行 saveLike() 操作,還沒來的及提交事務(wù)
  • 第二個請求過來了 ,checkIsLike() 返回 也是 false , 并去 執(zhí)行了 saveLike() 操作

這樣子,就造成了一個用戶同時對一篇文章進行了多次點贊操作。

這就是典型的冪等性問題, 操作了一次和操作了兩次結(jié)果不一樣,因為你多點了一次贊,按照冪等性原則 不管你點擊了多少次結(jié)果都一樣,只點了一次贊。

很多場景都是這樣造成的,比如用戶重復(fù)下單,重復(fù)評論,重復(fù)提交表單等。

那怎么解決呢?
假設(shè)網(wǎng)絡(luò)的請求是排隊進來的就不會出現(xiàn)這個問題了。

于是我們可以改成這樣:

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 也不適合分布式集群場景。

場景二 : 第三方回調(diào)

我們系統(tǒng)經(jīng)常需要和第三方系統(tǒng)打交道,比如微信充值,支付寶充值什么的,微信和支付寶常常會以回調(diào)你的接口通知你支付結(jié)果。為了保證你能收到回調(diào),往往可能會回調(diào)多次。

有時候我們也為了保證數(shù)據(jù)的準確性會有個定時器去查詢支付結(jié)果未知的流水,并執(zhí)行響應(yīng)的處理。
如果定時器的輪訓(xùn)和回調(diào)剛好是在同時進行,這可能又出BUG了,又進行了兩次重復(fù)操作。

那么問題來了
假設(shè)我是一個充值操作, 回調(diào)回來的時候 ,會做業(yè)務(wù)處理,成功了給用戶賬戶加錢。這是后就要保證冪等性了, 假設(shè)微信同一筆交易給你回調(diào)了兩次,如果你給用戶充值了兩次,這顯然不合理(我是老板肯定扣你工資),所以要保證 不管微信回調(diào)你多少次 ,同一筆交易你只能給用戶充一次錢。這就冪等性。

解決冪等性問題方案

  • synchronized
    適合單機應(yīng)用,不追求性能 ,不追求并發(fā)。
  • 分布式鎖
    但是往往我們的應(yīng)用是分布式的集群,并且很講究性能,并發(fā),所以我們需要用到 分布式鎖 來解決這個問題。

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 {
    //失敗
//現(xiàn)在時間
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) {
    //獲取鎖失敗  說明前面的請求已經(jīng)獲取了鎖
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 的設(shè)計也很講究:
數(shù)據(jù)不沖突的兩個業(yè)務(wù)場景,key不能沖突,不同人的key也不一樣,不同的文章Key也不一樣。
根據(jù)場景業(yè)務(wù)設(shè)定。

一個原則: 盡可能的縮小key的范圍。?這樣才能增強我們的并發(fā)。

首先我們先獲取鎖,獲取鎖成功 執(zhí)行完操作,保存數(shù)據(jù) ,刪除鎖。獲取不到鎖返回失敗。設(shè)置過期時間是為了防止‘死鎖’,比如機器獲取到了 鎖,沒有設(shè)置過期時間,但是他死機了,沒有刪除釋放鎖。

  • 版本號控制
    CAS 算法: CAS有3個操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時,將內(nèi)存值V修改為B,否則什么都不做。這個比較繁雜,有興趣的同學(xué)可以去看看。

網(wǎng)站欄目:高并發(fā)核心技術(shù)-冪等性與分布式鎖
鏈接地址:http://bm7419.com/article46/pcsghg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、網(wǎng)站建設(shè)、小程序開發(fā)、做網(wǎng)站企業(yè)網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

營銷型網(wǎng)站建設(shè)