幂等和高并发在电商系统中的使用
在Java web項目開發(fā)中,經(jīng)常會聽到在做訂單系統(tǒng)中生成訂單的時候,要做冪等性控制和并發(fā)控制,特對此部分內(nèi)容作出總結(jié),在高并發(fā)場景下,代碼層面需要實現(xiàn)并發(fā)控制;但是冪等性,其實更多的是系統(tǒng)的接口對外的一種承諾,承諾一次請求和多次請求會返回同樣的數(shù)據(jù)。關(guān)于冪等性將分別從高等代數(shù)中的冪等性、HTTP中的冪等性和訂單生成系統(tǒng)中的冪等性闡述;并發(fā)性控制則提供了分布式鎖等方式來對并發(fā)場景進(jìn)行代碼實現(xiàn)。
一、冪等性?idempotence??['a?d?mpo?t?ns]
1.高等代數(shù)中關(guān)于冪等idempotence概念解釋:
單目運(yùn)算, x為某集合內(nèi)的任意數(shù), 如果滿足f(x)=f(f(x)), 那么我們稱f運(yùn)算為具有冪等性(idempotent)。比如在實數(shù)集中,絕對值運(yùn)算就是一個例子: abs(a)=abs(abs(a))。
雙目運(yùn)算,x為某集合內(nèi)的任意數(shù), f為運(yùn)算子如果滿足f(x,x)=x, f運(yùn)算的前提是兩個參數(shù)都同為x, 那么我們也稱f運(yùn)算為具有冪等性。比如在實數(shù)集中,求兩個數(shù)的最大值的函數(shù): max(x,x) = x,?還有布爾代數(shù)中,邏輯運(yùn)算 "與", "或" 也都是冪等運(yùn)算, 因為他們符合AND(0,0) = 0, AND(1,1) = 1, OR(0,0) = 0, OR(1,1) = 1。
在將冪等性應(yīng)用到軟件開發(fā)中,需要一些更深的理解,我的理解如下:數(shù)學(xué)處理的是運(yùn)算和數(shù)值, 程序開發(fā)中往往處理的是對象和函數(shù). 但是我們不能簡單地理解為數(shù)學(xué)冪等中的運(yùn)算就是函數(shù),而數(shù)值就是對象。如Person對象有兩個屬性weight和age,但是所有的function只能對其中一個屬性操作。所以從這個層面我們可以理解為: 函數(shù)只對該函數(shù)所操作的對象某個屬性具有冪等性, 而不是說對整個對象有運(yùn)算冪等性。?
Person { private int weight; private int age; //是冪等函數(shù)public void setAge(int v){ this.age = v; }//不是冪等函數(shù)public void increaseAge(){ this.age++;} //是冪等函數(shù)public void setWeight(int v){ this.weight=v+10;//故意加10斤!! } }還有一點(diǎn)必須要澄清的是:?冪等性所表達(dá)的概念關(guān)注的是數(shù)學(xué)層面的運(yùn)算和數(shù)值, 并沒有提及到數(shù)值的安全性問題.如上面的Person的setAge函數(shù), 有兩種case不是冪等性所關(guān)心的, 但程序開發(fā)卻又必須要關(guān)心的:
- 兩個線程同時調(diào)用
- 因為age從業(yè)務(wù)上講不可能遞減, 如果前一次調(diào)用設(shè)置是30歲, 后一次調(diào)用變成了10歲或是更離譜的 -1?歲
冪等性是系統(tǒng)的接口對外一種承諾(而不是實現(xiàn)), 承諾只要調(diào)用接口成功, 外部多次調(diào)用對系統(tǒng)的影響是一致的。聲明為冪等的接口會認(rèn)為外部調(diào)用失敗是常態(tài), 并且失敗之后必然會有重試。所以RESTful設(shè)計中將冪等性和安全性作為兩個不同的指標(biāo)來衡量POST,PUT,GET,DELETE操作的。因此,post不是冪等性的,put get delete都是冪等性的,也即在生成訂單的post請求中,我們要做冪等性的控制。如下圖,一個ajax請求是一次post請求的示例,如果這個post請求被調(diào)用多次,它會向表插入多條記錄,很顯然post請求并不是冪等性的,所以冪等性的控制交由我們程序中來控制。
2.HTTP協(xié)議中的冪等性
項目中中的SOA和restful API接口的流行,都需要應(yīng)用層HTTP協(xié)議的支持,目前的項目結(jié)構(gòu):Web API + RIA(Rich Internet Applications富互聯(lián)網(wǎng)應(yīng)用),Web API專注于提供業(yè)務(wù)服務(wù),RIA專注于用戶界面和交互設(shè)計,從此兩個領(lǐng)域的分工更加明晰。正如簡單的Java語言并不意味著高質(zhì)量的Java程序,簡單的HTTP協(xié)議也不意味著高質(zhì)量的Web API。要想設(shè)計出高質(zhì)量的Web API,還需要深入理解分布式系統(tǒng)及HTTP協(xié)議的特性。在HTTP1.1規(guī)范中定義冪等性。
Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effectsof N > 0 identical requests is the same as for a single request.
從定義上看,HTTP方法的冪等性是指一次和多次請求某一個資源應(yīng)該具有同樣的作用。冪等性是分布式系統(tǒng)設(shè)計中十分重要的概念,而HTTP的分布式本質(zhì)也決定了它在HTTP中具有重要地位。比如有這樣一個業(yè)務(wù)邏輯,假設(shè)有一個從賬戶取錢的遠(yuǎn)程API(可以是HTTP的,也可以不是),我們暫時定義接口:
bool withdraw(account_id, amount)withdraw的語義是從account_id對應(yīng)的賬戶中扣除amount數(shù)額的錢;如果扣除成功則返回true,賬戶余額減少amount;如果扣除失敗則返回false,賬戶余額不變。值得注意的是:和本地環(huán)境相比,我們不能輕易假設(shè)分布式環(huán)境的可靠性。一種典型的情況是withdraw請求已經(jīng)被服務(wù)器端正確處理,但服務(wù)器端的返回結(jié)果由于網(wǎng)絡(luò)等原因被丟掉了,導(dǎo)致客戶端無法得知處理結(jié)果。如果是在網(wǎng)頁上,一些不恰當(dāng)?shù)脑O(shè)計可能會使用戶認(rèn)為上一次操作失敗了,然后刷新頁面,這就導(dǎo)致了withdraw被調(diào)用兩次,賬戶也被多扣了一次錢。如下圖所示:
?
這個問題的解決方案一是采用分布式事務(wù),通過引入支持分布式事務(wù)的中間件來保證withdraw功能的事務(wù)性。分布式事務(wù)的優(yōu)點(diǎn)是對于調(diào)用者很簡單,復(fù)雜性都交給了中間件來管理。缺點(diǎn)則是一方面架構(gòu)太重量級,容易被綁在特定的中間件上,不利于異構(gòu)系統(tǒng)的集成;另一方面分布式事務(wù)雖然能保證事務(wù)的ACID性質(zhì),而但卻無法提供性能和可用性的保證。
另一種更輕量級的解決方案是冪等設(shè)計。我們可以通過一些技巧把withdraw變成冪等的,比如:
int create_ticket() bool idempotent_withdraw(ticket_id, account_id, amount)create_ticket的語義是獲取一個服務(wù)器端生成的唯一的處理號token,它將用于標(biāo)識后續(xù)的操作。idempotent_withdraw和withdraw的區(qū)別在于關(guān)聯(lián)了一個token,一個token表示的操作至多只會被處理一次,每次調(diào)用都將返回第一次調(diào)用時的處理結(jié)果。這樣,idempotent_withdraw就符合冪等性了,客戶端就可以放心地多次調(diào)用。也就是說,多次點(diǎn)擊提交的時候,附帶提交的還有服務(wù)端生成的token,由于多次提交帶的是同一個token,所以服務(wù)端對于同一個token的post訂單,至多只會處理一次,所以間接的實現(xiàn)了冪等性的控制。
基于冪等性的解決方案中一個完整的取錢流程被分解成了兩個步驟:1.調(diào)用create_ticket()獲取token;2.調(diào)用idempotent_withdraw(token, account_id, amount)。雖然create_ticket不是冪等的,但在這種設(shè)計下,它對系統(tǒng)狀態(tài)的影響可以忽略,加上idempotent_withdraw是冪等的,所以任何一步由于網(wǎng)絡(luò)等原因失敗或超時,客戶端都可以重試,直到獲得結(jié)果。如圖2所示:
?
和分布式事務(wù)相比,冪等設(shè)計的優(yōu)勢在于它的輕量級,容易適應(yīng)異構(gòu)環(huán)境,以及性能和可用性方面。在某些性能要求比較高的應(yīng)用中,冪等設(shè)計往往是唯一的選擇。
by the Request-URI in the Request-Line ...... If a resource has been created on the origin server, the response SHOULD be 201 (Created) and contain
an entity which describes the status of the request and refers to the new resource, and a Location header.The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource,
the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an
existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI.
POST所對應(yīng)的URI并非創(chuàng)建的資源本身,而是資源的接收者。比如:POST http://www.forum.com/articles的語義是在http://www.forum.com/articles下創(chuàng)建一篇帖子,HTTP響應(yīng)中應(yīng)包含帖子的創(chuàng)建狀態(tài)以及帖子的URI。兩次相同的POST請求會在服務(wù)器端創(chuàng)建兩份資源,它們具有不同的URI;所以,POST方法不具備冪等性。而PUT所對應(yīng)的URI是要創(chuàng)建或更新的資源本身。比如:PUT http://www.forum/articles/4231的語義是創(chuàng)建或更新ID為4231的帖子。對同一URI進(jìn)行多次PUT的副作用和一次PUT是相同的;因此,PUT方法具有冪等性。論壇網(wǎng)站防止重復(fù)發(fā)帖和訂單生成都用到token方式的冪等性控制。
3.總結(jié)
在電商系統(tǒng)中,常見問題:如何防范post請求的重復(fù)提交?HTTP POST 操作既不是安全的,也不是冪等的。當(dāng)我們因為反復(fù)刷新瀏覽器導(dǎo)致多次提交表單,多次發(fā)出同樣的POST請求,導(dǎo)致遠(yuǎn)端服務(wù)器重復(fù)創(chuàng)建出了資源。所以,對于電商應(yīng)用來說,第一對應(yīng)的后端 WebService 一定要做到冪等性,第二服務(wù)器端收到 POST 請求,在操作成功后必須跳轉(zhuǎn)到另外一個頁面,這樣即使用戶刷新頁面,也不會重復(fù)提交表單。
二、高并發(fā)
1.分布式鎖的定義
分布式鎖是控制分布式系統(tǒng)之間同步訪問共享資源的一種方式。在分布式系統(tǒng)中,常常需要協(xié)調(diào)他們的動作。如果不同的系統(tǒng)或是同一個系統(tǒng)的不同主機(jī)之間共享了一個或一組資源,那么訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分布式鎖。分布式鎖是一個在很多環(huán)境中非常有用的原語,它是不同的系統(tǒng)或是同一個系統(tǒng)的不同主機(jī)之間互斥操作共享資源的有效方法。如在電商系統(tǒng)中,需要保證整個分布式系統(tǒng)內(nèi),對一個重要事物(訂單,賬戶等)的有效操作線程 ,同一時間內(nèi)有且只有一個。比如交易中心有N臺服務(wù)器,訂單中心有M臺服務(wù)器,如何保證一個訂單的同一筆支付處理,一個賬戶的同一筆充值操作是原子性的。
常見的實現(xiàn)分布式鎖的服務(wù)有:memcache zookeeper redis chubby hazelcast。
2.分布式鎖實現(xiàn)
分布式鎖在分布式應(yīng)用當(dāng)中是要經(jīng)常用到的,主要是解決分布式資源訪問沖突的問題。傳統(tǒng)的鎖ReentrantLock在去實現(xiàn)的時候是有問題的,ReentrantLock的lock和unlock要求必須是在同一線程進(jìn)行,而分布式應(yīng)用中,lock和unlock是兩次不相關(guān)的請求,因此肯定不是同一線程,因此導(dǎo)致無法使用ReentrantLock。
附:
1、什么是restful風(fēng)格的API接口?
http://www.ruanyifeng.com/blog/2014/05/restful_api.html
http://www.cnblogs.com/zhengyun_ustc/archive/2012/11/17/topic2.html
http://www.cnblogs.com/j2eetop/p/4612437.html
http://www.cnblogs.com/weidagang2046/p/exception-handling-principles.html
http://www.cnblogs.com/orange1438/p/4637776.html
?
轉(zhuǎn)載于:https://www.cnblogs.com/RunForLove/p/5640949.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的幂等和高并发在电商系统中的使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java将字符串转换成可执行代码
- 下一篇: 进阶篇-安卓系统:2.多点触控的交互处理