微服务和分布式交易

这篇文章解释了LIXAXTA如何实现多语言分布式事务系统的开发,希望对正在学习中的你有用!

 

两阶段提交协议是在大型机(如大型机和UNIX服务器)时代设计的。 XA规范是在1991年定义的,当时典型的部署模型是将所有软件安装在单个服务器中。 令人惊讶的是,可以重复使用规范的一致部分以支持基于微服务的体系结构内的分布式事务。 这篇文章解释了LIXA和XTA如何实现多语言分布式事务系统的开发。

介绍

简而言之,两阶段提交协议[1]是需要两个阶段的专门共识协议。 在第一阶段,即投票阶段,所有相关资源都需要“准备”:积极的回答意味着已经达到“持久”状态。 第二阶段,即提交阶段,确认所有相关资源的新状态。 发生错误时,协议回滚并且所有资源都达到先前的状态。

XA规范

XA规范[2]是两阶段提交协议的最常见实现之一:它在九十年代开发的许多中间件中得到了支持,并且在JTA规范[3]中得到了利用。

历史验收

自从两阶段提交协议开始以来,就一直在争论中。一方面,发烧友试图在任何情况下都使用它。另一方面,批评者在所有情况下都避免了这种情况。

必须报告的第一个注意事项与性能有关:对于每个共识协议,两阶段提交都会增加事务花费的时间。这种副作用是无法避免的,必须在设计时加以考虑。

 

甚至众所周知,某些资源管理器在管理XA事务时会受到可伸缩性限制的影响:这种行为更多地取决于实现的质量,而不是两阶段提交协议本身。

 

滥用两阶段提交会严重损害分布式系统的性能,但是在显而易见的解决方案中尝试避免使用它会导致巴洛克式和过度设计的系统难以维护。更具体地说,当必须确保事务行为并且不使用诸如两阶段提交之类的共识协议时,对现有服务的集成需要进行认真的重新设计。

两阶段提交协议和微服务

许多作者不鼓励在微服务领域同时使用XA标准和两阶段提交协议。 读者可以在参考部分[4]中找到一些帖子。

无论如何,这篇文章提供了一些支持分布式事务的开发工具。 读者可以在几分钟内尝试使用它们,并评估重用它们的机会。

体系结构

LIXA是一个事务管理器,它实现两阶段提交并支持XA规范。 它是根据GNU公共许可证和次级GNU公共许可证的条款许可的免费开源软件; 可以从GitHubSourceForge [5]免费下载。

XTA代表XA Transaction API,它是一个旨在满足当代编程需求的接口:它重用了LIXA项目开发的核心组件,并引入了新的编程模型(请参见下文)。 XTA当前可用于CC ++JavaPython。 它可以从源代码编译并安装在Linux系统中,也可以作为Docker容器执行。

 微服务和分布式交易_第1张图片

 

 

 

上图显示了有关XTA体系结构的主要情况:与编程语言无关,“应用程序”直接与XTA交互以管理事务,并与一个或多个“资源管理器”交互以管理持久数据。 典型的资源管理器是MySQL / MariaDBPostgreSQL

 微服务和分布式交易_第2张图片

 

 

 

上图显示了本文中解释的示例实现的高级体系结构:

  • 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支持的模式之一:“多个应用程序,并发分支/伪同步”。

 微服务和分布式交易_第3张图片

 

 

上面的图描述了客户端和服务器之间的交互。

序列图的事务部分由红色虚线框界定。

洋红色的虚线框包含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支持开发使用多种资源管理器和任何通信协议的多语言交易系统,而无需某种应用程序管理器。 

 

抽丝剥茧,细说架构那些事 通过 优锐课的微服务体系分享,自己又掌握了新技能~

如有不足之处,欢迎朋友们评论补充!

你可能感兴趣的:(微服务和分布式交易)