微服务和分布式交易

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介: 微服务和分布式交易

两阶段提交协议是在大型机(如大型机和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容器执行。

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

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

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

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

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

执行示例

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

设定

首先,克隆包含所需内容的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”):

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

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

 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.commitfalse)时,魔术就会发生。
  • 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状态服务器的交互。客户端必须传递给服务器的唯一信息是XIDASCII字符串表示形式。过去实施的其他方法需要在应用服务器之间进行配置和协调;大多数时候,甚至通信协议也必须了解事务性方面。

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

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

结论

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

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

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



相关文章
|
2月前
|
负载均衡 监控 Dubbo
Java微服务架构设计与实践:构建可伸缩的分布式系统
【4月更文挑战第2天】微服务架构响应现代业务需求,通过拆分大型应用为独立服务实现模块化和可扩展性。Java中的Spring Boot和Dubbo等框架支持服务注册、负载均衡等功能。遵循单一职责、自治性和面向接口原则,每个服务专注特定逻辑,独立部署运行。实际项目中,如电商系统,服务按功能拆分,提升可维护性和扩展性。还需考虑服务通信、数据一致性和监控等复杂话题。Java微服务架构助力构建高效、灵活的应用,应对未来挑战。
Java微服务架构设计与实践:构建可伸缩的分布式系统
|
2月前
|
消息中间件 分布式计算 中间件
秀出天际!阿里甩出的988页分布式微服务架构进阶神仙手册我粉了
秀出天际!阿里甩出的988页分布式微服务架构进阶神仙手册我粉了
|
2月前
|
SpringCloudAlibaba Java 持续交付
【构建一套Spring Cloud项目的大概步骤】&【Springcloud Alibaba微服务分布式架构学习资料】
【构建一套Spring Cloud项目的大概步骤】&【Springcloud Alibaba微服务分布式架构学习资料】
298 0
|
6天前
|
存储 搜索推荐 Java
微服务SpringCloud ES分布式全文搜索引擎简介 下载安装及简单操作入门
微服务SpringCloud ES分布式全文搜索引擎简介 下载安装及简单操作入门
21 2
|
3天前
|
存储 运维 Prometheus
微服务监控:确保分布式系统的可观察性与稳定性
微服务监控:确保分布式系统的可观察性与稳定性
|
2月前
|
Java 持续交付 API
Java的分布式系统与微服务架构
Java的分布式系统与微服务架构
|
9天前
|
存储 消息中间件 数据库
分布式系统详解--架构简介(微服务)
分布式系统详解--架构简介(微服务)
25 0
|
2月前
|
缓存 负载均衡 监控
探索分布式系统演进之路:从负载均衡到微服务架构
小米分享了分布式系统的发展,从早期的负载均衡(入口级、网关和客户端)到微服务架构的演进。微服务实现服务解耦,增强系统弹性,但带来了新的挑战。为优化数据库性能,实施了主备读写分离、全文搜索引擎、缓存集群等措施。通过微服务治理,如服务注册、动态配置、灰度发布等,提升了系统稳定性和可靠性。未来将继续优化分布式系统,提供更好的服务体验。关注公众号“软件求生”了解更多。
46 6
|
25天前
|
存储 缓存 NoSQL
了解Redis,第一弹,什么是RedisRedis主要适用于分布式系统,用来用缓存,存储数据,在内存中存储那么为什么说是分布式呢?什么叫分布式什么是单机架构微服务架构微服务的本质
了解Redis,第一弹,什么是RedisRedis主要适用于分布式系统,用来用缓存,存储数据,在内存中存储那么为什么说是分布式呢?什么叫分布式什么是单机架构微服务架构微服务的本质
|
2月前
|
算法 Serverless 调度
基于分布式ADMM算法的考虑碳排放交易的电力系统优化调度研究(matlab代码)
基于分布式ADMM算法的考虑碳排放交易的电力系统优化调度研究(matlab代码)