深入理解幂等技术
什么是冪等
冪等(idempotent)是一個(gè)數(shù)學(xué)與計(jì)算機(jī)學(xué)概念,常見于抽象代數(shù)中。
在編程中,一個(gè)冪等操作的特點(diǎn)是,其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同。冪等函數(shù),或冪等方法,是指可以使用相同參數(shù)重復(fù)執(zhí)行,并能獲得相同結(jié)果的函數(shù)。冪等函數(shù)可以改變系統(tǒng)的狀態(tài)。例如,setTrue()就是一個(gè)冪等函數(shù),因?yàn)闊o論執(zhí)行多少次,其結(jié)果都是一樣的。
冪等的場景有很多,例如:
- 前端重復(fù)提交選中的數(shù)據(jù),后臺只產(chǎn)生對應(yīng)這個(gè)數(shù)據(jù)的一個(gè)反應(yīng)結(jié)果;
- 我們發(fā)起一筆付款請求,應(yīng)該只扣用戶賬戶一次錢,當(dāng)遇到網(wǎng)絡(luò)重發(fā)或系統(tǒng)bug重發(fā),也應(yīng)該只扣一次錢;
- 發(fā)送消息,也應(yīng)該只發(fā)一次,同樣的短信發(fā)給用戶,用戶會崩潰;
- 創(chuàng)建業(yè)務(wù)訂單,一次業(yè)務(wù)請求只能創(chuàng)建一個(gè),創(chuàng)建多個(gè)就會出大問題。
冪等的技術(shù)手段
冪等并不是并發(fā)場景下的特有問題。冪等處理的是多次執(zhí)行的問題,而并發(fā)僅僅是多次執(zhí)行的一種形式。不管是依次執(zhí)行,還是并發(fā)執(zhí)行,都需要做好冪等。有些技術(shù)人員將解決并發(fā)問題的技術(shù)手段,例如悲觀鎖、樂觀鎖和分布式鎖,當(dāng)成冪等的技術(shù)手段,這是不對的。
再次強(qiáng)調(diào),冪等的核心是確保唯一性。
唯一索引
在數(shù)據(jù)庫中建立唯一索引,用作冪等記錄,可以防止插入重復(fù)的數(shù)據(jù)。 在冪等函數(shù)中,先執(zhí)行一次查詢操作,如存在冪等記錄則返回第一次執(zhí)行的結(jié)果,如不存在冪等記錄則繼續(xù)執(zhí)行。在并發(fā)場景下,可能存在多個(gè)線程同時(shí)插入冪等記錄,這時(shí)候唯一索引可以確保只有一個(gè)線程插入成功,其它線程拋出異常。
除了插入冪等記錄,應(yīng)該還要插入其它的業(yè)務(wù)數(shù)據(jù),這個(gè)時(shí)候務(wù)必使用事務(wù)。在實(shí)際工作中,冪等記錄與事務(wù)經(jīng)常同時(shí)出現(xiàn),如影相隨。
唯一數(shù)據(jù)
使用redis、memcache和zookeeper都可以實(shí)現(xiàn)唯一數(shù)據(jù),這里僅用redis的SETNX舉例。筆者從redis的官方文檔摘抄了SETNX的用法,如下所示。
SETNX key value將 key 的值設(shè)為 value ,當(dāng)且僅當(dāng) key 不存在。若給定的 key 已經(jīng)存在,則 SETNX 不做任何動作。SETNX 是『SET if Not eXists』(如果不存在,則 SET)的簡寫。可用版本: >= 1.0.0 時(shí)間復(fù)雜度: O(1) 返回值: 設(shè)置成功,返回 1 。 設(shè)置失敗,返回 0 。 復(fù)制代碼在冪等函數(shù)中,將唯一標(biāo)識作為key,任取value,調(diào)用SETNX。如果返回1,說明當(dāng)前是第一次執(zhí)行,繼續(xù)執(zhí)行冪等函數(shù);如果返回0,取出第一次執(zhí)行的結(jié)果并返回給調(diào)用方。在并發(fā)場景下,可能因尚未完成第一次執(zhí)行而取不到結(jié)果,這時(shí)候可以稍作等待。
除了redis、memcache和zookeeper,還有其它手段可以實(shí)現(xiàn)唯一數(shù)據(jù),讀者可自行探索。只要可以實(shí)現(xiàn)唯一數(shù)據(jù),就可以用來做冪等。
狀態(tài)機(jī)約束
在單據(jù)相關(guān)的業(yè)務(wù),或者是任務(wù)相關(guān)的業(yè)務(wù),基本會涉及到狀態(tài)機(jī)。業(yè)務(wù)單據(jù)上面有個(gè)狀態(tài),這個(gè)狀態(tài)根據(jù)一個(gè)有限狀態(tài)機(jī)進(jìn)行跳轉(zhuǎn)。如果狀態(tài)機(jī)已經(jīng)處于下一個(gè)狀態(tài),這時(shí)候是不能往回跳轉(zhuǎn)到上一個(gè)狀態(tài)的。通過狀態(tài)機(jī)的跳轉(zhuǎn)約束,可以做到有限狀態(tài)機(jī)的冪等。
冪等的場景
冪等經(jīng)常與事務(wù)同時(shí)出現(xiàn),而事務(wù)適合小任務(wù)場景、不適合大任務(wù)場景,因此筆者將冪等場景分為以下兩類進(jìn)行介紹。為了方便描述,我們假設(shè)bizId可以唯一標(biāo)識一筆業(yè)務(wù)。
小任務(wù)場景
這個(gè)場景的處理方式很簡單,可以追求強(qiáng)一致性。在冪等函數(shù)中,先判斷冪等記錄是否存在。如果存在,直接返回;如果不存在,開啟一個(gè)事務(wù)。在事務(wù)中,采用任務(wù)名+bizId作為冪等組合字段,插入冪等記錄和業(yè)務(wù)數(shù)據(jù)。它的流程圖如下。
大任務(wù)場景
在大任務(wù)場景下,需要將大任務(wù)拆成多個(gè)小任務(wù)分別執(zhí)行。在每個(gè)小任務(wù)中,都可以有事務(wù)。但是,沒有事務(wù)保證所有的小任務(wù)同時(shí)成功。因此,存在部分成功的場景。針對部分成功的場景,可以利用重試機(jī)制做到最終一致性。重試機(jī)制意味著多次執(zhí)行,回到了冪等問題。這里只介紹需要冪等的場景。如果同時(shí)存在需要冪等和不需要冪等的場景,請加入一個(gè)判斷標(biāo)。
同步執(zhí)行小任務(wù)
同步執(zhí)行小任務(wù)的流程圖如下。各小任務(wù)依次執(zhí)行,中間的小任務(wù)不返回結(jié)果,僅在最后一個(gè)小任務(wù)或之后返回結(jié)果。
異步執(zhí)行小任務(wù)
異步執(zhí)行小任務(wù)的流程圖如下。各小任務(wù)單獨(dú)執(zhí)行,互相不感知,也沒有地方返回結(jié)果。
冪等字段
不管是同步執(zhí)行小任務(wù),還是異步執(zhí)行小任務(wù),都需要為每個(gè)小任務(wù)設(shè)置一個(gè)冪等字段或冪等組合字段。筆者推薦采用小任務(wù)名+bizId作為冪等組合字段。一方面,bizId可以標(biāo)識這一批小任務(wù)屬于同一筆業(yè)務(wù);另一方便,小任務(wù)名可以區(qū)分不同的小任務(wù)。
碼字不易,如有建議請掃碼
總結(jié)
- 上一篇: Python 爬虫十六式 - 第六式:J
- 下一篇: http的请求方法 GET、HEAD、P