這篇文章解釋了LIXA和XTA如何實現多語言分佈式事務系統的開發,但願對正在學習中的你有用!java
簡而言之,兩階段提交協議[1]是須要兩個階段的專門共識協議。 在第一階段,即投票階段,全部相關資源都須要「準備」:積極的回答意味着已經達到「持久」狀態。 第二階段,即提交階段,確認全部相關資源的新狀態。 發生錯誤時,協議回滾而且全部資源都達到先前的狀態。mysql
XA規範[2]是兩階段提交協議的最多見實現之一:它在九十年代開發的許多中間件中獲得了支持,而且在JTA規範[3]中獲得了利用。git
歷史驗收github
自從兩階段提交協議開始以來,就一直在爭論中。一方面,發燒友試圖在任何狀況下都使用它。另外一方面,批評者在全部狀況下都避免了這種狀況。sql
必須報告的第一個注意事項與性能有關:對於每一個共識協議,兩階段提交都會增長事務花費的時間。這種反作用是沒法避免的,必須在設計時加以考慮。docker
甚至衆所周知,某些資源管理器在管理XA事務時會受到可伸縮性限制的影響:這種行爲更多地取決於實現的質量,而不是兩階段提交協議自己。數據庫
濫用兩階段提交會嚴重損害分佈式系統的性能,可是在顯而易見的解決方案中嘗試避免使用它會致使巴洛克式和過分設計的系統難以維護。更具體地說,當必須確保事務行爲而且不使用諸如兩階段提交之類的共識協議時,對現有服務的集成須要進行認真的從新設計。編程
許多做者不鼓勵在微服務領域同時使用XA標準和兩階段提交協議。 讀者能夠在參考部分[4]中找到一些帖子。服務器
不管如何,這篇文章提供了一些支持分佈式事務的開發工具。 讀者能夠在幾分鐘內嘗試使用它們,並評估重用它們的機會。架構
LIXA是一個事務管理器,它實現兩階段提交併支持XA規範。 它是根據GNU公共許可證和次級GNU公共許可證的條款許可的免費開源軟件; 能夠從GitHub和SourceForge [5]免費下載。
XTA表明XA Transaction API,它是一個旨在知足當代編程需求的接口:它重用了LIXA項目開發的核心組件,並引入了新的編程模型(請參見下文)。 XTA當前可用於C,C ++,Java和Python。 它能夠從源代碼編譯並安裝在Linux系統中,也能夠做爲Docker容器執行。
上圖顯示了有關XTA體系結構的主要狀況:與編程語言無關,「應用程序」直接與XTA交互以管理事務,並與一個或多個「資源管理器」交互以管理持久數據。 典型的資源管理器是MySQL / MariaDB和PostgreSQL。
上圖顯示了本文中解釋的示例實現的高級體系結構:
··「 lixad」是LIXA狀態服務器,它表明XTA進程保留事務狀態。
爲了簡便起見,全部組件都做爲Docker容器執行[6],可是也能夠完成傳統安裝。
在開始以前,你須要一個具備兩個基本工具的操做系統:git和Docker。 二者都是免費提供的,而且易於在Linux,MacOS和Windows中進行設置。
首先,克隆包含所需內容的git存儲庫:
1 git clone https://github.com/tiian/lixa-docker.git
而後轉到示例目錄:
1 cd lixa-docker/examples/PythonJavaREST
爲「 rest-client」和「 rest-server」構建Docker鏡像:
1 docker build -f Dockerfile-client -t rest-client . 2 3 docker build -f Dockerfile-server -t rest-server .
檢查生成的圖像,你應該看到相似如下內容的圖像:
1 docker images | grep rest 2 3 rest-server latest 81eda2af0fd4 25 hours ago 731MB 4 5 rest-client latest 322a3a26e040 25 hours ago 390MB
啓動MySQL,PostgreSQL和LIXA狀態服務器(lixad):
1 docker run --rm -e MYSQL_ROOT_PASSWORD=mysecretpw -p 3306:3306 -d lixa/mysql 2 3 docker run --rm -e POSTGRES_PASSWORD=lixa -p 5432:5432 -d lixa/postgres -c 'max_prepared_transactions=10' 4 5 docker run --rm -p 2345:2345 -d lixa/lixad
檢查啓動的容器,你應該看到相似如下內容的內容:
1 docker ps | grep lixa 2 3 16099992bd82 lixa/lixad "/home/lixa/lixad-en…" 6 seconds ago Up 3 seconds 0.0.0.0:2345->2345/tcp sharp_yalow 4 5 15297ed6ebb1 lixa/postgres "docker-entrypoint.s…" 13 seconds ago Up 9 seconds 0.0.0.0:5432->5432/tcp unruffled_brahmagupta 6 7 3275a2738237 lixa/mysql "docker-entrypoint.s…" 21 seconds ago Up 18 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp sharp_wilson
激活Java服務(用Docker主機的IP地址替換IP地址「 192.168.123.35」):
1 docker run -ti --rm -e MAVEN_OPTS="-Djava.library.path=/opt/lixa/lib" -e LIXA_STATE_SERVERS="tcp://192.168.123.35:2345/default" -e PQSERVER="192.168.123.35" -p 18080:8080 rest-server
等待服務就緒,而後在控制檯中檢查如下消息:
1 Mar 24, 2019 9:21:45 PM org.glassfish.grizzly.http.server.NetworkListener start 2 3 INFO: Started listener bound to [0.0.0.0:8080] 4 5 Mar 24, 2019 9:21:45 PM org.glassfish.grizzly.http.server.HttpServer start 6 7 INFO: [HttpServer] Started. 8 9 Jersey app started with WADL available at http://0.0.0.0:8080/xta/application.wadl 10 11 Hit enter to stop it...
從另外一個終端啓動Python客戶端(用Docker主機的IP地址替換IP地址「 192.168.123.35」):
1 docker run -ti --rm -e SERVER="192.168.123.35" -e LIXA_STATE_SERVERS="tcp://192.168.123.35:2345/default" rest-client
此時,你應該在Java服務的控制檯中看到一些消息:
1 ***** REST service called: xid='1279875137.c5b6d8bf7d584065b4ee29d62fe35ab5.ac3d62eb862b49fe6a50bfee46d142b9', oper='delete' ***** 2 3 2019-03-24 21:23:04.047857 [1/139693866854144] INFO: LXC000I this process is starting a new LIXA transaction manager (lixa package version is 1.7.6) 4 5 Created a subordinate branch with XID '1279875137.c5b6d8bf7d584065b4ee29d62fe35ab5.ac3d62eb862b49fe9de52bd41657494a' 6 7 PostgreSQL: executing SQL statement >DELETE FROM authors WHERE id=1804< 8 9 Executing first phase of commit (prepare) 10 11 Returning 'PREPARED' to the client 12 13 Executing second phase of commit 14 15 ***** REST service called: xid='1279875137.91f1712af1164dd38dcc0b14d58819d2.ac3d62eb862b49fe6a50bfee46d142b9', oper='insert' ***** 16 17 Created a subordinate branch with XID '1279875137.91f1712af1164dd38dcc0b14d58819d2.ac3d62eb862b49fe20cca6a7fc7c4802' 18 19 PostgreSQL: executing SQL statement >INSERT INTO authors VALUES(1804, 'Hawthorne', 'Nathaniel')< 20 21 Executing first phase of commit (prepare) 22 23 Returning 'PREPARED' to the client 24 25 Executing second phase of commit
還有Python客戶端控制檯中的其餘消息:
1 2019-03-24 21:23:03.909808 [1/140397122036736] INFO: LXC000I this process is starting a new LIXA transaction manager (lixa package version is 1.7.6) 2 3 ***** REST client ***** 4 5 MySQL: executing SQL statement >DELETE FROM authors WHERE id=1840< 6 7 Calling REST service passing: xid='1279875137.c5b6d8bf7d584065b4ee29d62fe35ab5.ac3d62eb862b49fe6a50bfee46d142b9', oper='delete' 8 9 Server replied >PREPARED< 10 11 Executing transaction commit 12 13 ***** REST client ***** 14 15 MySQL: executing SQL statement >INSERT INTO authors VALUES(1840, 'Zola', 'Emile')< 16 17 Calling REST service passing: xid='1279875137.91f1712af1164dd38dcc0b14d58819d2.ac3d62eb862b49fe6a50bfee46d142b9', oper='insert' 18 19 Server replied >PREPARED< 20 21 Executing transaction commit
執行說明
Python客戶端:
Java服務
其他步驟對第二個SQL語句(「 INSERT」)重複相同的操做。
上面描述的客戶端/服務器示例實現了XTA支持的模式之一:「多個應用程序,併發分支/僞同步」。
上面的圖描述了客戶端和服務器之間的交互。
序列圖的事務部分由紅色虛線框界定。
洋紅色的虛線框包含REST調用和提交協議的第一階段:tx.commit()被稱爲傳遞「 true」做爲「非阻塞」參數的值。
藍色虛線框包含提交協議的第二階段。
該圖未顯示「 rest-client」,「 rest-server」和LIXA狀態服務器之間的交互:
最後,兩個參與者均可以繼續執行提交協議的第二階段。
若是在客戶端或服務器端發生崩潰,則當事方會回滾或自動恢復第二次:有關這些狀況的說明留待之後發表。
提供如下代碼,以顯示必須如何使用XTA對象和方法。
忽略樣板和腳手架,這是Python客戶端源代碼中有趣的部分:
1 # initialize XTA environment 2 3 Xta_init() 4 5 # create a new MySQL connection 6 7 # Note: using MySQLdb functions 8 9 rm = MySQLdb.connect(host=hostname, user="lixa", password="passw0rd", db="lixa") 10 11 # create a new XTA Transaction Manager object 12 13 tm = TransactionManager() 14 15 # create an XA resource for MySQL 16 17 # second parameter "MySQL" is descriptive 18 19 # third parameter "localhost,0,lixa,,lixa" identifies the specific database 20 21 xar = MysqlXaResource(rm._get_native_connection(), "MySQL", 22 23 hostname + "/lixa") 24 25 # Create a new XA global transaction and retrieve a reference from 26 27 # the TransactionManager object 28 29 tx = tm.createTransaction() 30 31 # Enlist MySQL resource to transaction 32 33 tx.enlistResource(xar) 34 35 sys.stdout.write("***** REST client *****\n") 36 37 # Start a new XA global transaction with multiple branches 38 39 tx.start(True) 40 41 # Execute DELETE statement 42 43 sys.stdout.write("MySQL: executing SQL statement >" + delete_stmt + "<\n") 44 45 cur = rm.cursor() 46 47 cur.execute(delete_stmt) 48 49 # Retrieving xid 50 51 xid = tx.getXid().toString() 52 53 # Calling server passing xid 54 55 sys.stdout.write("Calling REST service passing: xid='" + xid + "', oper='delete'\n") 56 57 r = requests.post("http://" + hostname + ":18080/xta/myresource", 58 59 data={'xid':xid, 'oper':'delete'}) 60 61 sys.stdout.write("Server replied >" + r.text + "<\n") 62 63 # Commit the transaction 64 65 sys.stdout.write("Executing transaction commit\n") 66 67 tx.commit()
忽略樣板和腳手架,該示例使用Jersey框架,這是Java服務器源代碼中有趣的部分:
1 // 1. create an XA Data Source 2 3 xads = new PGXADataSource(); 4 5 // 2. set connection parameters (one property at a time) 6 7 xads.setServerName(System.getenv("PQSERVER")); 8 9 xads.setDatabaseName("lixa"); 10 11 xads.setUser("lixa"); 12 13 xads.setPassword("passw0rd"); 14 15 // 3. get an XA Connection from the XA Data Source 16 17 xac = xads.getXAConnection(); 18 19 // 4. get an XA Resource from the XA Connection 20 21 xar = xac.getXAResource(); 22 23 // 5. get an SQL Connection from the XA Connection 24 25 conn = xac.getConnection(); 26 27 // 28 29 // XTA code 30 31 // 32 33 // Create a mew XTA Transaction Manager 34 35 tm = new TransactionManager(); 36 37 // Create a new XA global transaction using the Transaction 38 39 // Manager as a factory 40 41 tx = tm.createTransaction(); 42 43 // Enlist PostgreSQL resource to transaction 44 45 tx.enlistResource(xar, "PostgreSQL", 46 47 System.getenv("PQSERVER") + ";u=lixa;db=lixa"); 48 49 // create a new branch in the same global transaction 50 51 tx.branch(xid); 52 53 System.out.println("Created a subordinate branch " + 54 55 "with XID '" + tx.getXid().toString() + "'"); 56 57 // 58 59 // Create and Execute a JDBC statement for PostgreSQL 60 61 // 62 63 System.out.println("PostgreSQL: executing SQL statement >" + 64 65 sqlStatement + "<"); 66 67 // create a Statement object 68 69 stmt = conn.createStatement(); 70 71 // Execute the statement 72 73 stmt.executeUpdate(sqlStatement); 74 75 // close the statement 76 77 stmt.close(); 78 79 // perform first phase of commit (PREPARE ONLY) 80 81 System.out.println("Executing first phase of commit (prepare)"); 82 83 tx.commit(true); 84 85 // start a backgroud thread: control must be returned to the client 86 87 // but finalization must go on in parallel 88 89 new Thread(new Runnable() { 90 91 @Override 92 93 public void run() { 94 95 try { 96 97 // perform second phase of commit 98 99 System.out.println("Executing second phase of commit"); 100 101 tx.commit(false); 102 103 // Close Statement, SQL Connection and XA 104 105 // Connection for PostgreSQL 106 107 stmt.close(); 108 109 conn.close(); 110 111 xac.close(); 112 113 } catch (XtaException e) { 114 115 System.err.println("XtaException: LIXA ReturnCode=" + 116 117 e.getReturnCode() + " ('" + 118 119 e.getMessage() + "')"); 120 121 e.printStackTrace(); 122 123 } catch (Exception e) { 124 125 e.printStackTrace(); 126 127 } 128 129 } 130 131 }).start(); 132 133 System.out.println("Returning 'PREPARED' to the client"); 134 135 return "PREPARED";
LIXA的設計和開發具備清晰明確的體系結構:全部事務信息都由狀態服務器(lixad)保留,大多數事務邏輯由客戶端庫(lixac及其派生類)管理。 「邏輯」與「狀態」之間的強大去耦關係使事務管理功能能夠嵌入「應用程序」中,而無需框架或應用服務器。
XTA進一步推進了「分佈式事務」的概念:客戶端和服務器沒必要由任何類型的「主管中間件」執行,由於它們能夠自動協調本身與LIXA狀態服務器的交互。客戶端必須傳遞給服務器的惟一信息是XID的ASCII字符串表示形式。過去實施的其餘方法須要在應用服務器之間進行配置和協調;大多數時候,甚至通訊協議也必須瞭解事務性方面。
此外,XTA在同一個全局事務中支持多個客戶端/服務器:同一個XID能夠由許多被稱爲服務的分支,甚至是分層的。
XTA支持同步協議(如本示例中所示的RESTful),以及經過不一樣模式(「多個應用程序,併發分支/僞異步」)的異步協議。
結論
XTA API與LIXA狀態服務器結合使用,能夠開發實現2個或更多應用程序(服務)之間的ACID [7]分佈式事務的系統。 XTA支持開發使用多種資源管理器和任何通訊協議的多語言交易系統,而無需某種應用程序管理器。
抽絲剝繭,細說架構那些事 經過 優銳課的微服務體系分享,本身又掌握了新技能~
若有不足之處,歡迎朋友們評論補充!