近些年Restful API變得很流行,一個重要的緣由是其充分利用HTTP協議標準,這樣API Consumer消費Restful API的成本就小不少,API開發人員也更加有據可循。html
Restful API複用HTTP協議的方法和狀態碼來指代不一樣的行爲,好比POST表明建立一條資源,建立成功用201表示,請求校驗失敗用400表示;GET表明獲取一條或多條資源,獲取成功用200表示;DELETE表明刪除一條資源,刪除成功用204表示,刪除記錄不存在用404表示。sql
我的以爲,Restful API相比傳統RPC(好比:SOAP)的區別,就可類比於面向對象編程和麪向過程編程。數據庫
這裏討論一個問題,對於Restful API的PUT操做,在併發環境下,兩個Request更新同一條資源,可能會出現更新內容丟失的狀況。針對這個問題,一般能夠「加鎖」來解決,那麼加什麼鎖呢?通常來講就是:樂觀鎖或者悲觀鎖。編程
假設PUT請求的處理邏輯是先校驗資源存不存在;而後存在的話更新資源到數據庫。這個邏輯對應就是下面兩條sql語句:併發
select * from product where productid = 110; update product set stock = 49 where productid = 110;
若是是加悲觀鎖的話,就是在執行第一條select語句時加一個排他鎖(select for update),在update語句執行完了才釋放鎖,這樣兩個PUT請求只能一個先執行一個後執行,就不會出現更新內容丟失的狀況。分佈式
使用悲觀鎖,因爲在相應記錄上加了排他鎖,而且鎖的範圍相對較大,會對讀操做產生必定影響;其次,若是索引建得不合適,容易致使鎖住整個表,進而影響系統吞吐量。ide
悲觀鎖有不少應用場景,以前我寫過一篇文章(liquibase和flyway中分佈式鎖實現的區別?)介紹liquibase和flyway,其中flyway就是利用悲觀鎖實現了分佈式鎖。高併發
和悲觀鎖不一樣,使用樂觀鎖,其實並非在相應的記錄或者表上加鎖,而是在update的時候加一個version的比對。性能
回到上面例子,select語句查詢到對應product的version,而後把version添加到update語句比對條件,以下:ui
update product set stock = 49 where productid = 110 and version = 5;
若是當前數據庫中記錄的version是5,則update語句執行成功,version增長;若是當前數據庫中記錄version不是5,則update語句執行失敗,返回相應狀態碼提示用戶請求執行失敗。
能夠看到,樂觀鎖並無添加額外的鎖,因此在某些狀況下,性能會好過悲觀鎖;可是,在高併發頻繁更新的狀況下,可能會致使不少請求失敗,對用戶體驗很很差,用戶須要重試不少次。
上面提到update執行失敗,返回相應狀態碼提示用戶請求執行失敗,那麼對於Restful API,應該返回什麼狀態碼呢?
根據HTTP規範,有兩個狀態碼可使用:409和412。
從409的規範能夠看出,當某一個資源的state發生了變化,致使request不能完成,能夠返回409,提示用戶解決衝突,從新提交請求。
The request could not be completed due to a conflict with the current state of the resource. This code is only allowed in situations where it is expected that the user might be able to resolve the conflict and resubmit the request.
https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.10
從412的規範能夠看出,412是前置條件不符合,而且前置條件須是位於HTTP的header。
The precondition given in one or more of the request-header fields evaluated to false when it was tested on the server.
https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.10
因此,409和412均可以做爲樂觀鎖失敗的返回狀態碼,412須要version信息做爲header(ETag、If-Match)傳入;409則不須要。