public class A { private volatile int lower, upper; //兩個狀態值 public int getLower() { return lower; } public int getUpper() { return upper; } public synchronized void setAUpper(int value){ if (value < a.getUpper()) a.setLower(value); } public asynchronization void setALower(int value){ if (value > a.getLower()) a.setUpper(value); } } |
上面這段代碼業務邏輯是想實現lower<upper:
1. lower和upper的初始值是(0, 5),
2.一個客戶端請求線程A: setLower(4)
一個客戶端請求線程B: setUpper(3)
3. lower和upper是 (4, 3)
這個結果破壞了lower<upper這個邏輯一致性,因此,用鎖並不能保證邏輯一致性,並且還帶來了堵塞。鎖用錯了地方,不但沒有獲得想要的,並且還失去更多。
下圖展現了鎖帶來堵塞,每一個時刻只能容許一個線程工做,如同只能容許一我的蹲馬桶同樣。html
從歷史上看,鎖的問題如鬼魂一直伴隨着咱們:
1.用數據表一個字段來表示狀態,好比1表示已付款未發貨,2表示已付款已發貨,而後用戶來一個請求用SQL或存儲過程修改,這時使用的數據庫鎖。
2.用ORM實現,好比Hibernate JPA來修改狀態,雖然不用SQL了,可是Hibernate的悲觀鎖和樂觀鎖也讓人抓狂。
3.完全拋棄數據庫,直接在內存緩存中進行修改,使用Java的同步鎖,性能仍是不夠,吞吐量上不去。如上圖提示,只能一個廁所蹲位一我的用,其餘人必須排隊。
4.Actor模型。
Actor模型原理
Actor模型=數據+行爲+消息。
Actor模型內部的狀態由本身的行爲維護,外部線程不能直接調用對象的行爲,必須經過消息才能激發行爲,這樣就保證Actor內部數據只有被本身修改。
Actor模型如何實現?
Scala或ErLang的進程信箱都是一種Actor模型,也有Java的專門的Actor模型,這裏是幾種Actor模型比較
明白了Actor模型原理,使用Disruptor這樣無鎖隊列也能夠本身實現Actor模型,讓一個普通對象與外界的交互調用經過Disruptor消息隊列實現,好比LMAX架構就是這樣實現高頻交易,從2009年成功運行至今,被Martin Fowler推崇。
回到本帖最初問題,如何使用Actor模型解決高併發事務呢?
轉帳是典型的符合該問題的案例,轉帳是將A賬號到B賬號轉帳,使用Actor模型解決以下:
發出是否可轉出消息--->消息隊列--->A
A做爲一個對象,注意不是數據表,對象是有行爲的,檢查本身餘額是否可轉帳,若是能夠,凍結這部分金額,好比轉帳100元,凍結100元,從餘額中扣除。由於外部命令是經過消息順序進來的,因此下一個消息若是也是扣除,再次檢查餘額是否足夠......
具體詳細流程可見:REST和DDD
那麼,既然Actor模型如此巧妙,而解決方向與咱們習慣的數據喂機器的方式如此不一樣,那麼如何在實戰中能明顯發現某個數據修改應該用Actor模型解決呢?由於咱們習慣將數據喂機器的思路啊?
使用DDD領域驅動設計或CQRS架構就能明顯發現這些特殊狀況,CQRS是讀寫分離,其中寫操做是應領域專家要求編寫的功能,在這類方向,咱們都有必要使用Actor模型實現,由於在這個方向上,領域專家的要求都表達爲聚合根實體,聚合根就是用Actor模型實現最合適不過了。而讀方向,好比大數據處理,報表查詢,OLTP等等都是數據喂機器的方式。
有的道友會疑問,咱們常用SSH,也就是Spring + Hibernate架構,這個默認是哪一種方向呢?很顯然,默認是數據喂機器的方向,因此在實現寫操做時,特別警戒高併發發生死鎖等影響性能問題,固然也包括EJB架構。
有一種togaf架構,將企業軟件架構分爲數據架構和應用架構等,實際是EJB或SSH的變相描述,這種架構的問題咱們已經一目瞭然了,特別這樣的系統若是從面向內部管理轉向到SaaS模型時,這類高併發死鎖問題就特別容易發生,幾乎不具有可用性。前期12306火車票系統是這類問題的典型體現。數據庫