「如何設計」具有冪等性的服務

「如何設計」:sql

「如何設計」具有冪等性的服務
「如何設計」微服務中高可用的分佈式鎖數據庫

冪等的目的

爲什麼會存在須要服務的冪等,在互聯網中因爲網絡的不穩定和一些業務重複確認設計,對一個接口的調用存在重試的機制,爲了確保執行同一個請求執行一次和執行屢次的效果是同樣的,因此就存在了冪等的設計。 舉個例子,若是在轉帳的交易中,A給B進行一筆轉帳,若是沒有冪等性,極可能就由於各類緣由致使了A給B進行了多筆轉帳,在銀行系統中,這個就是重大的災難。bash

冪等的定義

服務的冪等可能劃分爲2個層面,一個是從接口的請求層面,一個是從業務層面考慮。網絡

請求層面:

從請求層面考慮,就是一個接口得保證請求一個和請求屢次獲得的效果是一致的。 若是用數據表達式是這樣的架構

f...f(f(x)) = f(x) 
x是參數
f是執行函數
複製代碼

把相同的參數傳給執行函數,無論執行了多少次,結果是一致的。併發

業務層面:

從業務角度出來
例如一個用戶在一次購買中不能重複下單
例如庫存剩下了1個商品,如今有10我的搶購,怎麼保證不超賣
例如在MQ的生產者是不須要保證冪等,極可能把同一條消息發送屢次,須要保證MQ消費端去重,MQ消費者保證這批消息只會執行一個。分佈式

致使非冪等緣由

先了解下爲什麼會出現不冪等的緣由,由於retry重試,若是取消retry機制,是否就能杜毫不冪等呢,答應應該是確定的,但取消retry是否現實,咱們來看看究竟在什麼場合會出現retry函數

用戶進行下訂單,調用下單接口超時,調用方又發起一次建立下單接口。微服務

用戶下單進行扣減庫存,調用扣減庫存接口超時了,調用方又發起一次扣減庫存接口。post

下單完畢後,生產者發送一條MQ,MQ超時沒有及時響應ACK,生產者又再發送一條MQ,消費者連續就收到了兩條MQ

從整個系統或業務層面其實很難去作到去retry,因此在一些接口的冪等性仍是須要咱們本身來作。

冪等做用範圍

讀/寫請求層面範圍冪等

讀沒有形成數據的改變,只有寫請求才會形成數據改變。

架構層面範圍冪等

究竟在哪些層會形成數據的改變

反向代理?網關?業務邏輯?數據訪問?

image

從架構層面出發,哪些層會對數據形成改變,只有形成數據改變的層才須要作出冪等,很顯然,數據訪問層直接操做DB和Cache (業務邏輯層也可能訪問操做cache),從請求層面來看,咱們須要對數據訪問層進行冪等操做。

數據訪問層哪些操做須要冪等

從數據層面出發,數據訪問層 也就提供了 CRUD 四個請求層面,先站在數據層出發,看看是否能夠對數據訪問層進行必定改造讓數據訪問層達到冪等性

Create / Select 操做
insert into user (name,pm) values ('petty',18)
複製代碼

因爲user表的主鍵是自增ID,因此每次插入都會新增一條,能夠考慮進行改造,讓prikey程序實現而不依賴數據庫,或者建立具有unique的索引,確保不會出現重複錄入的行。

image

insert into user (id,name,pm) values (100,'petty',18) 
[執行成功]

insert into user (id,name,pm) values (100,'petty',18) 
[error : Duplicate entry '100' for key 'PRIMARY']
複製代碼

其實不管是否考慮到冪等性,在分佈式服務中,都應該儘可能避免使用數據庫直接生成Id的方式去建立主鍵。

Update 操做

對數據庫的update操做簡單來講,有【絕對值】修改,【相對值】修改 set [ num = 100 | num++ ]:

絕對值修改,例如商品下架

update product status = -1 where pid=1
複製代碼

屬於自然冪等,如論執行多少次,結果都一直,不須要改造

(可能有人會有疑問說,若是又有另一個線程把status修改爲0,而後retry又把status修改爲-1,那就達不到冪等效果,其實這個是另一個問題了,這個不是本接口冪等的問題,而是業務隔離的問題)

相對值修改,年齡增長:

update xx set pm=pm++ where id = 100 
複製代碼

這個就是明顯的不具有冪等性,解決思路,如今程序層中查出數值進行計算

update xx set pm=pm++ where id = 100 and pm = 18
or
update xx set pm=19 where id = 100 
複製代碼

這裏能夠在數據層面解決冪等性,不過這裏多查了一次sql也會帶來性能上的一個問題。

Delete 操做
delete from xx order by pm desc limit 10
複製代碼

delete 道理跟 update 一致,回到業務中,除非咱們想drop掉整個表,否則其實不能夠 delete 掉相對的條件的範圍,通常都是須要查詢出來哪些確切要delete的id,而後針對這批id進行delete

業務層面的冪等

上面從數據層面對CRUD作冪等處理,不過冪等性更可能是考慮到業務場景,看一個例子。

狀態機控制

例如咱們有一個訂單,假如訂單的狀態有:未確認->已經建立->已付款->已確認

update order set status = 已經建立 where orderid=1 and status = 未確認

update order set status = 已經建立 where orderid=1 and status = 未確認
【無效】
複製代碼

經過在設計狀態字段,使用狀態機的機制,確保status必須按照業務的流程往下走,這樣在第二次更新時也會無效達到了冪等性的效果。

分佈式鎖

剛說到在MQ的消費場景中,可能出現屢次消費的狀況,這個狀況只能由消費者本身解決,舉個例子:

當用戶下訂單由於生產者不須要冪等產生了2個MQ,分別msg=xx001,msg=xx002,他們的orderid都是001,兩個消息都發送給了MQ,MQ再將兩個消息發送給2個消費者。兩個消費者如今用orderid爲key,組成了一把分佈式鎖,兩個消費者同時去獲取這一把鎖,不過它們之間只能有一個消費者獲取成功,或者成功的消費者將消費這個mq並執行,或者不成功的消費者將取消執行。

image

分佈式鎖主要起到的做用是解決並行的問題,將本來可能出問題的並行轉爲串性。

分佈式鎖解決不了的問題:

當一個消費線程獲取鎖以後,很快就執行了,而後把鎖釋放掉。另一個線程由於MQ時延等問題在第一個線程執行完以後才接收到MQ,又獲取到分佈式鎖,又將接口再一次執行。

image

分佈式鎖其實並不解決冪等性問題,可是能夠看得出來,這兩次接口的執行並非併發執行,兩次是一個串行關係,只要是串行關係,那能夠藉助狀態機的機器去解決。這個時候就變成了一個業務隔離的問題

去重表

這個場景適合業務中有惟一插入的場景,例如在支付的場景中,訂單隻能被支付一次,能夠把orderid做爲一個惟一標示。新建一張去重表,並把orderid做爲惟一索引,在寫入訂單表時,聯通去重表一塊兒寫入並放在同一個事務中,若是重複調用,會由去重表拋出惟一索引約束的異常,進行回滾。

相關文章
相關標籤/搜索