分佈式事務產生的背景
在微服務環境下,因爲會根據不同的業務會拆分成不同的服務,比如會員服務、訂單服務、商品服務等,讓專業的人做專業的事情,每個服務都有自己獨立的數據庫,並且是獨立運行,互不影響。
服務與服務之間通訊採用RPC遠程調用技術,但是每個服務中都有自己獨立的數據源,即自己獨立的本地事務。兩個服務相互通訊的時候,兩個本地事務互不影響,從而出現分佈式事務產生的原因。
解決分佈式事務基本思路
ACID酸鹼平衡理論
如何保證強一致性呢?計算機專業的童鞋在學習關係型數據庫的時候都學習了ACID原理,這裏對ACID做個簡單的介紹。如果想全面的學習ACID原理,請參考ACID
關係型數據庫天生就是解決具有複雜事務場景的問題,關係型數據庫完全滿足ACID的特性。
數據庫管理系統中事務(transaction)的四個特性(分析時根據首字母縮寫依次解釋):
原子性(Atomicity)
一致性(Consistency)
隔離性(Isolation)
持久性(Durability)
所謂事務,它是一個操作序列,這些操作要麼都執行,要麼都不執行,它是一個不可分割的工作單位。(執行單個邏輯功能的一組指令或操作稱爲事務)
CAP(帽子 原理)
由於對系統或者數據進行了拆分,我們的系統不再是單機系統,而是分佈式系統,針對分佈式系統的CAP原理包含如下三個元素。
C:Consistency,致性。在分佈式系統中的所有數據 備份,在同一時刻具有同樣的值,所有節點在同一時刻讀取的數據都是最新的數據副本。
A:Availability,可用性,好的響應性能。完全的可用性指的是在任何故障模型下,服務都會在有限的時間內處理完成並進行響應。
P: Partition tolerance,分區容忍性。儘管網絡上有部分消息丟失,但系統仍然可繼續工作。
CAP原理證明,任何分佈式系統只可同時滿足以上兩點,無法三者兼顧。由於關係型數據庫是單節點無複製的,因此不具有分區容忍性,但是具有一致性和可用性,而分佈式的服務化系統都需要滿足分區容忍性,那麼我們必須在一致性和可用性之間進行權衡。如果在網絡上有消息丟失,也就是出現了網絡分區,則複製操作可能會被延後,如果這時我們的使用方等待複製完成再返回,則可能導致在有限時間內無法返回,就失去了可用性:而如果使用方不等待複製完成,而在主分片寫完後直接返回,則具有了可用性,但是失去了一致性。
Base(鹼)
BASE 是 Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent(最終一致性)三個短語的簡寫,由 eBay 架構師 Dan Pritchett 於 2008 年在《BASE: An Acid Alternative》(論文地址點 這裏)論文中首次提出。BASE 思想與 ACID 原理截然不同,它滿足 CAP 原理,通過犧牲強一致性獲得可用性, 一般應用於服務化系統的應用層或者大數據處理系統中,通過達到最終一致性來儘量滿足業務的絕大多數需求。
BASE 模型包含如下三個元素:
• BA:(Basically Available ),基本可用。
• S:( Soft State),軟狀態,狀態可以在一段時間內不同步。
• E:(Eventually Consistent ),最終一致,在一定的時間窗口內, 最終數據達成一致即可。
關於最終一致的幾種變種參見上面,在實際系統實踐中,可以將若干變種結合起來,來實現各種業務需求。
柔性事務和剛性事務
柔性事務滿足BASE理論(基本可用,最終一致)
剛性事務滿足ACID理論
本文主要圍繞分佈式事務當中的柔性事務的處理方式進行討論。
柔性事務分爲
- 兩階段型
- 補償型
- 異步確保型
- 最大努力通知型幾種。 由於支付寶整個架構是SOA架構,因此傳統單機環境下數據庫的ACID事務滿足了分佈式環境下的業務需要,以上幾種事務類似就是針對分佈式環境下業務需要設定的。
分佈式事務常見解決方案
分佈式一致性協議
XA接口
XA是由X/Open組織提出的分佈式事務的規範。XA規範主要定義了(全局)事務管理器(Transaction Manager)和(局部)資源管理器(Resource Manager)之間的接口。XA接口是雙向的系統接口,在事務管理器(Transaction Manager)以及一個或多個資源管理器(Resource Manager)之間形成通信橋樑。XA之所以需要引入事務管理器是因爲,在分佈式系統中,從理論上講(參考Fischer等的論文),兩臺機器理論上無法達到一致的狀態,需要引入一個單點進行協調。事務管理器控制着全局事務,管理事務生命週期,並協調資源。資源管理器負責控制和管理實際資源(如數據庫或JMS隊列)
Jta規範
作爲java平臺上事務規範JTA(Java Transaction API)也定義了對XA事務的支持,實際上,JTA是基於XA架構上建模的,在JTA 中,事務管理器抽象爲javax.transaction.TransactionManager接口,並通過底層事務服務(即JTS)實現。像很多其他的java規範一樣,JTA僅僅定義了接口,具體的實現則是由供應商(如J2EE廠商)負責提供,目前JTA的實現主要由以下幾種:
1.J2EE容器所提供的JTA實現(JBoss)
2.獨立的JTA實現:如JOTM,Atomikos.這些實現可以應用在那些不使用J2EE應用服務器的環境裏用以提供分佈事事務保證。如Tomcat,Jetty以及普通的java應用。
兩段提交協議
交易中間件與數據庫通過 XA 接口規範,使用兩階段提交來完成一個全局事務, XA 規範的基礎是兩階段提交協議。
第一階段是表決階段,所有參與者都將本事務能否成功的信息反饋發給協調者;第二階段是執行階段,協調者根據所有參與者的反饋,通知所有參與者,步調一致地在所有分支上提交或者回滾。
兩階段提交方案應用非常廣泛,幾乎所有商業OLTP數據庫都支持XA協議。但是兩階段提交方案鎖定資源時間長,對性能影響很大,基本不適合解決微服務事務問題。
三段提交協議
TCC
異步回調模式
最終一致性模式
可靠消息模式
基於LCN框架解決分佈式事務
LCN官網 https://www.txlcn.org/
“LCN並不生產事務,LCN只是本地事務的搬運工”
兼容 dubbo、springcloud、motan 框架,支持各種關係型數據庫
LCN框架底層實現原理
詳細參考: https://github.com/codingapi/tx-lcn/wiki/LCN%E5%8E%9F%E7%90%86
SpringCloud2.0整合LCN
目前LCN版本已經升級爲4.0了,但是官方沒有SpringCloud2.0的demo案例。
因爲LCN本身是開源的,網上有大牛對LCN框架源碼做修改,可以支持SpringCloud2.0版本。
SpringCloud2.0客戶端集成
Maven依賴信息
<dependency>
<groupId>com.codingapi</groupId>
<artifactId>transaction-springcloud</artifactId>
<version>4.1.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.codingapi</groupId>
<artifactId>tx-plugins-db</artifactId>
<version>4.1.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
application.yml引入
tm:
manager:
url: http://127.0.0.1:8899/tx/manager/
LCN基本配置代碼
@Service
public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService {
@Value("${tm.manager.url}")
private String url;
@Override
public String getTxUrl() {
System.out.println("load tm.manager.url ");
return url;
}
}
@Service
public class TxManagerHttpRequestServiceImpl implements TxManagerHttpRequestService {
@Override
public String httpGet(String url) {
System.out.println("httpGet-start");
String res = HttpUtils.get(url);
System.out.println("httpGet-end");
return res;
}
@Override
public String httpPost(String url, String params) {
System.out.println("httpPost-start");
String res = HttpUtils.post(url, params);
System.out.println("httpPost-end");
return res;
}
}
分佈式事務案例
// 下單扣庫存
@TxTransaction(isStart = true)
@Transactional
@GetMapping(value = "/addOrderAndStock")
public ResponseBase addOrderAndStock(int i) {
OrderEntity orderEntity = new OrderEntity();
orderEntity.setName("==================Test");
orderEntity.setOrderCreatetime(new Date());
// 價格是300元
orderEntity.setOrderMoney(300d);
// 狀態爲 未支付
orderEntity.setOrderState(0);
Long commodityId = 30l;
// 商品id
orderEntity.setCommodityId(commodityId);
// 1.先下單,創建訂單
int orderResult = orderMapper.addOrder(orderEntity);
System.out.println("orderResult:" + orderResult);
// 2.下單成功後,調用庫存服務
ResponseBase inventoryReduction = stockFeign.inventoryReduction(commodityId);
// if (inventoryReduction.getRtnCode() != 200) {
// // 手動回滾事務
// }
int reuslt = 1 / i;
return setResultSuccess("下單成功!");
}
使用@TxTransaction解決分佈式事務 isStart true 是:是發起方 false 否:是參與方
同理生產者 同樣配置集成即可,只需要把Transaction 中的isStart改爲fasle即可。
LCN協調者服務集羣
官方文檔: https://github.com/codingapi/tx-lcn/wiki/TxManager%E9%9B%86%E7%BE%A4%E8%AF%B4%E6%98%8E
核心原理 通過該實現類
NettyDistributeServiceImpl 38行獲取服務器集羣地址
private void getTxServer() {
//獲取負載均衡服務地址
String json = null;
while (StringUtils.isEmpty(json)) {
json = txManagerService.httpGetServer();
logger.info("get txManager ->" + json);
if (StringUtils.isEmpty(json)) {
logger.error("TxManager服務器無法訪問.");
try {
Thread.sleep(1000 * 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
TxServer txServer = TxServer.parser(json);
if (txServer != null) {
logger.debug("txServer -> " + txServer);
logger.info(txServer.toString());
Constants.txServer = txServer;
logger.info(Constants.txServer.toString());
connectCont = 0;
}
}
LCN協調者服務集羣原理
1.首先通過nginx配置多個tm協調者負載均衡配置,讓後 LCN客戶端啓動項目的時候訪問nginx負載均衡地址獲取lcn協議通訊IP地址和端口號,並且對該連接保持長連接。
2.因爲LCN客戶端與TM協調者保持的是長連接,當tm協調者宕機之後,LCN會客戶端會立即重新進入到獲取負載均衡地址lcn協議通訊IP地址和端口號。
Nginx負載均衡配置
上游服務器 集羣 默認輪訓機制
upstream backServer{
server 127.0.0.1:8899;
server 127.0.0.1:8898;
}
server {
listen 80;
server_name wg.test.com;
location / {
### 指定上游服務器負載均衡服務器
proxy_pass http://backServer/;
###nginx與上游服務器(真實訪問的服務器)超時時間 後端服務器連接的超時時間_發起握手等候響應超時時間
proxy_connect_timeout 5s;
###nginx發送給上游服務器(真實訪問的服務器)超時時間
proxy_send_timeout 5s;
### nginx接受上游服務器(真實訪問的服務器)超時時間
proxy_read_timeout 5s;
index index.html index.htm;
}
Yml配置文件連接
tm:
manager:
# url: http://127.0.0.1:8899/tx/manager/
url: http://lcn.test.com/tx/manager/