微服務和分佈式交易

這篇文章解釋了LIXAXTA如何實現多語言分佈式事務系統的開發,但願對正在學習中的你有用!java

 

兩階段提交協議是在大型機(如大型機和UNIX服務器)時代設計的。 XA規範是在1991年定義的,當時典型的部署模型是將全部軟件安裝在單個服務器中。 使人驚訝的是,能夠重複使用規範的一致部分以支持基於微服務的體系結構內的分佈式事務。 這篇文章解釋了LIXA和XTA如何實現多語言分佈式事務系統的開發。

介紹

簡而言之,兩階段提交協議[1]是須要兩個階段的專門共識協議。 在第一階段,即投票階段,全部相關資源都須要「準備」:積極的回答意味着已經達到「持久」狀態。 第二階段,即提交階段,確認全部相關資源的新狀態。 發生錯誤時,協議回滾而且全部資源都達到先前的狀態。mysql

XA規範

XA規範[2]是兩階段提交協議的最多見實現之一:它在九十年代開發的許多中間件中獲得了支持,而且在JTA規範[3]中獲得了利用。git

歷史驗收github

自從兩階段提交協議開始以來,就一直在爭論中。一方面,發燒友試圖在任何狀況下都使用它。另外一方面,批評者在全部狀況下都避免了這種狀況。sql

必須報告的第一個注意事項與性能有關:對於每一個共識協議,兩階段提交都會增長事務花費的時間。這種反作用是沒法避免的,必須在設計時加以考慮。docker

 

甚至衆所周知,某些資源管理器在管理XA事務時會受到可伸縮性限制的影響:這種行爲更多地取決於實現的質量,而不是兩階段提交協議自己。數據庫

 

濫用兩階段提交會嚴重損害分佈式系統的性能,可是在顯而易見的解決方案中嘗試避免使用它會致使巴洛克式和過分設計的系統難以維護。更具體地說,當必須確保事務行爲而且不使用諸如兩階段提交之類的共識協議時,對現有服務的集成須要進行認真的從新設計。編程

兩階段提交協議和微服務

許多做者不鼓勵在微服務領域同時使用XA標準和兩階段提交協議。 讀者能夠在參考部分[4]中找到一些帖子。服務器

不管如何,這篇文章提供了一些支持分佈式事務的開發工具。 讀者能夠在幾分鐘內嘗試使用它們,並評估重用它們的機會。架構

體系結構

LIXA是一個事務管理器,它實現兩階段提交併支持XA規範。 它是根據GNU公共許可證和次級GNU公共許可證的條款許可的免費開源軟件; 能夠從GitHubSourceForge [5]免費下載。

XTA表明XA Transaction API,它是一個旨在知足當代編程需求的接口:它重用了LIXA項目開發的核心組件,並引入了新的編程模型(請參見下文)。 XTA當前可用於CC ++JavaPython。 它能夠從源代碼編譯並安裝在Linux系統中,也能夠做爲Docker容器執行。

 

 

 

 

上圖顯示了有關XTA體系結構的主要狀況:與編程語言無關,「應用程序」直接與XTA交互以管理事務,並與一個或多個「資源管理器」交互以管理持久數據。 典型的資源管理器是MySQL / MariaDBPostgreSQL

 

 

 

 

上圖顯示了本文中解釋的示例實現的高級體系結構:

  • rest-client」是使用Python 3開發的客戶端程序; 它將數據持久保存在MySQL數據庫中。
  • rest-server」是用Java實現的服務,經過REST API進行調用; 它將數據持久存儲在PostgreSQL數據庫中。

··「 lixad」是LIXA狀態服務器,它表明XTA進程保留事務狀態。

 

爲了簡便起見,全部組件都做爲Docker容器執行[6],可是也能夠完成傳統安裝。

執行示例

在開始以前,你須要一個具備兩個基本工具的操做系統:gitDocker。 二者都是免費提供的,而且易於在LinuxMacOSWindows中進行設置。

設定

首先,克隆包含所需內容的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

 

啓動MySQLPostgreSQLLIXA狀態服務器(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客戶端

  • 1行:客戶啓動其事務管理器。·
  • 3行:客戶端在MySQL中執行SQL語句(「 DELETE」)。
  • 4行:客戶端調用Java服務,並傳遞事務標識符(xid)和所需的操做(「刪除」)。

Java服務

  • 1行:服務被調用,它接收事務標識符(xid)和所需的操做(「刪除」)。
  • 2行:服務啓動其事務管理器。
  • 3行:服務分支了全局事務,並建立了一個新的事務標識符。
  • 4行:服務在PostgreSQL中執行SQL語句(「 DELETE」)。·
  • 5行:服務執行提交協議的第一階段,「準備」 PostgreSQL
  • 6行:服務將結果「 PREPARED」返回給客戶端。·
  • 7行:服務執行提交協議的第二階段。·
  • Python客戶端:
  • 5行:客戶從服務中收到結果「 PREPARED」。·
  • 6行:客戶端執行提交協議。

 

其他步驟對第二個SQL語句(「 INSERT」)重複相同的操做。

XTA編程模型

上面描述的客戶端/服務器示例實現了XTA支持的模式之一:「多個應用程序,併發分支/僞同步」。

 

 

 

上面的圖描述了客戶端和服務器之間的交互。

序列圖的事務部分由紅色虛線框界定。

洋紅色的虛線框包含REST調用和提交協議的第一階段:tx.commit()被稱爲傳遞「 true」做爲「非阻塞」參數的值。

藍色虛線框包含提交協議的第二階段。

該圖未顯示rest-client」,「 rest-server」和LIXA狀態服務器之間的交互:

  • Java服務器的後臺線程調用tx.commitfalse)時,魔術就會發生。
  • LIXA狀態服務器識別多個分支事務,並阻塞「 rest-server」,直到「 rest-client」完成提交的第一階段。
  • 屆時,以圖中的虛線綠色標記,已達成全球共識。

最後,兩個參與者均可以繼續執行提交協議的第二階段。

若是在客戶端或服務器端發生崩潰,則當事方會回滾或自動恢復第二次:有關這些狀況的說明留待之後發表。

提供如下代碼,以顯示必須如何使用XTA對象和方法。

Python客戶端代碼

忽略樣板和腳手架,這是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()

 

  • 14行:XA資源(xar)連接到MySQL鏈接(rm)。
  • 22行:XA資源已加入事務對象。
  • 26行:全局事務已啓動。
  • 38行:客戶端調用REST服務。
  • 44行:交易已提交。

Java服務器代碼

忽略樣板和腳手架,該示例使用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";

 

  • 11行:PostgreSQL鏈接(xac)檢索XA資源(xar)。
  • 23行:XA資源已加入事務對象。
  • 26行:建立了全球交易的新分支。
  • 42行:落實協議的第一階段(「準備」)。
  • 51行:提交協議的第二階段由後臺線程執行。
  • 69行:服務將結果返回給調用方。

 

LIXA的設計和開發具備清晰明確的體系結構:全部事務信息都由狀態服務器(lixad)保留,大多數事務邏輯由客戶端庫(lixac及其派生類)管理。 「邏輯」與「狀態」之間的強大去耦關係使事務管理功能能夠嵌入「應用程序」中,而無需框架或應用服務器。

XTA進一步推進了「分佈式事務」的概念:客戶端和服務器沒必要由任何類型的「主管中間件」執行,由於它們能夠自動協調本身與LIXA狀態服務器的交互。客戶端必須傳遞給服務器的惟一信息是XIDASCII字符串表示形式。過去實施的其餘方法須要在應用服務器之間進行配置和協調;大多數時候,甚至通訊協議也必須瞭解事務性方面。

此外,XTA在同一個全局事務中支持多個客戶端/服務器:同一個XID能夠由許多被稱爲服務的分支,甚至是分層的。

XTA支持同步協議(如本示例中所示的RESTful),以及經過不一樣模式(「多個應用程序,併發分支/僞異步」)的異步協議。

結論

XTA APILIXA狀態服務器結合使用,能夠開發實現2個或更多應用程序(服務)之間的ACID [7]分佈式事務的系統。 XTA支持開發使用多種資源管理器和任何通訊協議的多語言交易系統,而無需某種應用程序管理器。 

 

抽絲剝繭,細說架構那些事 經過 優銳課的微服務體系分享,本身又掌握了新技能~

若有不足之處,歡迎朋友們評論補充!

相關文章
相關標籤/搜索