事務(wù)是恢復(fù)和并發(fā)控制的基本單位,保證 ACID :原子性、一致性、隔離性、持久性。
對(duì)于全是異步的 Nodejs 而言, 并不適合做事務(wù)操作:
代碼書(shū)寫(xiě)上:
try ... catch ... 是寫(xiě)給人看的,但是屬于同步方法,局限性很大。
callback 簡(jiǎn)直是噩夢(mèng)。
Promise.then(...).catch(...) 相對(duì)而言好一點(diǎn)。
ES7 的 async ... await ... 比較清爽,使用 Babel 編譯,這是筆者目前找到的最人類的方式,但是和原來(lái)的 Promise 混合使用的時(shí)候有時(shí)候會(huì)出問(wèn)題,因?yàn)榫幾g之后的代碼沒(méi)法看,最后還得重構(gòu)回 Promise 。
異步:
異步導(dǎo)致有可能有意料之外的 uncaughtExceptionError 。
對(duì)于 JAVA/C++ 這樣語(yǔ)言,出錯(cuò)能直接轉(zhuǎn)到 catch 中,但是 Node 不是, uncaughtExceptionError 將直接導(dǎo)致處理鏈斷掉,你只能通過(guò)其他方式保證數(shù)據(jù)一致性。
雖然 Node 做事務(wù)相當(dāng)非人類,但是考慮開(kāi)發(fā)效率 / 成本,使用 Node 進(jìn)行開(kāi)發(fā)并不比換語(yǔ)言開(kāi)差,畢竟事務(wù)只有核心業(yè)務(wù)需要用到。
單點(diǎn)
單機(jī)的事務(wù)相當(dāng)容易保證,特別在依賴 MySQL 或者其他關(guān)系數(shù)據(jù)庫(kù)時(shí)。
Nodejs 有 ORM (如 Sequelize ) 支持事務(wù),也可以直接使用 PROCEDURE/FUNCTION。
兩者各有優(yōu)勢(shì):
ORM 適合復(fù)雜邏輯的事務(wù);
存儲(chǔ)過(guò)程可以有效減少 IO 次數(shù),防止使用 ORM 時(shí)回滾失敗。
實(shí)際開(kāi)發(fā)過(guò)程中可以將兩者結(jié)合起來(lái)一起使用,使用 ORM 完成邏輯,使用存儲(chǔ)過(guò)程減少 IO 次數(shù)。

分布式
對(duì)于分布式系統(tǒng),相信很多人都知道 CAP 理論,即任何一個(gè)分布式系統(tǒng)無(wú)法同時(shí)滿足:
Consistency (一致性)
Availability (可用性)
Partition tolerance (分區(qū)容錯(cuò)性)
但是實(shí)際上 Consistency 是任何一個(gè)系統(tǒng)都不可能放棄的, 分布式事務(wù) 亦是為了保證數(shù)據(jù)一致性,有時(shí)候?yàn)榱送讌f(xié)另外兩個(gè)特性,會(huì)放棄 強(qiáng)一致性 ,保證 最終一致性。
解決方式
目前業(yè)界有很多解決分布式事務(wù)的方案,根據(jù)對(duì)數(shù)據(jù)一致性的強(qiáng)弱要求,可以選擇不同的方案,但是解決思路大致如下:
兩階段提交
如 XA 協(xié)議(TM(事務(wù)管理器)和RM(資源管理器)之間的接口)。
假設(shè)有 A、B、C 三個(gè)操作,第一階段,等待 A B C 均就緒,第二階段,提交 A B C;如果第一階段 A 失敗了,則第二階段回滾 B C。

本地事務(wù)
使用本地消息表,將遠(yuǎn)程事務(wù)拆分成一個(gè)個(gè)本地事務(wù),寫(xiě)入本地表中,然后 定時(shí) / 使用 MQ 通知事務(wù)方。
兩者各有利弊,定時(shí)掃描可能大部分時(shí)候都在做無(wú)用功,而只使用 MQ 可能會(huì)有失敗 / 多次消費(fèi)的問(wèn)題。
使用回滾接口
如 A B 兩個(gè)接口,串行處理,B 失敗了回滾 A ,但是回滾也可能失敗,所以也需要使用本地事務(wù)表 / MQ。

使用 Node 開(kāi)發(fā),1 比較重型,不適合;2 和 3 是比較好的選擇:
選擇一款可靠的 MQ 服務(wù)(單次消費(fèi) / 失敗重試);
拆分本地事務(wù);
不能拆分的事務(wù),保證回滾。
舉個(gè)栗子
做一個(gè)搶購(gòu)系統(tǒng),用戶使用虛擬幣進(jìn)行搶購(gòu),虛擬幣是另外一套系統(tǒng)。為了考慮到公平,每個(gè)用戶還可能要限制購(gòu)買(mǎi)上限。
這樣用戶一次搶購(gòu)的完整流程如下:
檢查購(gòu)買(mǎi)上限
檢查總數(shù)
扣除虛擬幣
寫(xiě)入數(shù)據(jù)庫(kù)
需要事務(wù)保證的地方就是 3 和 4,3 是遠(yuǎn)程事務(wù),4 是本地事務(wù),此栗子中必然是串行操作,3 在前,4 在后。
0x0001
這個(gè)時(shí)候流量很少,并發(fā)不高,將 3 和 4 作為一個(gè)事務(wù),保證一起成功,而失敗一起回滾。
事務(wù) 4 即使使用 ORM 完成,也能完成功能,這個(gè)時(shí)候系統(tǒng)能很好的工作。
0x0010
流量上升中,搶購(gòu)的商品變多,并發(fā)也變大,這個(gè)時(shí)候,考慮使用 Redis 來(lái)提高性能了(犧牲強(qiáng)一致性):
將 購(gòu)買(mǎi)上限 與 總數(shù) 寫(xiě)入 Redis,在壓力轉(zhuǎn)嫁到數(shù)據(jù)庫(kù)之前就擋掉,由于 Redis 的強(qiáng)大性能,可以假設(shè) Redis 等同于內(nèi)存操作,做好回滾就可以了。