<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【高并發(fā)】- 冪等性設(shè)計(jì)你知道多少?

          共 5241字,需瀏覽 11分鐘

           ·

          2023-05-05 22:25

          8f43be8401a347b1bbf14d52c1b8640b.webp


          前言 ????前面章節(jié),用一些典型案例,讓大家對(duì)高并發(fā)系統(tǒng)有了一個(gè)全局的認(rèn)知。本章會(huì)講解一下實(shí)戰(zhàn)中,關(guān)于冪等性設(shè)計(jì)的那些事。

          1. 有關(guān)冪等性設(shè)計(jì)的場(chǎng)景 ????用戶在電商平臺(tái)購(gòu)物,看到自己心儀的商品,于是將其加入購(gòu)物車,之后進(jìn)入購(gòu)物車下單結(jié)算。這時(shí),由于網(wǎng)絡(luò)不通暢,用戶在點(diǎn)擊“提交訂單”按鈕時(shí)卡住了,用戶以為沒有提交成功,就又點(diǎn)擊了一次“提交訂單”按鈕。最終,訂單系統(tǒng)給該用戶生成了兩個(gè)訂單,其實(shí)之前那個(gè)訂單已經(jīng)生成成功了。
          ????這就是一個(gè)典型的冪等性問(wèn)題。由于下單接口沒有做好冪等性設(shè)計(jì),所以導(dǎo)致用戶進(jìn)行了兩次同樣的下單操作,系統(tǒng)給用戶創(chuàng)建了兩個(gè)訂單。

          2. 什么是冪等性
          ????所謂冪等性是指,用戶對(duì)于同一個(gè)操作發(fā)起一次請(qǐng)求或者多次請(qǐng)求,得到的結(jié)果都是一樣的,不會(huì)因?yàn)檎?qǐng)求了多次而出現(xiàn)異常現(xiàn)象。
          ????為了理解冪等性,下面來(lái)看一個(gè)非冪等的場(chǎng)景:
          ????例如,在支付時(shí),用戶點(diǎn)擊了兩次“立即支付”按鈕,發(fā)生了重復(fù)支付。最終,用戶發(fā)現(xiàn)被扣了兩次款,支付系統(tǒng)也生成了兩條支付記錄,這就是一個(gè)非冪等的場(chǎng)景。
          ????

          fe6c9dede9d8358ce5338f5b5f23bd7f.webp



          2.1?需要冪等性的場(chǎng)景
          冪等性主要用在重復(fù)請(qǐng)求上,有如下幾種場(chǎng)景:
          • 用戶多次請(qǐng)求,比如重復(fù)點(diǎn)擊頁(yè)面上的按鈕。
          • 網(wǎng)絡(luò)異常,由于網(wǎng)絡(luò)原因?qū)е略谝欢〞r(shí)間內(nèi)未返回調(diào)用成功的信息,觸發(fā)了框架層的重試機(jī)制。
          • 頁(yè)面回退后再次提交的動(dòng)作。
          • 程序上的重試機(jī)制:對(duì)于未及時(shí)響應(yīng)的請(qǐng)求發(fā)起重試操作。

          2.2?數(shù)據(jù)庫(kù)操作的冪等性分析
          數(shù)據(jù)庫(kù)的上層業(yè)務(wù)操作分為CRUD(即新增、讀取、更新、刪除4個(gè)動(dòng)作)。
          • 新增(Create):如果自增主鍵唯一,則數(shù)據(jù)庫(kù)會(huì)生成多條相同記錄,不具備冪等性,如:
                
                  insert?into(id,?name,?age,?balance)?values(1,?'test', 18, 100);
                
              

          • 讀取(Read):無(wú)論請(qǐng)求多少次,讀取的結(jié)果都是一樣的,所以讀取是天然冪等的,如:
                
                  select name, age, balance from user where id = 1;
                
              
          • 更新(Update):條件語(yǔ)句中帶有計(jì)算的更新是非冪等的;反之,則是天然冪等的,如:
                
                  非冪等  update user set balance = balance + 100 where id = 1;
                
                
                  冪等????update?user set balance = 200 where id = 1;
                
              

          • 刪除(DELETE):無(wú)論刪除多少次,結(jié)果都是一樣的,所以刪除動(dòng)作是天然冪等的,如:
                
                  delete from user where id = 1;
                
              

          可以看出,讀取和刪除是天然冪等的,無(wú)論執(zhí)行多少次請(qǐng)求,最終的結(jié)果都是一樣的。從這里很容易聯(lián)想到RESTful規(guī)范中的HTTP請(qǐng)求方法:POST(C)、GET(R)、PUT(U)、DELETE(D)。
          • POST:相當(dāng)于新增,不具備冪等性。
          • GET:對(duì)資源的獲取。在瀏覽器中通過(guò)地址進(jìn)行訪問(wèn),每次結(jié)果都是一樣的,是天然冪等的。
          • PUT;將一個(gè)資源替換成另一個(gè)資源。這是非計(jì)算型的更新,無(wú)論更新多少次,結(jié)果都是一樣的,是天然冪等的。
          • DELETE:同數(shù)據(jù)庫(kù)的刪除動(dòng)作。無(wú)論刪除多少次,結(jié)果都是一樣的,是天然冪等的。
          (RESTful規(guī)范的HTTP請(qǐng)求的POST、GET、PUT、DELETE這4種方法,除POST外,其他3種都是天然冪等的,即只有POST請(qǐng)求是需要考慮冪等性設(shè)計(jì)的。)
          3.?如何避免重復(fù)提交 ????如果用戶連續(xù)點(diǎn)擊了兩次“立即下單”按鈕后,訂單系統(tǒng)為用戶生成了兩個(gè)訂單,這是不行的。這是重復(fù)請(qǐng)求所導(dǎo)致的。要防止重復(fù)提交訂單(即請(qǐng)求多次和請(qǐng)求一次的最終效果是一樣的),則需要訂單系統(tǒng)在創(chuàng)建訂單時(shí)具備冪等性。

          3.1 利用全局唯一ID防止重復(fù)提交 ????在向數(shù)據(jù)庫(kù)新增一條記錄時(shí),有時(shí)會(huì)出現(xiàn)錯(cuò)誤信息“result in duplicate entry for key primary”,原因是插入了相同ID的信息。
          ????利用數(shù)據(jù)庫(kù)的主鍵唯一特性,可以解決重復(fù)提交的問(wèn)題:對(duì)于相同Id的信息,數(shù)據(jù)庫(kù)會(huì)拋出異常,這樣新增數(shù)據(jù)的請(qǐng)求會(huì)失敗。現(xiàn)在已經(jīng)知道方案了,那么這個(gè)ID該如何和系統(tǒng)進(jìn)行綁定呢?下面來(lái)看一下具體的落地流程。
          (1)搭建一個(gè)生成全局唯一ID的服務(wù)。建議加入一些業(yè)務(wù)信息到該服務(wù)中,例如,在生成的訂單ID中可以包含業(yè)務(wù)信息的訂單元素(如“OD”20230201620005600001)。該全局唯一ID服務(wù)可以參考雪花算法SnowFlow進(jìn)行搭建。 (2)在訂單確定頁(yè)面中,調(diào)用全局唯一ID服務(wù)生成訂單號(hào)。 (3)在提交訂單時(shí)帶上訂單號(hào),請(qǐng)求到達(dá)訂單系統(tǒng)的下單接口。
          (4)將數(shù)據(jù)庫(kù)訂單表Id和訂單號(hào)進(jìn)行映射,將訂單號(hào)作為訂單表的ID。 (5)訂單系統(tǒng)在創(chuàng)建訂單信息時(shí),訂單號(hào)使用前端傳過(guò)來(lái)的訂單號(hào),然后直接將該訂單信息插入訂單庫(kù)中。 (6)如果訂單寫入成功,則是第一次提交,返回下單成功;如果報(bào)ID沖突信息,則是重復(fù)提交,在訂單表中只保留之前的記錄,不會(huì)寫入相同的新記錄。 (在報(bào)“訂單重復(fù)提交”錯(cuò)誤時(shí),不要向客戶端拋出錯(cuò)誤信息,因?yàn)橹貜?fù)提交的訂單不一定全部失敗。如果給用戶展示錯(cuò)誤,則用戶可能還會(huì)提交訂單,這會(huì)使得用戶體驗(yàn)不好。可以直接向用戶展示下單成功)

          e624c68c16fcf7b058c78116eb97519d.webp

          3.2?利用“Token + Redis”機(jī)制防止重復(fù)提交 ????另一個(gè)常用的保證冪等性的方案:使用“Token Redis”機(jī)制防止重復(fù)提交。
          (1)訂單系統(tǒng)提供一個(gè)發(fā)放Token的接口。這個(gè)Token是一個(gè)防重令牌,即一串唯一字符串(可以使用UUID算法生成)。
          (2)在“訂單確認(rèn)頁(yè)”中調(diào)用獲取Token的接口,該接口向訂單確認(rèn)頁(yè)返回Token,同時(shí)將此Token寫入Redis緩存中,并依據(jù)實(shí)際業(yè)務(wù)對(duì)其設(shè)置一定的有效期。
          (3)用戶在“訂單確認(rèn)頁(yè)”中點(diǎn)擊“提交訂單”按鈕時(shí),將第(2)步獲取的Token以參數(shù)或者請(qǐng)求頭的形式封裝進(jìn)訂單信息中,然后請(qǐng)求訂單系統(tǒng)的下單接口。
          (4)下單接口在收到提交下單的請(qǐng)求后,首先判斷在Redis中是否存在當(dāng)前傳入的Token:
          • 如果存在,則代表這是第一次請(qǐng)求,會(huì)刪除這個(gè)Token,繼續(xù)創(chuàng)建訂單的其他業(yè)務(wù)。
          • 如果不存在,則代表這不是第一次請(qǐng)求,而是重復(fù)的請(qǐng)求,會(huì)終止后面的業(yè)務(wù)操作。
          ???? ????對(duì)于并發(fā)場(chǎng)景下的思考:在創(chuàng)建訂單前刪除Token,還是在創(chuàng)建訂單后再刪除Token? ????如果是在創(chuàng)建訂單前刪除,那么在用戶多次請(qǐng)求到達(dá)下單接口(極端情況下)后,查詢緩存Redis,發(fā)現(xiàn)在Redis中已經(jīng)刪除了這個(gè)Token,這會(huì)導(dǎo)致重復(fù)提交。 ????如果是在創(chuàng)建訂單后再刪除,那么在用戶多次請(qǐng)求到達(dá)下單接口(計(jì)算情況下)后,第一個(gè)請(qǐng)求刪除Token沒有成功,之后的請(qǐng)求會(huì)發(fā)現(xiàn)該Token,并且比對(duì)它們是相等的,這也會(huì)導(dǎo)致重復(fù)提交。

          ????所以,在應(yīng)對(duì)并發(fā)修改場(chǎng)景下,對(duì)于Token的獲取、比對(duì)和刪除,需要使用原子操作。在Redis中可以用Lua腳本進(jìn)行原子操作。
          ????利用數(shù)據(jù)庫(kù)的主鍵唯一特性和Token機(jī)制來(lái)避免重復(fù)提交,是一種比較常用的接口冪等性方案。對(duì)于重復(fù)提交的場(chǎng)景,需要依據(jù)業(yè)務(wù)進(jìn)行分析,分析當(dāng)前場(chǎng)景是否具備冪等性,如果不具有冪等性,則需要進(jìn)行冪等性設(shè)計(jì)。
          (冪等性設(shè)計(jì)方案遠(yuǎn)不止以上兩種,例如,可以利用數(shù)據(jù)庫(kù)的悲觀鎖和樂觀鎖,或者分布式場(chǎng)景下的分布式鎖等,來(lái)防止重復(fù)提交)
          4?如何避免更新中的ABA問(wèn)題 ????前面講到數(shù)據(jù)庫(kù)更新時(shí),如果是類似計(jì)算型的更新,則其是非冪等的;如果不是計(jì)算型的更新,則其實(shí)天然冪等的。因?yàn)椋瑹o(wú)論更新多少次,最終結(jié)果都是一樣的。這種情況在大部分場(chǎng)景都是沒有問(wèn)題的,但是在高并發(fā)場(chǎng)景下,就有可能是非冪等的,從而造成數(shù)據(jù)的不一致。 ????場(chǎng)景: ????用戶在和商家討價(jià)還價(jià)后,提交訂單等待商家修改價(jià)格,商家在商品頁(yè)上將訂單價(jià)格從原來(lái)的100元,修改為80元。改完后,商家發(fā)現(xiàn)改錯(cuò)了,于是返回重新修改為90元,訂單系統(tǒng)對(duì)于上架這兩次的修改都執(zhí)行成功了。 ????但是,由于網(wǎng)絡(luò)異常原因,訂單系統(tǒng)未及時(shí)將“前一次修改為80元成功的結(jié)果”返回給訂單頁(yè),從而觸發(fā)了重試邏輯,所以,商品價(jià)格在被修改為90元之后,又被修改為80元,即最終成交價(jià)格變成了80元,這就造成了數(shù)據(jù)的不一致,對(duì)商家也是一個(gè)損失。這是一個(gè)經(jīng)典的ABA問(wèn)題。 ????解決ABA問(wèn)題一個(gè)常用的方案是:使用數(shù)據(jù)庫(kù)的樂觀鎖來(lái)解決。即在訂單系統(tǒng)的訂單表中增加一個(gè)字段版本號(hào)(version),在每次更新時(shí)都判斷兩個(gè)版本號(hào)是否相等,如果不相等則不更新。具體流程如下: (1)在訂單頁(yè)獲取修改訂單時(shí),同時(shí)將訂單版本號(hào)作為訂單屬性一起返回給前端頁(yè)面。 (2)在前端訂單頁(yè)修改訂單時(shí),將訂單關(guān)鍵信息,如訂單ID(orderId)、修改的價(jià)格(newPrice)及獲取的版本號(hào)(version)等,一起傳到訂單修改接口中。 (3)訂單系統(tǒng)的訂單修改接口,在收到價(jià)格修改的請(qǐng)求后,首先判斷當(dāng)前傳過(guò)來(lái)的版本號(hào)和數(shù)據(jù)庫(kù)當(dāng)前訂單的版本號(hào)是否一致。如果不一致,則拒絕當(dāng)前更新;如果一致,則更新當(dāng)前數(shù)據(jù),同時(shí)版本號(hào)加1。 ????“比較版本號(hào)”、“更新數(shù)據(jù)”、“對(duì)版本號(hào)加1”這幾個(gè)動(dòng)作都必須保證原子性,即要在一個(gè)事務(wù)內(nèi),SQL語(yǔ)句如下:
                
                  update?order?set?price?=?80,?version?=?version?+?1 
                
                
                  where?order_id?=?10001?and?version?=?1
                
              
          ????通過(guò)版本號(hào)可以保證從“訂單在訂單頁(yè)被查詢”到“成功更新這條數(shù)據(jù)”這期間內(nèi)沒有其他人能夠修改這條數(shù)據(jù)。只要有人修改了這條數(shù)據(jù),版本號(hào)就會(huì)增加。版本號(hào)增加了,當(dāng)前更新拿到的就是舊版本號(hào),會(huì)更新失敗,需要重新發(fā)起查詢請(qǐng)求以獲取最新的版本號(hào)。
          ????加上版本號(hào)控制后,再來(lái)看一下是否保證了“訂單并發(fā)修改”的數(shù)據(jù)一致性。

          82b3543a98640f5273d238e7f89b25dd.webp

          ????如上圖可知,商家的并發(fā)修改解決了ABA問(wèn)題:
          • 當(dāng)商家修改價(jià)格為80元時(shí),攜帶了版本號(hào)version = 0,訂單系統(tǒng)匹配發(fā)現(xiàn)剛傳的版本號(hào)與數(shù)據(jù)庫(kù)中的版本號(hào)是一致的,所以更新價(jià)格為80元成功,同時(shí)將版本號(hào)加1.
          • 當(dāng)商家又將價(jià)格修改為90元時(shí),剛傳的version = 0,但當(dāng)前訂單的version = 1,所以當(dāng)前價(jià)格更新失敗。
          • 商家看到更新失敗的原因,就可以刷新當(dāng)前頁(yè)面,重新獲取最新的版本號(hào)(version = 1),進(jìn)行訂單修改。

          ????本章節(jié)總結(jié)了高并發(fā)系統(tǒng)中關(guān)于冪等性設(shè)計(jì)的場(chǎng)景及解決方案,小伙伴們可以根據(jù)自身業(yè)務(wù)進(jìn)行取舍。設(shè)計(jì)出符合當(dāng)前公司實(shí)際的冪等性設(shè)計(jì)。


          f97f3e48c6efdb03c3c07708109185f8.webp

          點(diǎn)個(gè)在看你最好看

          瀏覽 88
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  五月婷婷深爱网 | 丝袜足交国产 | 一区二区高清 | 手机在线性爱视频 | 日屁视频网站 |