您的位置:首页 > 其它

微服务和分布式交易

2019-12-28 09:59 1691 查看

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

 

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

介绍

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

XA规范

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

历史验收

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

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

 

甚至众所周知,某些资源管理器在管理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。

 

 

 

 

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

  • “ rest-client”是使用Python 3开发的客户端程序; 它将数据持久保存在MySQL数据库中。
  • “ rest-server”是用Java实现的服务,通过REST API进行调用; 它将数据持久存储在PostgreSQL数据库中。

··“ lixad”是LIXA状态服务器,它代表XTA进程保留事务状态。

 

为了简便起见,所有组件都作为Docker容器执行[6],但是也可以完成传统安装。

执行示例

在开始之前,你需要一个具有两个基本工具的操作系统:git和Docker。 两者都是免费提供的,并且易于在Linux,MacOS和Windows中进行设置。

设定

首先,克隆包含所需内容的git存储库:

git clone https://github.com/tiian/lixa-docker.git

 

然后转到示例目录:

cd lixa-docker/examples/PythonJavaREST

 

为“ rest-client”和“ rest-server”构建Docker镜像:

docker build -f Dockerfile-client -t rest-client .

docker build -f Dockerfile-server -t rest-server .

 

检查生成的图像,你应该看到类似以下内容的图像:

docker images | grep rest

rest-server          latest              81eda2af0fd4        25 hours ago        731MB

rest-client          latest              322a3a26e040        25 hours ago        390MB

 

启动MySQL,PostgreSQL和LIXA状态服务器(lixad):

docker run --rm -e MYSQL_ROOT_PASSWORD=mysecretpw -p 3306:3306 -d lixa/mysql

docker run --rm -e POSTGRES_PASSWORD=lixa -p 5432:5432 -d lixa/postgres -c 'max_prepared_transactions=10'

docker run --rm -p 2345:2345 -d lixa/lixad

 

检查启动的容器,你应该看到类似以下内容的内容:

docker ps | grep lixa

16099992bd82        lixa/lixad          "/home/lixa/lixad-en…"   6 seconds ago       Up 3 seconds        0.0.0.0:2345->2345/tcp              sharp_yalow

15297ed6ebb1        lixa/postgres       "docker-entrypoint.s…"   13 seconds ago      Up 9 seconds        0.0.0.0:5432->5432/tcp              unruffled_brahmagupta

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”):

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

 

等待服务就绪,然后在控制台中检查以下消息:

Mar 24, 2019 9:21:45 PM org.glassfish.grizzly.http.server.NetworkListener start

INFO: Started listener bound to [0.0.0.0:8080]

Mar 24, 2019 9:21:45 PM org.glassfish.grizzly.http.server.HttpServer start

INFO: [HttpServer] Started.

Jersey app started with WADL available at http://0.0.0.0:8080/xta/application.wadl

Hit enter to stop it...

 

从另一个终端启动Python客户端(用Docker主机的IP地址替换IP地址“ 192.168.123.35”):

docker run -ti --rm -e SERVER="192.168.123.35" -e LIXA_STATE_SERVERS="tcp://192.168.123.35:2345/default" rest-client

 

此时,你应该在Java服务的控制台中看到一些消息:

***** REST service called: xid='1279875137.c5b6d8bf7d584065b4ee29d62fe35ab5.ac3d62eb862b49fe6a50bfee46d142b9', oper='delete' *****

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)

Created a subordinate branch with XID '1279875137.c5b6d8bf7d584065b4ee29d62fe35ab5.ac3d62eb862b49fe9de52bd41657494a'

PostgreSQL: executing SQL statement >DELETE FROM authors WHERE id=1804<

Executing first phase of commit (prepare)

Returning 'PREPARED' to the client

Executing second phase of commit

***** REST service called: xid='1279875137.91f1712af1164dd38dcc0b14d58819d2.ac3d62eb862b49fe6a50bfee46d142b9', oper='insert' *****

Created a subordinate branch with XID '1279875137.91f1712af1164dd38dcc0b14d58819d2.ac3d62eb862b49fe20cca6a7fc7c4802'

PostgreSQL: executing SQL statement >INSERT INTO authors VALUES(1804, 'Hawthorne', 'Nathaniel')<

Executing first phase of commit (prepare)

Returning 'PREPARED' to the client

Executing second phase of commit

 

还有Python客户端控制台中的其他消息:

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)

***** REST client *****

MySQL: executing SQL statement >DELETE FROM authors WHERE id=1840<

Calling REST service passing: xid='1279875137.c5b6d8bf7d584065b4ee29d62fe35ab5.ac3d62eb862b49fe6a50bfee46d142b9', oper='delete'

Server replied >PREPARED<

Executing transaction commit

***** REST client *****

MySQL: executing SQL statement >INSERT INTO authors VALUES(1840, 'Zola', 'Emile')<

Calling REST service passing: xid='1279875137.91f1712af1164dd38dcc0b14d58819d2.ac3d62eb862b49fe6a50bfee46d142b9', oper='insert'

Server replied >PREPARED<

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.commit(false)时,魔术就会发生。
  • LIXA状态服务器识别多个分支事务,并阻塞“ rest-server”,直到“ rest-client”完成提交的第一阶段。
  • 届时,以图中的虚线绿色标记,已达成全球共识。

最后,两个参与者都可以继续执行提交协议的第二阶段。

如果在客户端或服务器端发生崩溃,则当事方会回滚或自动恢复第二次:有关这些情况的说明留待以后发表。

提供以下代码,以显示必须如何使用XTA对象和方法。

Python客户端代码

忽略样板和脚手架,这是Python客户端源代码中有趣的部分:

# initialize XTA environment

Xta_init()

# create a new MySQL connection

# Note: using MySQLdb functions

rm = MySQLdb.connect(host=hostname, user="lixa", password="passw0rd", db="lixa")

# create a new XTA Transaction Manager object

tm = TransactionManager()

# create an XA resource for MySQL

# second parameter "MySQL" is descriptive

# third parameter "localhost,0,lixa,,lixa" identifies the specific database

xar = MysqlXaResource(rm._get_native_connection(), "MySQL",

hostname + "/lixa")

# Create a new XA global transaction and retrieve a reference from

# the TransactionManager object

tx = tm.createTransaction()

# Enlist MySQL resource to transaction

tx.enlistResource(xar)

sys.stdout.write("***** REST client *****\n")

# Start a new XA global transaction with multiple branches

tx.start(True)

# Execute DELETE statement

sys.stdout.write("MySQL: executing SQL statement >" + delete_stmt + "<\n")

cur = rm.cursor()

cur.execute(delete_stmt)

# Retrieving xid

xid = tx.getXid().toString()

# Calling server passing xid

sys.stdout.write("Calling REST service passing: xid='" + xid + "', oper='delete'\n")

r = requests.post("http://" + hostname + ":18080/xta/myresource",

data={'xid':xid, 'oper':'delete'})

sys.stdout.write("Server replied >" + r.text + "<\n")

# Commit the transaction

sys.stdout.write("Executing transaction commit\n")

tx.commit()

 

  • 第14行:XA资源(xar)链接到MySQL连接(rm)。
  • 第22行:XA资源已加入事务对象。
  • 第26行:全局事务已启动。
  • 第38行:客户端调用REST服务。
  • 第44行:交易已提交。

Java服务器代码

忽略样板和脚手架,该示例使用Jersey框架,这是Java服务器源代码中有趣的部分:

// 1. create an XA Data Source

xads = new PGXADataSource();

// 2. set connection parameters (one property at a time)

xads.setServerName(System.getenv("PQSERVER"));

xads.setDatabaseName("lixa");

xads.setUser("lixa");

xads.setPassword("passw0rd");

// 3. get an XA Connection from the XA Data Source

xac = xads.getXAConnection();

// 4. get an XA Resource from the XA Connection

xar = xac.getXAResource();

// 5. get an SQL Connection from the XA Connection

conn = xac.getConnection();

//

// XTA code

//

// Create a mew XTA Transaction Manager

tm = new TransactionManager();

// Create a new XA global transaction using the Transaction

// Manager as a factory

tx = tm.createTransaction();

// Enlist PostgreSQL resource to transaction

tx.enlistResource(xar, "PostgreSQL",

System.getenv("PQSERVER") + ";u=lixa;db=lixa");

// create a new branch in the same global transaction

tx.branch(xid);

System.out.println("Created a subordinate branch " +

"with XID '" + tx.getXid().toString() + "'");

//

// Create and Execute a JDBC statement for PostgreSQL

//

System.out.println("PostgreSQL: executing SQL statement >" +

sqlStatement + "<");

// create a Statement object

stmt = conn.createStatement();

// Execute the statement

stmt.executeUpdate(sqlStatement);

// close the statement

stmt.close();

// perform first phase of commit (PREPARE ONLY)

System.out.println("Executing first phase of commit (prepare)");

tx.commit(true);

// start a backgroud thread: control must be returned to the client

// but finalization must go on in parallel

new Thread(new Runnable() {

@Override

public void run() {

try {

// perform second phase of commit

System.out.println("Executing second phase of commit");

tx.commit(false);

// Close Statement, SQL Connection and XA

// Connection for PostgreSQL

stmt.close();

conn.close();

xac.close();

} catch  (XtaException e) {

System.err.println("XtaException: LIXA ReturnCode=" +

e.getReturnCode() + " ('" +

e.getMessage() + "')");

e.printStackTrace();

} catch (Exception e) {

e.printStackTrace();

}

}

}).start();

System.out.println("Returning 'PREPARED' to the client");

return "PREPARED";

 

  • 第11行:PostgreSQL连接(xac)检索XA资源(xar)。
  • 第23行:XA资源已加入事务对象。
  • 第26行:创建了全球交易的新分支。
  • 第42行:落实协议的第一阶段(“准备”)。
  • 第51行:提交协议的第二阶段由后台线程执行。
  • 第69行:服务将结果返回给调用方。

 

LIXA的设计和开发具有清晰明确的体系结构:所有事务信息都由状态服务器(lixad)保留,大多数事务逻辑由客户端库(lixac及其派生类)管理。 “逻辑”与“状态”之间的强大去耦关系使事务管理功能可以嵌入“应用程序”中,而无需框架或应用服务器。

XTA进一步推动了“分布式事务”的概念:客户端和服务器不必由任何类型的“主管中间件”执行,因为它们可以自动协调自己与LIXA状态服务器的交互。客户端必须传递给服务器的唯一信息是XID的ASCII字符串表示形式。过去实施的其他方法需要在应用服务器之间进行配置和协调;大多数时候,甚至通信协议也必须了解事务性方面。

此外,XTA在同一个全局事务中支持多个客户端/服务器:同一个XID可以由许多被称为服务的分支,甚至是分层的。

XTA支持同步协议(如本示例中所示的RESTful),以及通过不同模式(“多个应用程序,并发分支/伪异步”)的异步协议。

结论

XTA API与LIXA状态服务器结合使用,可以开发实现2个或更多应用程序(服务)之间的ACID [7]分布式事务的系统。 XTA支持开发使用多种资源管理器和任何通信协议的多语言交易系统,而无需某种应用程序管理器。 

 

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

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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: