歡迎光臨
每天分享高質量文章

小說:白話冪等性設計

來自:孤獨煙(微信號:zrj_guduyan)

以下故事純屬虛構,如有雷同純屬巧合!)
一月的深圳,一如既往的炎熱!
某天,煙哥正一邊喝著芝芝芒芒,一邊愉快的裝13!
突然,小劉滿臉愁容的找到了我,對我說:”煙哥,自從我們的單體應用拆成微服務架構後,不知為啥,老是出現資料不一致的問題!已經都快被客戶罵死了,人家客戶明明只點了一次,我們這邊卻有了兩條資料!”

其實煙哥在裝13的時候,被人打擾,這是大忌!大忌!然而,看在小劉是個妹紙的份上,我就不計較了!沒錯,我就是這麼沒原則的人!其實我本來可以直接告訴人家怎麼解決的,然而無恥的煙哥,為了多和妹紙說會話,決定多啰嗦一會!

只見煙哥嘴角嘿嘿一笑,問到:”你知道為什麼拆成微服務架構後,會出現資料不一致問題麽?
接下來的情形,無法用言語形容,各位看官請看下麵這張圖

煙哥解釋到:”因為在傳統應用中,呼叫接口,只有兩種狀態成功失敗。但是呢,在微服務的架構下,還有第三種情況,那就是超時!小劉啊,你們的服務,呼叫另一個服務,如果超時了,你們是怎麼處理的呢?”

小劉說:”重試啊!呼叫超時了,重試一下唄!”

這時,煙哥的表情突然變成了這樣

煙哥回答到:”對的!小劉真聰明!然而,重試只是一種方法!還有一種就是,呼叫方在超時後,去查詢一次被呼叫方。如果能查到資料,就代表呼叫過了,不繼續執行,如果查不到資料,走失敗流程!因為你們在呼叫超時的情況下,進行重試呼叫,就給系統帶來了不一致問題了!因此我們必須給呼叫接口提供冪等性保證,防止重覆呼叫出現不一致的情形!”

這個時候,小劉表情變成了這樣

“哦,小劉,你懂?來來來,說說什麼是接口冪等性?
說時遲,那時快,只見小劉揮舞著她的小手,說道:“接口的冪等性實際上就是接口可重覆呼叫,在呼叫方多次呼叫的情況下,接口最終得到的結果是一致的。”

這種時候,煙哥痛心疾首,

只見煙哥眉毛微微一皺,開始裝13的解釋道:“如果按這麼解釋,比如一個查詢接口。假設SQL是下麵的這樣的

select * from table

這個時候,有一個執行緒,一直往這個table插入資料,那你每次呼叫查詢接口的傳回值肯定不一樣啊!你能說查詢操作不是冪等性操作?
應該要這麼理解,冪等性強調的是外界通過接口對系統內部的影響, 外界怎麼看系統和冪等性沒有關係,只要一次或多次呼叫對某一個資源應該具有同樣的副作用就行。註意了,是對資源造成的副作用必須是一樣的,但是傳回值允許不同!

說到這裡,小劉一臉懵逼的看著我。。。

煙哥說道:“就用,增、刪、改、查來舉例一下吧!”
(1)查詢操作
查詢操作並不會產生或變更新的資料,因此查詢是天然具備冪等性。

(2)刪除操作
這裡分為物理刪除邏輯刪除

  • 物理刪除:刪除只會進行一次,無論執行幾次delete操作,造成的效果是一樣的!是冪等性操作
  • 邏輯刪除:這類刪除,是用update修改欄位而已,這種操作無論update幾次,造成的效果是一樣的!是冪等性操作

(3)增加操作
這裡要看這張表是否帶唯一索引。

  • 帶唯一索引Insert:此時如果重覆插入操作,是會插入失敗的!該操作是冪等性操作
  • 不帶唯一索引Insert: 這種情況是非冪等性操作。

(4)修改操作
要看修改了啥

  • 計算式Update:這類操作是指UPDATE table SET number=number-1 WHERE id=1,這類SQL的操作,是非冪等性操作!
  • 非計算式Update:這類操作是指UPDATE table SET number=3 WHERE id=1,這類SQL操作,這種修改是屬於冪等性的操作!

小劉:”煙哥,你可以先說說網上說的什麼on DUPLICATE KEY UPDATE是什麼東西麽,就像下麵這個SQL這樣!”

insert into table (goods_id,update_time)
values(#{goodsId},now())
on DUPLICATE KEY UPDATE
update_time=now()

煙哥:”好,你還記得我剛說的,在表有唯一索引的情況下,此時如果有重覆插入操作,是會插入失敗的麽!”

小劉:”嗯嗯。記得!”

煙哥:”OK,這種插入失敗,從嚴格意義上來說是分為兩種情況的!”
第一種就是報唯一鍵衝突異常!例如常規的INSERT INTO tablename(列名) VALUES(列值)這樣的陳述句!
第二種就是不報異常,Mysql提供了三組這樣防止重覆插入的陳述句,必須要有唯一索引才能用的

  • insert ignore into :若有導致unique key衝突的記錄,則該條記錄不會被插入到資料庫中.
  • replace :若插入時如發現unique key已存在,則替換原記錄,即先刪除原記錄,後insert新記錄。
  • on duplicate key update :若插入時如果發現unique key已存在,則執行update更新操作

小劉:”可是這些陳述句畢竟是Mysql的方言,換了資料庫就不能用了啊!通用性太差,而且還規定一定要有唯一索引才能用!麻煩!”
此時煙哥的反應是這樣的

煙哥捋了捋自己的思緒,說道:”回到我們剛纔的話題,現在只有不帶唯一索引Insert計算式Update會引起冪等性問題的,懂了嘛?”

小劉抹了抹自己的眼淚,像下麵這樣

煙哥淡然的解釋道:”現在網上大多數文章推薦全域性token的方案,就是這樣的。生成一個全域性性唯一的token,然後請求過來的時候,查一下token存在麽,存在代表做過了,就丟棄。不存在,就執行正常業務流程,把token丟到某個儲存介質里!”

小劉聽了聽,搖了搖頭:”煙哥啊,這個方案乍聽之下很完美!但是細想一下還是有一點不大好。你看啊,假設有1000個請求,重覆請求一般不到10個。為了這不到10個請求的正確性,讓剩下990個正常的請求都多一個查詢流程,這似乎不大妥吧!”

煙哥突然驚獃了,此刻感覺如下

煙哥補充道:“嗯,是的,所以我個人還是主張在資料庫的操作上解決這個問題!就插入操作來說,建議還是建一個唯一索引,來防止重覆插入!”

小劉:”可是我們的資料量很小,就是不想建索引怎麼辦?”

煙哥說道:“那你的插入陳述句可以像下麵這麼修改

INSERT INTO table(field1, field2, fieldn) SELECT 'field1'
'field2''fieldn' FROM DUAL WHERE NOT EXISTS(SELECT field
 FROM table WHERE field = ?)

用這種寫法,就可以防止重覆插入,而且不需要建立唯一索引!SQL可以判斷field欄位有值,則不insert。如果無值,則會執行insert操作!這種方法其實就是使用了mysql的一個臨時表的方式,但是裡面使用到了子查詢,效率也會有一點點影響。但是很重要的一點,這種寫法在oracle里也能跑的通,通用性之強,無與倫比。如果真的達到了影響性能那個級別了,估計資料量夠大,可以用上索引了。這會資料量太小,先這麼寫吧!”

小劉:”那針對修改場景怎麼辦?”

煙哥說道:“也很簡單,加一個版本欄位就行!比如,原來的sql為

UPDATE table SET number=number-1 WHERE id=1

,你加一個版本號欄位就好啦,變成

UPDATE table SET number=number-1,_version=_version+1  
WHERE id=1 AND _version= last_version

唯一的缺點,就是執行前,需要去資料查一下當前版本是啥!當然啦,如果你的表有唯一索引,用的又是mysql,又能保證將來不換其他資料庫。可以試試mysqlon duplicate key update陳述句,該操作插入時如果發現unique key已存在,則執行update更新操作”

煙哥補充道:”在資料庫層面的改變是最方便的,所以我一直主張,改sql,改表結構來解決冪等性問題。不要引入一堆七七八八的東東,徒增系統複雜度。好啦,小劉快回去改sql吧!”

於是,小劉就愉快的回去的改sql了!!

各位看官一定發現了,本來改個SQL就能解決的問題,煙哥硬是扯了半個多小時!當然,最後的結局就是下麵這樣

 


●編號891,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

 

Python編程

更多推薦25個技術類公眾微信

涵蓋:程式人生、演算法與資料結構、黑客技術與網絡安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

    赞(0)

    分享創造快樂