diff --git a/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg b/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg deleted file mode 100644 index 11bda09284..0000000000 Binary files a/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg and /dev/null differ diff --git a/blog/2021-08-26-welcome/index.md b/blog/2021-08-26-welcome/index.md deleted file mode 100644 index 9455168f17..0000000000 --- a/blog/2021-08-26-welcome/index.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -slug: welcome -title: Welcome -authors: [slorber, yangshun] -tags: [facebook, hello, docusaurus] ---- - -[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog). - -Simply add Markdown files (or folders) to the `blog` directory. - -Regular blog authors can be added to `authors.yml`. - -The blog post date can be extracted from filenames, such as: - -- `2019-05-30-welcome.md` -- `2019-05-30-welcome/index.md` - -A blog post folder can be convenient to co-locate blog post images: - -![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg) - -The blog supports tags as well! - -**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config. diff --git a/blog/authors.yml b/blog/authors.yml deleted file mode 100644 index bcb2991563..0000000000 --- a/blog/authors.yml +++ /dev/null @@ -1,17 +0,0 @@ -endi: - name: Endilie Yacop Sucipto - title: Maintainer of Docusaurus - url: https://github.com/endiliey - image_url: https://github.com/endiliey.png - -yangshun: - name: Yangshun Tay - title: Front End Engineer @ Facebook - url: https://github.com/yangshun - image_url: https://github.com/yangshun.png - -slorber: - name: Sébastien Lorber - title: Docusaurus maintainer - url: https://sebastienlorber.com - image_url: https://github.com/slorber.png diff --git a/blog/design-more-flexable-application-by-saga.md b/blog/design-more-flexable-application-by-saga.md index c498f7ac2e..e872b67af1 100644 --- a/blog/design-more-flexable-application-by-saga.md +++ b/blog/design-more-flexable-application-by-saga.md @@ -1,517 +1 @@ ---- -title: 基于 Seata Saga 设计更有弹性的金融应用 -keywords: [Saga,Seata,一致性,金融,弹性,分布式,事务] -description: 本文从金融分布式应用开发的一些痛点出发,结合理论和实践对社区和行业的解决方案进行了分析,并讲解了如何基于Seata saga设计更有弹性的金融应用 -author: long187 -date: 2019-11-04 ---- -# 基于 Seata Saga 设计更有弹性的金融应用 - -Seata 意为:Simple Extensible Autonomous Transaction Architecture,是一套一站式分布式事务解决方案,提供了 AT、TCC、Saga 和 XA 事务模式,本文详解其中的 Saga 模式。
项目地址:[https://github.com/seata/seata](https://github.com/seata/seata) - -本文作者:屹远(陈龙),蚂蚁金服分布式事务核心研发,Seata Committer。 - - -# 金融分布式应用开发的痛点 - -分布式系统有一个比较明显的问题就是,一个业务流程需要组合一组服务。这样的事情在微服务下就更为明显了,因为这需要业务上的一致性的保证。也就是说,如果一个步骤失败了,那么要么回滚到以前的服务调用,要么不断重试保证所有的步骤都成功。---《左耳听风-弹力设计之“补偿事务”》 - -而在金融领域微服务架构下的业务流程往往会更复杂,流程很长,比如一个互联网微贷业务流程调十几个服务很正常,再加上异常处理的流程那就更复杂了,做过金融业务开发的同学会很有体感。 - -所以在金融分布式应用开发过程中我们面临一些痛点: - -- **业务一致性难以保障**
- -我们接触到的大多数业务(比如在渠道层、产品层、集成层的系统),为了保障业务最终一致性,往往会采用“补偿”的方式来做,如果没有一个协调器来支持,开发难度是比较大的,每一步都要在 catch 里去处理前面所有的“回滚”操作,这将会形成“箭头形”的代码,可读性及维护性差。或者重试异常的操作,如果重试不成功可能要转异步重试,甚至最后转人工处理。这些都给开发人员带来极大的负担,开发效率低,且容易出错。 - -- **业务状态难以管理**
- -业务实体很多、实体的状态也很多,往往做完一个业务活动后就将实体的状态更新到了数据库里,没有一个状态机来管理整个状态的变迁过程,不直观,容易出错,造成业务进入一个不正确的状态。 - -- **幂等性难以保障**
- -服务的幂等性是分布式环境下的基本要求,为了保证服务的幂等性往往需要服务开发者逐个去设计,有用数据库唯一键实现的,有用分布式缓存实现的,没有一个统一的方案,开发人员负担大,也容易遗漏,从而造成资损。 - -- **业务监控运维难,缺乏统一的差错守护能力**
- -业务的执行情况监控一般通过打印日志,再基于日志监控平台查看,大多数情况是没有问题的,但是如果业务出错,这些监控缺乏当时的业务上下文,对排查问题不友好,往往需要再去数据库里查。同时日志的打印也依赖于开发,容易遗漏。对于补偿事务往往需要有“差错守护触发补偿”、“工人触发补偿”操作,没有统一的差错守护和处理规范,这些都要开发者逐个开发,负担沉重。 - - - -# 理论基础 - -一些场景下,我们对数据有强一致性的需求时,会采用在业务层上需要使用“两阶段提交”这样的分布式事务方案。而在另外一些场景下,我们并不需要这么强的一致性,那就只需要保证最终一致性就可以了。 - -例如蚂蚁金服目前在金融核心系统使用的就是 TCC 模式,金融核心系统的特点是一致性要求高(业务上的隔离性)、短流程、并发高。 - -而在很多金融核心以上的业务(比如在渠道层、产品层、集成层的系统),这些系统的特点是最终一致即可、流程多、流程长、还可能要调用其它公司的服务(如金融网络)。这是如果每个服务都开发 Try、Confirm、Cancel 三个方法成本高。如果事务中有其它公司的服务,也无法要求其它公司的服务也遵循 TCC 这种开发模式。同时流程长,事务边界太长会影响性能。 - -对于事务我们都知道 ACID,也很熟悉 CAP 理论最多只能满足其中两个,所以,为了提高性能,出现了 ACID 的一个变种 BASE。ACID 强调的是一致性(CAP 中的 C),而 BASE 强调的是可用性(CAP 中的 A)。我们知道,在很多情况下,我们是无法做到强一致性的 ACID 的。特别是我们需要跨多个系统的时候,而且这些系统还不是由一个公司所提供的。BASE 的系统倾向于设计出更加有弹力的系统,在短时间内,就算是有数据不同步的风险,我们也应该允许新的交易可以发生,而后面我们在业务上将可能出现问题的事务通过补偿的方式处理掉,以保证最终的一致性。 - -所以我们在实际开发中会进行取舍,对于更多的金融核心以上的业务系统可以采用补偿事务,补偿事务处理方面在30年前就提出了 Saga 理论,随着微服务的发展,近些年才逐步受到大家的关注。目前业界比较也公认 Saga 是作为长事务的解决方案。 -> [https://github.com/aphyr/dist-sagas/blob/master/sagas.pdf](https://github.com/aphyr/dist-sagas/blob/master/sagas.pdf)[1] -> [http://microservices.io/patterns/data/saga.html](http://microservices.io/patterns/data/saga.html)[2] - - - - -# 社区和业界的方案 - - -## Apache Camel Saga - -Camel 是实现 EIP(Enterprise Integration Patterns)企业集成模式的一款开源产品,它基于事件驱动的架构,有着良好的性能和吞吐量,它在2.21版本新增加了 Saga EIP。 - -Saga EIP 提供了一种方式可以通过 camel route 定义一系列有关联关系的 Action,这些 Action 要么都执行成功,要么都回滚,Saga 可以协调任何通讯协议的分布式服务或本地服务,并达到全局的最终一致性。Saga 不要求整个处理在短时间内完成,因为它不占用任何数据库锁,它可以支持需要长时间处理的请求,从几秒到几天,Camel 的 Saga EIP 是基于 [Microprofile 的 LRA](https://github.com/eclipse/microprofile-sandbox/tree/master/proposals/0009-LRA)[3](Long Running Action),同样也是支持协调任何通讯协议任何语言实现的分布式服务。 - -Saga 的实现不会对数据进行加锁,而是在给操作定义它的“补偿操作”,当正常流程执行出错的时候触发那些已经执行过的操作的“补偿操作”,将流程回滚掉。“补偿操作”可以在 Camel route 上用 Java 或 XML DSL(Definition Specific Language)来定义。 - -下面是一个 Java DSL 示例: -```java -// action -from("direct:reserveCredit") - .bean(idService, "generateCustomId") // generate a custom Id and set it in the body - .to("direct:creditReservation") - -// delegate action -from("direct:creditReservation") - .saga() - .propagation(SagaPropagation.SUPPORTS) - .option("CreditId", body()) // mark the current body as needed in the compensating action - .compensation("direct:creditRefund") - .bean(creditService, "reserveCredit") - .log("Credit ${header.amount} reserved. Custom Id used is ${body}"); - -// called only if the saga is cancelled -from("direct:creditRefund") - .transform(header("CreditId")) // retrieve the CreditId option from headers - .bean(creditService, "refundCredit") - .log("Credit for Custom Id ${body} refunded"); -``` - -XML DSL 示例: -```xml - - - - - - - - - - - -``` - - - -## Eventuate Tram Saga - -[Eventuate Tram Saga](https://github.com/eventuate-tram/eventuate-tram-sagas)[4] 框架是使用 JDBC / JPA 的 Java 微服务的一个 Saga 框架。它也和 Camel Saga 一样采用了 Java DSL 来定义补偿操作: - -```java -public class CreateOrderSaga implements SimpleSaga { - - private SagaDefinition sagaDefinition = - step() - .withCompensation(this::reject) - .step() - .invokeParticipant(this::reserveCredit) - .step() - .invokeParticipant(this::approve) - .build(); - - - @Override - public SagaDefinition getSagaDefinition() { - return this.sagaDefinition; - } - - - private CommandWithDestination reserveCredit(CreateOrderSagaData data) { - long orderId = data.getOrderId(); - Long customerId = data.getOrderDetails().getCustomerId(); - Money orderTotal = data.getOrderDetails().getOrderTotal(); - return send(new ReserveCreditCommand(customerId, orderId, orderTotal)) - .to("customerService") - .build(); - -... -``` - - - -## Apache ServiceComb Saga - -[ServiceComb Saga](https://github.com/apache/incubator-servicecomb-saga)[5] 也是一个微服务应用的数据最终一致性解决方案。相对于 [TCC](http://design.inf.usi.ch/sites/default/files/biblio/rest-tcc.pdf) 而言,在 try 阶段,Saga 会直接提交事务,后续 rollback 阶段则通过反向的补偿操作来完成。与前面两种不同是它是采用 Java 注解+拦截器的方式来进行“补偿”服务的定义。
- - - -#### 架构: - -Saga 是由 **alpha** 和 **omega **组成,其中: - -- alpha 充当协调者的角色,主要负责对事务进行管理和协调;
-- omega 是微服务中内嵌的一个 agent,负责对网络请求进行拦截并向 alpha 上报事务事件;
- -下图展示了 alpha,omega 以及微服务三者的关系:
-![ServiceComb Saga](/img/saga/service-comb-saga.png?raw=true) - - - -#### 使用示例: -```java -public class ServiceA extends AbsService implements IServiceA { - - private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - - @Autowired - private IServiceB serviceB; - - @Autowired - private IServiceC serviceC; - - @Override - public String getServiceName() { - return "servicea"; - } - - @Override - public String getTableName() { - return "testa"; - } - - @Override - @SagaStart - @Compensable(compensationMethod = "cancelRun") - @Transactional(rollbackFor = Exception.class) - public Object run(InvokeContext invokeContext) throws Exception { - LOG.info("A.run called"); - doRunBusi(); - if (invokeContext.isInvokeB(getServiceName())) { - serviceB.run(invokeContext); - } - if (invokeContext.isInvokeC(getServiceName())) { - serviceC.run(invokeContext); - } - if (invokeContext.isException(getServiceName())) { - LOG.info("A.run exception"); - throw new Exception("A.run exception"); - } - return null; - } - - public void cancelRun(InvokeContext invokeContext) { - LOG.info("A.cancel called"); - doCancelBusi(); - } -``` - - - -## 蚂蚁金服的实践 - -蚂蚁金服内部大规模在使用 TCC 模式分布式事务,主要用于金融核心等对一致性要求高、性能要求高的场景。在更上层的业务系统因为流程多流程长,开发 TCC 成本比较高,大都会权衡采用 Saga 模式来到达业务最终一致性,由于历史的原因不同的 BU 有自己的一套“补偿”事务的方案,基本上是两种: - -- 一种是当一个服务在失败时需要“重试”或“补偿”时,在执行服务前在数据库插入一条记录,记录状态,当异常时通过定时任务去查询数据库记录并进行“重试”或“补偿”,当业务流程执行成功则删除记录; -- 另一种是设计一个状态机引擎和简单的 DSL,编排业务流程和记录业务状态,状态机引擎可以定义“补偿服务”,当异常时由状态机引擎反向调用“补偿服务”进行回滚,同时还会有一个“差错守护”平台,监控那些执行失败或补偿失败的业务流水,并不断进行“补偿”或“重试”; - - - -## 方案对比 - -社区和业界的解决方案一般是两种,一种基本状态机或流程引擎通过 DSL 方式编排流程程和补偿定义,一种是基于 Java 注解+拦截器实现补偿,那么这两种方案有什么优缺点呢? - -| **方式** | **优点** | **缺点** | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 状态机+DSL |
- 可以用可视化工具来定义业务流程,标准化,可读性高,可实现服务编排的功能
- 提高业务分析人员与程序开发人员的沟通效率
- 业务状态管理:流程本质就是一个状态机,可以很好的反映业务状态的流转
- 提高异常处理灵活性:可以实现宕机恢复后的“向前重试”或“向后补偿”
- 天然可以使用 Actor 模型或 SEDA 架构等异步处理引擎来执行,提高整体吞吐量
|
- 业务流程实际是由 JAVA 程序与 DSL 配置组成,程序与配置分离,开发起来比较繁琐
- 如果是改造现有业务,对业务侵入性高
- 引擎实现成本高
| -| 拦截器+java 注解 |
- 程序与注解是在一起的,开发简单,学习成本低
- 方便接入现有业务
- 基于动态代理拦截器,框架实现成本低
|
- 框架无法提供 Actor 模型或 SEDA 架构等异步处理模式来提高系统吞吐量
- 框架无法提供业务状态管理
- 难以实现宕机恢复后的“向前重试”,因为无法恢复线程上下文
| - - - - -# - - - -# Seata Saga 的方案 - -Seata Saga 的简介可以看一下[《Seata Saga 官网文档》](http://seata.io/zh-cn/docs/user/saga.html)[6]。 - -Seata Saga 采用了状态机+DSL 方案来实现,原因有以下几个: - -- 状态机+DSL 方案在实际生产中应用更广泛; -- 可以使用 Actor 模型或 SEDA 架构等异步处理引擎来执行,提高整体吞吐量; -- 通常在核心系统以上层的业务系统会伴随有“服务编排”的需求,而服务编排又有事务最终一致性要求,两者很难分割开,状态机+DSL 方案可以同时满足这两个需求; -- 由于 Saga 模式在理论上是不保证隔离性的,在极端情况下可能由于脏写无法完成回滚操作,比如举一个极端的例子, 分布式事务内先给用户 A 充值,然后给用户 B 扣减余额,如果在给A用户充值成功,在事务提交以前,A 用户把线消费掉了,如果事务发生回滚,这时则没有办法进行补偿了,有些业务场景可以允许让业务最终成功,在回滚不了的情况下可以继续重试完成后面的流程,状态机+DSL的方案可以实现“向前”恢复上下文继续执行的能力, 让业务最终执行成功,达到最终一致性的目的。 - -> 在不保证隔离性的情况下:业务流程设计时要遵循“宁可长款, 不可短款”的原则,长款意思是客户少了线机构多了钱,以机构信誉可以给客户退款,反之则是短款,少的线可能追不回来了。所以在业务流程设计上一定是先扣款。 - - - - -### 状态定义语言(Seata State Language) - -1. 通过状态图来定义服务调用的流程并生成 json 状态语言定义文件; -2. 状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点; -3. 状态图 json 由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚; -> 注意: 异常发生时是否进行补偿也可由用户自定义决定 - -4. 可以实现服务编排需求,支持单项选择、并发、异步、子状态机、参数转换、参数映射、服务执行状态判断、异常捕获等功能; - -假设有一个业务流程要调两个服务,先调库存扣减(InventoryService),再调余额扣减(BalanceService),保证在一个分布式内要么同时成功,要么同时回滚。两个参与者服务都有一个 reduce 方法,表示库存扣减或余额扣减,还有一个 compensateReduce 方法,表示补偿扣减操作。以 InventoryService 为例看一下它的接口定义: - -```java -public interface InventoryService { - - /** - * reduce - * @param businessKey - * @param amount - * @param params - * @return - */ - boolean reduce(String businessKey, BigDecimal amount, Map params); - - /** - * compensateReduce - * @param businessKey - * @param params - * @return - */ - boolean compensateReduce(String businessKey, Map params); -} -``` - -这个业务流程对应的状态图: - -![示例状态图](/img/saga/demo_statelang.png?raw=true) -
对应的 JSON - -```json -{ - "Name": "reduceInventoryAndBalance", - "Comment": "reduce inventory then reduce balance in a transaction", - "StartState": "ReduceInventory", - "Version": "0.0.1", - "States": { - "ReduceInventory": { - "Type": "ServiceTask", - "ServiceName": "inventoryAction", - "ServiceMethod": "reduce", - "CompensateState": "CompensateReduceInventory", - "Next": "ChoiceState", - "Input": [ - "$.[businessKey]", - "$.[count]" - ], - "Output": { - "reduceInventoryResult": "$.#root" - }, - "Status": { - "#root == true": "SU", - "#root == false": "FA", - "$Exception{java.lang.Throwable}": "UN" - } - }, - "ChoiceState":{ - "Type": "Choice", - "Choices":[ - { - "Expression":"[reduceInventoryResult] == true", - "Next":"ReduceBalance" - } - ], - "Default":"Fail" - }, - "ReduceBalance": { - "Type": "ServiceTask", - "ServiceName": "balanceAction", - "ServiceMethod": "reduce", - "CompensateState": "CompensateReduceBalance", - "Input": [ - "$.[businessKey]", - "$.[amount]", - { - "throwException" : "$.[mockReduceBalanceFail]" - } - ], - "Output": { - "compensateReduceBalanceResult": "$.#root" - }, - "Status": { - "#root == true": "SU", - "#root == false": "FA", - "$Exception{java.lang.Throwable}": "UN" - }, - "Catch": [ - { - "Exceptions": [ - "java.lang.Throwable" - ], - "Next": "CompensationTrigger" - } - ], - "Next": "Succeed" - }, - "CompensateReduceInventory": { - "Type": "ServiceTask", - "ServiceName": "inventoryAction", - "ServiceMethod": "compensateReduce", - "Input": [ - "$.[businessKey]" - ] - }, - "CompensateReduceBalance": { - "Type": "ServiceTask", - "ServiceName": "balanceAction", - "ServiceMethod": "compensateReduce", - "Input": [ - "$.[businessKey]" - ] - }, - "CompensationTrigger": { - "Type": "CompensationTrigger", - "Next": "Fail" - }, - "Succeed": { - "Type":"Succeed" - }, - "Fail": { - "Type":"Fail", - "ErrorCode": "PURCHASE_FAILED", - "Message": "purchase failed" - } - } -} -``` - -状态语言在一定程度上参考了 [AWS Step Functions](https://docs.aws.amazon.com/zh_cn/step-functions/latest/dg/tutorial-creating-lambda-state-machine.html)[7]。 - - - -#### "状态机" 属性简介: - -- Name: 表示状态机的名称,必须唯一; -- Comment: 状态机的描述; -- Version: 状态机定义版本; -- StartState: 启动时运行的第一个"状态"; -- States: 状态列表,是一个 map 结构,key 是"状态"的名称,在状态机内必须唯一; - - - -#### "状态" 属性简介: - -- Type:"状态" 的类型,比如有: - - ServiceTask: 执行调用服务任务; - - Choice: 单条件选择路由; - - CompensationTrigger: 触发补偿流程; - - Succeed: 状态机正常结束; - - Fail: 状态机异常结束; - - SubStateMachine: 调用子状态机; -- ServiceName: 服务名称,通常是服务的beanId; -- ServiceMethod: 服务方法名称; -- CompensateState: 该"状态"的补偿"状态"; -- Input: 调用服务的输入参数列表,是一个数组,对应于服务方法的参数列表, $.表示使用表达式从状态机上下文中取参数,表达使用的 [SpringEL](https://docs.spring.io/spring/docs/4.3.10.RELEASE/spring-framework-reference/html/expressions.html)[8], 如果是常量直接写值即可; -- Output: 将服务返回的参数赋值到状态机上下文中,是一个 map 结构,key 为放入到状态机上文时的 key(状态机上下文也是一个 map),value 中 $. 是表示 SpringEL 表达式,表示从服务的返回参数中取值,#root 表示服务的整个返回参数; -- Status: 服务执行状态映射,框架定义了三个状态,SU 成功、FA 失败、UN 未知,我们需要把服务执行的状态映射成这三个状态,帮助框架判断整个事务的一致性,是一个 map 结构,key 是条件表达式,一般是取服务的返回值或抛出的异常进行判断,默认是 SpringEL 表达式判断服务返回参数,带 $Exception{开头表示判断异常类型,value 是当这个条件表达式成立时则将服务执行状态映射成这个值; -- Catch: 捕获到异常后的路由; -- Next: 服务执行完成后下一个执行的"状态"; -- Choices: Choice 类型的"状态"里, 可选的分支列表, 分支中的 Expression 为 SpringEL 表达式,Next 为当表达式成立时执行的下一个"状态"; -- ErrorCode: Fail 类型"状态"的错误码; -- Message: Fail 类型"状态"的错误信息; - -更多详细的状态语言解释请看[《Seata Saga 官网文档》](http://seata.io/zh-cn/docs/user/saga.html)[6[http://seata.io/zh-cn/docs/user/saga.html](http://seata.io/zh-cn/docs/user/saga.html)]。 - - - -### 状态机引擎原理: - -![状态机引擎原理](/img/saga/saga_engine_mechanism.png?raw=true) - -- 图中的状态图是先执行 stateA, 再执行 stataB,然后执行 stateC; -- "状态"的执行是基于事件驱动的模型,stataA 执行完成后,会产生路由消息放入 EventQueue,事件消费端从 EventQueue 取出消息,执行 stateB; -- 在整个状态机启动时会调用 Seata Server 开启分布式事务,并生产 xid, 然后记录"状态机实例"启动事件到本地数据库; -- 当执行到一个"状态"时会调用 Seata Server 注册分支事务,并生产 branchId, 然后记录"状态实例"开始执行事件到本地数据库; -- 当一个"状态"执行完成后会记录"状态实例"执行结束事件到本地数据库, 然后调用 Seata Server 上报分支事务的状态; -- 当整个状态机执行完成,会记录"状态机实例"执行完成事件到本地数据库, 然后调用 Seata Server 提交或回滚分布式事务; - - - -### 状态机引擎设计: - -![状态机引擎设计](/img/saga/saga_engine.png?raw=true) - -状态机引擎的设计主要分成三层, 上层依赖下层,从下往上分别是: - -- Eventing 层: - - 实现事件驱动架构, 可以压入事件, 并由消费端消费事件, 本层不关心事件是什么消费端执行什么,由上层实现; - -- ProcessController 层: - - 由于上层的 Eventing 驱动一个“空”流程执行的执行,"state"的行为和路由都未实现,由上层实现; -> 基于以上两层理论上可以自定义扩展任何"流程"引擎。这两层的设计是参考了内部金融网络平台的设计。 - - -- StateMachineEngine 层: - - 实现状态机引擎每种 state 的行为和路由逻辑; - - 提供 API、状态机语言仓库; - - - -### Saga 模式下服务设计的实践经验 - -下面是实践中总结的在 Saga 模式下微服务设计的一些经验,当然这是推荐做法,并不是说一定要 100% 遵循,没有遵循也有“绕过”方案。 -> 好消息:Seata Saga 模式对微服务的接口参数没有任务要求,这使得 Saga 模式可用于集成遗留系统或外部机构的服务。 - - - - -#### 允许空补偿 - -- 空补偿:原服务未执行,补偿服务执行了; -- 出现原因: - - 原服务 超时(丢包); - - Saga 事务触发 回滚; - - 未收到原服务请求,先收到补偿请求; - -所以服务设计时需要允许空补偿,即没有找到要补偿的业务主键时返回补偿成功并将原业务主键记录下来。 - - - -#### 防悬挂控制 - -- 悬挂:补偿服务 比 原服务 先执行; -- 出现原因: - - 原服务 超时(拥堵); - - Saga 事务回滚,触发 回滚; - - 拥堵的原服务到达; - -所以要检查当前业务主键是否已经在空补偿记录下来的业务主键中存在,如果存在则要拒绝服务的执行。 - - - -#### 幂等控制 - -- 原服务与补偿服务都需要保证幂等性, 由于网络可能超时,可以设置重试策略,重试发生时要通过幂等控制避免业务数据重复更新。 - - - -# 总结 - -很多时候我们不需要强调强一性,我们基于 BASE 和 Saga 理论去设计更有弹性的系统,在分布式架构下获得更好的性能和容错能力。分布式架构没有银弹,只有适合特定场景的方案,事实上 Seata Saga 是一个具备“服务编排”和“Saga 分布式事务”能力的产品,总结下来它的适用场景是: - -- 适用于微服务架构下的“长事务”处理; -- 适用于微服务架构下的“服务编排”需求; -- 适用于金融核心系统以上的有大量组合服务的业务系统(比如在渠道层、产品层、集成层的系统); -- 适用于业务流程中需要集成遗留系统或外部机构提供的服务的场景(这些服务不可变不能对其提出改造要求)。 - - - -## 文中涉及相关链接 - -[1][https://github.com/aphyr/dist-sagas/blob/master/sagas.pdf](https://github.com/aphyr/dist-sagas/blob/master/sagas.pdf)
[2][http://microservices.io/patterns/data/saga.html](http://microservices.io/patterns/data/saga.html)
[3][Microprofile 的 LRA](https://github.com/eclipse/microprofile-sandbox/tree/master/proposals/0009-LRA):[https://github.com/eclipse/microprofile-sandbox/tree/master/proposals/0009-LRA](https://github.com/eclipse/microprofile-sandbox/tree/master/proposals/0009-LRA)
[4][Eventuate Tram Saga](https://github.com/eventuate-tram/eventuate-tram-sagas):[https://github.com/eventuate-tram/eventuate-tram-sagas](https://github.com/eventuate-tram/eventuate-tram-sagas)
[5][ServiceComb Saga](https://github.com/apache/incubator-servicecomb-saga):[https://github.com/apache/servicecomb-pack](https://github.com/apache/servicecomb-pack)
[6][Seata Saga 官网文档](http://seata.io/zh-cn/docs/user/saga.html):[http://seata.io/zh-cn/docs/user/saga.html](http://seata.io/zh-cn/docs/user/saga.html)
[7][AWS Step Functions](https://docs.aws.amazon.com/zh_cn/step-functions/latest/dg/tutorial-creating-lambda-state-machine.html):[https://docs.aws.amazon.com/zh_cn/step-functions/latest/dg/tutorial-creating-lambda-state-machine.html](https://docs.aws.amazon.com/zh_cn/step-functions/latest/dg/tutorial-creating-lambda-state-machine.html)
[8][SpringEL](https://docs.spring.io/spring/docs/4.3.10.RELEASE/spring-framework-reference/html/expressions.html):[https://docs.spring.io/spring/docs/4.3.10.RELEASE/spring-framework-reference/html/expressions.html](https://docs.spring.io/spring/docs/4.3.10.RELEASE/spring-framework-reference/html/expressions.html)
+Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/how-to-support-spring-cloud.md b/blog/how-to-support-spring-cloud.md index 2246d5581d..e872b67af1 100644 --- a/blog/how-to-support-spring-cloud.md +++ b/blog/how-to-support-spring-cloud.md @@ -1,547 +1 @@ ---- -title: Fescar 与 Spring Cloud 集成源码深度剖析 -author: 郭树抗 季敏 -date: 2019/04/15 -keywords: [fescar、seata、分布式事务] ---- - -# Fescar 与 Spring Cloud 集成源码深度剖析 - -### Fescar 简介 - -常见的分布式事务方式有基于 2PC 的 XA (e.g. atomikos),从业务层入手的 TCC( e.g. byteTCC)、事务消息 ( e.g. RocketMQ Half Message) 等等。XA 是需要本地数据库支持的分布式事务的协议,资源锁在数据库层面导致性能较差,而支付宝作为布道师引入的 TCC 模式需要大量的业务代码保证,开发维护成本较高。 - -分布式事务是业界比较关注的领域,这也是短短时间 Fescar 能收获6k Star的原因之一。Fescar 名字取自 **Fast & Easy Commit And Rollback** ,简单来说 Fescar 通过对本地 RDBMS 分支事务的协调来驱动完成全局事务,是工作在应用层的中间件。主要优点是相对于XA模式是性能较好不长时间占用连接资源,相对于 TCC 方式开发成本和业务侵入性较低。 - -类似于 XA,Fescar 将角色分为 TC、RM、TM,事务整体过程模型如下: - -![Fescar事务过程](/img/blog/fescar-microservices.png) - -``` -1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。 -2. XID 在微服务调用链路的上下文中传播。 -3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。 -4. TM 向 TC 发起针对 XID 的全局提交或回滚决议。 -5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。 -``` - -其中在目前的实现版本中 TC 是独立部署的进程,维护全局事务的操作记录和全局锁记录,负责协调并驱动全局事务的提交或回滚。TM RM 则与应用程序工作在同一应用进程。RM 对 JDBC 数据源采用代理的方式对底层数据库做管理,利用语法解析,在执行事务时保留快照,并生成 undo log。大概的流程和模型划分就介绍到这里,下面开始对 Fescar 事务传播机制的分析。 - -### Fescar 事务传播机制 - -Fescar 事务传播包括应用内事务嵌套调用和跨服务调用的事务传播。Fescar 事务是怎么在微服务调用链中传播的呢?Fescar 提供了事务 API 允许用户手动绑定事务的 XID 并加入到全局事务中,所以我们根据不同的服务框架机制,将 XID 在链路中传递即可实现事务的传播。 - -RPC 请求过程分为调用方与被调用方两部分,我们需要对 XID 在请求与响应时做相应的处理。大致过程为:调用方即请求方将当前事务上下文中的 XID 取出,通过RPC协议传递给被调用方;被调用方从请求中的将 XID 取出,并绑定到自己的事务上下文中,纳入全局事务。微服务框架一般都有相应的 Filter 和 Interceptor 机制,我们来具体分析下 Spring Cloud 与Fescar 的整合过程。 - -### Fescar 与 Spring Cloud Alibaba 集成部分源码解析 - -本部分源码全部来自于 spring-cloud-alibaba-fescar. 源码解析部分主要包括 AutoConfiguration、微服务被调用方和微服务调用方三大部分。对于微服务调用方方式具体分为 RestTemplate 和 Feign,其中对于 Feign 请求方式又进一步细分为结合 Hystrix 和 Sentinel 的使用模式。 - -#### Fescar AutoConfiguration -对于 AutoConfiguration 的解析此处只介绍与 Fescar 启动相关的部分,其他部分的解析将穿插于【微服务被调用方】和 【微服务调用方】章节进行介绍。 - -Fescar 的启动需要配置 GlobalTransactionScanner,GlobalTransactionScanner 负责初始化 Fescar 的 RM client、TM client 和 自动代理标注 GlobalTransactional 注解的类。GlobalTransactionScanner bean 的启动通过 GlobalTransactionAutoConfiguration 加载并注入FescarProperties。 -FescarProperties 包含了 Fescar 的重要属性 txServiceGroup ,此属性的可通过 application.properties 文件中的 key: spring.cloud.alibaba.fescar.txServiceGroup 读取,默认值为 ${spring.application.name}-fescar-service-group 。txServiceGroup 表示 Fescar 的逻辑事务分组名,此分组名通过配置中心(目前支持文件、Apollo)获取逻辑事务分组名对应的 TC 集群名称,进一步通过集群名称构造出 TC 集群的服务名,通过注册中心(目前支持nacos、redis、zk和eureka)和服务名找到可用的 TC 服务节点,然后 RM client、TM client 与 TC 进行 rpc 交互。 - -#### 微服务被调用方 - -由于调用方的逻辑比较多一点,我们先分析被调用方的逻辑。针对于 Spring Cloud 项目,默认采用的 RPC 传输协议是 HTTP 协议,所以使用了 HandlerInterceptor 机制来对HTTP的请求做拦截。 - -HandlerInterceptor 是 Spring 提供的接口, 它有以下三个方法可以被覆写。 - -```java - /** - * Intercept the execution of a handler. Called after HandlerMapping determined - * an appropriate handler object, but before HandlerAdapter invokes the handler. - */ - default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) - throws Exception { - - return true; - } - - /** - * Intercept the execution of a handler. Called after HandlerAdapter actually - * invoked the handler, but before the DispatcherServlet renders the view. - * Can expose additional model objects to the view via the given ModelAndView. - */ - default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, - @Nullable ModelAndView modelAndView) throws Exception { - } - - /** - * Callback after completion of request processing, that is, after rendering - * the view. Will be called on any outcome of handler execution, thus allows - * for proper resource cleanup. - */ - default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, - @Nullable Exception ex) throws Exception { - } -``` - -根据注释,我们可以很明确的看到各个方法的作用时间和常用用途。对于 Fescar 集成来讲,它根据需要重写了 preHandle、afterCompletion 方法。 - -FescarHandlerInterceptor 的作用是将服务链路传递过来的 XID,绑定到服务节点的事务上下文中,并且在请求完成后清理相关资源。FescarHandlerInterceptorConfiguration 中配置了所有的 url 均进行拦截,对所有的请求过来均会执行该拦截器,进行 XID 的转换与事务绑定。 - -```java -/** - * @author xiaojing - * - * Fescar HandlerInterceptor, Convert Fescar information into - * @see com.alibaba.fescar.core.context.RootContext from http request's header in - * {@link org.springframework.web.servlet.HandlerInterceptor#preHandle(HttpServletRequest , HttpServletResponse , Object )}, - * And clean up Fescar information after servlet method invocation in - * {@link org.springframework.web.servlet.HandlerInterceptor#afterCompletion(HttpServletRequest, HttpServletResponse, Object, Exception)} - */ -public class FescarHandlerInterceptor implements HandlerInterceptor { - - private static final Logger log = LoggerFactory - .getLogger(FescarHandlerInterceptor.class); - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, - Object handler) throws Exception { - - String xid = RootContext.getXID(); - String rpcXid = request.getHeader(RootContext.KEY_XID); - if (log.isDebugEnabled()) { - log.debug("xid in RootContext {} xid in RpcContext {}", xid, rpcXid); - } - - if (xid == null && rpcXid != null) { - RootContext.bind(rpcXid); - if (log.isDebugEnabled()) { - log.debug("bind {} to RootContext", rpcXid); - } - } - return true; - } - - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, - Object handler, Exception e) throws Exception { - - String rpcXid = request.getHeader(RootContext.KEY_XID); - - if (StringUtils.isEmpty(rpcXid)) { - return; - } - - String unbindXid = RootContext.unbind(); - if (log.isDebugEnabled()) { - log.debug("unbind {} from RootContext", unbindXid); - } - if (!rpcXid.equalsIgnoreCase(unbindXid)) { - log.warn("xid in change during RPC from {} to {}", rpcXid, unbindXid); - if (unbindXid != null) { - RootContext.bind(unbindXid); - log.warn("bind {} back to RootContext", unbindXid); - } - } - } - -} - -``` - - - -preHandle 在请求执行前被调用,xid 为当前事务上下文已经绑定的全局事务的唯一标识,rpcXid 为请求通过 HTTP Header 传递过来需要绑定的全局事务标识。preHandle 方法中判断如果当前事务上下文中没有 XID,且 rpcXid 不为空,那么就将 rpcXid 绑定到当前的事务上下文。 - -afterCompletion 在请求完成后被调用,该方法用来执行资源的相关清理动作。Fescar 通过 RootContext.unbind() 方法对事务上下文涉及到的 XID 进行解绑。下面 if 中的逻辑是为了代码的健壮性考虑,如果遇到 rpcXid和 unbindXid 不相等的情况,再将 unbindXid 重新绑定回去。 - -对于 Spring Cloud 来讲,默认采用的 RPC 方式是 HTTP 的方式,所以对被调用方来讲,它的请求拦截方式不用做任何区分,只需要从 Header 中将 XID 就可以取出绑定到自己的事务上下文中即可。但是对于调用方由于请求组件的多样化,包括熔断隔离机制,所以要区分不同的情况做处理,后面我们来具体分析一下。 - -#### 微服务调用方 - -Fescar 将请求方式分为:RestTemplate、Feign、Feign+Hystrix 和 Feign+Sentinel 。不同的组件通过 Spring Boot 的 Auto Configuration 来完成自动的配置,具体的配置类清单可以看 spring.factories ,下文也会介绍相关的配置类。 - -##### RestTemplate - -先来看下如果调用方如果是是基于 RestTemplate 的请求,Fescar 是怎么传递 XID 的。 - -```java -public class FescarRestTemplateInterceptor implements ClientHttpRequestInterceptor { - @Override - public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, - ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { - HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest); - - String xid = RootContext.getXID(); - - if (!StringUtils.isEmpty(xid)) { - requestWrapper.getHeaders().add(RootContext.KEY_XID, xid); - } - return clientHttpRequestExecution.execute(requestWrapper, bytes); - } -} -``` - -FescarRestTemplateInterceptor 实现了 ClientHttpRequestInterceptor 接口的 intercept 方法,对调用的请求做了包装,在发送请求时若存在 Fescar 事务上下文 XID 则取出并放到 HTTP Header 中。 - -FescarRestTemplateInterceptor 通过 FescarRestTemplateAutoConfiguration 实现将 FescarRestTemplateInterceptor 配置到 RestTemplate 中去。 - -```java -@Configuration -public class FescarRestTemplateAutoConfiguration { - - @Bean - public FescarRestTemplateInterceptor fescarRestTemplateInterceptor() { - return new FescarRestTemplateInterceptor(); - } - - @Autowired(required = false) - private Collection restTemplates; - - @Autowired - private FescarRestTemplateInterceptor fescarRestTemplateInterceptor; - - @PostConstruct - public void init() { - if (this.restTemplates != null) { - for (RestTemplate restTemplate : restTemplates) { - List interceptors = new ArrayList( - restTemplate.getInterceptors()); - interceptors.add(this.fescarRestTemplateInterceptor); - restTemplate.setInterceptors(interceptors); - } - } - } - -} -``` - -init 方法遍历所有的 restTemplate ,并将原来 restTemplate 中的拦截器取出,增加 fescarRestTemplateInterceptor 后置入并重排序。 - -##### Feign - -![Feign 类关系图](/img/blog/20190305184812.png) - -接下来看下 Feign 的相关代码,该包下面的类还是比较多的,我们先从其 AutoConfiguration 入手。 - -```java -@Configuration -@ConditionalOnClass(Client.class) -@AutoConfigureBefore(FeignAutoConfiguration.class) -public class FescarFeignClientAutoConfiguration { - - @Bean - @Scope("prototype") - @ConditionalOnClass(name = "com.netflix.hystrix.HystrixCommand") - @ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "true") - Feign.Builder feignHystrixBuilder(BeanFactory beanFactory) { - return FescarHystrixFeignBuilder.builder(beanFactory); - } - - @Bean - @Scope("prototype") - @ConditionalOnClass(name = "com.alibaba.csp.sentinel.SphU") - @ConditionalOnProperty(name = "feign.sentinel.enabled", havingValue = "true") - Feign.Builder feignSentinelBuilder(BeanFactory beanFactory) { - return FescarSentinelFeignBuilder.builder(beanFactory); - } - - @Bean - @ConditionalOnMissingBean - @Scope("prototype") - Feign.Builder feignBuilder(BeanFactory beanFactory) { - return FescarFeignBuilder.builder(beanFactory); - } - - @Configuration - protected static class FeignBeanPostProcessorConfiguration { - - @Bean - FescarBeanPostProcessor fescarBeanPostProcessor( - FescarFeignObjectWrapper fescarFeignObjectWrapper) { - return new FescarBeanPostProcessor(fescarFeignObjectWrapper); - } - - @Bean - FescarContextBeanPostProcessor fescarContextBeanPostProcessor( - BeanFactory beanFactory) { - return new FescarContextBeanPostProcessor(beanFactory); - } - - @Bean - FescarFeignObjectWrapper fescarFeignObjectWrapper(BeanFactory beanFactory) { - return new FescarFeignObjectWrapper(beanFactory); - } - } - -} -``` - -FescarFeignClientAutoConfiguration 在存在 Client.class 时生效,且要求作用在 FeignAutoConfiguration 之前。由于FeignClientsConfiguration 是在 FeignAutoConfiguration 生成 FeignContext 生效的,所以根据依赖关系, FescarFeignClientAutoConfiguration 同样早于 FeignClientsConfiguration。 - -FescarFeignClientAutoConfiguration 自定义了 Feign.Builder,针对于 feign.sentinel,feign.hystrix 和 feign 的情况做了适配,目的是自定义 feign 中 Client 的真正实现为 FescarFeignClient。 - -```java -HystrixFeign.builder().retryer(Retryer.NEVER_RETRY) - .client(new FescarFeignClient(beanFactory)) -``` - -```java -SentinelFeign.builder().retryer(Retryer.NEVER_RETRY) - .client(new FescarFeignClient(beanFactory)); -``` - -```java -Feign.builder().client(new FescarFeignClient(beanFactory)); -``` - -FescarFeignClient 是对原来的 Feign 客户端代理增强,具体代码见下图: - -```java -public class FescarFeignClient implements Client { - - private final Client delegate; - private final BeanFactory beanFactory; - - FescarFeignClient(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - this.delegate = new Client.Default(null, null); - } - - FescarFeignClient(BeanFactory beanFactory, Client delegate) { - this.delegate = delegate; - this.beanFactory = beanFactory; - } - - @Override - public Response execute(Request request, Request.Options options) throws IOException { - - Request modifiedRequest = getModifyRequest(request); - - try { - return this.delegate.execute(modifiedRequest, options); - } - finally { - - } - } - - private Request getModifyRequest(Request request) { - - String xid = RootContext.getXID(); - - if (StringUtils.isEmpty(xid)) { - return request; - } - - Map> headers = new HashMap<>(); - headers.putAll(request.headers()); - - List fescarXid = new ArrayList<>(); - fescarXid.add(xid); - headers.put(RootContext.KEY_XID, fescarXid); - - return Request.create(request.method(), request.url(), headers, request.body(), - request.charset()); - } - -``` - -上面的过程中我们可以看到,FescarFeignClient 对原来的 Request 做了修改,它首先将 XID 从当前的事务上下文中取出,在 XID 不为空的情况下,将 XID 放到了 Header 中。 - -FeignBeanPostProcessorConfiguration 定义了3个 bean:FescarContextBeanPostProcessor、FescarBeanPostProcessor 和 FescarFeignObjectWrapper。其中 FescarContextBeanPostProcessor FescarBeanPostProcessor 实现了Spring BeanPostProcessor 接口。 -以下为 FescarContextBeanPostProcessor 实现。 - -```java - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { - if (bean instanceof FeignContext && !(bean instanceof FescarFeignContext)) { - return new FescarFeignContext(getFescarFeignObjectWrapper(), - (FeignContext) bean); - } - return bean; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - return bean; - } -``` - -BeanPostProcessor 中的两个方法可以对 Spring 容器中的 Bean 做前后处理,postProcessBeforeInitialization 处理时机是初始化之前,postProcessAfterInitialization 的处理时机是初始化之后,这2个方法的返回值可以是原先生成的实例 bean,或者使用 wrapper 包装后的实例。 - -FescarContextBeanPostProcessor 将 FeignContext 包装成 FescarFeignContext。 -FescarBeanPostProcessor 将 FeignClient 根据是否继承了 LoadBalancerFeignClient 包装成 FescarLoadBalancerFeignClient 和 FescarFeignClient。 - -FeignAutoConfiguration 中的 FeignContext 并没有加 ConditionalOnXXX 的条件,所以 Fescar 采用预置处理的方式将 FeignContext 包装成 FescarFeignContext。 - -```java - @Bean - public FeignContext feignContext() { - FeignContext context = new FeignContext(); - context.setConfigurations(this.configurations); - return context; - } -``` - -而对于 Feign Client,FeignClientFactoryBean 中会获取 FeignContext 的实例对象。对于开发者采用 @Configuration 注解的自定义配置的 Feign Client 对象,这里会被配置到 builder,导致 FescarFeignBuilder 中增强后的 FescarFeignCliet 失效。FeignClientFactoryBean 中关键代码如下: - -```java - /** - * @param the target type of the Feign client - * @return a {@link Feign} client created with the specified data and the context information - */ - T getTarget() { - FeignContext context = applicationContext.getBean(FeignContext.class); - Feign.Builder builder = feign(context); - - if (!StringUtils.hasText(this.url)) { - if (!this.name.startsWith("http")) { - url = "http://" + this.name; - } - else { - url = this.name; - } - url += cleanPath(); - return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, - this.name, url)); - } - if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { - this.url = "http://" + this.url; - } - String url = this.url + cleanPath(); - Client client = getOptional(context, Client.class); - if (client != null) { - if (client instanceof LoadBalancerFeignClient) { - // not load balancing because we have a url, - // but ribbon is on the classpath, so unwrap - client = ((LoadBalancerFeignClient)client).getDelegate(); - } - builder.client(client); - } - Targeter targeter = get(context, Targeter.class); - return (T) targeter.target(this, builder, context, new HardCodedTarget<>( - this.type, this.name, url)); - } -``` -上述代码根据是否指定了注解参数中的 URL 来选择直接调用 URL 还是走负载均衡,targeter.target 通过动态代理创建对象。大致过程为:将解析出的feign方法放入map -,再通过将其作为参数传入生成InvocationHandler,进而生成动态代理对象。 -FescarContextBeanPostProcessor 的存在,即使开发者对 FeignClient 自定义操作,依旧可以完成 Fescar 所需的全局事务的增强。 - -对于 FescarFeignObjectWrapper,我们重点关注下Wrapper方法: - -```java - Object wrap(Object bean) { - if (bean instanceof Client && !(bean instanceof FescarFeignClient)) { - if (bean instanceof LoadBalancerFeignClient) { - LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean); - return new FescarLoadBalancerFeignClient(client.getDelegate(), factory(), - clientFactory(), this.beanFactory); - } - return new FescarFeignClient(this.beanFactory, (Client) bean); - } - return bean; - } -``` - -wrap 方法中,如果 bean 是 LoadBalancerFeignClient 的实例对象,那么首先通过 client.getDelegate() 方法将 LoadBalancerFeignClient 代理的实际 Client 对象取出后包装成 FescarFeignClient,再生成 LoadBalancerFeignClient 的子类 FescarLoadBalancerFeignClient 对象。如果 bean 是 Client 的实例对象且不是 FescarFeignClient LoadBalancerFeignClient,那么 bean 会直接包装生成 FescarFeignClient。 - -上面的流程设计还是比较巧妙的,首先根据 Spring boot 的 Auto Configuration 控制了配置的先后顺序,同时自定义了 Feign Builder 的Bean,保证了 Client 均是经过增强后的 FescarFeignClient 。再通过 BeanPostProcessor 对Spring 容器中的 Bean 做了一遍包装,保证容器内的Bean均是增强后 FescarFeignClient ,避免 FeignClientFactoryBean getTarget 方法的替换动作。 - -##### Hystrix 隔离 - -下面我们再来看下 Hystrix 部分,为什么要单独把 Hystrix 拆出来看呢,而且 Fescar 代码也单独实现了个策略类。目前事务上下文 RootContext 的默认实现是基于 ThreadLocal 方式的 ThreadLocalContextCore,也就是上下文其实是和线程绑定的。Hystrix 本身有两种隔离状态的模式,基于信号量或者基于线程池进行隔离。Hystrix 官方建议是采取线程池的方式来充分隔离,也是一般情况下在采用的模式: - -``` -Thread or Semaphore -The default, and the recommended setting, is to run HystrixCommands using thread isolation (THREAD) and HystrixObservableCommands using semaphore isolation (SEMAPHORE). - -Commands executed in threads have an extra layer of protection against latencies beyond what network timeouts can offer. - -Generally the only time you should use semaphore isolation for HystrixCommands is when the call is so high volume (hundreds per second, per instance) that the overhead of separate threads is too high; this typically only applies to non-network calls. -``` - -service 层的业务代码和请求发出的线程肯定不是同一个,那么 ThreadLocal 的方式就没办法将 XID 传递给 Hystrix 的线程并传递给被调用方的。怎么处理这件事情呢,Hystrix 提供了机制让开发者去自定义并发策略,只需要继承 HystrixConcurrencyStrategy 重写 wrapCallable 方法即可。 - -```java -public class FescarHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { - - private HystrixConcurrencyStrategy delegate; - - public FescarHystrixConcurrencyStrategy() { - this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy(); - HystrixPlugins.reset(); - HystrixPlugins.getInstance().registerConcurrencyStrategy(this); - } - - @Override - public Callable wrapCallable(Callable c) { - if (c instanceof FescarContextCallable) { - return c; - } - - Callable wrappedCallable; - if (this.delegate != null) { - wrappedCallable = this.delegate.wrapCallable(c); - } - else { - wrappedCallable = c; - } - if (wrappedCallable instanceof FescarContextCallable) { - return wrappedCallable; - } - - return new FescarContextCallable<>(wrappedCallable); - } - - private static class FescarContextCallable implements Callable { - - private final Callable actual; - private final String xid; - - FescarContextCallable(Callable actual) { - this.actual = actual; - this.xid = RootContext.getXID(); - } - - @Override - public K call() throws Exception { - try { - RootContext.bind(xid); - return actual.call(); - } - finally { - RootContext.unbind(); - } - } - - } -} -``` - -Fescar 也提供一个 FescarHystrixAutoConfiguration,在存在 HystrixCommand 的时候生成FescarHystrixConcurrencyStrategy - -```java -@Configuration -@ConditionalOnClass(HystrixCommand.class) -public class FescarHystrixAutoConfiguration { - - @Bean - FescarHystrixConcurrencyStrategy fescarHystrixConcurrencyStrategy() { - return new FescarHystrixConcurrencyStrategy(); - } - -} -``` - -### 参考文献 - -- Fescar: https://github.com/alibaba/fescar - -- Spring Cloud Alibaba: https://github.com/spring-cloud-incubator/spring-cloud-alibaba - -- spring-cloud-openfeign: https://github.com/spring-cloud/spring-cloud-openfeign - - ### 本文作者 - - 郭树抗,社区昵称 ywind,曾就职于华为终端云,现搜狐智能媒体中心Java工程师,目前主要负责搜狐号相关开发,对分布式事务、分布式系统和微服务架构有异常浓厚的兴趣。 - 季敏(清铭),社区昵称 slievrly,Fescar 开源项目负责人,阿里巴巴中间件 TXC/GTS 核心研发成员,长期从事于分布式中间件核心研发工作,在分布式事务领域有着较丰富的技术积累。 - - +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/integrate-seata-tcc-mode-with-spring-cloud.md b/blog/integrate-seata-tcc-mode-with-spring-cloud.md index 8690a57b33..e872b67af1 100644 --- a/blog/integrate-seata-tcc-mode-with-spring-cloud.md +++ b/blog/integrate-seata-tcc-mode-with-spring-cloud.md @@ -1,186 +1 @@ ---- -title: Spring Cloud集成Seata分布式事务-TCC模式 -keywords: [TCC,Seata,Spring Cloud,分布式,事务] -description: 本文主要介绍Spring Cloud集成Seata分布式事务TCC模式 -author: 弓行(谭志坚) -date: 2021-01-23 - ---- - -# Spring Cloud集成Seata分布式事务-TCC模式 - -本文将介绍基于Spring Cloud + feign 如何集成 Seata(1.4.0)的TCC模式。实际上,Seata的AT模式基本上能满足我们使用分布式事务80%的需求,但涉及不支持事务的数据库与中间件(如redis)等的操作,或AT模式暂未支持的数据库(目前AT支持Mysql、Oracle与PostgreSQL)、跨公司服务的调用、跨语言的应用调用或有手动控制整个二阶段提交过程的需求,则需要结合TCC模式。不仅如此,TCC模式还支持与AT模式混合使用。 - -本文作者:弓行(谭志坚) - -# 一、TCC模式的概念 - -一个分布式的全局事务,整体是两阶段提交**Try-[Comfirm/Cancel]** 的模型。在Seata中,AT模式与TCC模式事实上都是两阶段提交的具体实现。他们的区别在于: - -AT 模式基于**支持本地 ACID 事务** 的 **关系型数据库**(目前支持Mysql、Oracle与PostgreSQL): - -一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。 -二阶段 commit 行为:马上成功结束,**自动**异步批量清理回滚日志。 -二阶段 rollback 行为:通过回滚日志,**自动**生成补偿操作,完成数据回滚。 - -相应的,TCC 模式,不依赖于底层数据资源的事务支持: - -一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。 -二阶段 commit 行为:调用 **自定义**的 commit 逻辑。 -二阶段 rollback 行为:调用 **自定义**的 rollback 逻辑。 - -所谓 TCC 模式,是指支持把 **自定义** 的分支事务纳入到全局事务的管理中。 - -简单点概括,SEATA的TCC模式就是**手工的AT模式**,它允许你自定义两阶段的处理逻辑而不依赖AT模式的undo_log。 - -# 二、前提准备 - -- 注册中心 [nacos](https://nacos.io/zh-cn/ "nacos") -- [seata服务端(TC)](http://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html "seata服务端(TC)") - - -# 三、TM与TCC-RM的搭建 - -本章着重讲基于Spring Cloud + Feign的TCC的实现,项目的搭建直接看源码(本工程提供了AT模式与TCC模式的DEMO) - -[DEMO工程源码](https://github.com/tanzzj/springcloud-seata-feign "服务端搭建文档") - -## 3.1 seata服务端的搭建 - -[服务端搭建文档](http://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html "服务端搭建文档") - -## 3.2 TM的搭建 - -[service-tm](https://github.com/tanzzj/springcloud-seata-feign/tree/master/service-tm) - -## 3.3 RM-TCC的搭建 - -### 3.3.1 定义TCC接口 - -由于我们使用的是 SpringCloud + Feign,Feign的调用基于http,因此此处我们使用`@LocalTCC`便可。值得注意的是,`@LocalTCC`一定需要注解在接口上,此接口可以是寻常的业务接口,只要实现了TCC的两阶段提交对应方法便可,TCC相关注解如下: - -- `@LocalTCC` 适用于SpringCloud+Feign模式下的TCC -- `@TwoPhaseBusinessAction` 注解try方法,其中name为当前tcc方法的bean名称,写方法名便可(全局唯一),commitMethod指向提交方法,rollbackMethod指向事务回滚方法。指定好三个方法之后,seata会根据全局事务的成功或失败,去帮我们自动调用提交方法或者回滚方法。 -- `@BusinessActionContextParameter` 注解可以将参数传递到二阶段(commitMethod/rollbackMethod)的方法。 -- `BusinessActionContext` 便是指TCC事务上下文 - -实例如下: - -```java -/** - * 这里定义tcc的接口 - * 一定要定义在接口上 - * 我们使用springCloud的远程调用 - * 那么这里使用LocalTCC便可 - * - * @author tanzj - */ -@LocalTCC -public interface TccService { - - /** - * 定义两阶段提交 - * name = 该tcc的bean名称,全局唯一 - * commitMethod = commit 为二阶段确认方法 - * rollbackMethod = rollback 为二阶段取消方法 - * BusinessActionContextParameter注解 传递参数到二阶段中 - * - * @param params -入参 - * @return String - */ - @TwoPhaseBusinessAction(name = "insert", commitMethod = "commitTcc", rollbackMethod = "cancel") - String insert( - @BusinessActionContextParameter(paramName = "params") Map params - ); - - /** - * 确认方法、可以另命名,但要保证与commitMethod一致 - * context可以传递try方法的参数 - * - * @param context 上下文 - * @return boolean - */ - boolean commitTcc(BusinessActionContext context); - - /** - * 二阶段取消方法 - * - * @param context 上下文 - * @return boolean - */ - boolean cancel(BusinessActionContext context); -} -``` - -### 3.3.2 TCC接口的业务实现 - -为了保证代码的简洁,此处将路由层与业务层结合讲解,实际项目则不然。 - -- 在try方法中使用`@Transational`可以直接通过spring事务回滚关系型数据库中的操作,而非关系型数据库等中间件的回滚操作可以交给rollbackMethod方法处理。 -- 使用context.getActionContext("params")便可以得到一阶段try中定义的参数,在二阶段对此参数进行业务回滚操作。 -- **注意1:**此处亦不可以捕获异常(同理切面处理异常),否则TCC将识别该操作为成功,二阶段直接执行commitMethod。 -- **注意2:**TCC模式要**开发者自行**保证幂等和事务防悬挂 - -```java -@Slf4j -@RestController -public class TccServiceImpl implements TccService { - - @Autowired - TccDAO tccDAO; - - /** - * tcc服务t(try)方法 - * 根据实际业务场景选择实际业务执行逻辑或者资源预留逻辑 - * - * @param params - name - * @return String - */ - @Override - @PostMapping("/tcc-insert") - @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED) - public String insert(@RequestBody Map params) { - log.info("xid = " + RootContext.getXID()); - //todo 实际的操作,或操作MQ、redis等 - tccDAO.insert(params); - //放开以下注解抛出异常 - //throw new RuntimeException("服务tcc测试回滚"); - return "success"; - } - - /** - * tcc服务 confirm方法 - * 若一阶段采用资源预留,在二阶段确认时要提交预留的资源 - * - * @param context 上下文 - * @return boolean - */ - @Override - public boolean commitTcc(BusinessActionContext context) { - log.info("xid = " + context.getXid() + "提交成功"); - //todo 若一阶段资源预留,这里则要提交资源 - return true; - } - - /** - * tcc 服务 cancel方法 - * - * @param context 上下文 - * @return boolean - */ - @Override - public boolean cancel(BusinessActionContext context) { - //todo 这里写中间件、非关系型数据库的回滚操作 - System.out.println("please manually rollback this data:" + context.getActionContext("params")); - return true; - } -} -``` - -### 3.3.3 在TM中开启全局事务,调用RM-TCC接口 - -工程源码见3.2 - ---- -至此,Spring Cloud整合TCC模式完成 - +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/integrate-seata-with-spring-cloud.md b/blog/integrate-seata-with-spring-cloud.md index e4d22598b4..e872b67af1 100644 --- a/blog/integrate-seata-with-spring-cloud.md +++ b/blog/integrate-seata-with-spring-cloud.md @@ -1,259 +1 @@ ---- -title: Seata(Fescar)分布式事务 整合 Spring Cloud -author: 大菲.Fei -date: 2019/04/15 -keywords: [fescar、seata、分布式事务] ---- - -# 1.前言 -针对Fescar 相信很多开发者已经对他并不陌生,当然Fescar 已经成为了过去时,为什么说它是过去时,因为Fescar 已经华丽的变身为Seata。如果还不知道Seata 的朋友,请登录下面网址查看。 - - - SEATA GITHUB:[https://github.com/seata/seata] - -对于阿里各位同学的前仆后继,给我们广大开发者带来很多开源软件,在这里对他们表示真挚的感谢与问候。 - -今天在这里和大家分享下Spring Cloud 整合Seata 的相关心得。也让更多的朋友在搭建的道路上少走一些弯路,少踩一些坑。 - -# 2.工程内容 - -本次搭建流程为:client->网关->服务消费者->服务提供者. - -``` - 技术框架:spring cloud gateway - - spring cloud fegin - - nacos1.0.RC2 - - fescar-server0.4.1(Seata) -``` -关于nacos的启动方式请参考:[Nacos启动参考](https://nacos.io/zh-cn/docs/quick-start.html) - -首先seata支持很多种注册服务方式,在 fescar-server-0.4.1\conf 目录下 - -``` - file.conf - logback.xml - nacos-config.sh - nacos-config.text - registry.conf -``` - -总共包含五个文件,其中 file.conf和 registry.conf 分别是我们在 服务消费者 & 服务提供者 代码段需要用到的文件。 -注:file.conf和 registry.conf 必须在当前使用的应用程序中,即: 服务消费者 & 服务提供者 两个应用在都需要包含。 - 如果你采用了配置中心 是nacos 、zk ,file.cnf 是可以忽略的。但是type=“file” 如果是为file 就必须得用file.cnf - -下面是registry.conf 文件中的配置信息,其中 registry 是注册服务中心配置。config为配置中心的配置地方。 - -从下面可知道目前seata支持nacos,file eureka redis zookeeper 等注册配置方式,默认下载的type=“file” 文件方式,当然这里选用什么方式,取决于 - -每个人项目的实际情况,这里我选用的是nacos,eureka的也是可以的,我这边分别对这两个版本进行整合测试均可以通过。 - -注:如果整合eureka请选用官方最新版本。 - -# 3.核心配置 - -```java -registry { - # file 、nacos 、eureka、redis、zk - type = "nacos" - - nacos { - serverAddr = "localhost" - namespace = "public" - cluster = "default" - } - eureka { - serviceUrl = "http://localhost:1001/eureka" - application = "default" - weight = "1" - } - redis { - serverAddr = "localhost:6379" - db = "0" - } - zk { - cluster = "default" - serverAddr = "127.0.0.1:2181" - session.timeout = 6000 - connect.timeout = 2000 - } - file { - name = "file.conf" - } -} - -config { - # file、nacos 、apollo、zk - type = "nacos" - - nacos { - serverAddr = "localhost" - namespace = "public" - cluster = "default" - } - apollo { - app.id = "fescar-server" - apollo.meta = "http://192.168.1.204:8801" - } - zk { - serverAddr = "127.0.0.1:2181" - session.timeout = 6000 - connect.timeout = 2000 - } - file { - name = "file.conf" - } -} -``` -这里要说明的是nacos-config.sh 是针对采用nacos配置中心的话,需要执行的一些默认初始化针对nacos的脚本。 - -SEATA的启动方式参考官方: 注意,这里需要说明下,命令启动官方是通过 空格区分参数,所以要注意。这里的IP 是可选参数,因为涉及到DNS解析,在部分情况下,有的时候在注册中心fescar 注入nacos的时候会通过获取地址,如果启动报错注册发现是计算机名称,需要指定IP。或者host配置IP指向。不过这个问题,在最新的SEATA中已经进行了修复。 - -```shell -sh fescar-server.sh 8091 /home/admin/fescar/data/ IP(可选) -``` - - -上面提到过,在我们的代码中也是需要file.conf 和registry.conf 这里着重的地方要说的是file.conf,file.conf只有当registry中 -配置file的时候才会进行加载,如果采用ZK、nacos、作为配置中心,可以忽略。因为type指定其他是不加载file.conf的,但是对应的 service.localRgroup.grouplist 和 service.vgroupMapping 需要在支持配置中心 进行指定,这样你的client 在启动后会通过自动从配置中心获取对应的 SEATA 服务 和地址。如果不配置会出现无法连接server的错误。当然如果你采用的eureka在config的地方就需要采用type="file" 目前SEATA config暂时不支持eureka的形势 - -```java -transport { - # tcp udt unix-domain-socket - type = "TCP" - #NIO NATIVE - server = "NIO" - #enable heartbeat - heartbeat = true - #thread factory for netty - thread-factory { - boss-thread-prefix = "NettyBoss" - worker-thread-prefix = "NettyServerNIOWorker" - server-executor-thread-prefix = "NettyServerBizHandler" - share-boss-worker = false - client-selector-thread-prefix = "NettyClientSelector" - client-selector-thread-size = 1 - client-worker-thread-prefix = "NettyClientWorkerThread" - # netty boss thread size,will not be used for UDT - boss-thread-size = 1 - #auto default pin or 8 - worker-thread-size = 8 - } -} -service { - #vgroup->rgroup - vgroup_mapping.service-provider-fescar-service-group = "default" - #only support single node - localRgroup.grouplist = "127.0.0.1:8091" - #degrade current not support - enableDegrade = false - #disable - disable = false -} - -client { - async.commit.buffer.limit = 10000 - lock { - retry.internal = 10 - retry.times = 30 - } -} -``` - -# 4.服务相关 - 这里有两个地方需要注意 - -```java - grouplist IP,这里是当前fescar-sever的IP端口, - vgroup_mapping的配置。 -``` - vgroup_mapping.服务名称-fescar-service-group,这里 要说下服务名称其实是你当前的consumer 或者provider application.properties的配置的应用名称:spring.application.name=service-provider,源代码中是 获取应用名称与 fescar-service-group 进行拼接,做key值。同理value是当前fescar的服务名称, cluster = "default" / application = "default" - -```java - vgroup_mapping.service-provider-fescar-service-group = "default" - #only support single node - localRgroup.grouplist = "127.0.0.1:8091" -``` -同理无论是provider 还是consumer 都需要这两个文件进行配置。 - -如果你采用nacos做配置中心,需要在nacos通过添加配置方式进行配置添加。 -# 5.事务使用 -我这里的代码逻辑是请求通过网关进行负载转发到我的consumer上,在consumer 中通过fegin进行provider请求。官方的例子中是通过fegin进行的,而我们这边直接通过网关转发,所以全局事务同官方的demo一样 也都是在controller层。 - -```java -@RestController -public class DemoController { - @Autowired - private DemoFeignClient demoFeignClient; - - @Autowired - private DemoFeignClient2 demoFeignClient2; - @GlobalTransactional(timeoutMills = 300000, name = "spring-cloud-demo-tx") - @GetMapping("/getdemo") - public String demo() { - - // 调用A 服务 简单save - ResponseData result = demoFeignClient.insertService("test",1); - if(result.getStatus()==400) { - System.out.println(result+"+++++++++++++++++++++++++++++++++++++++"); - throw new RuntimeException("this is error1"); - } - - // 调用B 服务。报错测试A 服务回滚 - ResponseData result2 = demoFeignClient2.saveService(); - - if(result2.getStatus()==400) { - System.out.println(result2+"+++++++++++++++++++++++++++++++++++++++"); - throw new RuntimeException("this is error2"); - } - - return "SUCCESS"; - } -} -``` -到此为止核心的事务整合基本到此结束了,我这里是针对A,B 两个provider进行调用,当B发生报错后,进行全局事务回滚。当然每个事务内部都可以通过自己的独立本地事务去处理自己本地事务方式。 - -SEATA是通过全局的XID方式进行事务统一标识方式。这里就不列出SEATA需要用的数据库表。具体参考:[spring-cloud-fescar 官方DEMO](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples/fescar-example) -# 5.数据代理 - -这里还有一个重要的说明就是,在分库服务的情况下,每一个数据库内都需要有一个undo_log的数据库表进行XID统一存储处理。 - -同事针对每个提供服务的项目,需要进行数据库连接池的代理。也就是: - -目前只支持Druid连接池,后续会继续支持。 - -```java -@Configuration -public class DatabaseConfiguration { - - - @Bean(destroyMethod = "close", initMethod = "init") - @ConfigurationProperties(prefix="spring.datasource") - public DruidDataSource druidDataSource() { - - return new DruidDataSource(); - } - - - @Bean - public DataSourceProxy dataSourceProxy(DruidDataSource druidDataSource) { - - return new DataSourceProxy(druidDataSource); - } - - - @Bean - public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception { - SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); - factoryBean.setDataSource(dataSourceProxy); - return factoryBean.getObject(); - } -} -``` -大家要注意的就是配置文件和数据代理。如果没有进行数据源代理,undo_log是无数据的,也就是没办进行XID的管理。 - - -本文作者:大菲.Fei - +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/iscas2023.md b/blog/iscas2023.md index 0741a17189..e872b67af1 100644 --- a/blog/iscas2023.md +++ b/blog/iscas2023.md @@ -1,153 +1 @@ ---- -title: 6大课题现已开放挑选 | 欢迎报名 Seata 开源之夏 -author: Seata社区 -date: 2023/05/12 -keywords: [开源之夏、seata、分布式事务] ---- - -# 6大课题现已开放挑选 | 欢迎报名 Seata 开源之夏 - -### 欢迎大家报名Seata 开源之夏2023课题 -开源之夏 2023 学生报名期为 **4 月 29 日-6月4日**,欢迎报名Seata 2023 课题!在这里,您将有机会深入探讨分布式事务的理论和应用,并与来自不同背景的同学一起合作完成实践项目。我们期待着您的积极参与和贡献,共同推动分布式事务领域的发展。 - -![summer2023-1](/img/blog/summer2023-1.jpg) - -### 开源之夏2023 -开源之夏是由中科院软件所“开源软件供应链点亮计划”发起并长期支持的一项暑期开源活动,旨在鼓励在校学生积极参与开源软件的开发维护,培养和发掘更多优秀的开发者,促进优秀开源软件社区的蓬勃发展,助力开源软件供应链建设。 - -参与学生通过远程线上协作方式,配有资深导师指导,参与到开源社区各组织项目开发中并收获奖金、礼品与证书。**这些收获,不仅仅是未来毕业简历上浓墨重彩的一笔,更是迈向顶尖开发者的闪亮起点,可以说非常值得一试。** 每个项目难度分为基础和进阶两档,对应学生结项奖金分别为税前人民币 8000 元和税前人民币 12000 元。 - -### Seata社区介绍 -**Seata** 是一款开源的分布式事务解决方案,GitHub获得超过23K+ Starts致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 在阿里内部一直扮演着分布式数据一致性的中间件角色,几乎每笔交易都要使用Seata,历经双11洪荒流量的洗礼,对业务进行了有力的技术支撑。 - -### Seata社区开源之夏2023项目课题汇总 -Seata社区为开源之夏2023组委会推荐6项精选项目课题,您可以访问以下链接进行选报: -https://summer-ospp.ac.cn/org/orgdetail/064c15df-705c-483a-8fc8-02831370db14?lang=zh -请及时与各导师沟通并准备项目申请材料,并登录官方注册申报(以下课题顺序不分先后): -![seata2023-2](/img/blog/summer2023-2.png) - -#### 项目一: 实现用于服务发现和注册的NamingServer - -**难度:** 进阶/Advanced - -**项目社区导师:** 陈健斌 - -**导师联系邮箱:** 364176773@qq.com - -**项目简述:** -目前seata的服务暴露及发现主要依赖于第三方注册中心,随着项目的演进发展,带来了额外的学习使用成本,而业内主流具有服务端的中间件大多都开始演进自身的服务自闭环和控制及提供于服务端更高契合度和可靠性的组件或功能如kafka 的KRaft,rocketmq的NameServer,clickhouse的ClickHouse Keeper等.故为了解决如上问题和架构演进要求,seata需要构建自身的nameserver来保证更加稳定可靠。 - -**项目链接:** -https://summer-ospp.ac.cn/org/prodetail/230640380?list=org&navpage=org -
-
- -#### 项目二: 在seata-go中实现saga事务模式 - -**难度:** 进阶/Advanced - -**项目社区导师:** 刘月财 - -**导师联系邮箱:** luky116@apache.org - -**项目简述:** -Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。Seata-go 中当前没有支持saga模式,希望后面参考Java版本的实现能将该功能支持上。 - -**项目链接:** -https://summer-ospp.ac.cn/org/prodetail/230640382?list=org&navpage=org -
-
- -#### 项目三: seata saga模式产品化能力提升 -**难度:** 进阶/Advanced - -**项目社区导师:** 李宗杰 - -**导师联系邮箱:** leezongjie@163.com - -**项目简述:** -saga作为分布式事务的解决方案之一,在长事务上应用尤其广泛,seata也提供了对应的状态机实现。随着业务的不断发展和接入,对seata提出了更高的要求,我们需要支持流式saga 编排,对当前状态机的实现进行优化和扩展,进一步服务业务。 - -**项目链接:** -https://summer-ospp.ac.cn/org/prodetail/230640415?list=org&navpage=org -
-
- -#### 项目四: 增加控制台事务控制能力 -**难度:** 进阶/Advanced - -**项目社区导师:** 王良 - -**导师联系邮箱:** 841369634@qq.com - -**项目简述:** -在分布式事务中,经常会存在非常多的异常情况,这些异常情况往往会导致系统无法继续运行。而这些异常往往需要人工介入排查原因并排除故障,才能够使系统继续正常运行。虽然seata 提供了控制台查询事务数据,但还未提供任何事务控制能力,帮助排除故障。所以,本课题主要是在seata服务端控制台上,添加事务控制能力。 - -**项目链接:** -https://summer-ospp.ac.cn/org/prodetail/230640423?list=org&navpage=org -
-
- -#### 项目五: 提高单测覆盖率和建立集成测试 -**难度:** 基础/Basic - -**项目社区导师:** 张嘉伟 - -**导师联系邮箱:** 349071347@qq.com - -**项目简述:** -为了进一步提高项目的质量以及稳定性, 需要进一步提升项目单元测试覆盖率以及加入集成测试来模拟生产中不同的场景. 集成测试的目的是为了模拟client与server的交互过程, 而非单一的对某个接口进行测试。 - -**项目链接:** -https://summer-ospp.ac.cn/org/prodetail/230640424?list=org&navpage=org -
-
- -#### 项目六: 实现Seata运维ctl工具 -**难度:** 进阶/Advanced - -**项目社区导师:** 季敏 - -**导师联系邮箱:** jimin.jm@alibaba-inc.com - -**项目简述:** 运维ctl命令在Seata中非常重要,它是Seata的命令行工具,可以帮助我们管理和操作Seata的各种组件。运维ctl命令可以让我们快速地启动、停止和管理Seata服务,定位和解决问题。此外,运维ctl 命令还提供了丰富的指令,可以让我们方便地检查Seata的健康状态、模拟事务和打印导出配置信息等,大大提高了我们的工作效率和运维体验。 - -以下是对实现定制ctl运维命令行的一些建议: -- 借鉴其他开源项目的实现方式,比如kubectl,helm等,并根据Seata的特点和需求进行定制。 -- 将常用的运维操作直接封装进命令行,减少用户的手动操作。 -- 考虑使用友好的命令和参数名称,将命令行设计得易于理解和记忆。 -- 提供详细的帮助文档和示例,帮助用户快速上手和了解如何使用各种参数和选项。 -- 考虑命令行的跨平台支持,例如支持Windows、Linux和MacOS等操作系统。 -一款好的ctl命令行应该是易用、灵活、可定制、健壮和易维护的。 - -项目链接:https://summer-ospp.ac.cn/org/prodetail/230640431?list=org&navpage=org -
-
- -### 如何参与开源之夏2023并快速选定项目? -**欢迎通过上方联系方式,与各导师沟通并准备项目申请材料。** - -课题参与期间,学生可以在世界任何地方线上工作,Seata相关项目结项需要在**9月30日**前以 PR 的形式提交到Seata社区仓库中并完成合并,请务必尽早准备。 -![seata2023-3](/img/blog/summer2023-3.png) - -**需要在课题期间第一时间获取导师及其他信息,可扫码进入钉钉群交流** ——了解Seata社区各领域项目、结识Seata社区开源导师,以助力后续申请。 -
- -
- -#### 参考资料: -**Seata网站 :** https://seata.io/ - -**Seata GitHub :** https://github.com/seata - -**开源之夏官网:** https://summer-ospp.ac.cn/org/orgdetail/ab188e59-fab8-468f-bc89-bdc2bd8b5e64?lang=zh - -如果同学们对微服务其他领域项目感兴趣,也可以尝试申请,例如: - -- 对于**微服务配置注册中心**有兴趣的同学,可以尝试填报[Nacos 开源之夏](https://nacos.io/zh-cn/blog/iscas2023.html); -- 对于**微服务框架和RPC框架**有兴趣的同学,可以尝试填报[Spring Cloud Alibaba 开源之夏](https://summer-ospp.ac.cn/org/orgdetail/41d68399-ed48-4d6d-9d4d-3ff4128dc132?lang=zh) 和 [Dubbo 开源之夏](https://summer-ospp.ac.cn/org/orgdetail/a7f6e2ad-4acc-47f8-9471-4e54b9a166a6?lang=zh) ; -- 对于**云原生网关**有兴趣的同学,可以尝试填报[Higress 开源之夏](https://higress.io/zh-cn/blog/ospp-2023); -- 对于**分布式高可用防护**有兴趣的同学,可以尝试填报 [Sentinel 开源之夏](https://summer-ospp.ac. cn/org/orgdetail/5e879522-bd90-4a8b-bf8b-b11aea48626b?lang=zh) ; -- 对于**微服务治理**有兴趣的同学,可以尝试填报 [OpenSergo 开源之夏](https://summer-ospp.ac. cn/org/orgdetail/aaff4eec-11b1-4375-997d-5eea8f51762b?lang=zh)。 - - +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/manual-transaction-mode.md b/blog/manual-transaction-mode.md index 4923623d82..e872b67af1 100644 --- a/blog/manual-transaction-mode.md +++ b/blog/manual-transaction-mode.md @@ -1,33 +1 @@ ---- -title: MT 模式 -keywords: [MT 模式] -description: 介绍 MT 模式 -author: kmmshmily -date: 2019-02-13 ---- -# Manual Transaction 模式 - -回顾总览中的描述:一个分布式的全局事务,整体是 **两阶段提交** 的模型。全局事务是由若干分支事务组成的,分支事务要满足 **两阶段提交** 的模型要求,即需要每个分支事务都具备自己的: - -- 一阶段 prepare 行为 -- 二阶段 commit 或 rollback 行为 - -![Overview of a global transaction](https://upload-images.jianshu.io/upload_images/4420767-e48f0284a037d1df.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -根据两阶段行为模式的不同,我们将分支事务划分为 **Automatic (Branch) Transaction Mode** 和 **Manual (Branch) Transaction Mode**. - -AT 模式基于 **支持本地 ACID 事务** 的 **关系型数据库**: - -- 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。 -- 二阶段 commit 行为:马上成功结束,**自动** 异步批量清理回滚日志。 -- 二阶段 rollback 行为:通过回滚日志,**自动** 生成补偿操作,完成数据回滚。 - -相应的,MT 模式,不依赖于底层数据资源的事务支持: - -- 一阶段 prepare 行为:调用 **自定义** 的 prepare 逻辑。 -- 二阶段 commit 行为:调用 **自定义** 的 commit 逻辑。 -- 二阶段 rollback 行为:调用 **自定义** 的 rollback 逻辑。 - -所谓 MT 模式,是指支持把 **自定义** 的分支事务纳入到全局事务的管理中。 - - +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/quick-start-use-seata-and-dubbo-services.md b/blog/quick-start-use-seata-and-dubbo-services.md index f3f8d87145..e872b67af1 100644 --- a/blog/quick-start-use-seata-and-dubbo-services.md +++ b/blog/quick-start-use-seata-and-dubbo-services.md @@ -1,226 +1 @@ ---- -title: 如何使用Seata保证Dubbo微服务间的一致性 -keywords: [Dubbo,Seata,一致性] -description: 本文主要介绍如何使用Seata保证Dubbo微服务间的一致性 -author: slievrly -date: 2019-03-07 ---- -# 如何使用Seata保证Dubbo微服务间的一致性 - -## 案例 - -用户采购商品业务,整个业务包含3个微服务: - -- 库存服务: 扣减给定商品的库存数量。 -- 订单服务: 根据采购请求生成订单。 -- 账户服务: 用户账户金额扣减。 - -### 业务结构图 - -![Architecture](/img/blog/seata/seata-1.png) - - -### StorageService - -```java -public interface StorageService { - - /** - * deduct storage count - */ - void deduct(String commodityCode, int count); -} -``` - -### OrderService - -```java -public interface OrderService { - - /** - * create order - */ - Order create(String userId, String commodityCode, int orderCount); -} -``` - -### AccountService - -```java -public interface AccountService { - - /** - * debit balance of user's account - */ - void debit(String userId, int money); -} -``` - -### 主要的业务逻辑: - -```java -public class BusinessServiceImpl implements BusinessService { - - private StorageService storageService; - - private OrderService orderService; - - /** - * purchase - */ - public void purchase(String userId, String commodityCode, int orderCount) { - - storageService.deduct(commodityCode, orderCount); - - orderService.create(userId, commodityCode, orderCount); - } -} -``` - -```java -public class StorageServiceImpl implements StorageService { - - private StorageDAO storageDAO; - - @Override - public void deduct(String commodityCode, int count) { - Storage storage = new Storage(); - storage.setCount(count); - storage.setCommodityCode(commodityCode); - storageDAO.update(storage); - } -} -``` - -```java -public class OrderServiceImpl implements OrderService { - - private OrderDAO orderDAO; - - private AccountService accountService; - - public Order create(String userId, String commodityCode, int orderCount) { - - int orderMoney = calculate(commodityCode, orderCount); - - accountService.debit(userId, orderMoney); - - Order order = new Order(); - order.userId = userId; - order.commodityCode = commodityCode; - order.count = orderCount; - order.money = orderMoney; - - return orderDAO.insert(order); - } -} -``` - -## Seata 分布式事务解决方案 - -![undefined](/img/blog/seata/seata-2.png) - -此处仅仅需要一行注解 `@GlobalTransactional` 写在业务发起方的方法上: - -```java - - @GlobalTransactional - public void purchase(String userId, String commodityCode, int orderCount) { - ...... - } -``` - -## Dubbo 与 Seata 结合的例子 - -### Step 1: 安装数据库 - -- 要求: MySQL (InnoDB 存储引擎)。 - -**提示:** 事实上例子中3个微服务需要3个独立的数据库,但为了方便我们使用同一物理库并配置3个逻辑连接串。 - -更改以下xml文件中的数据库url、username和password - -dubbo-account-service.xml -dubbo-order-service.xml -dubbo-storage-service.xml - -```xml - - - -``` -### Step 2: 为 Seata 创建 UNDO_LOG 表 - -`UNDO_LOG` 此表用于 Seata 的AT模式。 - -```sql -CREATE TABLE `undo_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `branch_id` bigint(20) NOT NULL, - `xid` varchar(100) NOT NULL, - `rollback_info` longblob NOT NULL, - `log_status` int(11) NOT NULL, - `log_created` datetime NOT NULL, - `log_modified` datetime NOT NULL, - `ext` varchar(100) DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `idx_unionkey` (`xid`,`branch_id`) -) ENGINE=InnoDB AUTO_INCREMENT=159 DEFAULT CHARSET=utf8 -``` - -### Step 3: 创建相关业务表 - -```sql - -DROP TABLE IF EXISTS `storage_tbl`; -CREATE TABLE `storage_tbl` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `commodity_code` varchar(255) DEFAULT NULL, - `count` int(11) DEFAULT 0, - PRIMARY KEY (`id`), - UNIQUE KEY (`commodity_code`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - - -DROP TABLE IF EXISTS `order_tbl`; -CREATE TABLE `order_tbl` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `user_id` varchar(255) DEFAULT NULL, - `commodity_code` varchar(255) DEFAULT NULL, - `count` int(11) DEFAULT 0, - `money` int(11) DEFAULT 0, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - - -DROP TABLE IF EXISTS `account_tbl`; -CREATE TABLE `account_tbl` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `user_id` varchar(255) DEFAULT NULL, - `money` int(11) DEFAULT 0, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -``` -### Step 4: 启动 Seata-Server 服务 - -- 下载Server [package](https://github.com/seata/seata/releases), 并解压。 -- 运行bin目录下的启动脚本。 - -```shell -sh seata-server.sh $LISTEN_PORT $PATH_FOR_PERSISTENT_DATA - -e.g. - -sh seata-server.sh 8091 /home/admin/seata/data/ -``` - -### Step 5: 运行例子 - -- 启动账户服务 ([DubboAccountServiceStarter](https://github.com/seata/seata-samples/blob/master/dubbo/src/main/java/com/seata/seata/samples/dubbo/starter/DubboAccountServiceStarter.java))。 -- 启动库存服务 ([DubboStorageServiceStarter](https://github.com/seata/seata-samples/blob/master/dubbo/src/main/java/com/seata/seata/samples/dubbo/starter/DubboStorageServiceStarter.java))。 -- 启动订单服务 ([DubboOrderServiceStarter](https://github.com/seata/seata-samples/blob/master/dubbo/src/main/java/com/seata/seata/samples/dubbo/starter/DubboOrderServiceStarter.java))。 -- 运行BusinessService入口 ([DubboBusinessTester](https://github.com/seata/seata-samples/blob/master/dubbo/src/main/java/com/seata/seata/samples/dubbo/starter/DubboBusinessTester.java))。 - -### 相关项目 -* seata: https://github.com/seata/seata/ -* seata-samples : https://github.com/seata/seata-samples +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-1.5.2.md b/blog/seata-1.5.2.md index d7908221da..e872b67af1 100644 --- a/blog/seata-1.5.2.md +++ b/blog/seata-1.5.2.md @@ -1,88 +1 @@ ---- -title: Seata 1.5.2 重磅发布,支持xid负载均衡 -author: Seata社区 -keywords: [seata、分布式事务、1.5.2] -description: Seata 1.5.2 重磅发布,支持xid负载均衡 -date: 2022/07/12 ---- - -### Seata 1.5.2 重磅发布,支持xid负载均衡 - -Seata 是一款开源的分布式事务解决方案,提供高性能和简单易用的分布式事务服务。 - -**seata-server 下载链接:** - -[source](https://github.com/seata/seata/archive/v1.5.2.zip) | -[binary](https://github.com/seata/seata/releases/download/v1.5.2/seata-server-1.5.2.zip) - -此版本更新如下: - -### feature: -- [[#4661](https://github.com/seata/seata/pull/4713)] 支持根据xid负载均衡算法 -- [[#4676](https://github.com/seata/seata/pull/4676)] 支持Nacos作为注册中心时,server通过挂载SLB暴露服务 -- [[#4642](https://github.com/seata/seata/pull/4642)] 支持client批量请求并行处理 -- [[#4567](https://github.com/seata/seata/pull/4567)] 支持where条件中find_in_set函数 - - -### bugfix: -- [[#4515](https://github.com/seata/seata/pull/4515)] 修复develop分支SeataTCCFenceAutoConfiguration在客户端未使用DB时,启动抛出ClassNotFoundException的问题。 -- [[#4661](https://github.com/seata/seata/pull/4661)] 修复控制台中使用PostgreSQL出现的SQL异常 -- [[#4667](https://github.com/seata/seata/pull/4682)] 修复develop分支RedisTransactionStoreManager迭代时更新map的异常 -- [[#4678](https://github.com/seata/seata/pull/4678)] 修复属性transport.enableRmClientBatchSendRequest没有配置的情况下缓存穿透的问题 -- [[#4701](https://github.com/seata/seata/pull/4701)] 修复命令行参数丢失问题 -- [[#4607](https://github.com/seata/seata/pull/4607)] 修复跳过全局锁校验的缺陷 -- [[#4696](https://github.com/seata/seata/pull/4696)] 修复 oracle 存储模式时的插入问题 -- [[#4726](https://github.com/seata/seata/pull/4726)] 修复批量发送消息时可能的NPE问题 -- [[#4729](https://github.com/seata/seata/pull/4729)] 修复AspectTransactional.rollbackForClassName设置错误 -- [[#4653](https://github.com/seata/seata/pull/4653)] 修复 INSERT_ON_DUPLICATE 主键为非数值异常 - -### optimize: -- [[#4650](https://github.com/seata/seata/pull/4650)] 修复安全漏洞 -- [[#4670](https://github.com/seata/seata/pull/4670)] 优化branchResultMessageExecutor线程池的线程数 -- [[#4662](https://github.com/seata/seata/pull/4662)] 优化回滚事务监控指标 -- [[#4693](https://github.com/seata/seata/pull/4693)] 优化控制台导航栏 -- [[#4700](https://github.com/seata/seata/pull/4700)] 修复 maven-compiler-plugin 和 maven-resources-plugin 执行失败 -- [[#4711](https://github.com/seata/seata/pull/4711)] 分离部署时 lib 依赖 -- [[#4720](https://github.com/seata/seata/pull/4720)] 优化pom描述 -- [[#4728](https://github.com/seata/seata/pull/4728)] 将logback版本依赖升级至1.2.9 -- [[#4745](https://github.com/seata/seata/pull/4745)] 发行包中支持 mysql8 driver -- [[#4626](https://github.com/seata/seata/pull/4626)] 使用 `easyj-maven-plugin` 插件代替 `flatten-maven-plugin`插件,以修复`shade` 插件与 `flatten` 插件不兼容的问题 -- [[#4629](https://github.com/seata/seata/pull/4629)] 更新globalSession状态时检查更改前后的约束关系 -- [[#4662](https://github.com/seata/seata/pull/4662)] 优化 EnhancedServiceLoader 可读性 - - -### test: -- [[#4544](https://github.com/seata/seata/pull/4544)] 优化TransactionContextFilterTest中jackson包依赖问题 -- [[#4731](https://github.com/seata/seata/pull/4731)] 修复 AsyncWorkerTest 和 LockManagerTest 的单测问题。 - - -非常感谢以下 contributors 的代码贡献。若有无意遗漏,请报告。 - - -- [slievrly](https://github.com/slievrly) -- [pengten](https://github.com/pengten) -- [YSF-A](https://github.com/YSF-A) -- [tuwenlin](https://github.com/tuwenlin) -- [2129zxl](https://github.com/2129zxl) -- [Ifdevil](https://github.com/Ifdevil) -- [wingchi-leung](https://github.com/wingchi-leung) -- [liurong](https://github.com/robynron) -- [opelok-z](https://github.com/opelok-z) -- [a364176773](https://github.com/a364176773) -- [Smery-lxm](https://github.com/Smery-lxm) -- [lvekee](https://github.com/lvekee) -- [doubleDimple](https://github.com/doubleDimple) -- [wangliang181230](https://github.com/wangliang181230) -- [Bughue](https://github.com/Bughue) -- [AYue-94](https://github.com/AYue-94) -- [lingxiao-wu](https://github.com/lingxiao-wu) -- [caohdgege](https://github.com/caohdgege) - -同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 - -#### Link - -- **Seata:** https://github.com/seata/seata -- **Seata-Samples:** https://github.com/seata/seata-samples -- **Release:** https://github.com/seata/seata/releases -- **WebSite:** https://seata.io +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-1.6.0.md b/blog/seata-1.6.0.md index e7c11bb841..e872b67af1 100644 --- a/blog/seata-1.6.0.md +++ b/blog/seata-1.6.0.md @@ -1,160 +1 @@ ---- -title: Seata 1.6.0 重磅发布,大幅提升性能 -author: Seata社区 -keywords: [seata、分布式事务、1.6.0] -description: Seata 1.6.0 重磅发布,大幅提升性能 -date: 2022/12/17 ---- - -### Seata 1.6.0 重磅发布,大幅提升性能 - -Seata 是一款开源的分布式事务解决方案,提供高性能和简单易用的分布式事务服务。 - -**seata-server 下载链接:** - -[source](https://github.com/seata/seata/archive/v1.6.0.zip) | -[binary](https://github.com/seata/seata/releases/download/v1.6.0/seata-server-1.6.0.zip) - -此版本更新如下: - -### feature: -- [[#4863](https://github.com/seata/seata/pull/4863)] 支持 oracle 和 postgresql 多主键 -- [[#4649](https://github.com/seata/seata/pull/4649)] seata-server支持多注册中心 -- [[#4779](https://github.com/seata/seata/pull/4779)] 支持 Apache Dubbo3 -- [[#4479](https://github.com/seata/seata/pull/4479)] TCC注解支持添加在接口和实现类上 -- [[#4877](https://github.com/seata/seata/pull/4877)] client sdk 支持jdk17 -- [[#4914](https://github.com/seata/seata/pull/4914)] 支持 mysql 的update join联表更新语法 -- [[#4542](https://github.com/seata/seata/pull/4542)] 支持 oracle timestamp 类型 -- [[#5111](https://github.com/seata/seata/pull/5111)] 支持Nacos contextPath 配置 -- [[#4802](https://github.com/seata/seata/pull/4802)] dockerfile 支持 arm64 - - -### bugfix: -- [[#4780](https://github.com/seata/seata/pull/4780)] 修复超时回滚成功后无法发送TimeoutRollbacked事件 -- [[#4954](https://github.com/seata/seata/pull/4954)] 修复output表达式错误时,保存执行结果空指针异常 -- [[#4817](https://github.com/seata/seata/pull/4817)] 修复高版本springboot配置不标准的问题 -- [[#4838](https://github.com/seata/seata/pull/4838)] 修复使用 Statement.executeBatch() 时无法生成undo log 的问题 -- [[#4533](https://github.com/seata/seata/pull/4533)] 修复handleRetryRollbacking的event重复导致的指标数据不准确 -- [[#4912](https://github.com/seata/seata/pull/4912)] 修复mysql InsertOnDuplicateUpdate 列名大小写不一致无法正确匹配 -- [[#4543](https://github.com/seata/seata/pull/4543)] 修复对 Oracle 数据类型nclob的支持 -- [[#4915](https://github.com/seata/seata/pull/4915)] 修复获取不到ServerRecoveryProperties属性的问题 -- [[#4919](https://github.com/seata/seata/pull/4919)] 修复XID的port和address出现null:0的情况 -- [[#4928](https://github.com/seata/seata/pull/4928)] 修复 rpcContext.getClientRMHolderMap NPE 问题 -- [[#4953](https://github.com/seata/seata/pull/4953)] 修复InsertOnDuplicateUpdate可绕过修改主键的问题 -- [[#4978](https://github.com/seata/seata/pull/4978)] 修复 kryo 支持循环依赖 -- [[#4985](https://github.com/seata/seata/pull/4985)] 修复 undo_log id重复的问题 -- [[#4874](https://github.com/seata/seata/pull/4874)] 修复OpenJDK 11 启动失败 -- [[#5018](https://github.com/seata/seata/pull/5018)] 修复启动脚本中 loader path 使用相对路径导致 server 启动失败问题 -- [[#5004](https://github.com/seata/seata/pull/5004)] 修复mysql update join行数据重复的问题 -- [[#5032](https://github.com/seata/seata/pull/5032)] 修复mysql InsertOnDuplicateUpdate中条件参数填充位置计算错误导致的镜像查询SQL语句异常问题 -- [[#5033](https://github.com/seata/seata/pull/5033)] 修复InsertOnDuplicateUpdate的SQL语句中无插入列字段导致的空指针问题 -- [[#5038](https://github.com/seata/seata/pull/5038)] 修复SagaAsyncThreadPoolProperties冲突问题 -- [[#5050](https://github.com/seata/seata/pull/5050)] 修复Saga模式下全局状态未正确更改成Committed问题 -- [[#5052](https://github.com/seata/seata/pull/5052)] 修复update join条件中占位符参数问题 -- [[#5031](https://github.com/seata/seata/pull/5031)] 修复InsertOnDuplicateUpdate中不应该使用null值索引作为查询条件 -- [[#5075](https://github.com/seata/seata/pull/5075)] 修复InsertOnDuplicateUpdate无法拦截无主键和唯一索引的SQL -- [[#5093](https://github.com/seata/seata/pull/5093)] 修复seata server重启后accessKey丢失问题 -- [[#5092](https://github.com/seata/seata/pull/5092)] 修复当seata and jpa共同使用时, AutoConfiguration的顺序不正确的问题 -- [[#5109](https://github.com/seata/seata/pull/5109)] 修复当RM侧没有加@GlobalTransactional报NPE的问题 -- [[#5098](https://github.com/seata/seata/pull/5098)] Druid 禁用 oracle implicit cache -- [[#4860](https://github.com/seata/seata/pull/4860)] 修复metrics tag覆盖问题 -- [[#5028](https://github.com/seata/seata/pull/5028)] 修复 insert on duplicate SQL中 null 值问题 -- [[#5078](https://github.com/seata/seata/pull/5078)] 修复SQL语句中无主键和唯一键拦截问题 -- [[#5097](https://github.com/seata/seata/pull/5097)] 修复当Server重启时 accessKey 丢失问题 -- [[#5131](https://github.com/seata/seata/pull/5131)] 修复XAConn处于active状态时无法回滚的问题 -- [[#5134](https://github.com/seata/seata/pull/5134)] 修复hikariDataSource 自动代理在某些情况下失效的问题 -- [[#5163](https://github.com/seata/seata/pull/5163)] 修复高版本JDK编译失败的问题 - -### optimize: -- [[#4681](https://github.com/seata/seata/pull/4681)] 优化竞争锁过程 -- [[#4774](https://github.com/seata/seata/pull/4774)] 优化 seataio/seata-server 镜像中的 mysql8 依赖 -- [[#4750](https://github.com/seata/seata/pull/4750)] 优化AT分支释放全局锁不使用xid -- [[#4790](https://github.com/seata/seata/pull/4790)] 添加自动发布 OSSRH github action -- [[#4765](https://github.com/seata/seata/pull/4765)] mysql8.0.29版本及以上XA模式不持connection至二阶段 -- [[#4797](https://github.com/seata/seata/pull/4797)] 优化所有github actions脚本 -- [[#4800](https://github.com/seata/seata/pull/4800)] 添加 NOTICE 文件 -- [[#4761](https://github.com/seata/seata/pull/4761)] 使用 hget 代替 RedisLocker 中的 hmget -- [[#4414](https://github.com/seata/seata/pull/4414)] 移除log4j依赖 -- [[#4836](https://github.com/seata/seata/pull/4836)] 优化 BaseTransactionalExecutor#buildLockKey(TableRecords rowsIncludingPK) 方法可读性 -- [[#4865](https://github.com/seata/seata/pull/4865)] 修复 Saga 可视化设计器 GGEditor 安全漏洞 -- [[#4590](https://github.com/seata/seata/pull/4590)] 自动降级支持开关支持动态配置 -- [[#4490](https://github.com/seata/seata/pull/4490)] tccfence 记录表优化成按索引删除 -- [[#4911](https://github.com/seata/seata/pull/4911)] 添加 header 和license 检测 -- [[#4917](https://github.com/seata/seata/pull/4917)] 升级 package-lock.json 修复漏洞 -- [[#4924](https://github.com/seata/seata/pull/4924)] 优化 pom 依赖 -- [[#4932](https://github.com/seata/seata/pull/4932)] 抽取部分配置的默认值 -- [[#4925](https://github.com/seata/seata/pull/4925)] 优化 javadoc 注释 -- [[#4921](https://github.com/seata/seata/pull/4921)] 修复控制台模块安全漏洞和升级 skywalking-eyes 版本 -- [[#4936](https://github.com/seata/seata/pull/4936)] 优化存储配置的读取 -- [[#4946](https://github.com/seata/seata/pull/4946)] 将获取锁时遇到的sql异常传递给客户端 -- [[#4962](https://github.com/seata/seata/pull/4962)] 优化构建配置,并修正docker镜像的基础镜像 -- [[#4974](https://github.com/seata/seata/pull/4974)] 取消redis模式下,查询globalStatus数量的限制 -- [[#4981](https://github.com/seata/seata/pull/4981)] 优化当tcc fence记录查不到时的错误提示 -- [[#4995](https://github.com/seata/seata/pull/4995)] 修复mysql InsertOnDuplicateUpdate后置镜像查询SQL中重复的主键查询条件 -- [[#5047](https://github.com/seata/seata/pull/5047)] 移除无用代码 -- [[#5051](https://github.com/seata/seata/pull/5051)] 回滚时undolog产生脏写需要抛出不再重试(BranchRollbackFailed_Unretriable)的异常 -- [[#5075](https://github.com/seata/seata/pull/5075)] 拦截没有主键及唯一索引值的insert on duplicate update语句 -- [[#5104](https://github.com/seata/seata/pull/5104)] ConnectionProxy脱离对druid的依赖 -- [[#5124](https://github.com/seata/seata/pull/5124)] 支持oracle删除TCC fence记录表 -- [[#4468](https://github.com/seata/seata/pull/4968)] 支持kryo 5.3.0 -- [[#4807](https://github.com/seata/seata/pull/4807)] 优化镜像和OSS仓库发布流水线 -- [[#4445](https://github.com/seata/seata/pull/4445)] 优化事务超时判断 -- [[#4958](https://github.com/seata/seata/pull/4958)] 优化超时事务 triggerAfterCommit() 的执行 -- [[#4582](https://github.com/seata/seata/pull/4582)] 优化redis存储模式的事务排序 -- [[#4963](https://github.com/seata/seata/pull/4963)] 增加 ARM64 流水线 CI 测试 -- [[#4434](https://github.com/seata/seata/pull/4434)] 移除 seata-server CMS GC 参数 - - -### test: -- [[#4411](https://github.com/seata/seata/pull/4411)] 测试Oracle数据库AT模式下类型支持 -- [[#4794](https://github.com/seata/seata/pull/4794)] 重构代码,尝试修复单元测试 `DataSourceProxyTest.getResourceIdTest()` -- [[#5101](https://github.com/seata/seata/pull/5101)] 修复zk注册和配置中心报ClassNotFoundException的问题 `DataSourceProxyTest.getResourceIdTest()` - - -非常感谢以下 contributors 的代码贡献。若有无意遗漏,请报告。 - - -- [slievrly](https://github.com/slievrly) -- [renliangyu857](https://github.com/renliangyu857) -- [wangliang181230](https://github.com/wangliang181230) -- [a364176773](https://github.com/a364176773) -- [tuwenlin](https://github.com/tuwenlin) -- [conghuhu](https://github.com/conghuhu) -- [a1104321118](https://github.com/a1104321118) -- [duanqiaoyanyu](https://github.com/duanqiaoyanyu) -- [robynron](https://github.com/robynron) -- [lcmvs](https://github.com/lcmvs) -- [github-ganyu](https://github.com/github-ganyu) -- [1181954449](https://github.com/1181954449) -- [zw201913](https://github.com/zw201913) -- [wingchi-leung](https://github.com/wingchi-leung) -- [AlexStocks](https://github.com/AlexStocks) -- [liujunlin5168](https://github.com/liujunlin5168) -- [pengten](https://github.com/pengten) -- [liuqiufeng](https://github.com/liuqiufeng) -- [yujianfei1986](https://github.com/yujianfei1986) -- [Bughue](https://github.com/Bughue) -- [AlbumenJ](https://github.com/AlbumenJ) -- [doubleDimple](https://github.com/doubleDimple) -- [jsbxyyx](https://github.com/jsbxyyx) -- [tuwenlin](https://github.com/tuwenlin) -- [CrazyLionLi](https://github.com/JavaLionLi) -- [whxxxxx](https://github.com/whxxxxx) -- [neillee95](https://github.com/neillee95) -- [crazy-sheep](https://github.com/crazy-sheep) -- [zhangzq7](https://github.com/zhangzq7) -- [l81893521](https://github.com/l81893521) -- [zhuyoufeng](https://github.com/zhuyoufeng) -- [xingfudeshi](https://github.com/xingfudeshi) -- [odidev](https://github.com/odidev) -- [miaoxueyu](https://github.com/miaoxueyu) - -同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 - - -#### Link - -- **Seata:** https://github.com/seata/seata -- **Seata-Samples:** https://github.com/seata/seata-samples -- **Release:** https://github.com/seata/seata/releases -- **WebSite:** https://seata.io \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-analysis-UUID-generator.md b/blog/seata-analysis-UUID-generator.md index 9d49f81ee7..e872b67af1 100644 --- a/blog/seata-analysis-UUID-generator.md +++ b/blog/seata-analysis-UUID-generator.md @@ -1,99 +1 @@ ---- -title: Seata基于改良版雪花算法的分布式UUID生成器分析 -author: selfishlover -keywords: [Seata, snowflake, UUID] -date: 2021/05/08 ---- - -# Seata基于改良版雪花算法的分布式UUID生成器分析 - -Seata内置了一个分布式UUID生成器,用于辅助生成全局事务ID和分支事务ID。我们希望该生成器具有如下特点: -- 高性能 -- 全局唯一 -- 趋势递增 - -高性能不必多言。全局唯一很重要,否则不同的全局事务/分支事务会混淆在一起。 -此外,趋势递增对于使用数据库作为TC集群的存储工具的用户而言,能降低数据页分裂的频率,从而减少数据库的IO压力 -(branch_table表以分支事务ID作为主键)。 - -在老版Seata(1.4以前),该生成器的实现基于标准版的雪花算法。标准版雪花算法网上已经有很多解读文章了,此处就不再赘述了。 -尚未了解的同学可以先看看网上的相关资料,再来看此文章。 -此处我们谈谈标准版雪花算法的几个缺点: -1. 时钟敏感。因为ID生成总是和当前操作系统的时间戳绑定的(利用了时间的单调递增性),因此若操作系统的时钟出现回拨, - 生成的ID就会重复(一般而言不会人为地去回拨时钟,但服务器会有偶发的"时钟漂移"现象)。 - 对于此问题,Seata的解决策略是记录上一次的时间戳,若发现当前时间戳小于记录值(意味着出现了时钟回拨),则拒绝服务, - 等待时间戳追上记录值。 但这也意味着这段时间内该TC将处于不可用状态。 -2. 突发性能有上限。标准版雪花算法宣称的QPS很大,约400w/s,但严格来说这算耍了个文字游戏~ - 因为算法的时间戳单位是毫秒,而分配给序列号的位长度为12,即每毫秒4096个序列空间。 - 所以更准确的描述应该是4096/ms。400w/s与4096/ms的区别在于前者不要求每一毫秒的并发都必须低于4096 - (也许有些毫秒会高于4096,有些则低于)。Seata亦遵循此限制,若当前时间戳的序列空间已耗尽,会自旋等待下一个时间戳。 - -在较新的版本上(1.4之后),该生成器针对原算法进行了一定的优化改良,很好地解决了上述的2个问题。 -改进的核心思想是解除与操作系统时间戳的时刻绑定,生成器只在初始化时获取了系统当前的时间戳,作为初始时间戳, -但之后就不再与系统时间戳保持同步了。它之后的递增,只由序列号的递增来驱动。比如序列号当前值是4095,下一个请求进来, -序列号+1溢出12位空间,序列号重新归零,而溢出的进位则加到时间戳上,从而让时间戳+1。 -至此,时间戳和序列号实际可视为一个整体了。实际上我们也是这样做的,为了方便这种溢出进位,我们调整了64位ID的位分配策略, -由原版的: -![原版位分配策略](/img/blog/seata/uuid/before.png) - -改成(即时间戳和节点ID换个位置): -![改进版位分配策略](/img/blog/seata/uuid/after.png) - -这样时间戳和序列号在内存上是连在一块的,在实现上就很容易用一个`AtomicLong`来同时保存它俩: -``` -/** - * timestamp and sequence mix in one Long - * highest 11 bit: not used - * middle 41 bit: timestamp - * lowest 12 bit: sequence - */ -private AtomicLong timestampAndSequence; -``` -最高11位可以在初始化时就确定好,之后不再变化: -``` -/** - * business meaning: machine ID (0 ~ 1023) - * actual layout in memory: - * highest 1 bit: 0 - * middle 10 bit: workerId - * lowest 53 bit: all 0 - */ -private long workerId; -``` -那么在生产ID时就很简单了: -``` -public long nextId() { - // 获得递增后的时间戳和序列号 - long next = timestampAndSequence.incrementAndGet(); - // 截取低53位 - long timestampWithSequence = next & timestampAndSequenceMask; - // 跟先前保存好的高11位进行一个或的位运算 - return workerId | timestampWithSequence; -} -``` - -至此,我们可以发现: -1. 生成器不再有4096/ms的突发性能限制了。倘若某个时间戳的序列号空间耗尽,它会直接推进到下一个时间戳, - "借用"下一个时间戳的序列号空间(不必担心这种"超前消费"会造成严重后果,下面会阐述理由)。 -2. 生成器弱依赖于操作系统时钟。在运行期间,生成器不受时钟回拨的影响(无论是人为回拨还是机器的时钟漂移), - 因为生成器仅在启动时获取了一遍系统时钟,之后两者不再保持同步。 - 唯一可能产生重复ID的只有在重启时的大幅度时钟回拨(人为刻意回拨或者修改操作系统时区,如北京时间改为伦敦时间~ - 机器时钟漂移基本是毫秒级的,不会有这么大的幅度)。 -3. 持续不断的"超前消费"会不会使得生成器内的时间戳大大超前于系统的时间戳, 从而在重启时造成ID重复? - 理论上如此,但实际几乎不可能。要达到这种效果,意味该生成器接收的QPS得持续稳定在400w/s之上~ - 说实话,TC也扛不住这么高的流量,所以说呢,天塌下来有个子高的先扛着,瓶颈一定不在生成器这里。 - -此外,我们还调整了下节点ID的生成策略。原版在用户未手动指定节点ID时,会截取本地IPv4地址的低10位作为节点ID。 -在实践生产中,发现有零散的节点ID重复的现象(多为采用k8s部署的用户)。例如这样的IP就会重复: -- 192.168.4.10 -- 192.168.8.10 - -即只要IP的第4个字节和第3个字节的低2位一样就会重复。 -新版的策略改为优先从本机网卡的MAC地址截取低10位,若本机未配置有效的网卡,则在[0, 1023]中随机挑一个作为节点ID。 -这样调整后似乎没有新版的用户再报同样的问题了(当然,有待时间的检验,不管怎样,不会比IP截取策略更糟糕)。 - -以上就是对Seata的分布式UUID生成器的简析,如果您喜欢这个生成器,也可以直接在您的项目里使用它, -它的类声明是`public`的,完整类名为: -`io.seata.common.util.IdWorker` - -当然,如果您有更好的点子,也欢迎跟Seata社区讨论。 +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-analysis-config-modular.md b/blog/seata-analysis-config-modular.md index 7d2881cb47..e872b67af1 100644 --- a/blog/seata-analysis-config-modular.md +++ b/blog/seata-analysis-config-modular.md @@ -1,104 +1 @@ ---- -title: Seata config 模块源码分析 -author: 赵润泽 -keywords: [Seata、分布式事务] -date: 2020/1/11 ---- -## 一 . 导读 -根据[大佬](https://www.iteye.com/blog/javatar-949527)定义的分类,配置可以有三种:环境配置、描述配置、扩展配置。 - -环境配置:像一些组件启动时的参数等,通常是离散的简单值,多是 key-value 型数据。 - -描述配置:与业务逻辑相关,比如:事务发起方和参与方,通常会嵌到业务的生命周期管理中。描述配置信息较多,甚至有层次关系。 - -扩展配置:产品需要发现第三方实现,对配置的聚合要求比较高,比如:各种配置中心和注册中心,通常做法是在 jar 包的 META-INF/services 下放置接口类全名文件,内容为每行一个实现类类名。 - -## 二. 环境配置 - -seata server 在加载的时候,会使用 resources/registry.conf 来确定配置中心和注册中心的类型。而 seata client 在 1.0 版本后,不仅能使用 conf 文件进行配置的加载,也可以在 springboot 的 yml 配置文件中,使用 seata.config.{type} 来进行配置中心的选择,注册中心与之类似。通过 yml 加载配置的源码在 io.seata.spring.boot.autoconfigure.properties.registry 包下。 - -如果 seata 客户端的使用者既在resources下放了 conf 配置文件又在 yml 文件中配置,那么会优先使用 yml 中配置的。代码: - -```java -CURRENT_FILE_INSTANCE = null == extConfiguration ? configuration : extConfiguration; -``` - -这里 extConfiguration 是外部配置实例,即 ExtConfigurationProvider#provide() 外部配置提供类提供的,而 configuration 是另一个配置提供类提供的 ConfigurationProvider#provide(),这两个配置提供类是在 config 模块 ConfigurationFactory 静态块中,通过 SPI 的方式加载。 - -```java -EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); -``` - -上面说的是配置中心类型的选择,而配置环境的加载,是在确定了使用什么配置中心类型后,再通过相应的配置中心加载环境配置。File 即文本方式配置也是一种配置中心。 - -client 和 server 获取配置参数,是通过 ConfigurationFactory#getInstance() 获取配置类实例,再使用配置类实例获取配置参数,配置的 key 这些常量的定义,主要在 core 模块下 config 文件中。 - -一些重要的环境配置属性的意义,[官网都有介绍](https://seata.io/zh-cn/docs/user/configurations.html)。 - -在实例化的时候通过 ConfigurationFactory 获取后注入构造函数中的,需要重启才能生效,而在使用时通过 ConfigurationFactory 实时获取的,配置改了就可以生效。 - -但是 config 模块提供了 ConfigurationChangeListener#onChangeEvent 接口方法来修改实例内部的属性。即在这个方法中,监听动态变化的属性,如果检测到自身使用的属性和刚开始注入时不一样了,就修改实例中保存的属性,和配置中心保持一致,这样就实现了动态配置。 - -```java -public class GlobalTransactionalInterceptor implements ConfigurationChangeListener { -private volatile boolean disable = ConfigurationFactory.getInstance().getBoolean(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,false); -@Override public Object invoke(Param param) { - if(disable){//事务业务处理} -} -@Override public void onChangeEvent(Param param) { - disable = param; -}} -``` - -上面是 spring 模块下的 GlobalTransactionalInterceptor 与降级属性相关的伪代码。 GlobalTrarnsactionalScanner 在上面的 interceptor 类被实例化时,把 interceptor 注册到了配置变化监听列表中,当配置被改变的时候,会调用监听器: - -```java -ConfigurationFactory.getInstance().addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,(ConfigurationChangeListener)interceptor); -``` - -降级的意思是,当服务某一项功能不可用的时候,通过动态配置的属性,把某一项功能给关了,这样就可以避免一直尝试失败的处理。interceptor#invoke() 只有当这个 disable 属性为 true 时,才会执行 seata 事务相关业务。 - -## 三. 描述配置 -一般性框架描述性配置通常信息比较多,甚至有层次关系,用 xml 配置比较方便,因为树结构描述性更强。而现在的习惯都在提倡去繁琐的约束性配置,采用约定的方式。 - -seata AT 模式是通过代理数据源的方式来进行事务处理,对业务方入侵较小,只需让 seata 在启动时,识别哪些业务方需要开启全局事务,所以用注解就可以实现描述性配置。 - -```java -@GlobalTransactional(timeoutMills = 300000, name = "busi-doBiz") -public String doBiz(String msg) {} -``` -如果是 tcc 模式,事务参与方还需使用注解标识: - -```java -@TwoPhaseBusinessAction(name = "tccActionForSpringTest" , commitMethod = "commit", rollbackMethod = "rollback") -public boolean prepare(BusinessActionContext actionContext, int i); -public boolean commit(BusinessActionContext actionContext); -public boolean rollback(BusinessActionContext actionContext); -``` - -## 四 .扩展配置 -扩展配置,通常对产品的聚合要求比较高,因为产品需要发现第三方实现,将其加入产品内部。 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20200110213751452.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3ODA0NzM3,size_16,color_FFFFFF,t_70) -这是一个自定义配置中心提供类的例子,在 META-INF/services 下放置一个接口同名的文本文件,文件的内容为接口的实现类。这是标准的 spi 方式。然后修改配置文件 registry.conf 中的 config.type=test 。 - -但是如果你认为这样就可以被 seata 识别到,并且替换掉配置中心,那你就错了。seata 在加载配置中心的时候,使用 enum ConfigType 包裹了一下配置文件中配置的配置中心的类型的值: - -```java -private static Configuration buildConfiguration() { - configTypeName = "test";//registry.conf中配置的config.type - configType = ConfigType.getType(configTypeName);//ConfigType获取不到会抛异常 -} -``` - -如果在 ConfigType 中没有定义 test 这种配置中心类型,那么会抛异常。所以单纯的修改配置文件而不改变源码是无法使用 ConfigType 中定义的配置中心提供类以外的配置中心提供类。 - -目前1.0版本在 ConfigType 中定义的配置中心类型有:File,ZK,Nacos,Apollo,Consul,Etcd3,SpringCloudConfig,Custom。如果用户想使用自定义的配置中心类型,可以使用 Custom 这种类型。 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20200110215249152.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3ODA0NzM3,size_16,color_FFFFFF,t_70) -这里可以使用不优雅的方式,即提供一个指定名称 ZK 但是级别 order=3 更高的实现类(ZK默认order=1),就可以让 ConfigurationFactory 使用 TestConfigurationProvider 作为配置中心提供类。 - -通过上面的步骤,就可以让 seata 使用我们自己提供的代码。seata 中 codec、compressor、discovery、integration 等模块,都是使用 spi 机制加载功能类,符合微内核 + 插件化,平等对待第三方的设计思想。 - -## 五 . seata源码分析系列地址 -作者:赵润泽,[系列地址](https://blog.csdn.net/qq_37804737/category_9530078.html)。 +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-analysis-core-modular.md b/blog/seata-analysis-core-modular.md index 840f34f694..e872b67af1 100644 --- a/blog/seata-analysis-core-modular.md +++ b/blog/seata-analysis-core-modular.md @@ -1,117 +1 @@ ---- -title: Seata core 模块源码分析 -author: 赵润泽 -keywords: [Seata、分布式事务] -date: 2019/12/23 ---- - -## 一 . 导读 -core 模块定义了事务的类型、状态,通用的行为,client 和 server 通信时的协议和消息模型,还有异常处理方式,编译、压缩类型方式,配置信息名称,环境context等,还基于 netty 封装了 rpc ,供客户端和服务端使用。 - -按包顺序来分析一下 core 模块主要功能类: - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20191223162313317.png) - -codec:定义了一个 codec 的工厂类,提供了一个方法,根据序列化类型来找对应的处理类。还提供了一个接口类 Codec ,有两个抽象方法: - -```java - byte[] encode(T t); -``` - -```java - T decode(byte[] bytes); -``` -目前1.0版本在 codec 模块,有三种序列化的实现:SEATA、PROTOBUF、KRYO。 - -compressor:和codec包下面类一样,都是三个类,一个压缩类型类,一个工厂类,一个压缩和解压缩操作的抽象类。1.0版本就只有一种压缩方式:Gzip - -constants:两个ClientTableColumnsName、ServerTableColumnsName类,分别是 client 端存储事务的表和 server 端存储事务表对应的model类。还有定义支持的数据库类型类和一些定义配置信息属性的前缀的类。 - -context:环境类 RootContext 持有一个 ThreadLocalContextCore 用来存储事务的标识信息。比如 TX_XID 用来唯一的表示一个事务。TX_LOCK 如果存在,则表示本地事务对于 update/delete/insert/selectForUpdate SQL 需要用全局锁控制。 - -event:这里用到了 guava 中 EventBus 事件总线来进行注册和通知,监听器模式。在 server 模块的 metrics 包中,MetricsManager 在初始化的时候,对 GlobalStatus 即 server 模块处理事务的几个状态变化时,注册了监挺事件,当 server 处理事务时,会回调监听的方法,主要是为了进行统计各种状态事务的数量。 - -lock: server 在收到 registerBranch 消息进行分支注册的时候,会加锁。1.0版本有两种锁的实现,DataBaseLocker 和 MemoryLocker,分别是数据库锁和内存锁,数据库锁根据 rowKey = resourceId + tableName + pk 进行加锁,内存锁直接就是根据 primary key。 - -model:BranchStatus、GlobalStatus、BranchType 用来定义事务的类型和全局、分支状态。还有TransactionManager和ResourceManager,是 rm 和 tm 的抽象类。具体的 rm 和 tm 的实现,因为各种事务类型都不同,所以这里没有具体的实现类。 - -protocol:定义了 rpc 模块传输用的实体类,即每个事务状态场景下 request 和 response 的model。 - -store:定了与数据库打交道的数据模型,和与数据库交互的语句。 - -## 二 . exception 包中 handler 类分析 -这是 AbstractExceptionHandler 的 UML 图,Callback 、AbstractCallback 是 AbstractExceptionHandler 的内部接口和内部类,AbstractCallback 抽象类实现了接口 Callback 的三个方法,还有一个 execute() 未实现。AbstractExceptionHandler 使用了 AbstractCallback 作为模板方法的参数,并使用了其实现的三个方法,但是 execute() 方法仍留给子类实现。 -![在这里插入图片描述](https://img-blog.csdnimg.cn/20191211165628768.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3ODA0NzM3,size_16,color_FFFFFF,t_70) -从对外暴露的角度看 AbstractExceptionHandler 定义了一个带有异常处理的模板方法,模板中有四个行为,在不同的情况下执行,其中三种行为已经实现,执行的行为交由子类自行实现,详解: - -1.使用模板方法模式,在 exceptionHandlerTemplate() 中,定义好了执行的模板 - -```java - public void exceptionHandleTemplate(Callback callback, AbstractTransactionRequest request, - AbstractTransactionResponse response) { - try { - callback.execute(request, response); //执行事务业务的方法 - callback.onSuccess(request, response); //设置response返回码 - } catch (TransactionException tex) { - LOGGER.error("Catch TransactionException while do RPC, request: {}", request, tex); - callback.onTransactionException(request, response, tex); //设置response返回码并设置msg - } catch (RuntimeException rex) { - LOGGER.error("Catch RuntimeException while do RPC, request: {}", request, rex); - callback.onException(request, response, rex); //设置response返回码并设置msg - } - } -``` -onSuccess、onTransactionException、onException 在 AbstarctCallback 中已经被实现,execute 则由AbstractExceptionHandler 子类即负责不同事务模式的 handler 类进行实现。 -AbstractExceptionHandler 目前有两个子类:AbstractTCInboundHandler 负责处理全局事务的业务,AbstractRMHandler 负责处理分支事务的业务。 - -2.使用回调机制,优点是:允许 AbstractExceptionHandler 把需要调用的类 Callback 作为参数传递进来,handler 不需要知道 callback 的具体执行逻辑,只要知道 callback 的特性原型和限制条件(参数、返回值),就可以使用了。 - -先使用模板方法,把事务业务流程定下来,再通过回调,把具体执行事务业务的方法,留给子类实现。设计的非常巧妙。 - -这个 exceptionHandlerTemplate() 应该翻译成带有异常处理的模板方法。异常处理已经被抽象类实现,具体的不同模式下 commit 、rollback 的业务处理则交给子类实现。 - -## 三 . rpc 包分析 -seata 对于 rpc 的封装,细节不需要纠结,可以研究一下一下对于事务业务的处理。 - -client 端的 rpc 类是 AbstractRpcRemotingClient: -![在这里插入图片描述](https://img-blog.csdnimg.cn/20191211180129741.png) - -重要的属性和方法都在类图中,消息发送和初始化方法没画在类图中,详细分析一下类图: - -clientBootstrap:是 netty 启动类 Bootstrap 的封装类,持有了 Bootstrap 的实例,并自定义自己想要的属性。 - -clientChannelManager:使用 ConcurrentHashMap 容器维护地址和 channel 的对应关系。 - -clientMessageListener: 消息的处理类,根据消息的类型的不同有三种具体的处理方法 - -```java -public void onMessage(RpcMessage request, String serverAddress, ClientMessageSender sender) { - Object msg = request.getBody(); - if (LOGGER.isInfoEnabled()) { - LOGGER.info("onMessage:" + msg); - } - if (msg instanceof BranchCommitRequest) { - handleBranchCommit(request, serverAddress, (BranchCommitRequest)msg, sender); - } else if (msg instanceof BranchRollbackRequest) { - handleBranchRollback(request, serverAddress, (BranchRollbackRequest)msg, sender); - } else if (msg instanceof UndoLogDeleteRequest) { - handleUndoLogDelete((UndoLogDeleteRequest)msg); - } - } -``` - -消息类中,持有 TransactionMessageHandler 对不同类型消息进行处理,最终会根据事务类型的不同(AT、TCC、SAGE)调用具体的处理类,即第二部分说的 exceptionHandleTemplate() 的实现类。 - -mergeSendExecutorService:是一个线程池,只有一个线程,负责对不同地址下的消息进行和并发送。在 sendAsyncRequest() 中,会给线程池的队列LinkedBlockingQueue<> offer 消息,然后这个线程负责 poll 和处理消息。 - -channelRead():处理服务端的 HeartbeatMessage.PONG 心跳消息。还有消息类型是 MergeResultMessage 即异步消息的响应消息,根据 msgId 找到对应 MessageFuture ,并设置异步消息的 result 结果。 - -dispatch():调用 clientMessageListener 处理 server 发送过来的消息,不同类型 request 有不同的处理类。 - -简单点看 netty,只需要关注序列化方式和消息处理handler类。seata 的 rpc 序列化方式通过工厂类找 Codec 实现类进行处理,handler 即上文说的 TransactionMessageHandler 。 - -## 四 . 总结 -core 模块涉及的功能很多,其中的类大多都是其他模块的抽象类。抽象出业务模型,具体的实现分布在不同的模块。core 模块的代码非常的优秀,很多设计都是经典,比如上文分析的基于模板模式改造的,非常实用也非常美,值得仔细研究。 - -## 五 . seata源码分析系列地址 -[系列地址](https://blog.csdn.net/qq_37804737/category_9530078.html) +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-analysis-dubbo-transmit-xid.md b/blog/seata-analysis-dubbo-transmit-xid.md index ae6ee70fe2..e872b67af1 100644 --- a/blog/seata-analysis-dubbo-transmit-xid.md +++ b/blog/seata-analysis-dubbo-transmit-xid.md @@ -1,167 +1 @@ ---- -title: 源码分析Seata-XID传递 Dubbo篇 -keywords: [Seata,Dubbo,分布式事务,spring] -description: 本文讲述通过源码解析Seata-Dubbo传递XID -author: FUNKYE -date: 2020/01/01 ---- - -# 源码分析Seata-XID传递 Dubbo篇 - -本文作者:FUNKYE(陈健斌),杭州某互联网公司主程。 - -# 前言 - -​ 1.首先来看下包结构,在seata-dubbo和seata-dubbo-alibaba下有统一由TransactionPropagationFilter这个类,分别对应apache-dubbo跟alibaba-dubbo. - -![20200101203229](/img/blog/20200101203229.png) - -## 分析源码 - -```java -package io.seata.integration.dubbo; - -import io.seata.core.context.RootContext; -import org.apache.dubbo.common.Constants; -import org.apache.dubbo.common.extension.Activate; -import org.apache.dubbo.rpc.Filter; -import org.apache.dubbo.rpc.Invocation; -import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.Result; -import org.apache.dubbo.rpc.RpcContext; -import org.apache.dubbo.rpc.RpcException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Activate(group = {Constants.PROVIDER, Constants.CONSUMER}, order = 100) -public class TransactionPropagationFilter implements Filter { - - private static final Logger LOGGER = LoggerFactory.getLogger(TransactionPropagationFilter.class); - - @Override - public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { - //获取本地XID - String xid = RootContext.getXID(); - String xidInterceptorType = RootContext.getXIDInterceptorType(); - //获取Dubbo隐式传参中的XID - String rpcXid = getRpcXid(); - String rpcXidInterceptorType = RpcContext.getContext().getAttachment(RootContext.KEY_XID_INTERCEPTOR_TYPE); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("xid in RootContext[{}] xid in RpcContext[{}]", xid, rpcXid); - } - boolean bind = false; - if (xid != null) { - //传递XID - RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid); - RpcContext.getContext().setAttachment(RootContext.KEY_XID_INTERCEPTOR_TYPE, xidInterceptorType); - } else { - if (rpcXid != null) { - //绑定XID - RootContext.bind(rpcXid); - RootContext.bindInterceptorType(rpcXidInterceptorType); - bind = true; - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("bind[{}] interceptorType[{}] to RootContext", rpcXid, rpcXidInterceptorType); - } - } - } - try { - return invoker.invoke(invocation); - } finally { - if (bind) { - //进行剔除已完成事务的XID - String unbindInterceptorType = RootContext.unbindInterceptorType(); - String unbindXid = RootContext.unbind(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("unbind[{}] interceptorType[{}] from RootContext", unbindXid, unbindInterceptorType); - } - //如果发现解绑的XID并不是当前接收到的XID - if (!rpcXid.equalsIgnoreCase(unbindXid)) { - LOGGER.warn("xid in change during RPC from {} to {}, xidInterceptorType from {} to {} ", rpcXid, unbindXid, rpcXidInterceptorType, unbindInterceptorType); - if (unbindXid != null) { - //重新绑定XID - RootContext.bind(unbindXid); - RootContext.bindInterceptorType(unbindInterceptorType); - LOGGER.warn("bind [{}] interceptorType[{}] back to RootContext", unbindXid, unbindInterceptorType); - } - } - } - } - } - - /** - * get rpc xid - * @return - */ - private String getRpcXid() { - String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID); - if (rpcXid == null) { - rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID.toLowerCase()); - } - return rpcXid; - } - -} -``` - -​ 1.根据源码,我们可以推出相应的逻辑处理 - -![20200101213336](/img/blog/20200101213336.png) - -## 要点知识 - -​ 1.Dubbo @Activate注解: - -```java -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE, ElementType.METHOD}) -public @interface Activate { - /** - * Group过滤条件。 - *
- * 包含{@link ExtensionLoader#getActivateExtension}的group参数给的值,则返回扩展。 - *
- * 如没有Group设置,则不过滤。 - */ - String[] group() default {}; - - /** - * Key过滤条件。包含{@link ExtensionLoader#getActivateExtension}的URL的参数Key中有,则返回扩展。 - *

- * 示例:
- * 注解的值 @Activate("cache,validatioin"), - * 则{@link ExtensionLoader#getActivateExtension}的URL的参数有cacheKey,或是validatioin则返回扩展。 - *
- * 如没有设置,则不过滤。 - */ - String[] value() default {}; - - /** - * 排序信息,可以不提供。 - */ - String[] before() default {}; - - /** - * 排序信息,可以不提供。 - */ - String[] after() default {}; - - /** - * 排序信息,可以不提供。 - */ - int order() default 0; -} -``` - -可以分析得知,Seata的dubbo过滤器上的注解@Activate(group = {Constants.PROVIDER, Constants.CONSUMER}, order = 100),表示dubbo的服务提供方跟消费方都会触发到这个过滤器,所以我们的Seata发起者会产生一个XID的传递,上述流程图跟代码已经很清晰的表示了. - -​ 2.Dubbo隐式传参可以通过 `RpcContext` 上的 `setAttachment` 和 `getAttachment` 在服务消费方和提供方之间进行参数的隐式传递。 - -获取:RpcContext.getContext().getAttachment(RootContext.KEY_XID); - -传递:RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid); - -# 总结 - -更多源码阅读请访问[Seata官网](http://seata.io/zh-cn/index.html) \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-analysis-go-server.md b/blog/seata-analysis-go-server.md index d321fe44a0..e872b67af1 100644 --- a/blog/seata-analysis-go-server.md +++ b/blog/seata-analysis-go-server.md @@ -1,82 +1 @@ ---- -title: Seata分布式Go Server正式开源-TaaS设计简介 -author: fagongzi(zhangxu19830126@gmail.com) -date: 2019/04/23 -keywords: [seata、分布式事务、高可用] ---- - -# Seata 高可用服务端 TaaS 正式开源 - -### 前言 -TaaS 是 Seata 服务端(TC, Transaction Coordinator)的一种高可用实现,使用 `Golang` 编写。Taas 由InfiniVision (http://infinivision.cn) 贡献给Seata开源社区。现已正式开源,并贡献给 Seata 社区。 - -在Seata开源之前,我们内部开始借鉴GTS以及一些开源项目来实现分布式事务的解决方案TaaS(Transaction as a Service)。 - -在我们完成TaaS的服务端的开发工作后,Seata(当时还叫Fescar)开源了,并且引起了开源社区的广泛关注,加上阿里巴巴的平台影响力以及社区活跃度,我们认为Seata会成为今后开源分布式事务的标准,我们决定TaaS兼容Seata。 - -在发现Seata的服务端的实现是单机的,高可用等并没有实现,于是我们与Seata社区负责人取得联系,并且决定把TaaS开源,回馈开源社区。 同时,我们会长期维护,并且和Seata版本保持同步。 - -目前,Seata官方的Java高可用版本也在开发中,TaaS和该高可用版本的设计思想不同,在今后会长期共存。 - -TaaS已经开源, github (https://github.com/seata/seata-go-server),欢迎大家试用。 - -### 设计原则 -1. 高性能,性能和机器数量成正比,即通过加入新机器到集群中,就可以提升性能 -2. 高可用,一台机器出现故障,系统能依旧可以对外提供服务,或者在较短的时间内恢复对外服务(Leader切换的时间) -3. Auto-Rebalance,集群中增加新的机器,或者有机器下线,系统能够自动的做负载均衡 -4. 强一致,系统的元数据强一致在多个副本中存储 - -### 设计 -![](/img/blog/taas.png) - -#### 高性能 -TaaS的性能和机器数量成正比,为了支持这个特性,在TaaS中处理全局事务的最小单元称为`Fragment`,系统在启动的时候会设定每个Fragment支持的活跃全局事务的并发数,同时系统会对每个Fragment进行采样,一旦发现Fragment超负荷,会生成新的Fragment来处理更多的并发。 - -#### 高可用 -每个`Fragment`有多个副本和一个Leader,由Leader来处理请求。当Leader出现故障,系统会产生一个新的Leader来处理请求,在新Leader的选举过程中,这个Fragment对外不提供服务,通常这个间隔时间是几秒钟。 - -#### 强一致 -TaaS本身不存储全局事务的元数据,元数据存储在Elasticell (https://github.com/deepfabric/elasticell) 中,Elasticell是一个兼容redis协议的分布式的KV存储,它基于Raft协议来保证数据的一致性。 - -#### Auto-Rebalance -随着系统的运行,在系统中会存在许多`Fragment`以及它们的副本,这样会导致在每个机器上,`Fragment`的分布不均匀,特别是当旧的机器下线或者新的机器上线的时候。TaaS在启动的时候,会选择3个节点作为调度器的角色,调度器负责调度这些`Fragment`,用来保证每个机器上的Fragment的数量以及Leader个数大致相等,同时还会保证每个Fragment的副本数维持在指定的副本个数。 - -##### Fragment副本创建 -![](/img/blog/taas_add.png) - -1. t0时间点,Fragment1在Seata-TC1机器上创建 -2. t1时间点,Fragment1的副本Fragment1'在Seata-TC2机器上创建 -3. t2时间点,Fragment1的副本Fragment1"在Seata-TC3机器上创建 - -在t2时间点,Fragment1的三个副本创建完毕。 - -##### Fragment副本迁移 -![](/img/blog/taas_move.png) -1. t0时刻点,系统一个存在4个Fragment,分别存在于Seata-TC1,Seata-TC2,Seata-TC3三台机器上 -2. t1时刻,加入新机器Seata-TC4 -3. t2时刻,有3个Fragment的副本被迁移到了Seata-TC4这台机器上 - -### 在线快速体验 -我们在公网搭建了一个体验的环境: -* Seata服务端地址: 39.97.115.141:8091 -* UI: http://39.97.115.141:8084/ui/index.html - -### 本地快速体验 -使用docker-compose快速体验TaaS的功能。 -```bash -git clone https://github.com/seata/taas.git -docker-compse up -d -``` -由于组件依赖较多,docker-compose启动30秒后,可以对外服务 - -#### Seata服务地址 -服务默认监听在8091端口,修改Seata对应的服务端地址体验 - -#### Seata UI -访问WEB UI `http://127.0.0.1:8084/ui/index.html` - -### 关于InfiniVision -深见网络是一家技术驱动的企业级服务提供商,致力于利用人工智能、云计算、区块链、大数据,以及物联网边缘计算技术助力传统企业的数字化转型和升级。深见网络积极拥抱开源文化并将核心算法和架构开源,知名人脸识别软件 InsightFace (https://github.com/deepinsight/insightface) (曾多次获得大规模人脸识别挑战冠军),以及分布式存储引擎 Elasticell (https://github.com/deepfabric/elasticell) 等均是深见网络的开源产品。 - -### 关于作者 -作者张旭,开源网关Gateway (https://github.com/fagongzi/gateway) 作者,目前就职于InfiniVision,负责基础架构相关的研发工作。 +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-analysis-java-client.md b/blog/seata-analysis-java-client.md index d3b5bf056e..e872b67af1 100644 --- a/blog/seata-analysis-java-client.md +++ b/blog/seata-analysis-java-client.md @@ -1,710 +1 @@ ---- -title: 分布式事务之Seata-Client原理及流程详解 -author: fangliangsheng -date: 2019/04/15 -keywords: [fescar、seata、分布式事务] ---- - -## 前言 -在分布式系统中,分布式事务是一个必须要解决的问题,目前使用较多的是最终一致性方案。自年初阿里开源了Fescar(四月初更名为Seata)后,该项目受到了极大的关注度,目前已接近8000Star。[Seata](https://github.com/seata/seata)以高性能和零侵入的方式为目标解决微服务领域的分布式事务难题,目前正处于快速迭代中,近期小目标是生产可用的Mysql版本。关于Seata的总体介绍,可以查看[官方WIKI](https://github.com/seata/seata/wiki/%E6%A6%82%E8%A7%88)获得更多更全面的内容介绍。 - -本文主要基于spring cloud+spring jpa+spring cloud alibaba fescar+mysql+seata的结构,搭建一个分布式系统的demo,通过seata的debug日志和源代码,从client端(RM、TM)的角度分析说明其工作流程及原理。 - -文中代码基于fescar-0.4.1,由于项目刚更名为seata不久,例如一些包名、类名、jar包名称还都是fescar的命名,故下文中仍使用fescar进行表述。 - -示例项目:[https://github.com/fescar-group/fescar-samples/tree/master/springcloud-jpa-seata](https://github.com/fescar-group/fescar-samples/tree/master/springcloud-jpa-seata) -## 相关概念 -- XID:全局事务的唯一标识,由ip:port:sequence组成 -- Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚 -- Transaction Manager (TM ):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议 -- Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚 -## 分布式框架支持 -Fescar使用XID表示一个分布式事务,XID需要在一次分布式事务请求所涉的系统中进行传递,从而向feacar-server发送分支事务的处理情况,以及接收feacar-server的commit、rollback指令。 -Fescar官方已支持全版本的dubbo协议,而对于spring cloud(spring-boot)的分布式项目社区也提供了相应的实现 - -```xml - - org.springframework.cloud - spring-cloud-alibaba-fescar - 2.1.0.BUILD-SNAPSHOT - -``` -该组件实现了基于RestTemplate、Feign通信时的XID传递功能。 -## 业务逻辑 -业务逻辑是经典的下订单、扣余额、减库存流程。 -根据模块划分为三个独立的服务,且分别连接对应的数据库 - - - 订单:order-server - - 账户:account-server - - 库存:storage-server - -另外还有发起分布式事务的业务系统 - - - 业务:business-server - -项目结构如下图 -![在这里插入图片描述](/img/blog/20190410114411366.png) - -**正常业务** - 1. business发起购买请求 - 2. storage扣减库存 - 3. order创建订单 - 4. account扣减余额 - -**异常业务** - 1. business发起购买请求 - 2. storage扣减库存 - 3. order创建订单 - 4. account`扣减余额异常` - -正常流程下2、3、4步的数据正常更新全局commit,异常流程下的数据则由于第4步的异常报错全局回滚。 - -## 配置文件 -fescar的配置入口文件是[registry.conf](https://github.com/seata/seata/blob/develop/config/src/main/resources/registry.conf),查看代码[ConfigurationFactory](https://github.com/seata/seata/blob/develop/config/src/main/java/com/alibaba/fescar/config/ConfigurationFactory.java)得知目前还不能指定该配置文件,所以配置文件名称只能为registry.conf - -```java -private static final String REGISTRY_CONF = "registry.conf"; -public static final Configuration FILE_INSTANCE = new FileConfiguration(REGISTRY_CONF); -``` - -在`registry`中可以指定具体配置的形式,默认使用file类型,在file.conf中有3部分配置内容 - - 1. transport - transport部分的配置对应[NettyServerConfig](https://github.com/seata/seata/blob/develop/core/src/main/java/com/alibaba/fescar/core/rpc/netty/NettyServerConfig.java)类,用于定义Netty相关的参数,TM、RM与fescar-server之间使用Netty进行通信 - 2. service - -```js - service { - #vgroup->rgroup - vgroup_mapping.my_test_tx_group = "default" - #配置Client连接TC的地址 - default.grouplist = "127.0.0.1:8091" - #degrade current not support - enableDegrade = false - #disable - 是否启用seata的分布式事务 - disableGlobalTransaction = false - } -``` - 3. client - -```js - client { - #RM接收TC的commit通知后缓冲上限 - async.commit.buffer.limit = 10000 - lock { - retry.internal = 10 - retry.times = 30 - } - } -``` -## 数据源Proxy -除了前面的配置文件,fescar在AT模式下稍微有点代码量的地方就是对数据源的代理指定,且目前只能基于`DruidDataSource`的代理。 -注:在最新发布的0.4.2版本中已支持任意数据源类型 - -```java -@Bean -@ConfigurationProperties(prefix = "spring.datasource") -public DruidDataSource druidDataSource() { - DruidDataSource druidDataSource = new DruidDataSource(); - return druidDataSource; -} - -@Primary -@Bean("dataSource") -public DataSourceProxy dataSource(DruidDataSource druidDataSource) { - return new DataSourceProxy(druidDataSource); -} -``` -使用`DataSourceProxy`的目的是为了引入`ConnectionProxy`,fescar无侵入的一方面就体现在`ConnectionProxy`的实现上,即分支事务加入全局事务的切入点是在本地事务的`commit`阶段,这样设计可以保证业务数据与`undo_log`是在一个本地事务中。 - -`undo_log`是需要在业务库上创建的一个表,fescar依赖该表记录每笔分支事务的状态及二阶段`rollback`的回放数据。不用担心该表的数据量过大形成单点问题,在全局事务`commit`的场景下事务对应的`undo_log`会异步删除。 - -```sql -CREATE TABLE `undo_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `branch_id` bigint(20) NOT NULL, - `xid` varchar(100) NOT NULL, - `rollback_info` longblob NOT NULL, - `log_status` int(11) NOT NULL, - `log_created` datetime NOT NULL, - `log_modified` datetime NOT NULL, - `ext` varchar(100) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; -``` -## 启动Server -前往[https://github.com/seata/seata/releases](https://github.com/seata/seata/releases) 下载与Client版本对应的fescar-server,避免由于版本的不同导致的协议不一致问题 -进入解压之后的 bin 目录,执行 - -```shell -./fescar-server.sh 8091 ../data -``` -启动成功输出 - -```shell -2019-04-09 20:27:24.637 INFO [main]c.a.fescar.core.rpc.netty.AbstractRpcRemotingServer.start:152 -Server started ... -``` -## 启动Client -fescar的加载入口类位于[GlobalTransactionAutoConfiguration](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/finchley/spring-cloud-alibaba-fescar/src/main/java/org/springframework/cloud/alibaba/fescar/GlobalTransactionAutoConfiguration.java),对基于spring boot的项目能够自动加载,当然也可以通过其他方式示例化`GlobalTransactionScanner` - -```java -@Configuration -@EnableConfigurationProperties({FescarProperties.class}) -public class GlobalTransactionAutoConfiguration { - private final ApplicationContext applicationContext; - private final FescarProperties fescarProperties; - - public GlobalTransactionAutoConfiguration(ApplicationContext applicationContext, FescarProperties fescarProperties) { - this.applicationContext = applicationContext; - this.fescarProperties = fescarProperties; - } - - /** - * 示例化GlobalTransactionScanner - * scanner为client初始化的发起类 - */ - @Bean - public GlobalTransactionScanner globalTransactionScanner() { - String applicationName = this.applicationContext.getEnvironment().getProperty("spring.application.name"); - String txServiceGroup = this.fescarProperties.getTxServiceGroup(); - if (StringUtils.isEmpty(txServiceGroup)) { - txServiceGroup = applicationName + "-fescar-service-group"; - this.fescarProperties.setTxServiceGroup(txServiceGroup); - } - - return new GlobalTransactionScanner(applicationName, txServiceGroup); - } -} -``` -可以看到支持一个配置项FescarProperties,用于配置事务分组名称 - -```json -spring.cloud.alibaba.fescar.tx-service-group=my_test_tx_group -``` -如果不指定服务组,则默认使用spring.application.name+ -fescar-service-group生成名称,所以不指定spring.application.name启动会报错 - -```java -@ConfigurationProperties("spring.cloud.alibaba.fescar") -public class FescarProperties { - private String txServiceGroup; - - public FescarProperties() { - } - - public String getTxServiceGroup() { - return this.txServiceGroup; - } - - public void setTxServiceGroup(String txServiceGroup) { - this.txServiceGroup = txServiceGroup; - } -} -``` -获取applicationId和txServiceGroup后,创建[GlobalTransactionScanner](https://github.com/seata/seata/blob/develop/spring/src/main/java/com/alibaba/fescar/spring/annotation/GlobalTransactionScanner.java)对象,主要看类中initClient方法 - -```java -private void initClient() { - if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) { - throw new IllegalArgumentException( - "applicationId: " + applicationId + ", txServiceGroup: " + txServiceGroup); - } - //init TM - TMClient.init(applicationId, txServiceGroup); - - //init RM - RMClient.init(applicationId, txServiceGroup); - -} -``` -方法中可以看到初始化了`TMClient`和`RMClient`,对于一个服务既可以是TM角色也可以是RM角色,至于什么时候是TM或者RM则要看在一次全局事务中`@GlobalTransactional`注解标注在哪。 -Client创建的结果是与TC的一个Netty连接,所以在启动日志中可以看到两个Netty Channel,其中标明了transactionRole分别为`TMROLE`和`RMROLE` - -```java -2019-04-09 13:42:57.417 INFO 93715 --- [imeoutChecker_1] c.a.f.c.rpc.netty.NettyPoolableFactory : NettyPool create channel to {"address":"127.0.0.1:8091","message":{"applicationId":"business-service","byteBuffer":{"char":"\u0000","direct":false,"double":0.0,"float":0.0,"int":0,"long":0,"readOnly":false,"short":0},"transactionServiceGroup":"my_test_tx_group","typeCode":101,"version":"0.4.1"},"transactionRole":"TMROLE"} -2019-04-09 13:42:57.505 INFO 93715 --- [imeoutChecker_1] c.a.f.c.rpc.netty.NettyPoolableFactory : NettyPool create channel to {"address":"127.0.0.1:8091","message":{"applicationId":"business-service","byteBuffer":{"char":"\u0000","direct":false,"double":0.0,"float":0.0,"int":0,"long":0,"readOnly":false,"short":0},"transactionServiceGroup":"my_test_tx_group","typeCode":103,"version":"0.4.1"},"transactionRole":"RMROLE"} -2019-04-09 13:42:57.629 DEBUG 93715 --- [lector_TMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Send:RegisterTMRequest{applicationId='business-service', transactionServiceGroup='my_test_tx_group'} -2019-04-09 13:42:57.629 DEBUG 93715 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Send:RegisterRMRequest{resourceIds='null', applicationId='business-service', transactionServiceGroup='my_test_tx_group'} -2019-04-09 13:42:57.699 DEBUG 93715 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Receive:version=0.4.1,extraData=null,identified=true,resultCode=null,msg=null,messageId:1 -2019-04-09 13:42:57.699 DEBUG 93715 --- [lector_TMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Receive:version=0.4.1,extraData=null,identified=true,resultCode=null,msg=null,messageId:2 -2019-04-09 13:42:57.701 DEBUG 93715 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting : com.alibaba.fescar.core.rpc.netty.RmRpcClient@3b06d101 msgId:1, future :com.alibaba.fescar.core.protocol.MessageFuture@28bb1abd, body:version=0.4.1,extraData=null,identified=true,resultCode=null,msg=null -2019-04-09 13:42:57.701 DEBUG 93715 --- [lector_TMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting : com.alibaba.fescar.core.rpc.netty.TmRpcClient@65fc3fb7 msgId:2, future :com.alibaba.fescar.core.protocol.MessageFuture@9a1e3df, body:version=0.4.1,extraData=null,identified=true,resultCode=null,msg=null -2019-04-09 13:42:57.710 INFO 93715 --- [imeoutChecker_1] c.a.fescar.core.rpc.netty.RmRpcClient : register RM success. server version:0.4.1,channel:[id: 0xe6468995, L:/127.0.0.1:57397 - R:/127.0.0.1:8091] -2019-04-09 13:42:57.710 INFO 93715 --- [imeoutChecker_1] c.a.f.c.rpc.netty.NettyPoolableFactory : register success, cost 114 ms, version:0.4.1,role:TMROLE,channel:[id: 0xd22fe0c5, L:/127.0.0.1:57398 - R:/127.0.0.1:8091] -2019-04-09 13:42:57.711 INFO 93715 --- [imeoutChecker_1] c.a.f.c.rpc.netty.NettyPoolableFactory : register success, cost 125 ms, version:0.4.1,role:RMROLE,channel:[id: 0xe6468995, L:/127.0.0.1:57397 - R:/127.0.0.1:8091] - -``` -日志中可以看到 -1. 创建Netty连接 -2. 发送注册请求 -3. 得到响应结果 -4. `RmRpcClient`、`TmRpcClient`成功实例化 -## TM处理流程 -在本例中,TM的角色是business-service,BusinessService的purchase方法标注了`@GlobalTransactional`注解 - -```java -@Service -public class BusinessService { - - @Autowired - private StorageFeignClient storageFeignClient; - @Autowired - private OrderFeignClient orderFeignClient; - - @GlobalTransactional - public void purchase(String userId, String commodityCode, int orderCount){ - storageFeignClient.deduct(commodityCode, orderCount); - - orderFeignClient.create(userId, commodityCode, orderCount); - } -} -``` -方法调用后将会创建一个全局事务,首先关注`@GlobalTransactional`注解的作用,在[GlobalTransactionalInterceptor](https://github.com/seata/seata/blob/develop/spring/src/main/java/com/alibaba/fescar/spring/annotation/GlobalTransactionalInterceptor.java)中被拦截处理 - -```java -/** - * AOP拦截方法调用 - */ -@Override -public Object invoke(final MethodInvocation methodInvocation) throws Throwable { - Class targetClass = (methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null); - Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass); - final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod); - - //获取方法GlobalTransactional注解 - final GlobalTransactional globalTransactionalAnnotation = getAnnotation(method, GlobalTransactional.class); - final GlobalLock globalLockAnnotation = getAnnotation(method, GlobalLock.class); - - //如果方法有GlobalTransactional注解,则拦截到相应方法处理 - if (globalTransactionalAnnotation != null) { - return handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation); - } else if (globalLockAnnotation != null) { - return handleGlobalLock(methodInvocation); - } else { - return methodInvocation.proceed(); - } -} -``` -`handleGlobalTransaction`方法中对[TransactionalTemplate](https://github.com/seata/seata/blob/develop/tm/src/main/java/com/alibaba/fescar/tm/api/TransactionalTemplate.java)的execute进行了调用,从类名可以看到这是一个标准的模版方法,它定义了TM对全局事务处理的标准步骤,注释已经比较清楚了 - -```java -public Object execute(TransactionalExecutor business) throws TransactionalExecutor.ExecutionException { - // 1. get or create a transaction - GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate(); - - try { - // 2. begin transaction - try { - triggerBeforeBegin(); - tx.begin(business.timeout(), business.name()); - triggerAfterBegin(); - } catch (TransactionException txe) { - throw new TransactionalExecutor.ExecutionException(tx, txe, - TransactionalExecutor.Code.BeginFailure); - } - Object rs = null; - try { - // Do Your Business - rs = business.execute(); - } catch (Throwable ex) { - // 3. any business exception, rollback. - try { - triggerBeforeRollback(); - tx.rollback(); - triggerAfterRollback(); - // 3.1 Successfully rolled back - throw new TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex); - } catch (TransactionException txe) { - // 3.2 Failed to rollback - throw new TransactionalExecutor.ExecutionException(tx, txe, - TransactionalExecutor.Code.RollbackFailure, ex); - } - } - // 4. everything is fine, commit. - try { - triggerBeforeCommit(); - tx.commit(); - triggerAfterCommit(); - } catch (TransactionException txe) { - // 4.1 Failed to commit - throw new TransactionalExecutor.ExecutionException(tx, txe, - TransactionalExecutor.Code.CommitFailure); - } - return rs; - } finally { - //5. clear - triggerAfterCompletion(); - cleanUp(); - } -} -``` -通过[DefaultGlobalTransaction](https://github.com/seata/seata/blob/develop/tm/src/main/java/com/alibaba/fescar/tm/api/DefaultGlobalTransaction.java)的begin方法开启全局事务 - -```java -public void begin(int timeout, String name) throws TransactionException { - if (role != GlobalTransactionRole.Launcher) { - check(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Ignore Begin(): just involved in global transaction [" + xid + "]"); - } - return; - } - if (xid != null) { - throw new IllegalStateException(); - } - if (RootContext.getXID() != null) { - throw new IllegalStateException(); - } - //具体开启事务的方法,获取TC返回的XID - xid = transactionManager.begin(null, null, name, timeout); - status = GlobalStatus.Begin; - RootContext.bind(xid); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Begin a NEW global transaction [" + xid + "]"); - } -} -``` -方法开头处`if (role != GlobalTransactionRole.Launcher)`对role的判断有关键的作用,表明当前是全局事务的发起者(Launcher)还是参与者(Participant)。如果在分布式事务的下游系统方法中也加上`@GlobalTransactional`注解,那么它的角色就是Participant,会忽略后面的begin直接return,而判断是Launcher还是Participant是根据当前上下文是否已存在XID来判断,没有XID的就是Launcher,已经存在XID的就是Participant. -由此可见,全局事务的创建只能由Launcher执行,而一次分布式事务中也只有一个Launcher存在。 - -[DefaultTransactionManager](https://github.com/seata/seata/blob/develop/tm/src/main/java/com/alibaba/fescar/tm/DefaultTransactionManager.java)负责TM与TC通讯,发送begin、commit、rollback指令 - -```java -@Override -public String begin(String applicationId, String transactionServiceGroup, String name, int timeout) - throws TransactionException { - GlobalBeginRequest request = new GlobalBeginRequest(); - request.setTransactionName(name); - request.setTimeout(timeout); - GlobalBeginResponse response = (GlobalBeginResponse)syncCall(request); - return response.getXid(); -} -``` -至此拿到fescar-server返回的XID表示一个全局事务创建成功,日志中也反应了上述流程 - -```java -2019-04-09 13:46:57.417 DEBUG 31326 --- [nio-8084-exec-1] c.a.f.c.rpc.netty.AbstractRpcRemoting : offer message: timeout=60000,transactionName=purchase(java.lang.String,java.lang.String,int) -2019-04-09 13:46:57.417 DEBUG 31326 --- [geSend_TMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting : write message:FescarMergeMessage timeout=60000,transactionName=purchase(java.lang.String,java.lang.String,int), channel:[id: 0xa148545e, L:/127.0.0.1:56120 - R:/127.0.0.1:8091],active?true,writable?true,isopen?true -2019-04-09 13:46:57.418 DEBUG 31326 --- [lector_TMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Send:FescarMergeMessage timeout=60000,transactionName=purchase(java.lang.String,java.lang.String,int) -2019-04-09 13:46:57.421 DEBUG 31326 --- [lector_TMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Receive:MergeResultMessage com.alibaba.fescar.core.protocol.transaction.GlobalBeginResponse@2dc480dc,messageId:1196 -2019-04-09 13:46:57.421 DEBUG 31326 --- [nio-8084-exec-1] c.a.fescar.core.context.RootContext : bind 192.168.224.93:8091:2008502699 -2019-04-09 13:46:57.421 DEBUG 31326 --- [nio-8084-exec-1] c.a.f.tm.api.DefaultGlobalTransaction : Begin a NEW global transaction [192.168.224.93:8091:2008502699] -``` -全局事务创建后,就开始执行business.execute(),即业务代码`storageFeignClient.deduct(commodityCode, orderCount)`进入RM处理流程,此处的业务逻辑为调用storage-service的扣减库存接口。 -## RM处理流程 - -```java -@GetMapping(path = "/deduct") -public Boolean deduct(String commodityCode, Integer count){ - storageService.deduct(commodityCode,count); - return true; -} - -@Transactional -public void deduct(String commodityCode, int count){ - Storage storage = storageDAO.findByCommodityCode(commodityCode); - storage.setCount(storage.getCount()-count); - - storageDAO.save(storage); -} -``` -storage的接口和service方法并未出现fescar相关的代码和注解,体现了fescar的无侵入。那它是如何加入到这次全局事务中的呢?答案在[ConnectionProxy](https://github.com/seata/seata/blob/develop/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/ConnectionProxy.java)中,这也是前面说为什么必须要使用`DataSourceProxy`的原因,通过DataSourceProxy才能在业务代码的本地事务提交时,fescar通过该切入点,向TC注册分支事务并发送RM的处理结果。 - -由于业务代码本身的事务提交被`ConnectionProxy`代理实现,所以在提交本地事务时,实际执行的是ConnectionProxy的commit方法 - -```java -public void commit() throws SQLException { - //如果当前是全局事务,则执行全局事务的提交 - //判断是不是全局事务,就是看当前上下文是否存在XID - if (context.inGlobalTransaction()) { - processGlobalTransactionCommit(); - } else if (context.isGlobalLockRequire()) { - processLocalCommitWithGlobalLocks(); - } else { - targetConnection.commit(); - } -} - -private void processGlobalTransactionCommit() throws SQLException { - try { - //首先是向TC注册RM,拿到TC分配的branchId - register(); - } catch (TransactionException e) { - recognizeLockKeyConflictException(e); - } - - try { - if (context.hasUndoLog()) { - //写入undolog - UndoLogManager.flushUndoLogs(this); - } - - //提交本地事务,写入undo_log和业务数据在同一个本地事务中 - targetConnection.commit(); - } catch (Throwable ex) { - //向TC发送RM的事务处理失败的通知 - report(false); - if (ex instanceof SQLException) { - throw new SQLException(ex); - } - } - //向TC发送RM的事务处理成功的通知 - report(true); - context.reset(); -} - -private void register() throws TransactionException { - //注册RM,构建request通过netty向TC发送注册指令 - Long branchId = DefaultResourceManager.get().branchRegister(BranchType.AT, getDataSourceProxy().getResourceId(), - null, context.getXid(), null, context.buildLockKeys()); - //将返回的branchId存在上下文中 - context.setBranchId(branchId); -} -``` -通过日志印证一下上面的流程 - -```java -2019-04-09 21:57:48.341 DEBUG 38933 --- [nio-8081-exec-1] o.s.c.a.f.web.FescarHandlerInterceptor : xid in RootContext null xid in RpcContext 192.168.0.2:8091:2008546211 -2019-04-09 21:57:48.341 DEBUG 38933 --- [nio-8081-exec-1] c.a.fescar.core.context.RootContext : bind 192.168.0.2:8091:2008546211 -2019-04-09 21:57:48.341 DEBUG 38933 --- [nio-8081-exec-1] o.s.c.a.f.web.FescarHandlerInterceptor : bind 192.168.0.2:8091:2008546211 to RootContext -2019-04-09 21:57:48.386 INFO 38933 --- [nio-8081-exec-1] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory -Hibernate: select storage0_.id as id1_0_, storage0_.commodity_code as commodit2_0_, storage0_.count as count3_0_ from storage_tbl storage0_ where storage0_.commodity_code=? -Hibernate: update storage_tbl set count=? where id=? -2019-04-09 21:57:48.673 INFO 38933 --- [nio-8081-exec-1] c.a.fescar.core.rpc.netty.RmRpcClient : will connect to 192.168.0.2:8091 -2019-04-09 21:57:48.673 INFO 38933 --- [nio-8081-exec-1] c.a.fescar.core.rpc.netty.RmRpcClient : RM will register :jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false -2019-04-09 21:57:48.673 INFO 38933 --- [nio-8081-exec-1] c.a.f.c.rpc.netty.NettyPoolableFactory : NettyPool create channel to {"address":"192.168.0.2:8091","message":{"applicationId":"storage-service","byteBuffer":{"char":"\u0000","direct":false,"double":0.0,"float":0.0,"int":0,"long":0,"readOnly":false,"short":0},"resourceIds":"jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false","transactionServiceGroup":"hello-service-fescar-service-group","typeCode":103,"version":"0.4.0"},"transactionRole":"RMROLE"} -2019-04-09 21:57:48.677 DEBUG 38933 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Send:RegisterRMRequest{resourceIds='jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false', applicationId='storage-service', transactionServiceGroup='hello-service-fescar-service-group'} -2019-04-09 21:57:48.680 DEBUG 38933 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Receive:version=0.4.1,extraData=null,identified=true,resultCode=null,msg=null,messageId:9 -2019-04-09 21:57:48.680 DEBUG 38933 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting : com.alibaba.fescar.core.rpc.netty.RmRpcClient@7d61f5d4 msgId:9, future :com.alibaba.fescar.core.protocol.MessageFuture@186cd3e0, body:version=0.4.1,extraData=null,identified=true,resultCode=null,msg=null -2019-04-09 21:57:48.680 INFO 38933 --- [nio-8081-exec-1] c.a.fescar.core.rpc.netty.RmRpcClient : register RM success. server version:0.4.1,channel:[id: 0xd40718e3, L:/192.168.0.2:62607 - R:/192.168.0.2:8091] -2019-04-09 21:57:48.680 INFO 38933 --- [nio-8081-exec-1] c.a.f.c.rpc.netty.NettyPoolableFactory : register success, cost 3 ms, version:0.4.1,role:RMROLE,channel:[id: 0xd40718e3, L:/192.168.0.2:62607 - R:/192.168.0.2:8091] -2019-04-09 21:57:48.680 DEBUG 38933 --- [nio-8081-exec-1] c.a.f.c.rpc.netty.AbstractRpcRemoting : offer message: transactionId=2008546211,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false,lockKey=storage_tbl:1 -2019-04-09 21:57:48.681 DEBUG 38933 --- [geSend_RMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting : write message:FescarMergeMessage transactionId=2008546211,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false,lockKey=storage_tbl:1, channel:[id: 0xd40718e3, L:/192.168.0.2:62607 - R:/192.168.0.2:8091],active?true,writable?true,isopen?true -2019-04-09 21:57:48.681 DEBUG 38933 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Send:FescarMergeMessage transactionId=2008546211,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false,lockKey=storage_tbl:1 -2019-04-09 21:57:48.687 DEBUG 38933 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Receive:MergeResultMessage BranchRegisterResponse: transactionId=2008546211,branchId=2008546212,result code =Success,getMsg =null,messageId:11 -2019-04-09 21:57:48.702 DEBUG 38933 --- [nio-8081-exec-1] c.a.f.rm.datasource.undo.UndoLogManager : Flushing UNDO LOG: {"branchId":2008546212,"sqlUndoLogs":[{"afterImage":{"rows":[{"fields":[{"keyType":"PrimaryKey","name":"id","type":4,"value":1},{"keyType":"NULL","name":"count","type":4,"value":993}]}],"tableName":"storage_tbl"},"beforeImage":{"rows":[{"fields":[{"keyType":"PrimaryKey","name":"id","type":4,"value":1},{"keyType":"NULL","name":"count","type":4,"value":994}]}],"tableName":"storage_tbl"},"sqlType":"UPDATE","tableName":"storage_tbl"}],"xid":"192.168.0.2:8091:2008546211"} -2019-04-09 21:57:48.755 DEBUG 38933 --- [nio-8081-exec-1] c.a.f.c.rpc.netty.AbstractRpcRemoting : offer message: transactionId=2008546211,branchId=2008546212,resourceId=null,status=PhaseOne_Done,applicationData=null -2019-04-09 21:57:48.755 DEBUG 38933 --- [geSend_RMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting : write message:FescarMergeMessage transactionId=2008546211,branchId=2008546212,resourceId=null,status=PhaseOne_Done,applicationData=null, channel:[id: 0xd40718e3, L:/192.168.0.2:62607 - R:/192.168.0.2:8091],active?true,writable?true,isopen?true -2019-04-09 21:57:48.756 DEBUG 38933 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Send:FescarMergeMessage transactionId=2008546211,branchId=2008546212,resourceId=null,status=PhaseOne_Done,applicationData=null -2019-04-09 21:57:48.758 DEBUG 38933 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Receive:MergeResultMessage com.alibaba.fescar.core.protocol.transaction.BranchReportResponse@582a08cf,messageId:13 -2019-04-09 21:57:48.799 DEBUG 38933 --- [nio-8081-exec-1] c.a.fescar.core.context.RootContext : unbind 192.168.0.2:8091:2008546211 -2019-04-09 21:57:48.799 DEBUG 38933 --- [nio-8081-exec-1] o.s.c.a.f.web.FescarHandlerInterceptor : unbind 192.168.0.2:8091:2008546211 from RootContext -``` - - 1. 获取business-service传来的XID - 2. 绑定XID到当前上下文中 - 3. 执行业务逻辑sql - 4. 向TC创建本次RM的Netty连接 - 5. 向TC发送分支事务的相关信息 - 6. 获得TC返回的branchId - 7. 记录Undo Log数据 - 8. 向TC发送本次事务PhaseOne阶段的处理结果 - 9. 从当前上下文中解绑XID - -其中第1步和第9步,是在[FescarHandlerInterceptor](https://github.com/dongsheep/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-fescar/src/main/java/org/springframework/cloud/alibaba/fescar/web/FescarHandlerInterceptor.java)中完成的,该类并不属于fescar,是前面提到的spring-cloud-alibaba-fescar,它实现了基于feign、rest通信时将xid bind和unbind到当前请求上下文中。到这里RM完成了PhaseOne阶段的工作,接着看PhaseTwo阶段的处理逻辑。 -## 事务提交 -各分支事务执行完成后,TC对各RM的汇报结果进行汇总,给各RM发送commit或rollback的指令 - -```java -2019-04-09 21:57:49.813 DEBUG 38933 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Receive:xid=192.168.0.2:8091:2008546211,branchId=2008546212,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false,applicationData=null,messageId:1 -2019-04-09 21:57:49.813 DEBUG 38933 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting : com.alibaba.fescar.core.rpc.netty.RmRpcClient@7d61f5d4 msgId:1, body:xid=192.168.0.2:8091:2008546211,branchId=2008546212,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false,applicationData=null -2019-04-09 21:57:49.814 INFO 38933 --- [atch_RMROLE_1_8] c.a.f.core.rpc.netty.RmMessageListener : onMessage:xid=192.168.0.2:8091:2008546211,branchId=2008546212,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false,applicationData=null -2019-04-09 21:57:49.816 INFO 38933 --- [atch_RMROLE_1_8] com.alibaba.fescar.rm.AbstractRMHandler : Branch committing: 192.168.0.2:8091:2008546211 2008546212 jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false null -2019-04-09 21:57:49.816 INFO 38933 --- [atch_RMROLE_1_8] com.alibaba.fescar.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed -2019-04-09 21:57:49.817 INFO 38933 --- [atch_RMROLE_1_8] c.a.fescar.core.rpc.netty.RmRpcClient : RmRpcClient sendResponse branchStatus=PhaseTwo_Committed,result code =Success,getMsg =null -2019-04-09 21:57:49.817 DEBUG 38933 --- [atch_RMROLE_1_8] c.a.f.c.rpc.netty.AbstractRpcRemoting : send response:branchStatus=PhaseTwo_Committed,result code =Success,getMsg =null,channel:[id: 0xd40718e3, L:/192.168.0.2:62607 - R:/192.168.0.2:8091] -2019-04-09 21:57:49.817 DEBUG 38933 --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler : Send:branchStatus=PhaseTwo_Committed,result code =Success,getMsg =null -``` -从日志中可以看到 -1. RM收到XID=192.168.0.2:8091:2008546211,branchId=2008546212的commit通知 -2. 执行commit动作 -3. 将commit结果发送给TC,branchStatus为PhaseTwo_Committed - -具体看下二阶段commit的执行过程,在[AbstractRMHandler](https://github.com/seata/seata/blob/develop/rm/src/main/java/com/alibaba/fescar/rm/AbstractRMHandler.java)类的doBranchCommit方法 - -```java -/** - * 拿到通知的xid、branchId等关键参数 - * 然后调用RM的branchCommit - */ -protected void doBranchCommit(BranchCommitRequest request, BranchCommitResponse response) throws TransactionException { - String xid = request.getXid(); - long branchId = request.getBranchId(); - String resourceId = request.getResourceId(); - String applicationData = request.getApplicationData(); - LOGGER.info("Branch committing: " + xid + " " + branchId + " " + resourceId + " " + applicationData); - BranchStatus status = getResourceManager().branchCommit(request.getBranchType(), xid, branchId, resourceId, applicationData); - response.setBranchStatus(status); - LOGGER.info("Branch commit result: " + status); -} -``` -最终会将branchCommit的请求调用到[AsyncWorker](https://github.com/seata/seata/blob/develop/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/AsyncWorker.java)的branchCommit方法。AsyncWorker的处理方式是fescar架构的一个关键部分,因为大部分事务都是会正常提交的,所以在PhaseOne阶段就已经结束了,这样就可以将锁最快的释放。PhaseTwo阶段接收commit的指令后,异步处理即可。将PhaseTwo的时间消耗排除在一次分布式事务之外。 - -```java -private static final List ASYNC_COMMIT_BUFFER = Collections.synchronizedList( new ArrayList()); - -/** - * 将需要提交的XID加入list - */ -@Override -public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId, String applicationData) throws TransactionException { - if (ASYNC_COMMIT_BUFFER.size() < ASYNC_COMMIT_BUFFER_LIMIT) { - ASYNC_COMMIT_BUFFER.add(new Phase2Context(branchType, xid, branchId, resourceId, applicationData)); - } else { - LOGGER.warn("Async commit buffer is FULL. Rejected branch [" + branchId + "/" + xid + "] will be handled by housekeeping later."); - } - return BranchStatus.PhaseTwo_Committed; -} - -/** - * 通过定时任务消费list中的XID - */ -public synchronized void init() { - LOGGER.info("Async Commit Buffer Limit: " + ASYNC_COMMIT_BUFFER_LIMIT); - timerExecutor = new ScheduledThreadPoolExecutor(1, - new NamedThreadFactory("AsyncWorker", 1, true)); - timerExecutor.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - doBranchCommits(); - } catch (Throwable e) { - LOGGER.info("Failed at async committing ... " + e.getMessage()); - } - } - }, 10, 1000 * 1, TimeUnit.MILLISECONDS); -} - -private void doBranchCommits() { - if (ASYNC_COMMIT_BUFFER.size() == 0) { - return; - } - Map> mappedContexts = new HashMap<>(); - Iterator iterator = ASYNC_COMMIT_BUFFER.iterator(); - - //一次定时循环取出ASYNC_COMMIT_BUFFER中的所有待办数据 - //以resourceId作为key分组待commit数据,resourceId是一个数据库的连接url - //在前面的日志中可以看到,目的是为了覆盖应用的多数据源创建 - while (iterator.hasNext()) { - Phase2Context commitContext = iterator.next(); - List contextsGroupedByResourceId = mappedContexts.get(commitContext.resourceId); - if (contextsGroupedByResourceId == null) { - contextsGroupedByResourceId = new ArrayList<>(); - mappedContexts.put(commitContext.resourceId, contextsGroupedByResourceId); - } - contextsGroupedByResourceId.add(commitContext); - - iterator.remove(); - - } - - for (Map.Entry> entry : mappedContexts.entrySet()) { - Connection conn = null; - try { - try { - //根据resourceId获取数据源以及连接 - DataSourceProxy dataSourceProxy = DataSourceManager.get().get(entry.getKey()); - conn = dataSourceProxy.getPlainConnection(); - } catch (SQLException sqle) { - LOGGER.warn("Failed to get connection for async committing on " + entry.getKey(), sqle); - continue; - } - List contextsGroupedByResourceId = entry.getValue(); - for (Phase2Context commitContext : contextsGroupedByResourceId) { - try { - //执行undolog的处理,即删除xid、branchId对应的记录 - UndoLogManager.deleteUndoLog(commitContext.xid, commitContext.branchId, conn); - } catch (Exception ex) { - LOGGER.warn( - "Failed to delete undo log [" + commitContext.branchId + "/" + commitContext.xid + "]", ex); - } - } - - } finally { - if (conn != null) { - try { - conn.close(); - } catch (SQLException closeEx) { - LOGGER.warn("Failed to close JDBC resource while deleting undo_log ", closeEx); - } - } - } - } -} -``` -所以对于commit动作的处理,RM只需删除xid、branchId对应的undo_log即可。 -## 事务回滚 -对于rollback场景的触发有两种情况 - 1. 分支事务处理异常,即[ConnectionProxy](https://github.com/seata/seata/blob/develop/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/ConnectionProxy.java)中`report(false)`的情况 - 2. TM捕获到下游系统上抛的异常,即发起全局事务标有`@GlobalTransactional`注解的方法捕获到的异常。在前面[TransactionalTemplate](https://github.com/seata/seata/blob/develop/tm/src/main/java/com/alibaba/fescar/tm/api/TransactionalTemplate.java)类的execute模版方法中,对business.execute()的调用进行了catch,catch后会调用rollback,由TM通知TC对应XID需要回滚事务 - -```java - public void rollback() throws TransactionException { - //只有Launcher能发起这个rollback - if (role == GlobalTransactionRole.Participant) { - // Participant has no responsibility of committing - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Ignore Rollback(): just involved in global transaction [" + xid + "]"); - } - return; - } - if (xid == null) { - throw new IllegalStateException(); - } - - status = transactionManager.rollback(xid); - if (RootContext.getXID() != null) { - if (xid.equals(RootContext.getXID())) { - RootContext.unbind(); - } - } -} -``` -TC汇总后向参与者发送rollback指令,RM在[AbstractRMHandler](https://github.com/seata/seata/blob/develop/rm/src/main/java/com/alibaba/fescar/rm/AbstractRMHandler.java)类的doBranchRollback方法中接收这个rollback的通知 - -```java -protected void doBranchRollback(BranchRollbackRequest request, BranchRollbackResponse response) throws TransactionException { - String xid = request.getXid(); - long branchId = request.getBranchId(); - String resourceId = request.getResourceId(); - String applicationData = request.getApplicationData(); - LOGGER.info("Branch rolling back: " + xid + " " + branchId + " " + resourceId); - BranchStatus status = getResourceManager().branchRollback(request.getBranchType(), xid, branchId, resourceId, applicationData); - response.setBranchStatus(status); - LOGGER.info("Branch rollback result: " + status); -} -``` -然后将rollback请求传递到`DataSourceManager`类的branchRollback方法 - -```java -public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId, String applicationData) throws TransactionException { - //根据resourceId获取对应的数据源 - DataSourceProxy dataSourceProxy = get(resourceId); - if (dataSourceProxy == null) { - throw new ShouldNeverHappenException(); - } - try { - UndoLogManager.undo(dataSourceProxy, xid, branchId); - } catch (TransactionException te) { - if (te.getCode() == TransactionExceptionCode.BranchRollbackFailed_Unretriable) { - return BranchStatus.PhaseTwo_RollbackFailed_Unretryable; - } else { - return BranchStatus.PhaseTwo_RollbackFailed_Retryable; - } - } - return BranchStatus.PhaseTwo_Rollbacked; -} -``` -最终会执行[UndoLogManager](https://github.com/seata/seata/blob/develop/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/UndoLogManager.java)类的undo方法,因为是纯jdbc操作代码比较长就不贴出来了,可以通过连接到github查看源码,说一下undo的具体流程 - - 1. 根据xid和branchId查找PhaseOne阶段提交的undo_log - 2. 如果找到了就根据undo_log中记录的数据生成回放sql并执行,即还原PhaseOne阶段修改的数据 - 3. 第2步处理完后,删除该条undo_log数据 - 4. 如果第1步没有找到对应的undo_log,就插入一条状态为`GlobalFinished`的undo_log. - 出现没找到的原因可能是PhaseOne阶段的本地事务异常了,导致没有正常写入。 - 因为xid和branchId是唯一索引,所以第4步的插入,可以防止PhaseOne阶段恢复后的成功写入,那么PhaseOne阶段就会异常,这样一来业务数据也就不会提交成功,数据达到了最终回滚了的效果 -## 总结 -本地结合分布式业务场景,分析了fescar client侧的主要处理流程,对TM和RM角色的主要源码进行了解析,希望能对大家理解fescar的工作原理有所帮助。 - -随着fescar的快速迭代以及后期的Roadmap规划,假以时日相信fescar能够成为开源分布式事务的标杆解决方案。 +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-analysis-java-server.md b/blog/seata-analysis-java-server.md index b056a104a5..e872b67af1 100644 --- a/blog/seata-analysis-java-server.md +++ b/blog/seata-analysis-java-server.md @@ -1,336 +1 @@ ---- -title: 深度剖析一站式分布式事务方案Seata-Server -author: 李钊,季敏 -date: 2019/04/08 -keywords: [fescar、seata、分布式事务] ---- - -# 1.关于Seata -再前不久,我写了一篇关于分布式事务中间件Fescar的解析,没过几天Fescar团队对其进行了品牌升级,取名为Seata(Simpe Extensible Autonomous Transcaction Architecture),而以前的Fescar的英文全称为Fast & EaSy Commit And Rollback。可以看见Fescar从名字上来看更加局限于Commit和Rollback,而新的品牌名字Seata旨在打造一套一站式分布式事务解决方案。更换名字之后,我对其未来的发展更有信心。 - -这里先大概回忆一下Seata的整个过程模型: - -![](/img/blog/20190327000119.png) - -- TM:事务的发起者。用来告诉TC,全局事务的开始,提交,回滚。 -- RM:具体的事务资源,每一个RM都会作为一个分支事务注册在TC。 -- TC:事务的协调者。也可以看做是Fescar-server,用于接收我们的事务的注册,提交和回滚。 - -在之前的文章中对整个角色有个大体的介绍,在这篇文章中我将重点介绍其中的核心角色TC,也就是事务协调器。 - -# 2.Transcation Coordinator -为什么之前一直强调 TC 是核心呢?那因为TC这个角色就好像上帝一样,管控着云云众生的RM和TM。如果TC一旦不好使,那么RM和TM一旦出现小问题,那必定会乱的一塌糊涂。所以要想了解Seata,那么必须要了解它的TC。 - -那么一个优秀的事务协调者应该具备哪些能力呢?我觉得应该有以下几个: -- 正确的协调:能正确的协调RM和TM接下来应该做什么,做错了应该怎么办,做对了应该怎么办。 -- 高可用: 事务协调器在分布式事务中很重要,如果不能保证高可用,那么它也没有存在的必要了。 -- 高性能:事务协调器的性能一定要高,如果事务协调器性能有瓶颈那么它所管理的RM和TM那么会经常遇到超时,从而引起回滚频繁。 -- 高扩展性:这个特点是属于代码层面的,如果是一个优秀的框架,那么需要给使用方很多自定义扩展,比如服务注册/发现,读取配置等等。 - -下面我也将逐步阐述Seata是如何做到上面四点。 - -## 2.1 Seata-Server的设计 - - -![](/img/seata-server/design.png) - -Seata-Server整体的模块图如上所示: -- Coordinator Core: 在最下面的模块是事务协调器核心代码,主要用来处理事务协调的逻辑,如是否commit,rollback等协调活动。 -- Store:存储模块,用来将我们的数据持久化,防止重启或者宕机数据丢失。 -- Discovery: 服务注册/发现模块,用于将Server地址暴露给我们Client。 -- Config: 用来存储和查找我们服务端的配置。 -- Lock: 锁模块,用于给Seata提供全局锁的功能。 -- RPC:用于和其它端通信。 -- HA-Cluster:高可用集群,目前还没开源,为Seata提供可靠的高可用服务,预计将会在0.6版本开源。 - -## 2.2 Discovery - -首先来讲讲比较基础的Discovery模块,又称服务注册/发现模块。我们将Seata-Sever启动之后,需要将自己的地址暴露给其它使用者,那么就需要我们这个模块帮忙。 - -![](/img/seata-server/discover.png) - -这个模块有个核心接口RegistryService,如上图所示: -- register:服务端使用,进行服务注册。 -- unregister:服务端使用,一般在JVM关闭钩子,ShutdownHook中调用。 -- subscribe:客户端使用,注册监听事件,用来监听地址的变化。 -- unsubscribe:客户端使用,取消注册监听事件。 -- lookup:客户端使用,根据key查找服务地址列表。 -- close:都可以使用,用于关闭Registry资源。 - -如果需要添加自己定义的服务注册/发现,那么实现这个接口即可。截止目前在社区的不断开发推动下,已经有五种服务注册/发现,分别是redis、zk、nacos、eruka 和 consul。下面简单介绍下Nacos的实现: - -#### 2.2.1 register接口: -![](/img/seata-server/register.png) - - -step1:校验地址是否合法 - -step2:获取Nacos的 Naming 实例,然后将地址注册到服务名为 serverAddr(固定服务名) 的对应集群分组(registry.conf 文件配置)上面。 - -unregister接口类似,这里不做详解。 - -#### 2.2.2 lookup接口: -![](/img/seata-server/lookup.png) - -step1:获取当前clusterName名字。 - -step2:判断当前集群名对应的服务是否已经订阅过了,如果是直接从map中取订阅返回的数据。 - -step3:如果没有订阅先主动查询一次服务实例列表,然后添加订阅并将订阅返回的数据存放到map中,之后直接从map获取最新数据。 - - -#### 2.2.3 subscribe接口 - -![](/img/seata-server/subscribe.png) - -这个接口比较简单,具体分两步: - -step1:对将要订阅的cluster-> listener 存放到map中,此处nacos未提交单机已订阅列表,所以需要自己实现。 - -step2:使用Nacos api 订阅。 -## 2.3 Config -配置模块也是一个比较基础,比较简单的模块。我们需要配置一些常用的参数比如:Netty的select线程数量,work线程数量,session允许最大为多少等等,当然这些参数再Seata中都有自己的默认设置。 - -同样的在Seata中也提供了一个接口Configuration,用来自定义我们需要的获取配置的地方: - -![](/img/seata-server/config.png) - -- getInt/Long/Boolean/getConfig():通过dataId来获取对应的值,读取不到配置、异常或超时将返回参数中的默认值。 -- putConfig:用于添加配置。 -- removeConfig:删除一个配置。 -- add/remove/get ConfigListener:添加/删除/获取 配置监听器,一般用来监听配置的变更。 - -目前为止有四种方式获取Config:File(文件获取)、Nacos、Apollo 和 ZK(不推荐)。在Seata中首先需要配置registry.conf,来配置config.type 。实现conf比较简单这里就不深入分析。 -## 2.4 Store -存储层的实现对于Seata是否高性能,是否可靠非常关键。 -如果存储层没有实现好,那么如果发生宕机,在TC中正在进行分布式事务处理的数据将会被丢失,既然使用了分布式事务,那么其肯定不能容忍丢失。如果存储层实现好了,但是其性能有很大问题,RM可能会发生频繁回滚那么其完全无法应对高并发的场景。 - -在Seata中默认提供了文件方式的存储,下面我们定义我们存储的数据为Session,而我们的TM创造的全局事务操作数据叫GloabSession,RM创造的分支事务操作数据叫BranchSession,一个GloabSession可以拥有多个BranchSession。我们的目的就是要将这么多Session存储下来。 - -在FileTransactionStoreManager#writeSession代码中: - -![](/img/seata-server/store.png) - -上面的代码主要分为下面几步: -- step1:生成一个TransactionWriteFuture。 -- step2:将这个futureRequest丢进一个LinkedBlockingQueue中。为什么需要将所有数据都丢进队列中呢?当然这里其实也可以用锁来实现,再另外一个阿里开源的RocketMQ中,使用的锁。不论是队列还是锁它们的目的是为了保证单线程写,这又是为什么呢?有人会解释说,需要保证顺序写,这样速度就很快,这个理解是错误的,我们的FileChannel的写方法是线程安全的,已经能保证顺序写了。保证单线程写其实是为了让我们这个写逻辑都是单线程的,因为可能有些文件写满或者记录写数据位置等等逻辑,当然这些逻辑都可以主动加锁去做,但是为了实现简单方便,直接再整个写逻辑排队处理是最为合适的。 -- step3:调用future.get,等待我们该条数据写逻辑完成通知。 - -我们将数据提交到队列之后,我们接下来需要对其进行消费,代码如下: - -![](/img/seata-server/storewrite.png) - -这里将一个WriteDataFileRunnable()提交进我们的线程池,这个Runnable的run()方法如下: - -![](/img/seata-server/storerun.png) - -分为下面几步: - -step1: 判断是否停止,如果stopping为true则返回null。 - -step2:从我们的队列中获取数据。 - -step3:判断future是否已经超时了,如果超时,则设置结果为false,此时我们生产者get()方法会接触阻塞。 - -step4:将我们的数据写进文件,此时数据还在pageCahce层并没有刷新到磁盘,如果写成功然后根据条件判断是否进行刷盘操作。 - -step5:当写入数量到达一定的时候,或者写入时间到达一定的时候,需要将我们当前的文件保存为历史文件,删除以前的历史文件,然后创建新的文件。这一步是为了防止我们文件无限增长,大量无效数据浪费磁盘资源。 - -在我们的writeDataFile中有如下代码: - -![](/img/seata-server/writedatafile.png) - -step1:首先获取我们的ByteBuffer,如果超出最大循环BufferSize就直接创建一个新的,否则就使用我们缓存的Buffer。这一步可以很大的减少GC。 - -step2:然后将数据添加进入ByteBuffer。 - -step3:最后将ByteBuffer写入我们的fileChannel,这里会重试三次。此时的数据还在pageCache层,受两方面的影响,OS有自己的刷新策略,但是这个业务程序不能控制,为了防止宕机等事件出现造成大量数据丢失,所以就需要业务自己控制flush。下面是flush的代码: - -![](/img/seata-server/flush.png) - -这里flush的条件写入一定数量或者写的时间超过一定时间,这样也会有个小问题如果是停电,那么pageCache中有可能还有数据并没有被刷盘,会导致少量的数据丢失。目前还不支持同步模式,也就是每条数据都需要做刷盘操作,这样可以保证每条消息都落盘,但是性能也会受到极大的影响,当然后续会不断的演进支持。 - -我们的store核心流程主要是上面几个方法,当然还有一些比如,session重建等,这些比较简单,读者可以自行阅读。 - -## 2.5 Lock -大家知道数据库实现隔离级别主要是通过锁来实现的,同样的再分布式事务框架Seata中要实现隔离级别也需要通过锁。一般在数据库中数据库的隔离级别一共有四种:读未提交,读已提交,可重复读,串行化。在Seata中可以保证隔离级别是读已提交,但是提供了达到读已提交隔离的手段。 - -Lock模块也就是Seata实现隔离级别的核心模块。在Lock模块中提供了一个接口用于管理我们的锁: -![](/img/seata-server/lockManager.png) - -其中有三个方法: -- acquireLock:用于对我们的BranchSession加锁,这里虽然是传的分支事务Session,实际上是对分支事务的资源加锁,成功返回true。 -- isLockable:根据事务ID,资源Id,锁住的Key来查询是否已经加锁。 -- cleanAllLocks:清除所有的锁。 -对于锁我们可以在本地实现,也可以通过redis或者mysql来帮助我们实现。官方默认提供了本地全局锁的实现: -![](/img/seata-server/defaultLock.png) - -在本地锁的实现中有两个常量需要关注: -- BUCKET_PER_TABLE:用来定义每个table有多少个bucket,目的是为了后续对同一个表加锁的时候减少竞争。 -- LOCK_MAP:这个map从定义上来看非常复杂,里里外外套了很多层Map,这里用个表格具体说明一下: - -层数 | key| value ----|---|--- -1-LOCK_MAP | resourceId(jdbcUrl) | dbLockMap -2- dbLockMap | tableName (表名) | tableLockMap -3- tableLockMap | PK.hashcode%Bucket (主键值的hashcode%bucket) | bucketLockMap -4- bucketLockMap | PK | trascationId - -可以看见实际上的加锁在bucketLockMap这个map中,这里具体的加锁方法比较简单就不作详细阐述,主要是逐步的找到bucketLockMap,然后将当前trascationId塞进去,如果这个主键当前有TranscationId,那么比较是否是自己,如果不是则加锁失败。 - -## 2.6 RPC -保证Seata高性能的关键之一也是使用了Netty作为RPC框架,采用默认配置的线程模型如下图所示: - -![](/img/seata-server/reactor.png) - -如果采用默认的基本配置那么会有一个Acceptor线程用于处理客户端的链接,会有cpu*2数量的NIO-Thread,再这个线程中不会做业务太重的事情,只会做一些速度比较快的事情,比如编解码,心跳事件,和TM注册。一些比较费时间的业务操作将会交给业务线程池,默认情况下业务线程池配置为最小线程为100,最大为500。 - -Seata 目前允许配置的传输层配置如图所示,用户可根据需要进行Netty传输层面的调优,配置通过配置中心配置,首次加载时生效。 - -![](/img/seata-server/transport.png) - -这里需要提一下的是Seata的心跳机制,这里是使用Netty的IdleStateHandler完成的,如下: - -![](/img/seata-server/idleStateHandler.png) - -在Sever端对于写没有设置最大空闲时间,对于读设置了最大空闲时间,默认为15s(客户端默认写空闲为5s,发送ping消息),如果超过15s则会将链接断开,关闭资源。 - - -![](/img/seata-server/userEventTriggered.png) - -step1:判断是否是读空闲的检测事件。 - -step2:如果是则断开链接,关闭资源。 -另外Seata 做了内存池、客户端做了批量小包合并发送、Netty连接池(减少连接创建时的服务不可用时间)等功能,以下为批量小包合并功能。 - -![](/img/seata-server/send.png) - -客户端的消息发送并不是真正的消息发送通过 AbstractRpcRemoting#sendAsyncRequest 包装成 RpcMessage 存储至 basket 中并唤醒合并发送线程。合并发送线程通过 while true 的形式 -最长等待1ms对basket的消息取出包装成 merge 消息进行真正发送,此时若 channel 出现异常则会通过 fail-fast 快速失败返回结果。merge消息发送前在 map 中标识,收到结果后批量确认(AbstractRpcRemotingClient#channelRead),并通过 dispatch 分发至 messageListener 和 handler 去处理。同时,timerExecutor 定时对已发送消息进行超时检测,若超时置为失败。具体消息协议设计将会在后续的文章中给出,敬请关注。 -Seata 的 Netty Client由 TMClient和RMClient 组成,根据事务角色功能区分,都继承 AbstractRpcRemotingClient,AbstractRpcRemotingClient 实现了 RemotingService(服务启停), RegisterMsgListener(netty 连接池连接创建回调)和 ClientMessageSender(消息发送)继承了 AbstractRpcRemoting( Client和Server 顶层消息发送和处理的模板)。 -RMClient类关系图如下图所示: -![](/img/seata-server/class.png) -TMClient 和 RMClient 又会根据自身的 poolConfig 配置与 NettyPoolableFactory implements KeyedPoolableObjectFactory 进行 channel 连接的交互,channel 连接池根据角色 key+ip 作为连接池的 key 来定位各个连接池 -,连接池对 channel 进行统一的管理。TMClient 和 RMClient 在发送过程中对于每个 ip 只会使用一个长连接,但连接不可用时,会从连接池中快速取出已经创建好并可用的连接,减少服务的不可用时间。 - -## 2.7 HA-Cluster -目前官方没有公布HA-Cluster,但是通过一些其它中间件和官方的一些透露,可以将HA-Cluster用如下方式设计: -![](/img/seata-server/hacluster.png) - -具体的流程如下: - -step1:客户端发布信息的时候根据transcationId保证同一个transcation是在同一个master上,通过多个Master水平扩展,提供并发处理性能。 - -step2:在server端中一个master有多个slave,master中的数据近实时同步到slave上,保证当master宕机的时候,还能有其它slave顶上来可以用。 - -当然上述一切都是猜测,具体的设计实现还得等0.5版本之后。目前有一个Go版本的Seata-Server也捐赠给了Seata(还在流程中),其通过raft实现副本一致性,其它细节不是太清楚。 - -## 2.8 Metrics -这个模块也是一个没有具体公布实现的模块,当然有可能会提供插件口,让其它第三方metric接入进来,最近Apache SkyWalking 正在和Seata小组商讨如何接入进来。 - -# 3.Coordinator Core -上面我们讲了很多Server基础模块,想必大家对Seata的实现已经有个大概,接下来我会讲解事务协调器具体逻辑是如何实现的,让大家更加了解Seata的实现内幕。 - -## 3.1 启动流程 -启动方法在Server类有个main方法,定义了我们启动流程: - -![](/img/seata-server/main.png) - -step1:创建一个RpcServer,再这个里面包含了我们网络的操作,用Netty实现了服务端。 - -step2:解析端口号、本地文件地址(用户Server宕机未处理完成事务恢复)、IP(可选,本机只能获取内网ip,在跨网络时需要一个对外的vip注册服务)。 - -step3:初始化SessionHoler,其中最重要的重要就是重我们dataDir这个文件夹中恢复我们的数据,重建我们的Session。 - -step4:创建一个CoorDinator,这个也是我们事务协调器的逻辑核心代码,然后将其初始化,其内部初始化的逻辑会创建四个定时任务: -- retryRollbacking:重试rollback定时任务,用于将那些失败的rollback进行重试的,每隔5ms执行一次。 -- retryCommitting:重试commit定时任务,用于将那些失败的commit进行重试的,每隔5ms执行一次。 -- asyncCommitting:异步commit定时任务,用于执行异步的commit,每隔10ms一次。 -- timeoutCheck:超时定时任务检测,用于检测超时的任务,然后执行超时的逻辑,每隔2ms执行一次。 - -step5: 初始化UUIDGenerator这个也是我们生成各种ID(transcationId,branchId)的基本类。 - -step6:将本地IP和监听端口设置到XID中,初始化rpcServer等待客户端的连接。 - -启动流程比较简单,下面我会介绍分布式事务框架中的常见的一些业务逻辑Seata是如何处理的。 -## 3.2 Begin-开启全局事务 -一次分布式事务的起始点一定是开启全局事务,首先我们看看全局事务Seata是如何实现的: - -![](/img/seata-server/begin.png) - -step1: 根据应用ID,事务分组,名字,超时时间创建一个GloabSession,这个在前面也提到过它和branchSession分别是什么。 - -step2:对其添加一个RootSessionManager用于监听一些事件,这里要说一下目前在Seata里面有四种类型的Listener(这里要说明的是所有的sessionManager都实现了SessionLifecycleListener): -- ROOT_SESSION_MANAGER:最全,最大的,拥有所有的Session。 -- ASYNC_COMMITTING_SESSION_MANAGER:用于管理需要做异步commit的Session。 -- RETRY_COMMITTING_SESSION_MANAGER:用于管理重试commit的Session。 -- RETRY_ROLLBACKING_SESSION_MANAGER:用于管理重试回滚的Session。 -由于这里是开启事务,其它SessionManager不需要关注,我们只添加RootSessionManager即可。 - -step3:开启Globalsession - -![](/img/seata-server/begin2.png) - -这一步会把状态变为Begin,记录开始时间,并且调用RootSessionManager的onBegin监听方法,将Session保存到map并写入到我们的文件。 - -step4:最后返回XID,这个XID是由 ip+port+transactionId 组成的,非常重要,当TM申请到之后需要将这个ID传到RM中,RM通过XID来决定到底应该访问哪一台Server。 - -## 3.3 BranchRegister-分支事务注册 -当我们全局事务在TM开启之后,我们RM的分支事务也需要注册到我们的全局事务之上,这里看看是如何处理的: - -![](/img/seata-server/branchRegister.png) - -step1:通过transactionId获取并校验全局事务是否是开启状态。 - -step2:创建一个新的分支事务,也就是我们的BranchSession。 - -step3:对分支事务进行加全局锁,这里的逻辑就是使用的我们锁模块的逻辑。 - -step4:添加branchSession,主要是将其添加到globalSession对象中,并写入到我们的文件中。 - -step5:返回branchId,这个ID也很重要,我们后续需要用它来回滚我们的事务,或者对我们分支事务状态更新。 - -分支事务注册之后,还需要汇报分支事务的本地事务的执行到底是成功还是失败,在Server目前只是简单的做一下保存记录,汇报的目的是,就算这个分支事务失败,如果TM还是执意要提交全局事务(catch 异常不抛出),那么再遍历提交分支事务的时候,这个失败的分支事务就不需要提交(用户选择性跳过)。 - -## 3.4 GlobalCommit - 全局提交 - -当我们分支事务执行完成之后,就轮到我们的TM-事务管理器来决定是提交还是回滚,如果是提交,那么就会走到下面的逻辑: - - -![](/img/seata-server/commit.png) - -step1:首先找到我们的globalSession。如果它为null证明已经被commit过了,那么直接幂等操作,返回成功。 - -step2:关闭我们的GloabSession防止再次有新的branch进来(跨服务调用超时回滚,provider在继续执行)。 - -step3:如果status是等于Begin,那么久证明还没有提交过,改变其状态为Committing也就是正在提交。 - -step4:判断是否是可以异步提交,目前只有AT模式可以异步提交,二阶段全局提交时只是删除undolog并无严格顺序,此处使用定时任务,客户端收到后批量合并删除。 - -step5:如果是异步提交,直接将其放进我们ASYNC_COMMITTING_SESSION_MANAGER,让其再后台线程异步去做我们的step6,如果是同步的那么直接执行我们的step6。 - -step6:遍历我们的BranchSession进行提交,如果某个分支事务失败,根据不同的条件来判断是否进行重试,可异步执行此branchSession不成功可以继续执行下一个,因为其本身都在manager中,只要没有成功就不会被删除会一直重试,如果是同步提交的会放进重试队列进行定时重试并卡住按照顺序提交。 - -## 3.5 GlobalRollback - 全局回滚 - -如果我们的TM决定全局回滚,那么会走到下面的逻辑: - -![](/img/seata-server/rollback.png) - -这个逻辑和提交流程基本一致,可以看作是它的反向,这里就不展开讲了。 - -# 4.总结 -最后在总结一下开始我们提出了分布式事务的关键4点,Seata到底是怎么解决的: -- 正确的协调:通过后台定时任务各种正确的重试,并且未来会推出监控平台有可能可以手动回滚。 -- 高可用: 通过HA-Cluster保证高可用。 -- 高性能:文件顺序写,RPC通过netty实现,Seata未来可以水平扩展,提高处理性能。 -- 高扩展性:提供给用户可以自由实现的地方,比如配置,服务发现和注册,全局锁等等。 - -最后希望大家能从这篇文章能了解Seata-Server的核心设计原理,当然你也可以想象如果你自己去实现一个分布式事务的Server应该怎样去设计? - -Seata GitHub地址:https://github.com/seata/seata - -本文作者: - -李钊,GitHub ID @CoffeeLatte007,公众号「咖啡拿铁」作者,Seata社区 Committer,猿辅导Java工程师,曾就职于美团。对分布式中间件,分布式系统有浓厚的兴趣。 -季敏(清铭),GitHub ID @slievrly,Seata 开源项目负责人,阿里巴巴中间件 TXC/GTS 核心研发成员,长期从事于分布式中间件核心研发工作,在分布式事务领域有着较丰富的技术积累。 +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-analysis-simple.md b/blog/seata-analysis-simple.md index 20c764eb20..e872b67af1 100644 --- a/blog/seata-analysis-simple.md +++ b/blog/seata-analysis-simple.md @@ -1,521 +1 @@ ---- -title: Fescar分布式事务原理解析探秘 -author: 陈凯玲 -keywords: [Fescar、分布式事务] -date: 2019/02/18 ---- - -# 前言 - -fescar发布已有时日,分布式事务一直是业界备受关注的领域,fescar发布一个月左右便受到了近5000个star足以说明其热度。当然,在fescar出来之前, -已经有比较成熟的分布式事务的解决方案开源了,比较典型的方案如 [LCN](https://github.com/codingapi/tx-lcn) 的2pc型无侵入事务, -目前lcn已发展到5.0,已支持和fescar事务模型类似的TCX型事务。还有如TCC型事务实现 [hmily](https://github.com/yu199195/hmily) [tcc-transaction](https://github.com/changmingxie/tcc-transaction) 等。 -在微服务架构流行的当下、阿里这种开源大户背景下,fescar的发布无疑又掀起了研究分布式事务的热潮。fescar脱胎于阿里云商业分布式事务服务GTS,在线上环境提供这种公共服务其模式肯定经受了非常严苛的考验。其分布式事务模型TXC又仿于传统事务模型XA方案,主要区别在于资源管理器的定位一个在应用层一个在数据库层。博主觉得fescar的txc模型实现非常有研究的价值,所以今天我们来好好翻一翻fescar项目的代码。本文篇幅较长,浏览并理解本文大概耗时30~60分钟左右。 - -# 项目地址 - -fescar:https://github.com/alibaba/fescar - -本博文所述代码为fescar的0.1.2-SNAPSHOT版本,根据fescar后期的迭代计划,其项目结构和模块实现都可能有很大的改变,特此说明。 - -# fescar的TXC模型 - -![](/img/blog/c45496461bca15ecd522e497d98ba954f95.jpg) - -上图为fescar官方针对TXC模型制作的示意图。不得不说大厂的图制作的真的不错,结合示意图我们可以看到TXC实现的全貌。TXC的实现通过三个组件来完成。也就是上图的三个深黄色部分,其作用如下: - -1. TM:全局事务管理器,在标注开启fescar分布式事务的服务端开启,并将全局事务发送到TC事务控制端管理 -2. TC:事务控制中心,控制全局事务的提交或者回滚。这个组件需要独立部署维护,目前只支持单机版本,后续迭代计划会有集群版本 -3. RM:资源管理器,主要负责分支事务的上报,本地事务的管理 - -一段话简述其实现过程:服务起始方发起全局事务并注册到TC。在调用协同服务时,协同服务的事务分支事务会先完成阶段一的事务提交或回滚,并生成事务回滚的undo_log日志,同时注册当前协同服务到TC并上报其事务状态,归并到同一个业务的全局事务中。此时若没有问题继续下一个协同服务的调用,期间任何协同服务的分支事务回滚,都会通知到TC,TC在通知全局事务包含的所有已完成一阶段提交的分支事务回滚。如果所有分支事务都正常,最后回到全局事务发起方时,也会通知到TC,TC在通知全局事务包含的所有分支删除回滚日志。在这个过程中为了解决写隔离和度隔离的问题会涉及到TC管理的全局锁。 - -本博文的目标是深入代码细节,探究其基本思路是如何实现的。首先会从项目的结构来简述每个模块的作用,继而结合官方自带的examples实例来探究整个分布式事务的实现过程。 - -# 项目结构解析 - -项目拉下来,用IDE打开后的目录结构如下,下面先大致的看下每个模块的实现 - -![](/img/blog/a88cf147f489f913886ef1785d94183bf09.jpg) - -- common :公共组件,提供常用辅助类,静态变量、扩展机制类加载器、以及定义全局的异常等 -- config : 配置加载解析模块,提供了配置的基础接口,目前只有文件配置实现,后续会有nacos等配置中心的实现 -- core : 核心模块主要封装了TM、RM和TC通讯用RPC相关内容 -- dubbo :dubbo模块主要适配dubbo通讯框架,使用dubbo的filter机制来传统全局事务的信息到分支 -- examples :简单的演示实例模块,等下从这个模块入手探索 -- rm-datasource :资源管理模块,比较核心的一个模块,个人认为这个模块命名为core要更合理一点。代理了JDBC的一些类,用来解析sql生成回滚日志、协调管理本地事务 -- server : TC组件所在,主要协调管理全局事务,负责全局事务的提交或者回滚,同时管理维护全局锁。 -- spring :和spring集成的模块,主要是aop逻辑,是整个分布式事务的入口,研究fescar的突破口 -- tm : 全局事务事务管理模块,管理全局事务的边界,全局事务开启回滚点都在这个模块控制 - -# 通过【examples】模块的实例看下效果 - -第一步、先启动TC也就是【Server】模块,main方法直接启动就好,默认服务端口8091 - -第二步、回到examples模块,将订单,业务,账户、仓库四个服务的配置文件配置好,主要是mysql数据源和zookeeper连接地址,这里要注意下,默认dubbo的zk注册中心依赖没有,启动的时候回抛找不到class的异常,需要添加如下的依赖: - -```xml - - com.101tec - zkclient - 0.10 - - - slf4j-log4j12 - org.slf4j - - - -``` -第三步、在BusinessServiceImpl中的模拟抛异常的地方打个断点,依次启动OrderServiceImpl、StorageServiceImpl、AccountServiceImpl、BusinessServiceImpl四个服务、等进断点后,查看数据库account\_tbl表,金额已减去400元,变成了599元。然后放开断点、BusinessServiceImpl模块模拟的异常触发,全局事务回滚,account\_tbl表的金额就又回滚到999元了 - -如上,我们已经体验到fescar事务的控制能力了,下面我们具体看下它是怎么控制的。 - -# fescar事务过程分析 - -## 首先分析配置文件 - -这个是一个铁律,任何一个技术或框架要集成,配置文件肯定是一个突破口。从上面的例子我们了解到,实例模块的配置文件中配置了一个全局事务扫描器实例,如: - -```xml - - - - -``` -这个实例在项目启动时会扫描所有实例,具体实现见【spring】模块。并将标注了@GlobalTransactional注解的方法织入GlobalTransactionalInterceptor的invoke方法逻辑。同时应用启动时,会初始化TM(TmRpcClient)和RM(RmRpcClient)的实例,这个时候,服务已经和TC事务控制中心勾搭上了。在往下看就涉及到TM模块的事务模板类TransactionalTemplate。 - -## 【TM】模块启动全局事务 - -全局事务的开启,提交、回滚都被封装在TransactionalTemplate中完成了,代码如: - -```java - -public Object execute(TransactionalExecutor business) throws TransactionalExecutor.ExecutionException { - // 1. get or create a transaction - GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate(); - // 2. begin transaction - try { - tx.begin(business.timeout(), business.name()); - } catch (TransactionException txe) { - throw new TransactionalExecutor.ExecutionException(tx, txe, - TransactionalExecutor.Code.BeginFailure); - } - Object rs = null; - try { - // Do Your Business - rs = business.execute(); - } catch (Throwable ex) { - // 3. any business exception, rollback. - try { - tx.rollback(); - // 3.1 Successfully rolled back - throw new TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex); - } catch (TransactionException txe) { - // 3.2 Failed to rollback - throw new TransactionalExecutor.ExecutionException(tx, txe, - TransactionalExecutor.Code.RollbackFailure, ex); - } - } - // 4. everything is fine, commit. - try { - tx.commit(); - } catch (TransactionException txe) { - // 4.1 Failed to commit - throw new TransactionalExecutor.ExecutionException(tx, txe, - TransactionalExecutor.Code.CommitFailure); - } - return rs; -} -``` -更详细的实现在【TM】模块中被分成了两个Class实现,如下: - -DefaultGlobalTransaction :全局事务具体的开启,提交、回滚动作 - -DefaultTransactionManager :负责使用TmRpcClient向TC控制中心发送指令,如开启全局事务(GlobalBeginRequest)、提交(GlobalCommitRequest)、回滚(GlobalRollbackRequest)、查询状态(GlobalStatusRequest)等。 - -以上是TM模块核心内容点,TM模块完成全局事务开启后,接下来就开始看看全局事务iD,xid是如何传递、RM组件是如何介入的 - -## 【dubbo】全局事务xid的传递 - -首先是xid的传递,目前已经实现了dubbo框架实现的微服务架构下的传递,其他的像spring cloud和motan等的想要实现也很容易,通过一般RPC通讯框架都有的filter机制,将xid从全局事务的发起节点传递到服务协从节点,从节点接收到后绑定到当前线程上线文环境中,用于在分支事务执行sql时判断是否加入全局事务。fescar的实现见【dubbo】模块如下: - -```java -@Activate(group = { Constants.PROVIDER, Constants.CONSUMER }, order = 100) -public class TransactionPropagationFilter implements Filter { - - private static final Logger LOGGER = LoggerFactory.getLogger(TransactionPropagationFilter.class); - - @Override - public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { - String xid = RootContext.getXID(); - String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("xid in RootContext\[" + xid + "\] xid in RpcContext\[" + rpcXid + "\]"); - } - boolean bind = false; - if (xid != null) { - RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid); - } else { - if (rpcXid != null) { - RootContext.bind(rpcXid); - bind = true; - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("bind\[" + rpcXid + "\] to RootContext"); - } - } - } - try { - return invoker.invoke(invocation); - - } finally { - if (bind) { - String unbindXid = RootContext.unbind(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("unbind\[" + unbindXid + "\] from RootContext"); - } - if (!rpcXid.equalsIgnoreCase(unbindXid)) { - LOGGER.warn("xid in change during RPC from " + rpcXid + " to " + unbindXid); - if (unbindXid != null) { - RootContext.bind(unbindXid); - LOGGER.warn("bind \[" + unbindXid + "\] back to RootContext"); - } - } - } - } - } -} -``` -上面代码rpcXid不为空时,就加入到了RootContext的ContextCore中,这里稍微深入讲下。ContextCore是一个可扩展实现的接口,目前默认的实现是ThreadLocalContextCore,基于ThreadLocal来保存维护当前的xid。这里fescar提供了可扩展的机制,实现在【common】模块中,通过一个自定义的类加载器EnhancedServiceLoader加载需要扩展的服务类,这样只需要在扩展类加上@LoadLevel注解。标记order属性声明高优先级别,就可以达到扩展实现的目的。 - -## 【RM】模块本地资源管理的介入 - -fescar针对本地事务相关的接口,通过代理机制都实现了一遍代理类,如数据源(DataSourceProxy)、ConnectionProxy、StatementProxy等。这个在配置文件中也可以看出来,也就是说,我们要使用fescar分布式事务,一定要配置fescar提供的代理数据源。如: - -![](/img/blog/af317255c71a5c1bf46f7140387acf365f8.jpg) - -配置好代理数据源后,从DataSourceProxy出发,本地针对数据库的所有操作过程我们就可以随意控制了。从上面xid传递,已经知道了xid被保存在RootContext中了,那么请看下面的代码,就非常清楚了: - -首先看StatementProxy的一段代码 - -![](/img/blog/17896deea47b9aee518812b039c39101d8f.jpg) - -在看ExecuteTemplate中的代码 - -![](/img/blog/04488a2745a5d564498462ed64c506174d2.jpg) - -和【TM】模块中的事务管理模板类TransactionlTemplate类似,这里非常关键的逻辑代理也被封装在了ExecuteTemplate模板类中。因重写了Statement有了StatementProxy实现,在执行原JDBC的executeUpdate方法时,会调用到ExecuteTemplate的execute逻辑。在sql真正执行前,会判断RootCOntext当前上下文中是否包含xid,也就是判断当前是否是全局分布式事务。如果不是,就直接使用本地事务,如果是,这里RM就会增加一些分布式事务相关的逻辑了。这里根据sql的不同的类型,fescar封装了五个不同的执行器来处理,分别是UpdateExecutor、DeleteExecutor、InsertExecutor、SelectForUpdateExecutor、PlainExecutor,结构如下图: - -![](/img/blog/bb9a2f07054f19bc21adc332671de4f7b75.jpg) - -### PlainExecutor: - -原生的JDBC接口实现,未做任何处理,提供给全局事务中的普通的select查询使用 - -### UpdateExecutor、DeleteExecutor、InsertExecutor: - -三个DML增删改执行器实现,主要在sql执行的前后对sql语句进行了解析,实现了如下两个抽象接口方法: - -```java -protected abstract TableRecords beforeImage() throws SQLException; - -protected abstract TableRecords afterImage(TableRecords beforeImage) throws SQLException; -``` -在这个过程中通过解析sql生成了提供回滚操作的undo_log日志,日志目前是保存在msyql中的,和业务sql操作共用同一个事务。表的结构如下: - -![](/img/blog/fd5c423815d3b84bdbc70d7efeb9cd16757.jpg) - -rollback\_info保存的undo\_log详细信息,是longblob类型的,结构如下: - -```json -{ -    "branchId":3958194, -    "sqlUndoLogs":[ -        { -            "afterImage":{ -                "rows":[ -                    { -                        "fields":[ -                            { -                                "keyType":"PrimaryKey", -                                "name":"ID", -                                "type":4, -                                "value":10 -                            }, -                            { -                                "keyType":"NULL", -                                "name":"COUNT", -                                "type":4, -                                "value":98 -                            } -                        ] -                    } -                ], -                "tableName":"storage_tbl" -            }, -            "beforeImage":{ -                "rows":[ -                    { -                        "fields":[ -                            { -                                "keyType":"PrimaryKey", -                                "name":"ID", -                                "type":4, -                                "value":10 -                            }, -                            { -                                "keyType":"NULL", -                                "name":"COUNT", -                                "type":4, -                                "value":100 -                            } -                        ] -                    } -                ], -                "tableName":"storage_tbl" -            }, -            "sqlType":"UPDATE", -            "tableName":"storage_tbl" -        } -    ], -    "xid":"192.168.7.77:8091:3958193" -} - - -``` - -这里贴的是一个update的操作,undo\_log记录的非常的详细,通过全局事务xid关联branchid,记录数据操作的表名,操作字段名,以及sql执行前后的记录数,如这个记录,表名=storage\_tbl,sql执行前ID=10,count=100,sql执行后id=10,count=98。如果整个全局事务失败,需要回滚的时候就可以生成: - -```sql -update storage_tbl set count = 100 where id = 10; -``` -这样的回滚sql语句执行了。 - -### SelectForUpdateExecutor: - -fescar的AT模式在本地事务之上默认支持读未提交的隔离级别,但是通过SelectForUpdateExecutor执行器,可以支持读已提交的隔离级别。代码如: - -```java -@Override -public Object doExecute(Object... args) throws Throwable { - SQLSelectRecognizer recognizer = (SQLSelectRecognizer) sqlRecognizer; - - Connection conn = statementProxy.getConnection(); - ResultSet rs = null; - Savepoint sp = null; - LockRetryController lockRetryController = new LockRetryController(); - boolean originalAutoCommit = conn.getAutoCommit(); - - StringBuffer selectSQLAppender = new StringBuffer("SELECT "); - selectSQLAppender.append(getTableMeta().getPkName()); - selectSQLAppender.append(" FROM " + getTableMeta().getTableName()); - String whereCondition = null; - ArrayList paramAppender = new ArrayList<>(); - if (statementProxy instanceof ParametersHolder) { - whereCondition = recognizer.getWhereCondition((ParametersHolder) statementProxy, paramAppender); - } else { - whereCondition = recognizer.getWhereCondition(); - } - if (!StringUtils.isEmpty(whereCondition)) { - selectSQLAppender.append(" WHERE " + whereCondition); - } - selectSQLAppender.append(" FOR UPDATE"); - String selectPKSQL = selectSQLAppender.toString(); - - try { - if (originalAutoCommit) { - conn.setAutoCommit(false); - } - sp = conn.setSavepoint(); - rs = statementCallback.execute(statementProxy.getTargetStatement(), args); - - while (true) { - // Try to get global lock of those rows selected - Statement stPK = null; - PreparedStatement pstPK = null; - ResultSet rsPK = null; - try { - if (paramAppender.isEmpty()) { - stPK = statementProxy.getConnection().createStatement(); - rsPK = stPK.executeQuery(selectPKSQL); - } else { - pstPK = statementProxy.getConnection().prepareStatement(selectPKSQL); - for (int i = 0; i < paramAppender.size(); i++) { - pstPK.setObject(i + 1, paramAppender.get(i)); - } - rsPK = pstPK.executeQuery(); - } - - TableRecords selectPKRows = TableRecords.buildRecords(getTableMeta(), rsPK); - statementProxy.getConnectionProxy().checkLock(selectPKRows); - break; - - } catch (LockConflictException lce) { - conn.rollback(sp); - lockRetryController.sleep(lce); - - } finally { - if (rsPK != null) { - rsPK.close(); - } - if (stPK != null) { - stPK.close(); - } - if (pstPK != null) { - pstPK.close(); - } - } - } - - } finally { - if (sp != null) { - conn.releaseSavepoint(sp); - } - if (originalAutoCommit) { - conn.setAutoCommit(true); - } - } - return rs; -} -``` -关键代码见: - -```java -TableRecords selectPKRows = TableRecords.buildRecords(getTableMeta(), rsPK); -statementProxy.getConnectionProxy().checkLock(selectPKRows); -``` -通过selectPKRows表操作记录拿到lockKeys,然后到TC控制器端查询是否被全局锁定了,如果被锁定了,就重新尝试,直到锁释放返回查询结果。 - -## 分支事务的注册和上报 - -在本地事务提交前,fescar会注册和上报分支事务相关的信息,见ConnectionProxy类的commit部分代码: - -```java -@Override -public void commit() throws SQLException { - if (context.inGlobalTransaction()) { - try { - register(); - } catch (TransactionException e) { - recognizeLockKeyConflictException(e); - } - - try { - if (context.hasUndoLog()) { - UndoLogManager.flushUndoLogs(this); - } - targetConnection.commit(); - } catch (Throwable ex) { - report(false); - if (ex instanceof SQLException) { - throw (SQLException) ex; - } else { - throw new SQLException(ex); - } - } - report(true); - context.reset(); - - } else { - targetConnection.commit(); - } -} -``` -从这段代码我们可以看到,首先是判断是了是否是全局事务,如果不是,就直接提交了,如果是,就先向TC控制器注册分支事务,为了写隔离,在TC端会涉及到全局锁的获取。然后保存了用于回滚操作的undo_log日志,继而真正提交本地事务,最后向TC控制器上报事务状态。此时,阶段一的本地事务已完成了。 - -## 【server】模块协调全局 - -关于server模块,我们可以聚焦在DefaultCoordinator这个类,这个是AbstractTCInboundHandler控制处理器默认实现。主要实现了全局事务开启,提交,回滚,状态查询,分支事务注册,上报,锁检查等接口,如: - -![](/img/blog/3da6fd82debb9470eb4a5feb1eecac6d6a2.jpg) - -回到一开始的TransactionlTemplate,如果整个分布式事务失败需要回滚了,首先是TM向TC发起回滚的指令,然后TC接收到后,解析请求后会被路由到默认控制器类的doGlobalRollback方法内,最终在TC控制器端执行的代码如下: - -```java -@Override -public void doGlobalRollback(GlobalSession globalSession, boolean retrying) throws TransactionException { - for (BranchSession branchSession : globalSession.getReverseSortedBranches()) { - BranchStatus currentBranchStatus = branchSession.getStatus(); - if (currentBranchStatus == BranchStatus.PhaseOne_Failed) { - continue; - } - try { - BranchStatus branchStatus = resourceManagerInbound.branchRollback(XID.generateXID(branchSession.getTransactionId()), branchSession.getBranchId(), - branchSession.getResourceId(), branchSession.getApplicationData()); - - switch (branchStatus) { - case PhaseTwo_Rollbacked: - globalSession.removeBranch(branchSession); - LOGGER.error("Successfully rolled back branch " + branchSession); - continue; - case PhaseTwo\_RollbackFailed\_Unretryable: - GlobalStatus currentStatus = globalSession.getStatus(); - if (currentStatus.name().startsWith("Timeout")) { - globalSession.changeStatus(GlobalStatus.TimeoutRollbackFailed); - } else { - globalSession.changeStatus(GlobalStatus.RollbackFailed); - } - globalSession.end(); - LOGGER.error("Failed to rollback global\[" + globalSession.getTransactionId() + "\] since branch\[" + branchSession.getBranchId() + "\] rollback failed"); - return; - default: - LOGGER.info("Failed to rollback branch " + branchSession); - if (!retrying) { - queueToRetryRollback(globalSession); - } - return; - - } - } catch (Exception ex) { - LOGGER.info("Exception rollbacking branch " + branchSession, ex); - if (!retrying) { - queueToRetryRollback(globalSession); - if (ex instanceof TransactionException) { - throw (TransactionException) ex; - } else { - throw new TransactionException(ex); - } - } - - } - - } - GlobalStatus currentStatus = globalSession.getStatus(); - if (currentStatus.name().startsWith("Timeout")) { - globalSession.changeStatus(GlobalStatus.TimeoutRollbacked); - } else { - globalSession.changeStatus(GlobalStatus.Rollbacked); - } - globalSession.end(); -} -``` -如上代码可以看到,回滚时从全局事务会话中迭代每个分支事务,然后通知每个分支事务回滚。分支服务接收到请求后,首先会被路由到RMHandlerAT中的doBranchRollback方法,继而调用了RM中的branchRollback方法,代码如下: - -```java -@Override -public BranchStatus branchRollback(String xid, long branchId, String resourceId, String applicationData) throws TransactionException { - DataSourceProxy dataSourceProxy = get(resourceId); - if (dataSourceProxy == null) { - throw new ShouldNeverHappenException(); - } - try { - UndoLogManager.undo(dataSourceProxy, xid, branchId); - } catch (TransactionException te) { - if (te.getCode() == TransactionExceptionCode.BranchRollbackFailed_Unretriable) { - return BranchStatus.PhaseTwo\_RollbackFailed\_Unretryable; - } else { - return BranchStatus.PhaseTwo\_RollbackFailed\_Retryable; - } - } - return BranchStatus.PhaseTwo_Rollbacked; -} -``` -RM分支事务端最后执行的是UndoLogManager的undo方法,通过xid和branchid从数据库查询出回滚日志,完成数据回滚操作,整个过程都是同步完成的。如果全局事务是成功的,TC也会有类似的上述协调过程,只不过是异步的将本次全局事务相关的undo_log清除了而已。至此,就完成了2阶段的提交或回滚,也就完成了完整的全局事务事务的控制。 - -# 结语 - -如果你看到这里,那么非常感谢你,在繁忙工作之余耐心的花时间来学习。同时,我相信花的时间没白费,完整的浏览理解估计对fescar实现的大致流程了解的十之八九了。本文从构思立题到完成大概耗时1人天左右,博主在这个过程中,对fescar的实现也有了更加深入的了解。由于篇幅原因,并没有面面俱到的对每个实现的细节去深究,如sql是如何解析的等,更多的是在fescar的TXC模型的实现过程的关键点做了详细阐述。本文已校对,但由于个人知识水平及精力有限,文中不免出现错误或理解不当的地方,欢迎指正。 - -### 作者简介: - -陈凯玲,2016年5月加入凯京科技。曾任职高级研发和项目经理,现任凯京科技研发中心架构&运维部负责人。pmp项目管理认证,阿里云MVP。热爱开源,先后开源过多个热门项目。热爱分享技术点滴,独立博客KL博客([http://www.kailing.pub](http://www.kailing.pub/))博主。 +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-analysis-tcc-modular.md b/blog/seata-analysis-tcc-modular.md index 84770fefd5..e872b67af1 100644 --- a/blog/seata-analysis-tcc-modular.md +++ b/blog/seata-analysis-tcc-modular.md @@ -1,254 +1 @@ ---- -title: Seata tcc 模块源码分析 -author: 赵润泽 -keywords: [Seata、分布式事务] -date: 2019/12/25 ---- - -## 一 .导读 - -spring 模块分析中讲到,Seata 的 spring 模块会对涉及到分布式业务的 bean 进行处理。项目启动时,当 GlobalTransactionalScanner 扫描到 TCC 服务的 reference 时(即tcc事务参与方),会对其进行动态代理,即给 bean 织入 TCC 模式下的 MethodInterceptor 的实现类。tcc 事务发起方依然使用 @GlobalTransactional 注解开启,织入的是通用的 MethodInterceptor 的实现类。 - -TCC 模式下的 MethodInterceptor 实现类即 TccActionInterceptor(spring模块) ,这个类中调用了 ActionInterceptorHandler(tcc模块) 进行 TCC 模式下事务流程的处理。 - -TCC 动态代理的主要功能是:生成TCC运行时上下文、透传业务参数、注册分支事务记录。 - -## 二 .TCC模式介绍 - -在2PC(两阶段提交)协议中,事务管理器分两阶段协调资源管理,资源管理器对外提供三个操作,分别是一阶段的准备操作,和二阶段的提交操作和回滚操作。 - -```java -public interface TccAction { - - @TwoPhaseBusinessAction(name = "tccActionForTest" , commitMethod = "commit", rollbackMethod = "rollback") - public boolean prepare(BusinessActionContext actionContext, - @BusinessActionContextParameter(paramName = "a") int a, - @BusinessActionContextParameter(paramName = "b", index = 0) List b, - @BusinessActionContextParameter(isParamInProperty = true) TccParam tccParam); - - public boolean commit(BusinessActionContext actionContext); - - public boolean rollback(BusinessActionContext actionContext); -} -``` - -这是 TCC 参与者实例,参与者需要实现三个方法,第一个参数必须是 BusinessActionContext ,方法返回类型固定,对外发布成微服务,供事务管理器调用。 - -prepare:资源的检查和预留。例:扣减账户的余额,并增加相同的冻结余额。 - -commit:使用预留的资源,完成真正的业务操作。例:减少冻结余额,扣减资金业务完成。 - -cancel:释放预留资源。例:冻结余额加回账户的余额。 - -其中 BusinessActionContext 封装了本次事务的上下文环境:xid、branchId、actionName 和被 @BusinessActionContextParam 注解的参数等。 - -参与方业务有几个需要注意的地方: -1.控制业务幂等性,需要支持同一笔事务的重复提交和重复回滚。 -2.防悬挂,即二阶段的回滚,比一阶段的 try 先执行。 -3.放宽一致性协议,最终一致,所以是读已修改 - -## 三 . remoting 包解析 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20191124211806237.png?) - -包中所有的类都是为包中的 DefaultRemotingParser 服务,Dubbo、LocalTCC、SofaRpc 分别负责解析各自RPC协议下的类。 - -DefaultRemotingParser 的主要方法: -1.判断 bean 是否是 remoting bean,代码: - -```java - @Override - public boolean isRemoting(Object bean, String beanName) throws FrameworkException { - //判断是否是服务调用方或者是否是服务提供方 - return isReference(bean, beanName) || isService(bean, beanName); - } -``` - -2.远程 bean 解析,把 rpc类 解析成 RemotingDesc,,代码: - -```java -@Override - public boolean isRemoting(Object bean, String beanName) throws FrameworkException { - //判断是否是服务调用方或者是否是服务提供方 - return isReference(bean, beanName) || isService(bean, beanName); - } -``` - -利用 allRemotingParsers 来解析远程 bean 。allRemotingParsers是在:initRemotingParser() 中调用EnhancedServiceLoader.loadAll(RemotingParser.class) 动态进行 RemotingParser 子类的加载,即 SPI 加载机制。 - -如果想扩展,比如实现一个feign远程调用的解析类,只要把RemotingParser相关实现类写在 SPI 的配置中就可以了,扩展性很强。 - -RemotingDesc 事务流程需要的远程 bean 的一些具体信息,比如 targetBean、interfaceClass、interfaceClassName、protocol、isReference等等。 - -3.TCC资源注册 - -```java -public RemotingDesc parserRemotingServiceInfo(Object bean, String beanName) { - RemotingDesc remotingBeanDesc = getServiceDesc(bean, beanName); - if (remotingBeanDesc == null) { - return null; - } - remotingServiceMap.put(beanName, remotingBeanDesc); - - Class interfaceClass = remotingBeanDesc.getInterfaceClass(); - Method[] methods = interfaceClass.getMethods(); - if (isService(bean, beanName)) { - try { - //service bean, registry resource - Object targetBean = remotingBeanDesc.getTargetBean(); - for (Method m : methods) { - TwoPhaseBusinessAction twoPhaseBusinessAction = m.getAnnotation(TwoPhaseBusinessAction.class); - if (twoPhaseBusinessAction != null) { - TCCResource tccResource = new TCCResource(); - tccResource.setActionName(twoPhaseBusinessAction.name()); - tccResource.setTargetBean(targetBean); - tccResource.setPrepareMethod(m); - tccResource.setCommitMethodName(twoPhaseBusinessAction.commitMethod()); - tccResource.setCommitMethod(ReflectionUtil - .getMethod(interfaceClass, twoPhaseBusinessAction.commitMethod(), - new Class[] {BusinessActionContext.class})); - tccResource.setRollbackMethodName(twoPhaseBusinessAction.rollbackMethod()); - tccResource.setRollbackMethod(ReflectionUtil - .getMethod(interfaceClass, twoPhaseBusinessAction.rollbackMethod(), - new Class[] {BusinessActionContext.class})); - //registry tcc resource - DefaultResourceManager.get().registerResource(tccResource); - } - } - } catch (Throwable t) { - throw new FrameworkException(t, "parser remoting service error"); - } - } - if (isReference(bean, beanName)) { - //reference bean, TCC proxy - remotingBeanDesc.setReference(true); - } - return remotingBeanDesc; - } -``` - -首先判断是否是事务参与方,如果是,拿到 RemotingDesc 中的 interfaceClass,遍历接口中的方法,判断方法上是否有@TwoParserBusinessAction 注解,如果有,把参数封装成 TCCRecource,通过 DefaultResourceManager 进行 TCC 资源的注册。 - -这里 DefaultResourceManager 会根据 Resource 的 BranchType 来寻找对应的资源管理器,TCC 模式下资源管理类,在 tcc 模块中。 - -这个 rpc 解析类主要提供给 spring 模块进行使用。parserRemotingServiceInfo() 被封装到了 spring 模块的 TCCBeanParserUtils 工具类中。spring 模块的 GlobalTransactionScanner 在项目启动的时候,通过工具类解析 TCC bean,工具类 TCCBeanParserUtils 会调用 TCCResourceManager 进行资源的注册,并且如果是全局事务的服务提供者,会织入 TccActionInterceptor 代理。这些个流程是 spring 模块的功能,tcc 模块是提供功能类给 spring 模块使用。 - -## 三 .tcc 资源管理器 - -TCCResourceManager 负责管理 TCC 模式下资源的注册、分支的注册、提交、和回滚。 - -1.在项目启动时, spring 模块的 GlobalTransactionScanner 扫描到 bean 是 tcc bean 时,会本地缓存资源,并向 server 注册: - -```java - @Override - public void registerResource(Resource resource) { - TCCResource tccResource = (TCCResource)resource; - tccResourceCache.put(tccResource.getResourceId(), tccResource); - super.registerResource(tccResource); - } -``` - -与server通信的逻辑被封装在了父类 AbstractResourceManage 中,这里根据 resourceId 对 TCCResource 进行缓存。父类 AbstractResourceManage 注册资源的时候,使用 resourceGroupId + actionName,actionName 就是 @TwoParseBusinessAction 注解中的 name,resourceGroupId 默认是 DEFAULT。 - -2.事务分支的注册在 rm-datasource 包下的 AbstractResourceManager 中,注册时参数 lockKeys 为 null,和 AT 模式下事务分支的注册还是有些不一样的。 - -3.分支的提交或者回滚: - -```java - @Override - public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId, - String applicationData) throws TransactionException { - TCCResource tccResource = (TCCResource)tccResourceCache.get(resourceId); - if (tccResource == null) { - throw new ShouldNeverHappenException("TCC resource is not exist, resourceId:" + resourceId); - } - Object targetTCCBean = tccResource.getTargetBean(); - Method commitMethod = tccResource.getCommitMethod(); - if (targetTCCBean == null || commitMethod == null) { - throw new ShouldNeverHappenException("TCC resource is not available, resourceId:" + resourceId); - } - try { - boolean result = false; - //BusinessActionContext - BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId, - applicationData); - Object ret = commitMethod.invoke(targetTCCBean, businessActionContext); - if (ret != null) { - if (ret instanceof TwoPhaseResult) { - result = ((TwoPhaseResult)ret).isSuccess(); - } else { - result = (boolean)ret; - } - } - return result ? BranchStatus.PhaseTwo_Committed : BranchStatus.PhaseTwo_CommitFailed_Retryable; - } catch (Throwable t) { - LOGGER.error(msg, t); - throw new FrameworkException(t, msg); - } - } -``` -通过参数 xid、branchId、resourceId、applicationData 恢复业务的上下文 businessActionContext。 - -根据获取到的上下文通过反射执行 commit 方法,并返回执行结果。回滚方法类似。 - -这里 branchCommit() 和 branchRollback() 提供给 rm 模块资源处理的抽象类 AbstractRMHandler 调用,这个 handler 是 core 模块定义的模板方法的进一步实现类。和 registerResource() 不一样,后者是 spring 扫描时主动注册资源。 - -## 四 . tcc 模式事务处理 - -spring 模块中的 TccActionInterceptor 的 invoke() 方法在被代理的 rpc bean 被调用时执行。该方法先获取 rpc 拦截器透传过来的全局事务 xid ,然后 TCC 模式下全局事务参与者的事务流程还是交给 tcc 模块 ActionInterceptorHandler 处理。 - -也就是说,事务参与者,在项目启动的时候,被代理。真实的业务方法,在 ActionInterceptorHandler 中,通过回调执行。 - -```java - public Map proceed(Method method, Object[] arguments, String xid, TwoPhaseBusinessAction businessAction, - Callback targetCallback) throws Throwable { - Map ret = new HashMap(4); - - //TCC name - String actionName = businessAction.name(); - BusinessActionContext actionContext = new BusinessActionContext(); - actionContext.setXid(xid); - //set action anme - actionContext.setActionName(actionName); - - //Creating Branch Record - String branchId = doTccActionLogStore(method, arguments, businessAction, actionContext); - actionContext.setBranchId(branchId); - - //set the parameter whose type is BusinessActionContext - Class[] types = method.getParameterTypes(); - int argIndex = 0; - for (Class cls : types) { - if (cls.getName().equals(BusinessActionContext.class.getName())) { - arguments[argIndex] = actionContext; - break; - } - argIndex++; - } - //the final parameters of the try method - ret.put(Constants.TCC_METHOD_ARGUMENTS, arguments); - //the final result - ret.put(Constants.TCC_METHOD_RESULT, targetCallback.execute()); - return ret; - } -``` - -这里有两个重要操作: - -1.doTccActionLogStore() 这个方法中,调用了两个比较重要的方法: -fetchActionRequestContext(method, arguments),这个方法把被 @BusinessActionContextParam 注解的参数取出来,在下面的 init 方法中塞入 BusinessActionComtext ,同时塞入的还有事务相关参数。 -DefaultResourceManager.get().branchRegister(BranchType.TCC, actionName, null, xid,applicationContextStr, null),这个方法执行 TCC 模式下事务参与者事务分支的注册。 - -2.回调执行 targetCallback.execute() ,被代理的 bean 具体的业务,即 prepare() 方法。 - - -## 五 .总结 -tcc模块,主要提供以下功能 : - -1. 定义两阶段协议注解,提供 tcc 模式下事务流程需要的属性。 -2. 提供解析不同 rpc 框架 remoting bean 的 ParserRemoting 实现,供 spring 模块调用。 -3. 提供 TCC 模式下资源管理器,进行资源注册、事务分支注册提交回滚等。 -4. 提供 TCC 模式下事务流程的处理类,让 MethodInterceptor 代理类不执行具体模式的事务流程,而是下放到 tcc 模块。 - -## 五 .相关 -作者:赵润泽,[系列地址](https://blog.csdn.net/qq_37804737/category_9530078.html)。 +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-at-demo-in-mac.md b/blog/seata-at-demo-in-mac.md index 5d41b5f4e0..e872b67af1 100644 --- a/blog/seata-at-demo-in-mac.md +++ b/blog/seata-at-demo-in-mac.md @@ -1,349 +1 @@ ---- -title: Mac下的Seata Demo环境搭建(AT模式) -author: portman xu -date: 2020/07/20 -keywords: [seata, 分布式事务, demo, mac, at] ---- - -# Mac下的Seata Demo环境搭建(AT模式) - -## 前言 - -最近因为工作需要,研究学习了Seata分布式事务框架,本文把自己学习的知识记录一下 - -## Seata总览 - -### cloc代码统计 - -先看一下seata项目cloc代码统计(截止到2020-07-20) - -![cloc-seata](https://github.com/iportman/p/blob/master/blog/seata-at-demo-in-mac/cloc-seata.png?raw=true) - -Java代码行数大约是 97K - -### 代码质量 - -单元测试覆盖率50% - -![cloc-seata](https://github.com/iportman/p/blob/master/blog/seata-at-demo-in-mac/coverage.png?raw=true) - -### Demo代码 - -本文讲的Demo代码是seata-samples项目下的seata-samples-dubbo模块,地址如下: - -https://github.com/seata/seata-samples/tree/master/dubbo - -## 解决的核心问题 - -AT模式的Demo例子给出了一个典型的分布式事务场景: - -- 在一个采购交易中,需要: - -1. 扣减商品库存 -2. 扣减用户账号余额 -3. 生成采购订单 - -- 很明显,以上3个步骤必须:要么全部成功,要么全部失败,否则系统的数据会错乱 -- 而现在流行的微服务架构,一般来说,库存,账号余额,订单是3个独立的系统 -- 每个微服务有自己的数据库,相互独立 - -这里就是分布式事务的场景。 - -![设计图](http://seata.io/img/architecture.png) - -## 解决方案 - -AT模式解决这个问题的思路其实很简单,一句话概括就是: - -在分布式事务过程中,记录待修改的数据修改前和修改后的值到undo_log表,万一交易中出现异常,通过这个里的数据做回滚 - -当然,具体代码实现起来,我相信很多细节远没这么简单。 - -## Demo代码结构 - -从github上clone最新的代码 - -```sh -git clone git@github.com:seata/seata-samples.git -``` - -阅读Demo代码结构 - -```sh -$ cd seata-samples/dubbo/ -$ tree -C -I 'target' . -. -├── README.md -├── pom.xml -├── seata-samples-dubbo.iml -└── src - └── main - ├── java - │ └── io - │ └── seata - │ └── samples - │ └── dubbo - │ ├── ApplicationKeeper.java - │ ├── Order.java - │ ├── service - │ │ ├── AccountService.java - │ │ ├── BusinessService.java - │ │ ├── OrderService.java - │ │ ├── StorageService.java - │ │ └── impl - │ │ ├── AccountServiceImpl.java - │ │ ├── BusinessServiceImpl.java - │ │ ├── OrderServiceImpl.java - │ │ └── StorageServiceImpl.java - │ └── starter - │ ├── DubboAccountServiceStarter.java - │ ├── DubboBusinessTester.java - │ ├── DubboOrderServiceStarter.java - │ └── DubboStorageServiceStarter.java - └── resources - ├── file.conf - ├── jdbc.properties - ├── log4j.properties - ├── registry.conf - ├── spring - │ ├── dubbo-account-service.xml - │ ├── dubbo-business.xml - │ ├── dubbo-order-service.xml - │ └── dubbo-storage-service.xml - └── sql - ├── dubbo_biz.sql - └── undo_log.sql - -13 directories, 27 files -``` - -- 在io.seata.samples.dubbo.starter包下的4个\*Starter类,分别模拟上面所述的4个微服务 - - Account - - Business - - Order - - Storage - -- 4个服务都是标准的dubbo服务,配置文件在seata-samples/dubbo/src/main/resources/spring目录下 -- 运行demo需要把这4个服务都启动起来,Business最后启动 -- 主要的逻辑在io.seata.samples.dubbo.service,4个实现类分别对应4个微服务的业务逻辑 -- 数据库信息的配置文件:src/main/resources/jdbc.properties - -### 时序图 - -![cloc-seata](https://github.com/iportman/p/blob/master/blog/seata-at-demo-in-mac/timing-diagram.png?raw=true) - -Ok, 赶紧动手, Make It Happen! - -## 运行Demo - -### MySQL - -### 建表 - -执行seata-samples/dubbo/src/main/resources/sql的脚本dubbo_biz.sql和undo_log.sql - -```sh -mysql> show tables; -+-----------------+ -| Tables_in_seata | -+-----------------+ -| account_tbl | -| order_tbl | -| storage_tbl | -| undo_log | -+-----------------+ -4 rows in set (0.01 sec) -``` - -执行完之后,数据库里应该有4个表 - -修改seata-samples/dubbo/src/main/resources/jdbc.properties文件 - -根据你MySQL运行的环境修改变量的值 - -```properties -jdbc.account.url=jdbc:mysql://localhost:3306/seata -jdbc.account.username=your_username -jdbc.account.password=your_password -jdbc.account.driver=com.mysql.jdbc.Driver -# storage db config -jdbc.storage.url=jdbc:mysql://localhost:3306/seata -jdbc.storage.username=your_username -jdbc.storage.password=your_password -jdbc.storage.driver=com.mysql.jdbc.Driver -# order db config -jdbc.order.url=jdbc:mysql://localhost:3306/seata -jdbc.order.username=your_username -jdbc.order.password=your_password -jdbc.order.driver=com.mysql.jdbc.Driver -``` - -### ZooKeeper - -启动ZooKeeper,我的本地的Mac是使用Homebrew安装启动的 - -```sh -$ brew services start zookeeper -==> Successfully started `zookeeper` (label: homebrew.m - -$ brew services list -Name Status User Plist -docker-machine stopped -elasticsearch stopped -kafka stopped -kibana stopped -mysql started portman /Users/portman/Librar -y/LaunchAgents/homebrew.mxcl.mysql.plist -nginx stopped -postgresql stopped -redis stopped -zookeeper started portman /Users/portman/Librar -y/LaunchAgents/homebrew.mxcl.zookeeper.plist -``` - -### 启动TC事务协调器 - -在这个[链接](https://github.com/seata/seata/releases)里页面中,下载对应版本的seata-server程序,我本地下载的是1.2.0版本 - -1. 进入文件所在目录并解压文件 -2. 进入seata目录 -3. 执行启动脚本 - -```sh -$ tar -zxvf seata-server-1.2.0.tar.gz -$ cd seata -$ bin/seata-server.sh -``` - -观察启动日志是否有报错信息,如果一切正常,并看到了以下的Server started的信息,说明启动成功了。 - -```sh -2020-07-23 13:45:13.810 INFO [main]io.seata.core.rpc.netty.RpcServerBootstrap.start:155 -Server started ... -``` - -### IDE中启动模拟的微服务 - -1. 首先要把seata-samples项目导入到本地IDE中,这里我用的是IntelliJ IDEA -2. 刷新Maven的工程依赖 -3. 先启动Account,Order,Storage这个3个服务,然后Business才能去调用,对应的启动类分别是: - -```java -io.seata.samples.dubbo.starter.DubboStorageServiceStarter -io.seata.samples.dubbo.starter.DubboOrderServiceStarter -io.seata.samples.dubbo.starter.DubboStorageServiceStarter -``` - -每个服务启动完之后,看到这句提示信息,说明服务启动成功了 - -```sh -Application is keep running ... -``` - -![cloc-seata](https://github.com/iportman/p/blob/master/blog/seata-at-demo-in-mac/service-boot.png?raw=true) - -启动成功后,account_tbl,storage_tbl表会有两条初始化的数据,分别是账户余额和商品库存 - -```sh -mysql> SELECT * FROM account_tbl; SELECT * FROM storage_tbl; -+----+---------+-------+ -| id | user_id | money | -+----+---------+-------+ -| 1 | U100001 | 999 | -+----+---------+-------+ -1 row in set (0.00 sec) - -+----+----------------+-------+ -| id | commodity_code | count | -+----+----------------+-------+ -| 1 | C00321 | 100 | -+----+----------------+-------+ -1 row in set (0.00 sec) -``` - -### 使用Business验证效果 - -#### 正常情况 - -还是在IDE中执行DubboBusinessTester类的主函数,程序跑完会自动退出 - -在程序一切正常的情况下,每个微服务的事物都应该是提交了的,数据保持一致 - -我们来看一下MySQL中数据的变化 - -```sh -mysql> SELECT * FROM account_tbl; SELECT * FROM order_tbl; SELECT * FROM storage_tbl; -+----+---------+-------+ -| id | user_id | money | -+----+---------+-------+ -| 1 | U100001 | 599 | -+----+---------+-------+ -1 row in set (0.00 sec) - -+----+---------+----------------+-------+-------+ -| id | user_id | commodity_code | count | money | -+----+---------+----------------+-------+-------+ -| 1 | U100001 | C00321 | 2 | 400 | -+----+---------+----------------+-------+-------+ -1 row in set (0.00 sec) - -+----+----------------+-------+ -| id | commodity_code | count | -+----+----------------+-------+ -| 1 | C00321 | 98 | -+----+----------------+-------+ -1 row in set (0.00 sec) -``` - -从3个表的数据可以看到:账户余额扣减了400块;订单表增加了1条记录;商品库存扣减了2个 - -这个结果是程序的逻辑是一致的,说明事务没有问题 - -#### 异常情况 - -其实即使不加入分布式事务的控制,一切都正常情况下,事务本身就不会有问题的 - -所以我们来重点关注,当程序出现异常时的情况 - -现在我把BusinessServiceImpl的抛异常的代码注释放开,然后再执行一次DubboBusinessTester,来看看有什么情况发生 - -```java - @Override - @GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx") - public void purchase(String userId, String commodityCode, int orderCount) { - LOGGER.info("purchase begin ... xid: " + RootContext.getXID()); - storageService.deduct(commodityCode, orderCount); - orderService.create(userId, commodityCode, orderCount); - - //放开这句抛异常的注释,模拟程序出现异常 - throw new RuntimeException("portman's foooooobar error."); - - } -``` - -接着,我再一次执行DubboBusinessTester,执行过程中在控制台可以看到异常报错信息 - -```java -Exception in thread "main" java.lang.RuntimeException: portman's foooooobar error. -``` - -现在我们再看一下MySQL里的数据变化,发现数据没有任何变化,说明分布式事务的控制已经起作用了 - -## 待思考问题 - -上面的步骤只是演示了seata最简单的demo程序,更多更复杂的情况后续大家可以一起讨论和验证 - -学习过程中还有一些问题和疑惑,后续进一步学习 - -- 全局锁对性能的影响程度 -- undo_log日志可以回滚到原来状态,但是如果数据状态已经发生变化如何处理(比如增加的用户积分已经被别的本地事务花掉了) - -## 参考文献 - -- [Seata 是什么?](http://seata.io/zh-cn/docs/overview/what-is-seata.html) -- [快速开始](http://seata.io/zh-cn/docs/user/quickstart.html) - -## 作者信息 - -许晓加,金蝶软件架构师 - -[Github](https://github.com/iportman) +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-at-lock.md b/blog/seata-at-lock.md index 4860f5edc9..e872b67af1 100644 --- a/blog/seata-at-lock.md +++ b/blog/seata-at-lock.md @@ -1,186 +1 @@ ---- -title: 详解 Seata AT 模式事务隔离级别与全局锁设计 -author: 张乘辉 -keywords: [Seata、分布式事务、AT模式、Transaction、GlobalLock] -description: Seata AT 模式的事务隔离是建立在支事务的本地隔离级别基础之上的,在数据库本地隔离级别读已提交或以上的前提下,Seata 设计了由事务协调器维护的全局写排他锁,来保证事务间的写隔离,同时,将全局事务默认定义在读未提交的隔离级别上。 -date: 2022/01/12 ---- - -# 前言 - -Seata AT 模式是一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。 - -为什么要检查全局锁呢,这是由于 Seata AT 模式的事务隔离是建立在支事务的本地隔离级别基础之上的,在数据库本地隔离级别读已提交或以上的前提下,Seata 设计了由事务协调器维护的全局写排他锁,来保证事务间的写隔离,同时,将全局事务默认定义在读未提交的隔离级别上。 - -# Seata 事务隔离级别解读 - -在讲 Seata 事务隔离级之前,我们先来回顾一下数据库事务的隔离级别,目前数据库事务的隔离级别一共有 4 种,由低到高分别为: - -1. Read uncommitted:读未提交 -2. Read committed:读已提交 -3. Repeatable read:可重复读 -4. Serializable:序列化 - -数据库一般默认的隔离级别为读已提交,比如 Oracle,也有一些数据的默认隔离级别为可重复读,比如 Mysql,一般而言,数据库的读已提交能够满足业务绝大部分场景了。 - -我们知道 Seata 的事务是一个全局事务,它包含了若干个分支本地事务,在全局事务执行过程中(全局事务还没执行完),某个本地事务提交了,如果 Seata 没有采取任务措施,则会导致已提交的本地事务被读取,造成脏读,如果数据在全局事务提交前已提交的本地事务被修改,则会造成脏写。 - -由此可以看出,传统意义的脏读是读到了未提交的数据,Seata 脏读是读到了全局事务下未提交的数据,全局事务可能包含多个本地事务,某个本地事务提交了不代表全局事务提交了。 - -在绝大部分应用在读已提交的隔离级别下工作是没有问题的,而实际上,这当中又有绝大多数的应用场景,实际上工作在读未提交的隔离级别下同样没有问题。 - -在极端场景下,应用如果需要达到全局的读已提交,Seata 也提供了全局锁机制实现全局事务读已提交。但是默认情况下,Seata 的全局事务是工作在读未提交隔离级别的,保证绝大多数场景的高效性。 - -# 全局锁实现 - -AT 模式下,会使用 Seata 内部数据源代理 DataSourceProxy,全局锁的实现就是隐藏在这个代理中。我们分别在执行、提交的过程都做了什么。 - -## 1、执行过程 - -执行过程在 StatementProxy 类,在执行过程中,如果执行 SQL 是 `select for update`,则会使用 SelectForUpdateExecutor 类,如果执行方法中带有 `@GlobalTransactional` or `@GlobalLock`注解,则会检查是否有全局锁,如果当前存在全局锁,则会回滚本地事务,通过 while 循环不断地重新竞争获取本地锁和全局锁。 - -io.seata.rm.datasource.exec.SelectForUpdateExecutor#doExecute - -```java -public T doExecute(Object... args) throws Throwable { - Connection conn = statementProxy.getConnection(); - // ... ... - try { - // ... ... - while (true) { - try { - // ... ... - if (RootContext.inGlobalTransaction() || RootContext.requireGlobalLock()) { - // Do the same thing under either @GlobalTransactional or @GlobalLock, - // that only check the global lock here. - statementProxy.getConnectionProxy().checkLock(lockKeys); - } else { - throw new RuntimeException("Unknown situation!"); - } - break; - } catch (LockConflictException lce) { - if (sp != null) { - conn.rollback(sp); - } else { - conn.rollback(); - } - // trigger retry - lockRetryController.sleep(lce); - } - } - } finally { - // ... - } -``` - -## 2、提交过程 - -提交过程在 ConnectionProxy#doCommit方法中。 - -1)如果执行方法中带有`@GlobalTransactional`注解,则会在注册分支时候获取全局锁: - -- 请求 TC 注册分支 - -io.seata.rm.datasource.ConnectionProxy#register - -```java -private void register() throws TransactionException { - if (!context.hasUndoLog() || !context.hasLockKey()) { - return; - } - Long branchId = DefaultResourceManager.get().branchRegister(BranchType.AT, getDataSourceProxy().getResourceId(), - null, context.getXid(), null, context.buildLockKeys()); - context.setBranchId(branchId); -} -``` - -- TC 注册分支的时候,获取全局锁 - -io.seata.server.transaction.at.ATCore#branchSessionLock - -```java -protected void branchSessionLock(GlobalSession globalSession, BranchSession branchSession) throws TransactionException { - if (!branchSession.lock()) { - throw new BranchTransactionException(LockKeyConflict, String - .format("Global lock acquire failed xid = %s branchId = %s", globalSession.getXid(), - branchSession.getBranchId())); - } -} -``` - -2)如果执行方法中带有`@GlobalLock`注解,在提交前会查询全局锁是否存在,如果存在则抛异常: - -io.seata.rm.datasource.ConnectionProxy#processLocalCommitWithGlobalLocks - -```java -private void processLocalCommitWithGlobalLocks() throws SQLException { - checkLock(context.buildLockKeys()); - try { - targetConnection.commit(); - } catch (Throwable ex) { - throw new SQLException(ex); - } - context.reset(); -} -``` - -## GlobalLock 注解说明 - -从执行过程和提交过程可以看出,既然开启全局事务 `@GlobalTransactional`注解可以在事务提交前,查询全局锁是否存在,那为什么 Seata 还要设计多处一个 `@GlobalLock`注解呢? - -因为并不是所有的数据库操作都需要开启全局事务,而开启全局事务是一个比较重的操作,需要向 TC 发起开启全局事务等 RPC 过程,而`@GlobalLock`注解只会在执行过程中查询全局锁是否存在,不会去开启全局事务,因此在不需要全局事务,而又需要检查全局锁避免脏读脏写时,使用`@GlobalLock`注解是一个更加轻量的操作。 - - - -# 如何防止脏写 - -先来看一下使用 Seata AT 模式是怎么产生脏写的: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20211226164628.png) - -*注:分支事务执行过程省略其它过程。* - -业务一开启全局事务,其中包含分支事务A(修改 A)和分支事务 B(修改 B),业务二修改 A,其中业务一执行分支事务 A 先获取本地锁,业务二则等待业务一执行完分支事务 A 之后,获得本地锁修改 A 并入库,业务一在执行分支事务时发生异常了,由于分支事务 A 的数据被业务二修改,导致业务一的全局事务无法回滚。 - -如何防止脏写? - -1、业务二执行时加 `@GlobalTransactional`注解: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20211226210337.png) - -*注:分支事务执行过程省略其它过程。* - -业务二在执行全局事务过程中,分支事务 A 提交前注册分支事务获取全局锁时,发现业务业务一全局锁还没执行完,因此业务二提交不了,抛异常回滚,所以不会发生脏写。 - -2、业务二执行时加 `@GlobalLock`注解: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20211226210502.png) - -*注:分支事务执行过程省略其它过程。* - -与 `@GlobalTransactional`注解效果类似,只不过不需要开启全局事务,只在本地事务提交前,检查全局锁是否存在。 - -2、业务二执行时加 `@GlobalLock` 注解 + `select for update`语句: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20211226172358.png) - -如果加了`select for update`语句,则会在 update 前检查全局锁是否存在,只有当全局锁释放之后,业务二才能开始执行 updateA 操作。 - -如果单单是 transactional,那么就有可能会出现脏写,根本原因是没有 Globallock 注解时,不会检查全局锁,这可能会导致另外一个全局事务回滚时,发现某个分支事务被脏写了。所以加 select for update 也有个好处,就是可以重试。 - -# 如何防止脏读 - -Seata AT 模式的脏读是指在全局事务未提交前,被其它业务读到已提交的分支事务的数据,本质上是Seata默认的全局事务是读未提交。 - -那么怎么避免脏读现象呢? - -业务二查询 A 时加 `@GlobalLock` 注解 + `select for update`语句: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20211226210633.png) - -加`select for update`语句会在执行 SQL 前检查全局锁是否存在,只有当全局锁完成之后,才能继续执行 SQL,这样就防止了脏读。 - - -# 作者简介: - -张乘辉,目前就职于蚂蚁集团,热爱分享技术,微信公众号「后端进阶」作者,技术博客([https://objcoding.com/](https://objcoding.com/))博主,Seata Contributor,GitHub ID:objcoding。 \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-at-mode-design.md b/blog/seata-at-mode-design.md index 866db0f33f..e872b67af1 100644 --- a/blog/seata-at-mode-design.md +++ b/blog/seata-at-mode-design.md @@ -1,123 +1 @@ ---- -title: 分布式事务中间件 Seata 的设计原理 -author: 张乘辉 -keywords: [Seata、分布式事务、AT模式] -description: AT 模式设计原理 -date: 2019/07/11 ---- - -# 前言 - -在微服务架构体系下,我们可以按照业务模块分层设计,单独部署,减轻了服务部署压力,也解耦了业务的耦合,避免了应用逐渐变成一个庞然怪物,从而可以轻松扩展,在某些服务出现故障时也不会影响其它服务的正常运行。总之,微服务在业务的高速发展中带给我们越来越多的优势,但是微服务并不是十全十美,因此不能盲目过度滥用,它有很多不足,而且会给系统带来一定的复杂度,其中伴随而来的分布式事务问题,是微服务架构体系下必然需要处理的一个痛点,也是业界一直关注的一个领域,因此也出现了诸如 CAP 和 BASE 等理论。 - -在今年年初,阿里开源了一个分布式事务中间件,起初起名为 Fescar,后改名为 Seata,在它开源之初,我就知道它肯定要火,因为这是一个解决痛点的开源项目,Seata 一开始就是冲着对业务无侵入与高性能方向走,这正是我们对解决分布式事务问题迫切的需求。因为待过的几家公司,用的都是微服务架构,但是在解决分布式事务的问题上都不太优雅,所以我也在一直关注 Seata 的发展,今天就简要说说它的一些设计上的原理,后续我将会对它的各个模块进行深入源码分析,感兴趣的可以持续关注我的公众号或者博客,不要跟丢。 - - - - -# 分布式事务解决的方案有哪些? - -目前分布式事务解决的方案主要有对业务无入侵和有入侵的方案,无入侵方案主要有基于数据库 XA 协议的两段式提交(2PC)方案,它的优点是对业务代码无入侵,但是它的缺点也是很明显:必须要求数据库对 XA 协议的支持,且由于 XA 协议自身的特点,它会造成事务资源长时间得不到释放,锁定周期长,而且在应用层上面无法干预,因此它性能很差,它的存在相当于七伤拳那样“伤人七分,损己三分”,因此在互联网项目中并不是很流行这种解决方案。 - -为了这个弥补这种方案带来性能低的问题,大佬们又想出了很多种方案来解决,但这无一例外都需要通过在应用层做手脚,即入侵业务的方式,比如很出名的 TCC 方案,基于 TCC 也有很多成熟的框架,如 ByteTCC、tcc-transaction 等。以及基于可靠消息的最终一致性来实现,如 RocketMQ 的事务消息。 - -入侵代码的方案是基于现有情形“迫不得已”才推出的解决方案,实际上它们实现起来非常不优雅,一个事务的调用通常伴随而来的是对该事务接口增加一系列的反向操作,比如 TCC 三段式提交,提交逻辑必然伴随着回滚的逻辑,这样的代码会使得项目非常臃肿,维护成本高。 - - - -# Seata 各模块之间的关系 - -针对上面所说的分布式事务解决方案的痛点,那很显然,我们理想的分布式事务解决方案肯定是性能要好而且要对业务无入侵,业务层上无需关心分布式事务机制的约束,Seata 正是往这个方向发展的,因此它非常值得期待,它将给我们的微服务架构带来质的提升。 - -那 Seata 是怎么做到的呢?下面说说它的各个模块之间的关系。 - -Seata 的设计思路是将一个分布式事务可以理解成一个全局事务,下面挂了若干个分支事务,而一个分支事务是一个满足 ACID 的本地事务,因此我们可以操作分布式事务像操作本地事务一样。 - -Seata 内部定义了 3个模块来处理全局事务和分支事务的关系和处理过程,这三个组件分别是: - -- Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。 -- Transaction Manager (TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。 -- Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。 - -![](https://gitee.com/objcoding/md-picture/raw/master/img/seata.png) - -简要说说整个全局事务的执行步骤: - -1. TM 向 TC 申请开启一个全局事务,TC 创建全局事务后返回全局唯一的 XID,XID 会在全局事务的上下文中传播; -2. RM 向 TC 注册分支事务,该分支事务归属于拥有相同 XID 的全局事务; -3. TM 向 TC 发起全局提交或回滚; -4. TC 调度 XID 下的分支事务完成提交或者回滚。 - - - -# 与 XA 方案有什么不同? - -Seata 的事务提交方式跟 XA 协议的两段式提交在总体上来说基本是一致的,那它们之间有什么不同呢? - -我们都知道 XA 协议它依赖的是数据库层面来保障事务的一致性,也即是说 XA 的各个分支事务是在数据库层面上驱动的,由于 XA 的各个分支事务需要有 XA 的驱动程序,一方面会导致数据库与 XA 驱动耦合,另一方面它会导致各个分支的事务资源锁定周期长,这也是它没有在互联网公司流行的重要因素。 - -基于 XA 协议以上的问题,Seata 另辟蹊径,既然在依赖数据库层会导致这么多问题,那我就从应用层做手脚,这还得从 Seata 的 RM 模块说起,前面也说过 RM 的主要作用了,其实 RM 在内部做了对数据库操作的代理层,如下: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/seata5.png) - -Seata 在数据源做了一层代理层,所以我们使用 Seata 时,我们使用的数据源实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,主要是解析 SQL,把业务数据在更新前后的数据镜像组织成回滚日志,并将 undo log 日志插入 undo_log 表中,保证每条更新数据的业务 sql 都有对应的回滚日志存在。 - -这样做的好处就是,本地事务执行完可以立即释放本地事务锁定的资源,然后向 TC 上报分支状态。当 TM 决议全局提交时,就不需要同步协调处理了,TC 会异步调度各个 RM 分支事务删除对应的 undo log 日志即可,这个步骤非常快速地可以完成;当 TM 决议全局回滚时,RM 收到 TC 发送的回滚请求,RM 通过 XID 找到对应的 undo log 回滚日志,然后执行回滚日志完成回滚操作。 - -![](https://gitee.com/objcoding/md-picture/raw/master/img/seata6.png) - -如上图所示,XA 方案的 RM 是放在数据库层的,它依赖了数据库的 XA 驱动程序。 - -![](https://gitee.com/objcoding/md-picture/raw/master/img/Seata7.png) - -如上图所示,Seata 的 RM 实际上是已中间件的形式放在应用层,不用依赖数据库对协议的支持,完全剥离了分布式事务方案对数据库在协议支持上的要求。 - - - -# 分支事务如何提交和回滚? - -下面详细说说分支事务是如何提交和回滚的: - -- 第一阶段: - - -分支事务利用 RM 模块中对 JDBC 数据源代理,加入了若干流程,对业务 SQL 进行解释,把业务数据在更新前后的数据镜像组织成回滚日志,并生成 undo log 日志,对全局事务锁的检查以及分支事务的注册等,利用本地事务 ACID 特性,将业务 SQL 和 undo log 写入同一个事物中一同提交到数据库中,保证业务 SQL 必定存在相应的回滚日志,最后对分支事务状态向 TC 进行上报。 - -![](https://gitee.com/objcoding/md-picture/raw/master/img/seata2.png) - -- 第二阶段: - - -TM决议全局提交: - -当 TM 决议提交时,就不需要同步协调处理了,TC 会异步调度各个 RM 分支事务删除对应的 undo log 日志即可,这个步骤非常快速地可以完成。这个机制对于性能提升非常关键,我们知道正常的业务运行过程中,事务执行的成功率是非常高的,因此可以直接在本地事务中提交,这步对于提升性能非常显著。 - - - -![](https://gitee.com/objcoding/md-picture/raw/master/img/seata3.png) - - - -TM决议全局回滚: - -当 TM 决议回滚时,RM 收到 TC 发送的回滚请求,RM 通过 XID 找到对应的 undo log 回滚日志,然后利用本地事务 ACID 特性,执行回滚日志完成回滚操作并删除 undo log 日志,最后向 TC 进行回滚结果上报。 - -![](https://gitee.com/objcoding/md-picture/raw/master/img/seata4.png) - -业务对以上所有的流程都无感知,业务完全不关心全局事务的具体提交和回滚,而且最重要的一点是 Seata 将两段式提交的同步协调分解到各个分支事务中了,分支事务与普通的本地事务无任何差异,这意味着我们使用 Seata 后,分布式事务就像使用本地事务一样,完全将数据库层的事务协调机制交给了中间件层 Seata 去做了,这样虽然事务协调搬到应用层了,但是依然可以做到对业务的零侵入,从而剥离了分布式事务方案对数据库在协议支持上的要求,且 Seata 在分支事务完成之后直接释放资源,极大减少了分支事务对资源的锁定时间,完美避免了 XA 协议需要同步协调导致资源锁定时间过长的问题。 - - - -# 其它方案的补充 - -上面说的其实是 Seata 的默认模式,也叫 AT 模式,它是类似于 XA 方案的两段式提交方案,并且是对业务无侵入,但是这种机制依然是需要依赖数据库本地事务的 ACID 特性,有没有发现,我在上面的图中都强调了必须是支持 ACID 特性的关系型数据库,那么问题就来了,非关系型或者不支持 ACID 的数据库就无法使用 Seata 了,别慌,Seata 现阶段为我们准备了另外一种模式,叫 MT 模式,它是一种对业务有入侵的方案,提交回滚等操作需要我们自行定义,业务逻辑需要被分解为 Prepare/Commit/Rollback 3 部分,形成一个 MT 分支,加入全局事务,它存在的意义是为 Seata 触达更多的场景。 - -![](https://gitee.com/objcoding/md-picture/raw/master/img/seata8.png) - -只不过,它不是 Seata “主打”的模式,它的存在仅仅作为补充的方案,从以上官方的发展远景就可以看出来,Seata 的目标是始终是对业务无入侵的方案。 - -*注:本文图片设计参考Seata官方图* - -# 作者简介: - -张乘辉,目前就职于中通科技信息中心技术平台部,担任 Java 工程师,主要负责中通消息平台与全链路压测项目的研发,热爱分享技术,微信公众号「后端进阶」作者,技术博客([https://objcoding.com/](https://objcoding.com/))博主,Seata Contributor,GitHub ID:objcoding。 \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-at-mode-start-rm-tm.md b/blog/seata-at-mode-start-rm-tm.md index bd10c2a841..e872b67af1 100644 --- a/blog/seata-at-mode-start-rm-tm.md +++ b/blog/seata-at-mode-start-rm-tm.md @@ -1,36 +1 @@ ---- -title: Seata 客户端需要同时启动 RM 和 TM 吗? -author: 张乘辉 -keywords: [Seata、分布式事务、AT模式、RM、TM] -description: 关于 Seata 后续优化的一个讨论点 -date: 2019/11/28 ---- - - -在分析启动部分源码时,我发现 GlobalTransactionScanner 会同时启动 RM 和 TM client,但根据 Seata 的设计来看,TM 负责全局事务的操作,如果一个服务中不需要开启全局事务,此时是不需要启动 TM client的,也就是说项目中如果没有全局事务注解,此时是不是就不需要初始化 TM client 了,因为不是每个微服务,都需要 GlobalTransactional,它此时仅仅作为一个 RM client 而已。 - - -于是我着手将 GlobalTransactionScanner 稍微更改了初始化的规则,由于之前 GlobalTransactionScanner 调用 初始化方法是在 InitializingBean 中的 afterPropertiesSet() 方法中进行,afterPropertySet() 仅仅是当前 bean 初始化后被调用,此时无法得知当前 Spring 容器是否有全局事务注解。 - -因此我去掉了 InitializingBean,改成了是实现 ApplicationListener,在实例化 bean 的过程中检查是否有 GlobalTransactional 注解的存在,最后在 Spring 容器初始化完成之后再调用 RM 和 TM client 初始化方法,这时候就可以根据项目是否有用到全局事务注解来决定是否启动 TM client 了。 - -这里附上 PR 地址:[https://github.com/seata/seata/pull/1936](https://github.com/seata/seata/pull/1936) - -随后在 pr 中讨论中得知,目前 Seata 的设计是只有在发起方的 TM 才可以发起 GlobalRollbackRequest,RM 只能发送 BranchReport(false) 上报分支状态个 TC 服务端,无法直接发送 GlobalRollbackRequest 进行全局回滚操作。具体的交互逻辑如下: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191128094250.png) - -那么根据上面的设计模型,自然可以按需启动 TM client 了。 - -但是 Seata 后面的优化迭代中,还需要考虑的一点是: - -当参与方出现异常时,是否可以直接由参与方的 TM client 发起全局回滚?这也就意味着可以缩短分布式事务的周期时间,尽快释放全局锁让其他数据冲突的事务尽早的获取到锁执行。 - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191127202606.png) - - -也就是说在一个全局事务当中,只要有一个 RM client 执行本地事务失败了,直接当前服务的 TM client 发起全局事务回滚,不必要等待发起方的 TM 发起的决议回滚通知了。如果要实现这个优化,那么就需要每个服务都需要同时启动 TM client 和 RM client。 - -# 作者简介: - -张乘辉,目前就职于中通科技信息中心技术平台部,担任 Java 工程师,主要负责中通消息平台与全链路压测项目的研发,热爱分享技术,微信公众号「后端进阶」作者,技术博客([https://objcoding.com/](https://objcoding.com/))博主,Seata Contributor,GitHub ID:objcoding。 \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-at-mode-start.md b/blog/seata-at-mode-start.md index ca8fe7ad57..e872b67af1 100644 --- a/blog/seata-at-mode-start.md +++ b/blog/seata-at-mode-start.md @@ -1,460 +1 @@ ---- -title: Seata AT 模式启动源码分析 -author: 张乘辉 -keywords: [Seata、分布式事务、AT模式] -description: Seata 源码分析系列 -date: 2019/11/27 ---- - - -# 前言 - -从上一篇文章「[分布式事务中间件Seata的设计原理](https://mp.weixin.qq.com/s/Pypkm5C9aLPJHYwcM6tAtA)」讲了下 Seata AT 模式的一些设计原理,从中也知道了 AT 模式的三个角色(RM、TM、TC),接下来我会更新 Seata 源码分析系列文章。今天就来分析 Seata AT 模式在启动的时候都做了哪些操作。 - - - -# 客户端启动逻辑 - -TM 是负责整个全局事务的管理器,因此一个全局事务是由 TM 开启的,TM 有个全局管理类 GlobalTransaction,结构如下: - -io.seata.tm.api.GlobalTransaction - -```java -public interface GlobalTransaction { - - void begin() throws TransactionException; - - void begin(int timeout) throws TransactionException; - - void begin(int timeout, String name) throws TransactionException; - - void commit() throws TransactionException; - - void rollback() throws TransactionException; - - GlobalStatus getStatus() throws TransactionException; - - // ... -} -``` - -可以通过 GlobalTransactionContext 创建一个 GlobalTransaction,然后用 GlobalTransaction 进行全局事务的开启、提交、回滚等操作,因此我们直接用 API 方式使用 Seata AT 模式: - -```java -//init seata; -TMClient.init(applicationId, txServiceGroup); -RMClient.init(applicationId, txServiceGroup); -//trx -GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate(); -try { - tx.begin(60000, "testBiz"); - // 事务处理 - // ... - tx.commit(); -} catch (Exception exx) { - tx.rollback(); - throw exx; -} -``` - -如果每次使用全局事务都这样写,难免会造成代码冗余,我们的项目都是基于 Spring 容器,这时我们可以利用 Spring AOP 的特性,用模板模式把这些冗余代码封装模版里,参考 Mybatis-spring 也是做了这么一件事情,那么接下来我们来分析一下基于 Spring 的项目启动 Seata 并注册全局事务时都做了哪些工作。 - -我们开启一个全局事务是在方法上加上 `@GlobalTransactional`注解,Seata 的 Spring 模块中,有个 GlobalTransactionScanner,它的继承关系如下: - -```java -public class GlobalTransactionScanner extends AbstractAutoProxyCreator implements InitializingBean, ApplicationContextAware, DisposableBean { - // ... -} -``` - -在基于 Spring 项目的启动过程中,对该类会有如下初始化流程: - -![image-20191124155455309](https://gitee.com/objcoding/md-picture/raw/master/img/image-20191124155455309.png) - -InitializingBean 的 afterPropertiesSet() 方法调用了 initClient() 方法: - -io.seata.spring.annotation.GlobalTransactionScanner#initClient - -```java -TMClient.init(applicationId, txServiceGroup); -RMClient.init(applicationId, txServiceGroup); -``` - -对 TM 和 RM 做了初始化操作。 - -- TM 初始化 - -io.seata.tm.TMClient#init - -```java -public static void init(String applicationId, String transactionServiceGroup) { - // 获取 TmRpcClient 实例 - TmRpcClient tmRpcClient = TmRpcClient.getInstance(applicationId, transactionServiceGroup); - // 初始化 TM Client - tmRpcClient.init(); -} -``` - -调用 TmRpcClient.getInstance() 方法会获取一个 TM 客户端实例,在获取过程中,会创建 Netty 客户端配置文件对象,以及创建 messageExecutor 线程池,该线程池用于在处理各种与服务端的消息交互,在创建 TmRpcClient 实例时,创建 ClientBootstrap,用于管理 Netty 服务的启停,以及 ClientChannelManager,它是专门用于管理 Netty 客户端对象池,Seata 的 Netty 部分配合使用了对象池,后面在分析网络模块会讲到。 - -io.seata.core.rpc.netty.AbstractRpcRemotingClient#init - -```java -public void init() { - clientBootstrap.start(); - // 定时尝试连接服务端 - timerExecutor.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - clientChannelManager.reconnect(getTransactionServiceGroup()); - } - }, SCHEDULE_INTERVAL_MILLS, SCHEDULE_INTERVAL_MILLS, TimeUnit.SECONDS); - mergeSendExecutorService = new ThreadPoolExecutor(MAX_MERGE_SEND_THREAD, - MAX_MERGE_SEND_THREAD, - KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue<>(), - new NamedThreadFactory(getThreadPrefix(), MAX_MERGE_SEND_THREAD)); - mergeSendExecutorService.submit(new MergedSendRunnable()); - super.init(); -} -``` - -调用 TM 客户端 init() 方法,最终会启动 netty 客户端(此时还未真正启动,在对象池被调用时才会被真正启动);开启一个定时任务,定时重新发送 RegisterTMRequest(RM 客户端会发送 RegisterRMRequest)请求尝试连接服务端,具体逻辑是在 NettyClientChannelManager 中的 channels 中缓存了客户端 channel,如果此时 channels 不存在获取已过期,那么就会尝试连接服务端以重新获取 channel 并将其缓存到 channels 中;开启一条单独线程,用于处理异步请求发送,这里用得很巧妙,之后在分析网络模块在具体对其进行分析。 - -io.seata.core.rpc.netty.AbstractRpcRemoting#init - -```java -public void init() { - timerExecutor.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - for (Map.Entry entry : futures.entrySet()) { - if (entry.getValue().isTimeout()) { - futures.remove(entry.getKey()); - entry.getValue().setResultMessage(null); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("timeout clear future: {}", entry.getValue().getRequestMessage().getBody()); - } - } - } - - nowMills = System.currentTimeMillis(); - } - }, TIMEOUT_CHECK_INTERNAL, TIMEOUT_CHECK_INTERNAL, TimeUnit.MILLISECONDS); -} -``` - -在 AbstractRpcRemoting 的 init 方法中,又是开启了一个定时任务,该定时任务主要是用于定时清除 futures 已过期的 futrue,futures 是保存发送请求需要返回结果的 future 对象,该对象有个超时时间,过了超时时间就会自动抛异常,因此需要定时清除已过期的 future 对象。 - -- RM 初始化 - -io.seata.rm.RMClient#init - -```java -public static void init(String applicationId, String transactionServiceGroup) { - RmRpcClient rmRpcClient = RmRpcClient.getInstance(applicationId, transactionServiceGroup); - rmRpcClient.setResourceManager(DefaultResourceManager.get()); - rmRpcClient.setClientMessageListener(new RmMessageListener(DefaultRMHandler.get())); - rmRpcClient.init(); -} -``` - - RmRpcClient.getInstance 处理逻辑与 TM 大致相同;ResourceManager 是 RM 资源管理器,负责分支事务的注册、提交、上报、以及回滚操作,以及全局锁的查询操作,DefaultResourceManager 会持有当前所有的 RM 资源管理器,进行统一调用处理,而 get() 方法主要是加载当前的资源管理器,主要用了类似 SPI 的机制,进行灵活加载,如下图,Seata 会扫描 META-INF/services/ 目录下的配置类并进行动态加载。 - -ClientMessageListener 是 RM 消息处理监听器,用于负责处理从 TC 发送过来的指令,并对分支进行分支提交、分支回滚,以及 undo log 删除操作;最后 init 方法跟 TM 逻辑也大体一致;DefaultRMHandler 封装了 RM 分支事务的一些具体操作逻辑。 - -接下来再看看 wrapIfNecessary 方法究竟做了哪些操作。 - -io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary - -```java -protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { - // 判断是否有开启全局事务 - if (disableGlobalTransaction) { - return bean; - } - try { - synchronized (PROXYED_SET) { - if (PROXYED_SET.contains(beanName)) { - return bean; - } - interceptor = null; - //check TCC proxy - if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) { - //TCC interceptor, proxy bean of sofa:reference/dubbo:reference, and LocalTCC - interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName)); - } else { - Class serviceInterface = SpringProxyUtils.findTargetClass(bean); - Class[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean); - - // 判断 bean 中是否有 GlobalTransactional 和 GlobalLock 注解 - if (!existsAnnotation(new Class[]{serviceInterface}) - && !existsAnnotation(interfacesIfJdk)) { - return bean; - } - - if (interceptor == null) { - // 创建代理类 - interceptor = new GlobalTransactionalInterceptor(failureHandlerHook); - } - } - - LOGGER.info("Bean[{}] with name [{}] would use interceptor [{}]", - bean.getClass().getName(), beanName, interceptor.getClass().getName()); - if (!AopUtils.isAopProxy(bean)) { - bean = super.wrapIfNecessary(bean, beanName, cacheKey); - } else { - AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean); - // 执行包装目标对象到代理对象 - Advisor[] advisor = super.buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null)); - for (Advisor avr : advisor) { - advised.addAdvisor(0, avr); - } - } - PROXYED_SET.add(beanName); - return bean; - } - } catch (Exception exx) { - throw new RuntimeException(exx); - } -} -``` - - -GlobalTransactionScanner 继承了 AbstractAutoProxyCreator,用于对 Spring AOP 支持,从代码中可看出,用GlobalTransactionalInterceptor 代替了被 GlobalTransactional 和 GlobalLock 注解的方法。 - -GlobalTransactionalInterceptor 实现了 MethodInterceptor: - -io.seata.spring.annotation.GlobalTransactionalInterceptor#invoke - -```java -public Object invoke(final MethodInvocation methodInvocation) throws Throwable { - Class targetClass = methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null; - Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass); - final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod); - - final GlobalTransactional globalTransactionalAnnotation = getAnnotation(method, GlobalTransactional.class); - final GlobalLock globalLockAnnotation = getAnnotation(method, GlobalLock.class); - if (globalTransactionalAnnotation != null) { - // 全局事务注解 - return handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation); - } else if (globalLockAnnotation != null) { - // 全局锁注解 - return handleGlobalLock(methodInvocation); - } else { - return methodInvocation.proceed(); - } -} -``` - -以上是代理方法执行的逻辑逻辑,其中 handleGlobalTransaction() 方法里面调用了 TransactionalTemplate 模版: - -io.seata.spring.annotation.GlobalTransactionalInterceptor#handleGlobalTransaction - -```java -private Object handleGlobalTransaction(final MethodInvocation methodInvocation, - final GlobalTransactional globalTrxAnno) throws Throwable { - try { - return transactionalTemplate.execute(new TransactionalExecutor() { - @Override - public Object execute() throws Throwable { - return methodInvocation.proceed(); - } - @Override - public TransactionInfo getTransactionInfo() { - // ... - } - }); - } catch (TransactionalExecutor.ExecutionException e) { - // ... - } -} -``` - -handleGlobalTransaction() 方法执行了就是 TransactionalTemplate 模版类的 execute 方法: - -io.seata.tm.api.TransactionalTemplate#execute - -```java -public Object execute(TransactionalExecutor business) throws Throwable { - // 1. get or create a transaction - GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate(); - - // 1.1 get transactionInfo - TransactionInfo txInfo = business.getTransactionInfo(); - if (txInfo == null) { - throw new ShouldNeverHappenException("transactionInfo does not exist"); - } - try { - - // 2. begin transaction - beginTransaction(txInfo, tx); - - Object rs = null; - try { - - // Do Your Business - rs = business.execute(); - - } catch (Throwable ex) { - - // 3.the needed business exception to rollback. - completeTransactionAfterThrowing(txInfo,tx,ex); - throw ex; - } - - // 4. everything is fine, commit. - commitTransaction(tx); - - return rs; - } finally { - //5. clear - triggerAfterCompletion(); - cleanUp(); - } -} -``` - -以上是不是有一种似曾相识的感觉?没错,以上就是我们使用 API 时经常写的冗余代码,现在 Spring 通过代理模式,把这些冗余代码都封装带模版里面了,它将那些冗余代码统统封装起来统一流程处理,并不需要你显示写出来了,有兴趣的也可以去看看 Mybatis-spring 的源码,也是写得非常精彩。 - - - -# 服务端处理逻辑 - -服务端收到客户端的连接,那当然是将其 channel 也缓存起来,前面也说到客户端会发送 RegisterRMRequest/RegisterTMRequest 请求给服务端,服务端收到后会调用 ServerMessageListener 监听器处理: - -io.seata.core.rpc.ServerMessageListener - -```java -public interface ServerMessageListener { - // 处理各种事务,如分支注册、分支提交、分支上报、分支回滚等等 - void onTrxMessage(RpcMessage request, ChannelHandlerContext ctx, ServerMessageSender sender); - // 处理 RM 客户端的注册连接 - void onRegRmMessage(RpcMessage request, ChannelHandlerContext ctx, - ServerMessageSender sender, RegisterCheckAuthHandler checkAuthHandler); - // 处理 TM 客户端的注册连接 - void onRegTmMessage(RpcMessage request, ChannelHandlerContext ctx, - ServerMessageSender sender, RegisterCheckAuthHandler checkAuthHandler); - // 服务端与客户端保持心跳 - void onCheckMessage(RpcMessage request, ChannelHandlerContext ctx, ServerMessageSender sender) - -} -``` - -ChannelManager 是服务端 channel 的管理器,服务端每次和客户端通信,都需要从 ChannelManager 中获取客户端对应的 channel,它用于保存 TM 和 RM 客户端 channel 的缓存结构如下: - -```java -/** - * resourceId -> applicationId -> ip -> port -> RpcContext - */ -private static final ConcurrentMap>>> - RM_CHANNELS = new ConcurrentHashMap>>>(); - -/** - * ip+appname,port - */ -private static final ConcurrentMap> TM_CHANNELS - = new ConcurrentHashMap>(); -``` - -以上的 Map 结构有点复杂: - -RM_CHANNELS: - -1. resourceId 指的是 RM client 的数据库地址; -2. applicationId 指的是 RM client 的服务 Id,比如 springboot 的配置 spring.application.name=account-service 中的 account-service 即是 applicationId; -3. ip 指的是 RM client 服务地址; -4. port 指的是 RM client 服务地址; -5. RpcContext 保存了本次注册请求的信息。 - -TM_CHANNELS: - -1. ip+appname:这里的注释应该是写错了,应该是 appname+ip,即 TM_CHANNELS 的 Map 结构第一个 key 为 appname+ip; -2. port:客户端的端口号。 - -以下是 RM Client 注册逻辑: - -io.seata.core.rpc.ChannelManager#registerRMChannel - -```java -public static void registerRMChannel(RegisterRMRequest resourceManagerRequest, Channel channel) - throws IncompatibleVersionException { - Version.checkVersion(resourceManagerRequest.getVersion()); - // 将 ResourceIds 数据库连接连接信息放入一个set中 - Set dbkeySet = dbKeytoSet(resourceManagerRequest.getResourceIds()); - RpcContext rpcContext; - // 从缓存中判断是否有该channel信息 - if (!IDENTIFIED_CHANNELS.containsKey(channel)) { - // 根据请求注册信息,构建 rpcContext - rpcContext = buildChannelHolder(NettyPoolKey.TransactionRole.RMROLE, resourceManagerRequest.getVersion(), - resourceManagerRequest.getApplicationId(), resourceManagerRequest.getTransactionServiceGroup(), - resourceManagerRequest.getResourceIds(), channel); - // 将 rpcContext 放入缓存中 - rpcContext.holdInIdentifiedChannels(IDENTIFIED_CHANNELS); - } else { - rpcContext = IDENTIFIED_CHANNELS.get(channel); - rpcContext.addResources(dbkeySet); - } - if (null == dbkeySet || dbkeySet.isEmpty()) { return; } - for (String resourceId : dbkeySet) { - String clientIp; - // 将请求信息存入 RM_CHANNELS 中,这里用了 java8 的 computeIfAbsent 方法操作 - ConcurrentMap portMap = RM_CHANNELS.computeIfAbsent(resourceId, resourceIdKey -> new ConcurrentHashMap<>()) - .computeIfAbsent(resourceManagerRequest.getApplicationId(), applicationId -> new ConcurrentHashMap<>()) - .computeIfAbsent(clientIp = getClientIpFromChannel(channel), clientIpKey -> new ConcurrentHashMap<>()); - // 将当前 rpcContext 放入 portMap 中 - rpcContext.holdInResourceManagerChannels(resourceId, portMap); - updateChannelsResource(resourceId, clientIp, resourceManagerRequest.getApplicationId()); - } -} -``` - -从以上代码逻辑能够看出,注册 RM client 主要是将注册请求信息,放入 RM_CHANNELS 缓存中,同时还会从 IDENTIFIED_CHANNELS 中判断本次请求的 channel 是否已验证过,IDENTIFIED_CHANNELS 的结构如下: - -```java -private static final ConcurrentMap IDENTIFIED_CHANNELS - = new ConcurrentHashMap<>(); -``` - -IDENTIFIED_CHANNELS 包含了所有 TM 和 RM 已注册的 channel。 - -以下是 TM 注册逻辑: - -io.seata.core.rpc.ChannelManager#registerTMChannel - -```java -public static void registerTMChannel(RegisterTMRequest request, Channel channel) - throws IncompatibleVersionException { - Version.checkVersion(request.getVersion()); - // 根据请求注册信息,构建 RpcContext - RpcContext rpcContext = buildChannelHolder(NettyPoolKey.TransactionRole.TMROLE, request.getVersion(), - request.getApplicationId(), - request.getTransactionServiceGroup(), - null, channel); - // 将 RpcContext 放入 IDENTIFIED_CHANNELS 缓存中 - rpcContext.holdInIdentifiedChannels(IDENTIFIED_CHANNELS); - // account-service:127.0.0.1:63353 - String clientIdentified = rpcContext.getApplicationId() + Constants.CLIENT_ID_SPLIT_CHAR - + getClientIpFromChannel(channel); - // 将请求信息存入 TM_CHANNELS 缓存中 - TM_CHANNELS.putIfAbsent(clientIdentified, new ConcurrentHashMap()); - // 将上一步创建好的get出来,之后再将rpcContext放入这个map的value中 - ConcurrentMap clientIdentifiedMap = TM_CHANNELS.get(clientIdentified); - rpcContext.holdInClientChannels(clientIdentifiedMap); -} -``` - -TM client 的注册大体类似,把本次注册的信息放入对应的缓存中保存,但比 RM client 的注册逻辑简单一些,主要是 RM client 会涉及分支事务资源的信息,需要注册的信息也会比 TM client 多。 - -以上源码分析基于 0.9.0 版本。 - - -# 作者简介 - -张乘辉,目前就职于中通科技信息中心技术平台部,担任 Java 工程师,主要负责中通消息平台与全链路压测项目的研发,热爱分享技术,微信公众号「后端进阶」作者,技术博客([https://objcoding.com/](https://objcoding.com/))博主,Seata Contributor,GitHub ID:objcoding。 - - - - +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-at-tcc-saga.md b/blog/seata-at-tcc-saga.md index 8c95b86c9b..e872b67af1 100644 --- a/blog/seata-at-tcc-saga.md +++ b/blog/seata-at-tcc-saga.md @@ -1,256 +1 @@ ---- -title: 分布式事务 Seata 及其三种模式详解 -keywords: [Saga,Seata,AT,TCC,一致性,金融,分布式,事务] -description: 着重分享分布式事务产生的背景、理论基础,以及 Seata 分布式事务的原理以及三种模式(AT、TCC、Saga)的分布式事务实现 -author: long187 -date: 2019-08-11 ---- -# 分布式事务 Seata 及其三种模式详解 | Meetup#3 回顾 - -作者:屹远(陈龙),蚂蚁金服分布式事务框架核心研发,Seata Committer。 -
本文根据 8 月 11 日 SOFA Meetup#3 广州站 《分布式事务 Seata 及其三种模式详解》主题分享整理,着重分享分布式事务产生的背景、理论基础,以及 Seata 分布式事务的原理以及三种模式(AT、TCC、Saga)的分布式事务实现。 - -现场回顾视频以及 PPT 见文末链接。 - -![3 分布式事务 Seata 三种模式详解-屹远.jpg](/img/saga/sofameetup3_img/1.jpeg) - - -## 一、分布式事务产生的背景 - - -### 1.1 分布式架构演进之 - 数据库的水平拆分 - -蚂蚁金服的业务数据库起初是单库单表,但随着业务数据规模的快速发展,数据量越来越大,单库单表逐渐成为瓶颈。所以我们对数据库进行了水平拆分,将原单库单表拆分成数据库分片。 - -如下图所示,分库分表之后,原来在一个数据库上就能完成的写操作,可能就会跨多个数据库,这就产生了跨数据库事务问题。 - -![image.png](/img/saga/sofameetup3_img/2.png) - - - -### 1.2 分布式架构演进之 - 业务服务化拆分 - -在业务发展初期,“一块大饼”的单业务系统架构,能满足基本的业务需求。但是随着业务的快速发展,系统的访问量和业务复杂程度都在快速增长,单系统架构逐渐成为业务发展瓶颈,解决业务系统的高耦合、可伸缩问题的需求越来越强烈。 - -如下图所示,蚂蚁金服按照面向服务架构(SOA)的设计原则,将单业务系统拆分成多个业务系统,降低了各系统之间的耦合度,使不同的业务系统专注于自身业务,更有利于业务的发展和系统容量的伸缩。 - -![image.png](/img/saga/sofameetup3_img/3.png) - -业务系统按照服务拆分之后,一个完整的业务往往需要调用多个服务,如何保证多个服务间的数据一致性成为一个难题。 - - - -## 二、分布式事务理论基础 - - -### 2.1 两阶段提交协议 - -![16_16_18__08_13_2019.jpg](/img/saga/sofameetup3_img/4.jpeg) - -两阶段提交协议:事务管理器分两个阶段来协调资源管理器,第一阶段准备资源,也就是预留事务所需的资源,如果每个资源管理器都资源预留成功,则进行第二阶段资源提交,否则协调资源管理器回滚资源。 - - -### 2.2 TCC - -![16_16_51__08_13_2019.jpg](/img/saga/sofameetup3_img/5.jpeg) - -TCC(Try-Confirm-Cancel) 实际上是服务化的两阶段提交协议,业务开发者需要实现这三个服务接口,第一阶段服务由业务代码编排来调用 Try 接口进行资源预留,所有参与者的 Try 接口都成功了,事务管理器会提交事务,并调用每个参与者的 Confirm 接口真正提交业务操作,否则调用每个参与者的 Cancel 接口回滚事务。 - - -### 2.3 Saga - -![3 分布式事务 Seata 三种模式详解-屹远-9.jpg](/img/saga/sofameetup3_img/6.jpeg) - -Saga 是一种补偿协议,在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。 - -分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。 - -Saga 理论出自 Hector & Kenneth 1987发表的论文 Sagas。
-
Saga 正向服务与补偿服务也需要业务开发者实现。 - - -## 三、Seata 及其三种模式详解 - - -### 3.1 分布式事务 Seata 介绍 - -Seata(Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架)是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。Seata 开源半年左右,目前已经有超过 1.1 万 star,社区非常活跃。我们热忱欢迎大家参与到 Seata 社区建设中,一同将 Seata 打造成开源分布式事务标杆产品。 - -Seata:[https://](https://github.com/seata/seata)[github.com/seata/seata](https://github.com/seata/seata)
-
![image.png](/img/saga/sofameetup3_img/7.png) - - -### 3.2 分布式事务 Seata 产品模块 - -如下图所示,Seata 中有三大模块,分别是 TM、RM 和 TC。 其中 TM 和 RM 是作为 Seata 的客户端与业务系统集成在一起,TC 作为 Seata 的服务端独立部署。 - -![image.png](/img/saga/sofameetup3_img/8.png) - -在 Seata 中,分布式事务的执行流程: - -- TM 开启分布式事务(TM 向 TC 注册全局事务记录); -- 按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 ); -- TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务); -- TC 汇总事务信息,决定分布式事务是提交还是回滚; -- TC 通知所有 RM 提交/回滚 资源,事务二阶段结束; - - -### 3.3 分布式事务 Seata 解决方案 - -Seata 会有 4 种分布式事务解决方案,分别是 AT 模式、TCC 模式、Saga 模式和 XA 模式。 - -![15_49_23__08_13_2019.jpg](/img/saga/sofameetup3_img/9.jpeg)
- - -#### 2.3.1 AT 模式 - -今年 1 月份,Seata 开源了 AT 模式。AT 模式是一种无侵入的分布式事务解决方案。在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。 - -![image.png](/img/saga/sofameetup3_img/10.png)
- - -##### AT 模式如何做到对业务的无侵入 : - -- 一阶段: - -在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。 - -![图片3.png](/img/saga/sofameetup3_img/11.png) - -- 二阶段提交: - -二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。 - -![图片4.png](/img/saga/sofameetup3_img/12.png) - -- 二阶段回滚: - -二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。 - -![图片5.png](/img/saga/sofameetup3_img/13.png) - -AT 模式的一阶段、二阶段提交和回滚均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。 - - -#### 2.3.2 TCC 模式 - -2019 年 3 月份,Seata 开源了 TCC 模式,该模式由蚂蚁金服贡献。TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。 - -![图片6.png](/img/saga/sofameetup3_img/14.png) - -TCC 三个方法描述: - -- Try:资源的检测和预留; -- Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功; -- Cancel:预留资源释放; - -**蚂蚁金服在 TCC 的实践经验**
**
![16_48_02__08_13_2019.jpg](/img/saga/sofameetup3_img/15.jpeg) - -**1 TCC 设计 - 业务模型分 2 阶段设计:** - -用户接入 TCC ,最重要的是考虑如何将自己的业务模型拆成两阶段来实现。 - -以“扣钱”场景为例,在接入 TCC 前,对 A 账户的扣钱,只需一条更新账户余额的 SQL 便能完成;但是在接入 TCC 之后,用户就需要考虑如何将原来一步就能完成的扣钱操作,拆成两阶段,实现成三个方法,并且保证一阶段 Try  成功的话 二阶段 Confirm 一定能成功。 - -![图片7.png](/img/saga/sofameetup3_img/16.png) - -如上图所示, - -Try 方法作为一阶段准备方法,需要做资源的检查和预留。在扣钱场景下,Try 要做的事情是就是检查账户余额是否充足,预留转账资金,预留的方式就是冻结 A 账户的 转账资金。Try 方法执行之后,账号 A 余额虽然还是 100,但是其中 30 元已经被冻结了,不能被其他事务使用。 - -二阶段 Confirm 方法执行真正的扣钱操作。Confirm 会使用 Try 阶段冻结的资金,执行账号扣款。Confirm 方法执行之后,账号 A 在一阶段中冻结的 30 元已经被扣除,账号 A 余额变成 70 元 。 - -如果二阶段是回滚的话,就需要在 Cancel 方法内释放一阶段 Try 冻结的 30 元,使账号 A 的回到初始状态,100 元全部可用。 - -用户接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方法,并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。 - -**2 TCC 设计 - 允许空回滚:**
**
![16_51_44__08_13_2019.jpg](/img/saga/sofameetup3_img/17.jpeg) - -Cancel 接口设计时需要允许空回滚。在 Try 接口因为丢包时没有收到,事务管理器会触发回滚,这时会触发 Cancel 接口,这时 Cancel 执行时发现没有对应的事务 xid 或主键时,需要返回回滚成功。让事务服务管理器认为已回滚,否则会不断重试,而 Cancel 又没有对应的业务数据可以进行回滚。 - -**3 TCC 设计 - 防悬挂控制:**
**
![16_51_56__08_13_2019.jpg](/img/saga/sofameetup3_img/18.jpeg) - -悬挂的意思是:Cancel 比 Try 接口先执行,出现的原因是 Try 由于网络拥堵而超时,事务管理器生成回滚,触发 Cancel 接口,而最终又收到了 Try 接口调用,但是 Cancel 比 Try 先到。按照前面允许空回滚的逻辑,回滚会返回成功,事务管理器认为事务已回滚成功,则此时的 Try 接口不应该执行,否则会产生数据不一致,所以我们在 Cancel 空回滚返回成功之前先记录该条事务 xid 或业务主键,标识这条记录已经回滚过,Try 接口先检查这条事务xid或业务主键如果已经标记为回滚成功过,则不执行 Try 的业务操作。 - -**4 TCC 设计 - 幂等控制:**
**
![16_52_07__08_13_2019.jpg](/img/saga/sofameetup3_img/19.jpeg) - -幂等性的意思是:对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。因为网络抖动或拥堵可能会超时,事务管理器会对资源进行重试操作,所以很可能一个业务操作会被重复调用,为了不因为重复调用而多次占用资源,需要对服务设计时进行幂等控制,通常我们可以用事务 xid 或业务主键判重来控制。 - - -#### 2.3.3 Saga 模式 - -Saga 模式是 Seata 即将开源的长事务解决方案,将由蚂蚁金服主要贡献。在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。 - -分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会去退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。 - -![图片8.png](/img/saga/sofameetup3_img/20.png) - - -Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。 - -**1 Saga 模式使用场景**
**
![16_44_58__08_13_2019.jpg](/img/saga/sofameetup3_img/21.jpeg) - -Saga 模式适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁、长流程情况下可以保证性能。 - -事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,可以使用 Saga 模式。 - -Saga模式的优势是: - -- 一阶段提交本地数据库事务,无锁,高性能; -- 参与者可以采用事务驱动异步执行,高吞吐; -- 补偿服务即正向服务的“反向”,易于理解,易于实现; - -缺点:Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔离性。后续会讲到对于缺乏隔离性的应对措施。
**2 基于状态机引擎的 Saga 实现** - -![17_13_19__08_13_2019.jpg](/img/saga/sofameetup3_img/22.png) - -目前 Saga 的实现一般有两种,一种是通过事件驱动架构实现,一种是基于注解加拦截器拦截业务的正向服务实现。Seata 目前是采用事件驱动的机制来实现的,Seata 实现了一个状态机,可以编排服务的调用流程及正向服务的补偿服务,生成一个 json 文件定义的状态图,状态机引擎驱动到这个图的运行,当发生异常的时候状态机触发回滚,逐个执行补偿服务。当然在什么情况下触发回滚用户是可以自定义决定的。该状态机可以实现服务编排的需求,它支持单项选择、并发、异步、子状态机调用、参数转换、参数映射、服务执行状态判断、异常捕获等功能。 - -**3 状态机引擎原理**
- -![16_45_32__08_13_2019.jpg](/img/saga/sofameetup3_img/23.png) - -该状态机引擎的基本原理是,它基于事件驱动架构,每个步骤都是异步执行的,步骤与步骤之间通过事件队列流转,
极大的提高系统吞吐量。每个步骤执行时会记录事务日志,用于出现异常时回滚时使用,事务日志会记录在与业务表所在的数据库内,提高性能。 - -**4 状态机引擎设计** - -![16_45_46__08_13_2019.jpg](/img/saga/sofameetup3_img/24.jpeg) - -该状态机引擎分成了三层架构的设计,最底层是“事件驱动”层,实现了 EventBus 和消费事件的线程池,是一个 Pub-Sub 的架构。第二层是“流程控制器”层,它实现了一个极简的流程引擎框架,它驱动一个“空”的流程执行,“空”的意思是指它不关心流程节点做什么事情,它只执行每个节点的 process 方法,然后执行 route 方法流转到下一个节点。这是一个通用框架,基于这两层,开发者可以实现任何流程引擎。最上层是“状态机引擎”层,它实现了每种状态节点的“行为”及“路由”逻辑代码,提供 API 和状态图仓库,同时还有一些其它组件,比如表达式语言、逻辑计算器、流水生成器、拦截器、配置管理、事务日志记录等。 - -**5 Saga 服务设计经验** - -和TCC类似,Saga的正向服务与反向服务也需求遵循以下设计原则: - -**1)Saga 服务设计 - 允许空补偿**
**
![16_52_22__08_13_2019.jpg](/img/saga/sofameetup3_img/25.jpeg) - -**2)Saga 服务设计 - 防悬挂控制**
**
![16_52_52__08_13_2019.jpg](/img/saga/sofameetup3_img/26.jpeg) - -**3)Saga 服务设计 - 幂等控制**
**
![3 分布式事务 Seata 三种模式详解-屹远-31.jpg](/img/saga/sofameetup3_img/27.jpeg) - -**4)Saga 设计 - 自定义事务恢复策略**
**
![16_53_07__08_13_2019.jpg](/img/saga/sofameetup3_img/28.jpeg) - -前面讲到 Saga 模式不保证事务的隔离性,在极端情况下可能出现脏写。比如在分布式事务未提交的情况下,前一个服务的数据被修改了,而后面的服务发生了异常需要进行回滚,可能由于前面服务的数据被修改后无法进行补偿操作。这时的一种处理办法可以是“重试”继续往前完成这个分布式事务。由于整个业务流程是由状态机编排的,即使是事后恢复也可以继续往前重试。所以用户可以根据业务特点配置该流程的事务处理策略是优先“回滚”还是“重试”,当事务超时的时候,Server 端会根据这个策略不断进行重试。 - -由于 Saga 不保证隔离性,所以我们在业务设计的时候需要做到“宁可长款,不可短款”的原则,长款是指在出现差错的时候站在我方的角度钱多了的情况,钱少了则是短款,因为如果长款可以给客户退款,而短款则可能钱追不回来了,也就是说在业务设计的时候,一定是先扣客户帐再入帐,如果因为隔离性问题造成覆盖更新,也不会出现钱少了的情况。 - -**6 基于注解和拦截器的 Saga 实现**
**
![17_13_37__08_13_2019.jpg](/img/saga/sofameetup3_img/29.jpeg) - -还有一种 Saga 的实现是基于注解+拦截器的实现,Seata 目前没有实现,可以看上面的伪代码来理解一下,one 方法上定义了 @SagaCompensable 的注解,用于定义 one 方法的补偿方法是 compensateOne 方法。然后在业务流程代码 processA 方法上定义 @SagaTransactional 注解,启动 Saga 分布式事务,通过拦截器拦截每个正向方法当出现异常的时候触发回滚操作,调用正向方法的补偿方法。 - -**7 两种 Saga 实现优劣对比** - -两种 Saga 的实现各有又缺点,下面表格是一个对比: - -![17_13_49__08_13_2019.jpg](/img/saga/sofameetup3_img/30.jpeg) - -状态机引擎的最大优势是可以通过事件驱动的方法异步执行提高系统吞吐,可以实现服务编排需求,在 Saga 模式缺乏隔离性的情况下,可以多一种“向前重试”的事情恢复策略。注解加拦截器的的最大优势是,开发简单、学习成本低。 - - -## 总结 - -本文先回顾了分布式事务产生的背景及理论基础,然后重点讲解了 Seata 分布式事务的原理以及三种模式(AT、TCC、Saga)的分布式事务实现。 - -Seata 的定位是分布式事全场景解决方案,未来还会有 XA 模式的分布式事务实现,每种模式都有它的适用场景,AT 模式是无侵入的分布式事务解决方案,适用于不希望对业务进行改造的场景,几乎0学习成本。TCC 模式是高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景。Saga 模式是长事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成层业务系统。事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,也可以使用 Saga 模式。 - -本次分享的视频回顾以及PPT 查看地址:[https://tech.antfin.com/community/activities/779/review](https://tech.antfin.com/community/activities/779/review) +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-client-start-analysis-01.md b/blog/seata-client-start-analysis-01.md index 4049955119..e872b67af1 100644 --- a/blog/seata-client-start-analysis-01.md +++ b/blog/seata-client-start-analysis-01.md @@ -1,158 +1 @@ ---- -layout: post -comments: true -title: Seata应用侧启动过程剖析——RM & TM如何与TC建立连接 -date: 2021-02-28 21:08:00 -author: "booogu" -catalog: true -tags: - - Seata ---- - -> “刚上手Seata,对其各个模块了解还不够深入?
-想深入研究Seata源码,却还未付诸实践?
-想探究下在集成Seata后,自己的应用在启动过程中“偷偷”干了些啥?
-想学习Seata作为一款优秀开源框架蕴含的设计理念和最佳实践?
-如果你有上述任何想法之一,那么今天这篇文章,就是为你量身打造的~ - -## 前言 -看过官网README的第一张图片的同学都应该清楚,Seata协调分布式事务的原理便在于通过其**协调器侧**的TC,来与**应用侧**的TM、RM进行各种通信与交互,来保证分布式事务中,多个事务参与者的数据一致性。那么Seata的协调器侧与应用侧之间,是如何建立连接并进行通信的呢? - -没错,答案就是Netty,Netty作为一款高性能的RPC通信框架,保证了TC与RM之间的高效通信,关于Netty的详细介绍,本文不再展开,今天我们探究的重点,在于**应用侧在启动过程中,如何通过一系列Seata关键模块之间的协作(如RPC、Config/Registry Center等),来建立与协调器侧之间的通信** - -## 从GlobalTransactionScanner说起 -我们知道Seata提供了多个开发期注解,比如用于开启分布式事务的@GlobalTransactional、用于声明TCC两阶段服务的@TwoPhraseBusinessAction等,它们都是基于Spring AOP机制,对使用了注解的Bean方法分配对应的拦截器进行增强,来完成对应的处理逻辑。而GlobalTransactionScanner这个Spring Bean,就承载着为各个注解分配对应的拦截器的职责,从其Scanner的命名,我们也不难推断出,它是为了在Spring应用启动过程中,对与全局事务(GlobalTransactionScanner)相关的Bean进行扫描、处理的。 - -除此之外,应用侧RPC客户端(TMClient、RMClient)初始化、与TC建立连接的流程,也是在GlobalTransactionScanner#afterPropertiesSet()中发起的: - -````js - /** - * package:io.seata.spring.annotation - * class:GlobalTransactionScanner - */ - @Override - public void afterPropertiesSet() { - if (disableGlobalTransaction) { - if (LOGGER.isInfoEnabled()) { - LOGGER.info("Global transaction is disabled."); - } - return; - } - //在Bean属性初始化之后,执行TM、RM的初始化 - initClient(); - - } -```` - -## RM & TM 的初始化与连接过程 -这里,我们以RMClient.init()为例说明,TMClient的初始化过程亦同理。 -### 类关系的设计 -查看RMClient#init()的源码,我们发现,RMClient先**构造**了一个RmNettyRemotingClient,然后执行其**初始化**init()方法。而RmNettyRemotingClient的**构造器**和**初始化**方法,都会逐层调用父类的构造器与初始化方法 - -```js - /** - * RMClient的初始化逻辑 - * package:io.seata.rm - * class:RMClient - */ - public static void init(String applicationId, String transactionServiceGroup) { - //① 首先从RmNettyRemotingClient类开始,依次调用父类的构造器 - RmNettyRemotingClient rmNettyRemotingClient = RmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup); - rmNettyRemotingClient.setResourceManager(DefaultResourceManager.get()); - rmNettyRemotingClient.setTransactionMessageHandler(DefaultRMHandler.get()); - //② 然后从RmNettyRemotingClient类开始,依次调用父类的init() - rmNettyRemotingClient.init(); - } -``` -上述RMClient系列各类之间的关系以及调用构造器和init()初始化方法的过程如下图示意: -![RMClient.init简化版流程与主要类之间的关系](http://booogu.top/img/in-post/rmclient_relation.jpg) - - -那么为何要将RMClient设计成这样较为复杂的继承关系呢?其实是为了将各层的职责、边界划分清楚,使得各层可以专注于特定逻辑处理,实现更好的扩展性,这部分的详细设计思路,可参考Seata RPC模块重构PR的操刀者乘辉兄的文章[Seata-RPC重构之路](https://mp.weixin.qq.com/s/PCSZ4a8cgmyZNhbUrO-BZQ)) - -### 初始化的完整流程 -各类的构造器与初始化方法中的主要逻辑,大家可以借助下面这个能表意的序列图来梳理下,此图大家也可先跳过不看,在下面我们分析过几个重点类后,再回头来看这些类是何时登场、如何交互的协作的。 -![RMClient的初始化流程](http://booogu.top/img/in-post/rmclient_initialization.png) - -### 抓住核心——Channel的创建 -首先我们需要知道,应用侧与协调器侧的通信是借助Netty的Channel(网络通道)来完成的,因此**通信过程的关键在于Channel的创建**,在Seata中,通过池化的方式(借助了common-pool中的对象池)方式来创建、管理Channel。 - -这里我们有必要简要介绍下对象池的简单概念及其在Seata中的实现: -涉及到的common-pool中的主要类: -* **GenericKeydObjectPool**:KV泛型对象池,提供对所有对象的存取管理,而对象的创建由其内部的工厂类来完成 -* **KeyedPoolableObjectFactory**:KV泛型对象工厂,负责池化对象的创建,被对象池持有 - -涉及到的Seata中对象池实现相关的主要类: -* 首先,被池化管理的对象就是**Channel**,对应common-pool中的泛型V -* **NettyPoolKey**:Channel对应的Key,对应common-pool中的泛型K,NettyPoolKey主要包含两个信息: - - *address*:创建Channel时,对应的TC Server地址 - - *message*:创建Channel时,向TC Server发送的RPC消息体 -* **GenericKeydObjectPool**:Channel对象池 -* **NettyPoolableFactory**:创建Channel的工厂类 - - -认识了上述对象池相关的主要类之后,我们再来看看Seata中涉及Channel管理以及与RPC相关的几个主要类: - -* NettyClientChannelManager: - - 持有Channel对象池 - - 与Channel对象池交互,对应用侧Channel进行管理(获取、释放、销毁、缓存等) -* RpcClientBootstrap:RPC客户端核心引导类,持有Netty框架的Bootstrap对象,具备启停能力;具有根据连接地址来获取新Channel的能力,供Channel工厂类调用 -* AbstractNettyRemotingClient: - - 初始化并持有RpcClientBootstrap - - 应用侧Netty客户端的顶层抽象,抽象了应用侧RM/TM取得各自Channel对应的NettyPoolKey的能力,供NettyClientChannelManager调用 - - 初始化NettyPoolableFactory - -了解上述概念后,我们可以把Seata中创建Channel的过程简化如下: -![创建Channel对象过程](http://booogu.top/img/in-post/create_channel.jpg) - -看到这里,大家可以回过头再看看上面的**RMClient的初始化序列图**,应该会对图中各类的职责、关系,以及整个初始化过程的意图有一个比较清晰的理解了。 - -### 建立连接的时机与流程 -那么,RMClient是何时与Server建立连接的呢? - -在RMClient初始化的过程中,大家会发现,很多init()方法都设定了一些定时任务,而Seata应用侧与协调器的重连(连接)机制,就是通过定时任务来实现的: - -```js - /** - * package:io.seata.core.rpcn.netty - * class:AbstractNettyRemotingClient - */ - public void init() { - //设置定时器,定时重连TC Server - timerExecutor.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - clientChannelManager.reconnect(getTransactionServiceGroup()); - } - }, SCHEDULE_DELAY_MILLS, SCHEDULE_INTERVAL_MILLS, TimeUnit.MILLISECONDS); - if (NettyClientConfig.isEnableClientBatchSendRequest()) { - mergeSendExecutorService = new ThreadPoolExecutor(MAX_MERGE_SEND_THREAD, - MAX_MERGE_SEND_THREAD, - KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue<>(), - new NamedThreadFactory(getThreadPrefix(), MAX_MERGE_SEND_THREAD)); - mergeSendExecutorService.submit(new MergedSendRunnable()); - } - super.init(); - clientBootstrap.start(); - } -``` - - -我们通过跟踪一次reconnect的执行,看看上面探究的几个类之间是如何协作,完成RMClient与TC的连接的(实际上首次连接可能发生在registerResource的过程中,但流程一致) -![RMClient与TC Server连接过程](http://booogu.top/img/in-post/rmclient_connect_tcserver.png) - -这个图中,大家可以重点关注这几个点: -* NettyClientChannelManager执行具体AbstractNettyRemotingClient中,获取NettyPoolKey的回调函数(getPoolKeyFunction()):应用侧的不同Client(RMClient与TMClient),在创建Channel时使用的Key不同,使**两者在重连TC Server时,发送的注册消息不同**,这也是由两者在Seata中扮演的角色不同而决定的: - - TMClient:扮演事务管理器角色,创建Channel时,仅向TC发送TM注册请求(RegisterTMRequest)即可 - - RMClient:扮演资源管理器角色,需要管理应用侧所有的事务资源,因此在创建Channel时,需要在发送RM注册请求(RegesterRMRequest)前,获取应用侧所有事务资源(Resource)信息,注册至TC Server -* 在Channel对象工厂NettyPoolableFactory的makeObject(制造Channel)方法中,使用NettyPoolKey中的两项信息,完成了两项任务: - - 使用NettyPoolKey的address创建新的Channel - - 使用NettyPoolKey的message以及新的Channel向TC Server发送注册请求,这就是Client向TC Server的连接(首次执行)或重连(非首次,由定时任务驱动执行)请求 - -以上内容,就是关于Seata应用侧的初始化及其与TC Server协调器侧建立连接的全过程分析。 - -更深层次的细节,建议大家再根据本文梳理的脉络和提到的几个重点,细致地阅读下源码,相信定会有更深层次的理解和全新的收获! - -> 后记:考虑到篇幅以及保持一篇源码分析文章较为合适的信息量,本文前言中所说的**配置、注册等模块协作配合**并没有在文章中展开和体现。
-在下篇源码剖析中,我会以**配置中心**和**注册中心**为重点,为大家分析,在RMClient/TM Client与TC Server建立连接之前,Seata应用侧是**如何通过服务发现**找到TC Server、如何**从配置模块获取各种信息**的。 \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-client-start-analysis-02.md b/blog/seata-client-start-analysis-02.md index eb9ca32248..e872b67af1 100644 --- a/blog/seata-client-start-analysis-02.md +++ b/blog/seata-client-start-analysis-02.md @@ -1,302 +1 @@ ---- -layout: post -comments: true -title: Seata应用侧启动过程剖析——注册中心与配置中心模块 -date: 2021-03-04 1:35:01 -author: "booogu" -catalog: true -tags: - - Seata ---- - -> “刚上手Seata,对其各个模块了解还不够深入?
-想深入研究Seata源码,却还未付诸实践?
-想探究下在集成Seata后,自己的应用在启动过程中“偷偷”干了些啥?
-想学习Seata作为一款优秀开源框架蕴含的设计理念和最佳实践?
-如果你有上述任何想法之一,那么今天这篇文章,就是为你量身打造的~ - -## 前言 -在Seata的应用侧(RM、TM)启动过程中,首先要做的就是与协调器侧(TC)建立通信,这是Seata能够完成分布式事务协调的前提,那么Seata在完成应用侧初始化以及与TC建立连接的过程中,是**如何找到TC事务协调器的集群和地址**的?又是**如何从配置模块中获取各种配置信息**的呢?这正是本文要探究的重点。 - -## 给个限定 -Seata作为一款中间件级的底层组件,是很谨慎引入第三方框架具体实现的,感兴趣的同学可以深入了解下Seata的SPI机制,看看Seata是如何通过大量扩展点(Extension),来将依赖组件的具体实现倒置出去,转而依赖抽象接口的,同时,Seata为了更好地融入微服务、云原生等流行架构所衍生出来的生态中,也基于SPI机制对多款主流的微服务框架、注册中心、配置中心以及Java开发框架界“扛把子”——SpringBoot等做了主动集成,在保证微内核架构、松耦合、可扩展的同时,又可以很好地与各类组件“打成一片”,使得采用了各种技术栈的环境都可以比较方便地引入Seata。 - -本文为了贴近大家**刚引入Seata试用时**的场景,在以下介绍中,选择**应用侧**的限定条件如下:使用**File(文件)作为配置中心与注册中心**,并基于**SpringBoot**启动。 - -有了这个限定条件,接下来就让我们深入Seata源码,一探究竟吧。 - -## 多模块交替协作的RM/TM初始化过程 -在[ Seata客户端启动过程剖析(一)](http://booogu.top/2021/02/28/seata-client-start-analysis-01/)中,我们分析了Seata应用侧TM与RM的初始化、以及应用侧如何创建Netty Channel并向TC Server发送注册请求的过程。除此之外,在RM初始化过程中,Seata的其他多个模块(注册中心、配置中心、负载均衡)也都纷纷登场,相互协作,共同完成了连接TC Server的过程。 - -当执行Client重连TC Server的方法:NettyClientChannelManager.Channreconnect()时,首先需要根据当前的**事务分组**获取可用的TC Server地址列表: -```js - /** - * NettyClientChannelManager.reconnect() - * Reconnect to remote server of current transaction service group. - * - * @param transactionServiceGroup transaction service group - */ - void reconnect(String transactionServiceGroup) { - List availList = null; - try { - //从注册中心中获取可用的TC Server地址 - availList = getAvailServerList(transactionServiceGroup); - } catch (Exception e) { - LOGGER.error("Failed to get available servers: {}", e.getMessage(), e); - return; - } - //以下代码略 - } -``` - -关于事务分组的详细概念介绍,大家可以参考官方文档[事务分组介绍](https://seata.io/zh-cn/docs/user/txgroup/transaction-group.html)。这里简单介绍一下: -- 每个Seata应用侧的RM、TM,都具有一个**事务分组**名 -- 每个Seata协调器侧的TC,都具有一个**集群名**和**地址** -应用侧连接协调器侧时,经历如下两步: -- 通过事务分组的名称,从配置中获取到该应用侧对应的TC集群名 -- 通过集群名称,可以从注册中心中获取TC集群的地址列表 -以上概念、关系与过程,如下图所示: -![Seata事务分组与建立连接的关系](http://booogu.top/img/in-post/TXGroup_Group_Relation.jpg) - -### 从**注册中心**获取TC Server集群地址 -了解RM/TC连接TC时涉及的主要概念与步骤后,我们继续探究getAvailServerList方法: -```js - private List getAvailServerList(String transactionServiceGroup) throws Exception { - //① 使用注册中心工厂,获取注册中心实例 - //② 调用注册中心的查找方法lookUp(),根据事务分组名称获取TC集群中可用Server的地址列表 - List availInetSocketAddressList = RegistryFactory.getInstance().lookup(transactionServiceGroup); - if (CollectionUtils.isEmpty(availInetSocketAddressList)) { - return Collections.emptyList(); - } - - return availInetSocketAddressList.stream() - .map(NetUtil::toStringAddress) - .collect(Collectors.toList()); - } -``` -#### 用哪个注册中心?**Seata元配置文件**给出答案 -上面已提到,Seata支持多种注册中心的实现,那么,Seata首先需要从一个地方先获取到“注册中心的类型”这个信息。 - -从哪里获取呢?Seata设计了一个“配置文件”用于存放其框架内所用组件的一些基本信息,我更愿意称这个配置文件为 **『元配置文件』**,这是因为它包含的信息,其实是“配置的配置”,也即“元”的概念,大家可以对比数据库表中的信息,和数据库表本身结构的信息(表数据和表元数据)来理解。 - -我们可以把注册中心、配置中心中的信息,都看做是**配置信息本身**,而这些**配置信息的配置**是什么?这些信息,就包含在Seata的元配置文件中。实际上,『元配置文件』中只包含**两类信息**: -- 一是注册中心的类型:registry.type,以及该类型注册中心的一些基本信息,比如当注册中心类型为文件时,元配置文件中存放了文件的名字信息;当注册中心类型是Nacos时,元配置文件中则存放着Nacos的地址、命名空间、集群名等信息 -- 二是配置中心的类型:config.type,以及该类型配置中心的一些基本信息,比如当配置中心为文件时,元配置文件中存放了文件的名字信息;当注册中心类型为Consul时,元配置文件中存放了Consul的地址信息 - -Seata的元配置文件支持Yaml、Properties等多种格式,而且可以集成到SpringBoot的application.yaml文件中(使用seata-spring-boot-starter即可),方便与SpringBoot集成。 - -Seata中自带的默认元配置文件是registry.conf,当我们采用文件作为注册与配置中心时,registry.conf中的内容设置如下: -```js -registry { - # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa - type = "file" - file { - name = "file.conf" - } -} - -config { - # file、nacos 、apollo、zk、consul、etcd3 - type = "file" - file { - name = "file.conf" - } -} -``` -在如下源码中,我们可以发现,Seata使用的注册中心的类型,是从ConfigurationFactory.CURRENT_FILE_INSTANCE中获取的,而这个CURRENT_FILE_INSTANCE,就是我们所说的,Seata**元配置文件的实例** -```js - //在getInstance()中,调用buildRegistryService,构建具体的注册中心实例 - public static RegistryService getInstance() { - if (instance == null) { - synchronized (RegistryFactory.class) { - if (instance == null) { - instance = buildRegistryService(); - } - } - } - return instance; - } - - private static RegistryService buildRegistryService() { - RegistryType registryType; - //获取注册中心类型 - String registryTypeName = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig( - ConfigurationKeys.FILE_ROOT_REGISTRY + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR - + ConfigurationKeys.FILE_ROOT_TYPE); - try { - registryType = RegistryType.getType(registryTypeName); - } catch (Exception exx) { - throw new NotSupportYetException("not support registry type: " + registryTypeName); - } - if (RegistryType.File == registryType) { - return FileRegistryServiceImpl.getInstance(); - } else { - //根据注册中心类型,使用SPI的方式加载注册中心的实例 - return EnhancedServiceLoader.load(RegistryProvider.class, Objects.requireNonNull(registryType).name()).provide(); - } - } -``` -我们来看一下元配置文件的初始化过程,当首次获取静态字段CURRENT_FILE_INSTANCE时,触发ConfigurationFactory类的初始化: -```js - //ConfigurationFactory类的静态块 - static { - load(); - } - - /** - * load()方法中,加载Seata的元配置文件 - */ - private static void load() { - //元配置文件的名称,支持通过系统变量、环境变量扩展 - String seataConfigName = System.getProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME); - if (seataConfigName == null) { - seataConfigName = System.getenv(ENV_SEATA_CONFIG_NAME); - } - if (seataConfigName == null) { - seataConfigName = REGISTRY_CONF_DEFAULT; - } - String envValue = System.getProperty(ENV_PROPERTY_KEY); - if (envValue == null) { - envValue = System.getenv(ENV_SYSTEM_KEY); - } - //根据元配置文件名称,创建一个实现了Configuration接口的文件配置实例 - Configuration configuration = (envValue == null) ? new FileConfiguration(seataConfigName, - false) : new FileConfiguration(seataConfigName + "-" + envValue, false); - Configuration extConfiguration = null; - //通过SPI加载,来判断是否存在扩展配置提供者 - //当应用侧使用seata-spring-boot-starer时,将通过SpringBootConfigurationProvider作为扩展配置提供者,这时当获取元配置项时,将不再从file.conf(默认)中获取,而是从application.properties/application.yaml中获取 - try { - //通过ExtConfigurationProvider的provide方法,将原有的Configuration实例替换为扩展配置的实例 - extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); - if (LOGGER.isInfoEnabled()) { - LOGGER.info("load Configuration:{}", extConfiguration == null ? configuration.getClass().getSimpleName() - : extConfiguration.getClass().getSimpleName()); - } - } catch (EnhancedServiceNotFoundException ignore) { - - } catch (Exception e) { - LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e); - } - //存在扩展配置,则返回扩展配置实例,否则返回文件配置实例 - CURRENT_FILE_INSTANCE = extConfiguration == null ? configuration : extConfiguration; - } -``` -load()方法的调用序列图如下: -![Seata元配置文件的加载过程](http://booogu.top/img/in-post/seata_config_initialization.png) - -上面的序列图中,大家可以关注以下几点: -- Seata元配置文件**名称支持扩展** -- Seata元配置文件后缀**支持3种后缀**,分别为yaml/properties/conf,在创建元配置文件实例时,会依次尝试匹配 -- Seata中**配置能力相关的顶级接口为Configuration**,各种配置中心均需实现此接口,Seata的元配置文件就是使用FileConfiguration(文件类型的配置中心)实现了此接口 - -```js -/** - * Seata配置能力接口 - * package:io.seata.config - */ - -public interface Configuration { - /** - * Gets short. - * - * @param dataId the data id - * @param defaultValue the default value - * @param timeoutMills the timeout mills - * @return the short - */ - short getShort(String dataId, int defaultValue, long timeoutMills); - - //以下内容略,主要能力为配置的增删改查 -} -``` -- Seata提供了一个类型为ExtConfigurationProvider的扩展点,开放了对配置具体实现的扩展能力,它具有一个provide()方法,接收原有的Configuration,返回一个全新的Configuration,此接口方法的形式决定了,一般可以采用静态代理、动态代理、装饰器等设计模式来实现此方法,实现对原有Configuration的增强 -```js -/** - * Seata扩展配置提供者接口 - * package:io.seata.config - */ -public interface ExtConfigurationProvider { - /** - * provide a AbstractConfiguration implementation instance - * @param originalConfiguration - * @return configuration - */ - Configuration provide(Configuration originalConfiguration); -} -``` -- 当应用侧基于seata-seata-spring-boot-starter启动时,将**采用『SpringBootConfigurationProvider』作为扩展配置提供者**,在其provide方法中,使用动态字节码生成(CGLIB)的方式为『FileConfiguration』实例创建了一个动态代理类,拦截了所有以"get"开头的方法,来从application.properties/application.yaml中获取元配置项。 - -关于SpringBootConfigurationProvider类,本文只说明下实现思路,不再展开分析源码,这也仅是ExtConfigurationProvider接口的一种实现方式,从Configuration可扩展、可替换的角度来看,Seata正是通过ExtConfigurationProvider这样一个扩展点,为多种配置的实现提供了一个广阔的舞台,允许配置的多种实现与接入方案。 - -经历过上述加载流程后,如果我们**没有扩展配置提供者**,我们将从Seata元配置文件中获取到注册中心的类型为file,同时创建了一个文件注册中心实例:FileRegistryServiceImpl -#### 从注册中心获取TC Server地址 -获取注册中心的实例后,需要执行lookup()方法(RegistryFactory.getInstance().**lookup(transactionServiceGroup)**),FileRegistryServiceImpl.lookup()的实现如下: -```js - /** - * 根据事务分组名称,获取TC Server可用地址列表 - * package:io.seata.discovery.registry - * class:FileRegistryServiceImpl - */ - @Override - public List lookup(String key) throws Exception { - //获取TC Server集群名称 - String clusterName = getServiceGroup(key); - if (clusterName == null) { - return null; - } - //从配置中心中获取TC集群中所有可用的Server地址 - String endpointStr = CONFIG.getConfig( - PREFIX_SERVICE_ROOT + CONFIG_SPLIT_CHAR + clusterName + POSTFIX_GROUPLIST); - if (StringUtils.isNullOrEmpty(endpointStr)) { - throw new IllegalArgumentException(clusterName + POSTFIX_GROUPLIST + " is required"); - } - //将地址封装为InetSocketAddress并返回 - String[] endpoints = endpointStr.split(ENDPOINT_SPLIT_CHAR); - List inetSocketAddresses = new ArrayList<>(); - for (String endpoint : endpoints) { - String[] ipAndPort = endpoint.split(IP_PORT_SPLIT_CHAR); - if (ipAndPort.length != 2) { - throw new IllegalArgumentException("endpoint format should like ip:port"); - } - inetSocketAddresses.add(new InetSocketAddress(ipAndPort[0], Integer.parseInt(ipAndPort[1]))); - } - return inetSocketAddresses; - } - - /** - * 注册中心接口中的default方法 - * package:io.seata.discovery.registry - * class:RegistryService - */ - default String getServiceGroup(String key) { - key = PREFIX_SERVICE_ROOT + CONFIG_SPLIT_CHAR + PREFIX_SERVICE_MAPPING + key; - //在配置缓存中,添加事务分组名称变化监听事件 - if (!SERVICE_GROUP_NAME.contains(key)) { - ConfigurationCache.addConfigListener(key); - SERVICE_GROUP_NAME.add(key); - } - //从配置中心中获取事务分组对应的TC集群名称 - return ConfigurationFactory.getInstance().getConfig(key); - } -``` -可以看到,代码逻辑与第一节中图**Seata事务分组与建立连接的关系**中的流程相符合, -这时,注册中心将需要**配置中心的协助**,来获取事务分组对应的集群名称、并查找集群中可用的服务地址。 - -### 从**配置中心**获取TC集群名称 -#### 配置中心的初始化 -配置中心的初始化(在ConfigurationFactory.buildConfiguration()),与注册中心的初始化流程类似,都是先从**元配置文件**中获取配置中心的类型等信息,然后初始化一个具体的配置中心实例,有了之前的分析基础,这里不再赘述。 - -#### 获取配置项的值 -上方代码段的两个方法:*FileRegistryServiceImpl.lookup()*以及*RegistryService.getServiceGroup()*中,都从配置中心中获取的配置项的值: -- lookup()需要由具体的注册中心实现,使用文件作为注册中心,其实是一种直连TC Server的情况,其特殊点在于**TC Server的地址是写死在配置中的**的(正常应存于注册中心中),因此FileRegistryServiceImpl.lookup()方法,是通过配置中心获取的TC集群中Server的地址信息 -- getServiceGroup()是RegistryServer接口中的default方法,即所有注册中心的公共实现,Seata中任何一种注册中心,都需要通过配置中心来根据事务分组名称来获取TC集群名称 - -### 负载均衡 -经过上述环节配置中心、注册中心的协作,现在我们已经获取到了当前应用侧所有可用的TC Server地址,那么在发送真正的请求之前,还需要通过特定的负载均衡策略,选择一个TC Server地址,这部分源码比较简单,就不带着大家分析了。 - -> 关于负载均衡的源码,大家可以阅读AbstractNettyRemotingClient.doSelect(),因本文分析的代码是RMClient/TMClient的重连方法,此方法中,所有获取到的Server地址,都会通过遍历依次连接(重连),因此这里不需要再做负载均衡。 - -以上就是Seata应用侧在启动过程中,注册中心与配置中心这两个关键模块之间的协作关系与工作流程,欢迎共同探讨、学习! - -> 后记:本文及其上篇[ Seata客户端启动过程剖析(一)](http://booogu.top/2021/02/28/seata-client-start-analysis-01/),是本人撰写的首批技术博客,将上手Seata时,个人认为Seata中较为复杂、需要研究和弄通的部分源码进行了分析和记录。 -在此欢迎各位读者提出各种改进建议,谢谢! +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-community-meetup-hangzhou-ready.md b/blog/seata-community-meetup-hangzhou-ready.md index bec9b6f51f..e872b67af1 100644 --- a/blog/seata-community-meetup-hangzhou-ready.md +++ b/blog/seata-community-meetup-hangzhou-ready.md @@ -1,29 +1 @@ ---- -title: Seata Community Meetup·杭州站 -keywords: [Seata, 杭州, meetup] -description: Seata Community Meetup·杭州站,将于12月21号在杭州市梦想小镇浙江青年众创空间正式召开 ---- - -# Seata Community Meetup·杭州站 - -### 活动介绍 - -### 亮点解读 - -- Seata 开源项目发起人带来《Seata 过去、现在和未来》以及 Seata 1.0 的新特性。 -- Seata 核心贡献者详解 Seata AT, TCC, Saga 模式。 -- Seata 落地互联网医疗,滴滴出行实践剖析。 - -### 如您不能前来参会 - -- [预约直播(开发者社区)](https://developer.aliyun.com/live/1760) -- [加入 Seata 千人钉钉群](http://w2wz.com/h2nb) - -### 现场福利 - -- 讲师 PPT 打包下载 -- 精美茶歇,阿里公仔,天猫精灵等好礼等你来拿 - -### 议程 - -![](https://img.alicdn.com/tfs/TB1K5nYwVP7gK0jSZFjXXc5aXXa-3175-14507.png) \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-config-center.md b/blog/seata-config-center.md index 446741bc4b..e872b67af1 100644 --- a/blog/seata-config-center.md +++ b/blog/seata-config-center.md @@ -1,185 +1 @@ ---- -title: Seata 配置中心实现原理 -author: 张乘辉 -keywords: [Seata、Config] -description: Seata 可以支持多个第三方配置中心,那么 Seata 是如何同时兼容那么多个配置中心的呢? -date: 2019/12/12 ---- - -# 前言 -Seata 可以支持多个第三方配置中心,那么 Seata 是如何同时兼容那么多个配置中心的呢?下面我给大家详细介绍下 Seata 配置中心的实现原理。 - - -# 配置中心属性加载 - -在 Seata 配置中心,有两个默认的配置文件: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191211193041.png) - -file.conf 是默认的配置属性,registry.conf 主要存储第三方注册中心与配置中心的信息,主要有两大块: - -```json -registry { - # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa - # ... -} - -config { - # file、nacos 、apollo、zk、consul、etcd3 - type = "file" - nacos { - serverAddr = "localhost" - namespace = "" - } - file { - name = "file.conf" - } - # ... -} -``` - -其中 registry 为注册中心的配置属性,这里先不讲,config 为配置中心的属性值,默认为 file 类型,即会加载本地的 file.conf 里面的属性,如果 type 为其它类型,那么会从第三方配置中心加载配置属性值。 - -在 config 模块的 core 目录中,有个配置工厂类 ConfigurationFactory,它的结构如下: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191210211022.png) - -可以看到都是一些配置的静态常量: - -REGISTRY_CONF_PREFIX、REGISTRY_CONF_SUFFIX:配置文件名、默认配置文件类型; - -SYSTEM_PROPERTY_SEATA_CONFIG_NAME、ENV_SEATA_CONFIG_NAME、ENV_SYSTEM_KEY、ENV_PROPERTY_KEY:自定义文件名配置变量,也说明我们可以自定义配置中心的属性文件。 - -ConfigurationFactory 里面有一处静态代码块,如下: - -io.seata.config.ConfigurationFactory - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191211102702.png) - -根据自定义文件名配置变量找出配置文件名称与类型,如果没有配置,默认使用 registry.conf,FileConfiguration 是 Seata 默认的配置实现类,如果为默认值,则会更具 registry.conf 配置文件生成 FileConfiguration 默认配置对象,这里也可以利用 SPI 机制支持第三方扩展配置实现,具体实现是继承 ExtConfigurationProvider 接口,在`META-INF/services/`创建一个文件并填写实现类的全路径名,如下所示: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191211194643.png) - - - -# 第三方配置中心实现类加载 - -在静态代码块逻辑加载完配置中心属性之后,Seata 是如何选择配置中心并获取配置中心的属性值的呢? - -我们刚刚也说了 FileConfiguration 是 Seata 的默认配置实现类,它继承了 AbstractConfiguration,它的基类为 Configuration,提供了获取参数值的方法: - -```java -short getShort(String dataId, int defaultValue, long timeoutMills); -int getInt(String dataId, int defaultValue, long timeoutMills); -long getLong(String dataId, long defaultValue, long timeoutMills); -// .... -``` - -那么意味着只需要第三方配置中心实现该接口,就可以整合到 Seata 配置中心了,下面我拿 zk 来做例子: - -首先,第三方配置中心需要实现一个 Provider 类: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191211200155.png) - -实现的 provider 方法如其名,主要是输出具体的 Configuration 实现类。 - -那么我们是如何获取根据配置去获取对应的第三方配置中心实现类呢? - -在 Seata 项目中,获取一个第三方配置中心实现类通常是这么做的: - -```java -Configuration CONFIG = ConfigurationFactory.getInstance(); -``` - -在 getInstance() 方法中主要是使用了单例模式构造配置实现类,它的构造具体实现如下: - -io.seata.config.ConfigurationFactory#buildConfiguration: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191211102905.png) - -首先从 ConfigurationFactory 中的静态代码块根据 registry.conf 创建的 CURRENT_FILE_INSTANCE 中获取当前环境使用的配置中心,默认为为 File 类型,我们也可以在 registry.conf 配置其它第三方配置中心,这里也是利用了 SPI 机制去加载第三方配置中心的实现类,具体实现如下: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191211205127.png) - -如上,即是刚刚我所说的 ZookeeperConfigurationProvider 配置实现输出类,我们再来看看这行代码: - -```java -EnhancedServiceLoader.load(ConfigurationProvider.class,Objects.requireNonNull(configType).name()).provide(); -``` - -EnhancedServiceLoader 是 Seata SPI 实现核心类,这行代码会加载 `META-INF/services/`和 `META-INF/seata/`目录中文件填写的类名,那么如果其中有多个配置中心实现类都被加载了怎么办呢? - -我们注意到 ZookeeperConfigurationProvider 类的上面有一个注解: - -```java -@LoadLevel(name = "ZK", order = 1) -``` - -在加载多个配置中心实现类时,会根据 order 进行排序: - -io.seata.common.loader.EnhancedServiceLoader#findAllExtensionClass: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191211210438.png) - -io.seata.common.loader.EnhancedServiceLoader#loadFile: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191211210347.png) - -这样,就不会产生冲突了。 - -但是我们发现 Seata 还可以用这个方法进行选择,Seata 在调用 load 方法时,还传了一个参数: - -```java -Objects.requireNonNull(configType).name() -``` - -ConfigType 为配置中心类型,是个枚举类: - -```java -public enum ConfigType { - File, ZK, Nacos, Apollo, Consul, Etcd3, SpringCloudConfig, Custom; -} -``` - -我们注意到,LoadLevel 注解上还有一个 name 属性,在进行筛选实现类时,Seata 还做了这个操作: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191211211210.png) - -根据当前 configType 来判断是否等于 LoadLevel 的 name 属性,如果相等,那么就是当前配置的第三方配置中心实现类。 - - - -# 第三方配置中心实现类 - -ZookeeperConfiguration 继承了 AbstractConfiguration,它的构造方法如下: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191211202510.png) - -构造方法创建了一个 zkClient 对象,这里的 FILE_CONFIG 是什么呢? - -```java -private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; -``` - -原来就是刚刚静态代码块中创建的 registry.conf 配置实现类,从该配置实现类拿到第三方配置中心的相关属性,构造第三方配置中心客户端,然后实现 Configuration 接口时: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191211203735.png) - -就可以利用客户端相关方法去第三方配置获取对应的参数值了。 - - - -# 第三方配置中心配置同步脚本 - -上周末才写好,已经提交 PR 上去了,还处于 review 中,预估会在 Seata 1.0 版本提供给大家使用,敬请期待。 - -具体位置在 Seata 项目的 script 目录中: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191211212141.png) - -config.txt 为本地配置好的值,搭建好第三方配置中心之后,运行脚本会将 config.txt 的配置同步到第三方配置中心。 - - -# 作者简介 - -张乘辉,目前就职于中通科技信息中心技术平台部,担任 Java 工程师,主要负责中通消息平台与全链路压测项目的研发,热爱分享技术,微信公众号「后端进阶」作者,技术博客([https://objcoding.com/](https://objcoding.com/))博主,Seata Contributor,GitHub ID:objcoding。 \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-config-manager.md b/blog/seata-config-manager.md index 545db65477..e872b67af1 100644 --- a/blog/seata-config-manager.md +++ b/blog/seata-config-manager.md @@ -1,251 +1 @@ ---- -title: Seata配置管理原理解析 -keywords: [Seata、配置中心、配置管理、Spring配置] -description: 本文主要介绍Seata配置管理的核心实现以及和Spring配置的交互过程 -author: 罗小勇 -date: 2021/01/10 ---- - - -说到Seata中的配置管理,大家可能会想到Seata中适配的各种配置中心,其实今天要说的不是这个,虽然也会简单分析Seata和各配置中心的适配过程,但主要还是讲解Seata配置管理的核心实现 - -# Server启动流程 -在讲配置中心之前,先简单介绍一下Server端的启动流程,因为这一块就涉及到配置管理的初始化,核心流程如下: -1. 程序入口在`Server#main`方法中 -2. 获取port的几种形式:从容器中获取;从命令行获取;默认端口 -3. 解析命令行参数:host、port、storeMode等参数,这个过程可能涉及到配置管理的初始化 -4. Metric相关:无关紧要,跳过 -5. NettyServer初始化 -6. 核心控制器初始化:Server端的核心,还包括几个定时任务 -7. NettyServer启动 - - -代码如下,删除了非核心代码 -```java -public static void main(String[] args) throws IOException { - // 获取port的几种形式:从容器中获取;从命令行获取;默认端口, use to logback.xml - int port = PortHelper.getPort(args); - System.setProperty(ConfigurationKeys.SERVER_PORT, Integer.toString(port)); - - // 解析启动参数,分容器和非容器两种情况 - ParameterParser parameterParser = new ParameterParser(args); - - // Metric相关 - MetricsManager.get().init(); - - // NettyServer初始化 - NettyRemotingServer nettyRemotingServer = new NettyRemotingServer(workingThreads); - - // 核心控制器初始化 - DefaultCoordinator coordinator = new DefaultCoordinator(nettyRemotingServer); - coordinator.init(); - - // NettyServer启动 - nettyRemotingServer.init(); -} -``` - -为社么说`步骤3`中肯能涉及到配置管理的初始化呢?核心代码如下: -```java -if (StringUtils.isBlank(storeMode)) { - storeMode = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_MODE, - SERVER_DEFAULT_STORE_MODE); -} -``` -如果在启动参数中没有特别指定`storeMode`,就会通过`ConfigurationFactory`相关API去获取该配置,在`ConfigurationFactory.getInstance()`这行代码中,涉及到两部分内容:`ConfigurationFactory`静态代码初始化和`Configuration`初始化。接下来我们重点分析这部分内容 - -# 配置管理初始化 - -## ConfigurationFactory初始化 -我们知道在Seata中有两个关键配置文件:一个是`registry.conf`,这是核心配置文件,必须要有;另一个是`file.conf`,只有在配置中心是`File`的情况下才需要用到。`ConfigurationFactory`静态代码块中,其实主要就是加载`registry.conf`文件,大概如下: - -1、在没有手动配置的情况,默认读取`registry.conf`文件,封装成一个`FileConfiguration`对象,核心代码如下: -```java -Configuration configuration = new FileConfiguration(seataConfigName,false); -FileConfigFactory.load("registry.conf", "registry"); -FileConfig fileConfig = EnhancedServiceLoader.load(FileConfig.class, "CONF", argsType, args); -``` - -2、配置增强:类似代理模式,获取配置时,在代理对象里面做一些其他处理,下面详细介绍 -```java -Configuration extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); -``` - -3、将步骤2中的代理对象赋值给`CURRENT_FILE_INSTANCE`引用,在很多地方都直接用到了`CURRENT_FILE_INSTANCE`这个静态引用 -```java -CURRENT_FILE_INSTANCE = extConfiguration == null ? configuration : extConfiguration; -``` - -可以简单的认为:`CURRENT_FILE_INSTANCE`对应的就是`registry.conf`里面的内容。我认为`registry.conf`这个文件名取的不太好,歧义太大,叫做`bootstrap.conf`是不是更好一些? - - -## Configuration初始化 -`Configuration`其实就是对应配置中心,Seata目前支持的配置中心很多,几乎主流的配置中心都支持,如:apollo、consul、etcd、nacos、zk、springCloud、本地文件。当使用本地文件作为配置中心的时候,涉及到`file.conf`的加载,当然这是默认的名字,可以自己配置。到这里,大家也基本上知道了`registry.conf`和`file.conf`的关系了。 - -`Configuration`作为单例放在`ConfigurationFactory`中,所以`Configuration`的初始化逻辑也是在`ConfigurationFactory`中,大概流程如下: -1、先从`registry.conf`文件中读取`config.type`属性,默认就是`file` -```java -configTypeName = CURRENT_FILE_INSTANCE.getConfig(ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR+ ConfigurationKeys.FILE_ROOT_TYPE); -``` -2、基于`config.type`属性值加载配置中心,比如默认是:`file`,则先从`registry.conf`文件中读取`config.file.name`读取本地文件配置中心对应的文件名,然后基于该文件名创建`FileConfiguration`对象,这样就将`file.conf`中的配置加载到内存中了。同理,如果配置的是其他配置中心,则会通过SPI初始化其他配置中心。还有一点需要注意的是,在这阶段,如果配置中心是本地文件,则会为其创建代理对象;如果不是本地文件,则通过SPI加载对应的配置中心 -```java -if (ConfigType.File == configType) { - String pathDataId = String.join("config.file.name"); - String name = CURRENT_FILE_INSTANCE.getConfig(pathDataId); - configuration = new FileConfiguration(name); - try { - // 配置增强 代理 - extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); - } catch (Exception e) { - LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e); - } -} else { - configuration = EnhancedServiceLoader - .load(ConfigurationProvider.class, Objects.requireNonNull(configType).name()).provide(); -} -``` - -3、基于步骤2创建的`Configuration`对象,为其再创建一层代理,这个代理对象有两个作用:一个是本地缓存,不需要每次获取配置的时候都从配置中获取;另一个是监听,当配置发生变更会更新它维护的缓存。如下: -```java -if (null != extConfiguration) { - configurationCache = ConfigurationCache.getInstance().proxy(extConfiguration); -} else { - configurationCache = ConfigurationCache.getInstance().proxy(configuration); -} -``` - -到这里,配置管理的初始化就完成了。**Seata通过先先加载`registry.conf`文件获取对应的配置中心信息、注册中心,然后再根据获取到的的对应信息初始化配置中心。在使用本地文件作为配置中心的情况下,默认是加载`file.conf`文件。然后再为对应的配置中心创建对应的代理对象,使其支持本地缓存和配置监听** - -整理流程还是比较简单,在这里我要抛出几个问题: -1. 什么是配置增强?Seata中的配置增强是怎么做的? -2. 如果使用本地文件作为配置中心,就必须要将配置定义在`file.conf`文件中。如果是Spring应用,能不能直接将对应的配置项定义在`application.yaml`中? -3. 在上面说的步骤2中,为什么在使用本地文件配置中心的情况下,要先为`Configuration`创建对应配置增强代理对象,而其他配置中心不用? - -这3个问题都是紧密联系的,都和Seata的配置增加相关。下面详细介绍 - -# 配置管理增强 -配置增强,简单来说就是为其创建一个代理对象,在执行目标独对象的目标方法时候,执行代理逻辑,从配置中心的角度来讲,就是在通过配置中心获取对应配置的时候,执行代理逻辑。 - -1. 通过`ConfigurationFactory.CURRENT_FILE_INSTANCE.getgetConfig(key)`获取配置 -2. 加载`registry.conf`文件创建FileConfiguration对象`configuration` -3. 基于`SpringBootConfigurationProvider`为`configuration`创建代理对象`configurationProxy` -4. 从`configurationProxy`中获取配置中心的连接信息`file zk nacos 等` -5. 基于连接信息创建配中心Configuration对象`configuration2` -6. 基于`SpringBootConfigurationProvider`为`configuration2`创建代理对象`configurationProxy2` -7. 执行`configurationProxy2`的代理逻辑 -8. 基于key找到对应的SpringBean -9. 执行SpringBean的getXxx方法 - -![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0cb93fec40df40ba9e8ab9db06cc9b93~tplv-k3u1fbpfcp-watermark.image) - -## 配置增强实现 -上面也简单提到过配置增强,相关代码如下: -```java -EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); -``` -1. 首先通过SPI机获取一个`ExtConfigurationProvider`对象,在Seata中默认只有一个实现:`SpringBootConfigurationProvider` -2. 通过`ExtConfigurationProvider#provider`方法为`Configuration`创建代理对象 - -核心代码如下: -```java -public Configuration provide(Configuration originalConfiguration) { - return (Configuration) Enhancer.create(originalConfiguration.getClass(), new MethodInterceptor() { - @Override - public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) - throws Throwable { - if (method.getName().startsWith("get") && args.length > 0) { - Object result = null; - String rawDataId = (String) args[0]; - if (args.length == 1) { - result = get(convertDataId(rawDataId)); - } else if (args.length == 2) { - result = get(convertDataId(rawDataId), args[1]); - } else if (args.length == 3) { - result = get(convertDataId(rawDataId), args[1], (Long) args[2]); - } - if (result != null) { - //If the return type is String,need to convert the object to string - if (method.getReturnType().equals(String.class)) { - return String.valueOf(result); - } - return result; - } - } - - return method.invoke(originalConfiguration, args); - } - }); -} - -private Object get(String dataId) throws IllegalAccessException, InstantiationException { - String propertyPrefix = getPropertyPrefix(dataId); - String propertySuffix = getPropertySuffix(dataId); - ApplicationContext applicationContext = (ApplicationContext) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT); - Class propertyClass = PROPERTY_BEAN_MAP.get(propertyPrefix); - Object valueObject = null; - if (propertyClass != null) { - try { - Object propertyBean = applicationContext.getBean(propertyClass); - valueObject = getFieldValue(propertyBean, propertySuffix, dataId); - } catch (NoSuchBeanDefinitionException ignore) { - - } - } else { - throw new ShouldNeverHappenException("PropertyClass for prefix: [" + propertyPrefix + "] should not be null."); - } - if (valueObject == null) { - valueObject = getFieldValue(propertyClass.newInstance(), propertySuffix, dataId); - } - - return valueObject; -} -``` -1、如果方法是以`get`开头,并且参数个数为1/2/3,则执行其他的获取配置的逻辑,否则执行原生`Configuration`对象的逻辑 -2、我们没必要纠结为啥是这样的规则,这就是Seata的一个约定 -3、`其他获取配置的逻辑`,就是指通过Spring的方式获取对应配置值 - -到这里已经清楚了配置增强的原理,同时,也可以猜测得出唯一的`ExtConfigurationProvider`实现`SpringBootConfigurationProvider`,肯定是和Spring相关 - - -## 配置增强与Spring -在介绍这块内容之前,我们先简单介绍一下Seata的使用方式: -1. 非Starter方式:引入依赖 `seata-all`, 然后手动配置几个核心的Bean -2. Starter方式: 引入依赖`seata-spring-boot-starter`,全自动准配,不需要自动注入核心Bean - -`SpringBootConfigurationProvider`就在`seata-spring-boot-starter`模块中,也就是说,当我们通过引入`seata-all`的方式来使用Seata时,配置增强其实没有什么作用,因为此时根本找不到`ExtConfigurationProvider`实现类,自然就不会增强。 - -那`seata-spring-boot-starter`是如何将这些东西串联起来的? - -1、首先,在`seata-spring-boot-starter`模块的`resources/META-INF/services`目录下,存在一个`spring.factories`文件,内容分如下 -``` -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -io.seata.spring.boot.autoconfigure.SeataAutoConfiguration,\ - -# 暂时不管 -io.seata.spring.boot.autoconfigure.HttpAutoConfiguration -``` - -2、在`SeataAutoConfiguration`文件中,会创建以下Bean: GlobalTransactionScanner 、SeataDataSourceBeanPostProcessor、SeataAutoDataSourceProxyCreator、SpringApplicationContextProvider。前3个和我们本文要讲的内容不相关,主要关注`SpringApplicationContextProvider`,核心代码非常简单,就是将`ApplicationContext`保存下来: -```java -public class SpringApplicationContextProvider implements ApplicationContextAware { - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT, applicationContext); - } -} -``` -3、然后,在`SeataAutoConfiguration`文件中,还会将一些`xxxProperties.Class`和对应的Key前缀缓存到`PROPERTY_BEAN_MAP`中。``xxxProperties`就简单理解成`application.yaml`中的各种配置项: -```java -static { - PROPERTY_BEAN_MAP.put(SEATA_PREFIX, SeataProperties.class); - PROPERTY_BEAN_MAP.put(CLIENT_RM_PREFIX, RmProperties.class); - PROPERTY_BEAN_MAP.put(SHUTDOWN_PREFIX, ShutdownProperties.class); - ...省略... -} -``` - -至此,整个流程其实已经很清晰,在有`SpringBootConfigurationProvider`配置增强的时候,我们获取一个配置项的流程如下: -1. 先根据`p配置项Key`获取对应的`xxxProperties`对象 -2. 通过`ObjectHolder`中的`ApplicationContext`获取对应`xxxProperties`的SpringBean -3. 基于`xxxProperties`的SpringBean获取对应配置的值 -4. 至此,通过配置增强,我们成功的获取到`application.yaml`中的值 +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-connect-data-and-application.md b/blog/seata-connect-data-and-application.md index e541bca97d..e872b67af1 100644 --- a/blog/seata-connect-data-and-application.md +++ b/blog/seata-connect-data-and-application.md @@ -1,142 +1 @@ ---- -title: Seata:连接数据与应用 -keywords: [Seata、分布式事务、数据一致性、微服务] -description: 本文介绍Seata的过去、现在和未来演进 -author: 季敏-Seata 开源社区创始人,分布式事务团队负责人 -date: 2023/06/10 ---- - -本文主要介绍分布式事务从内部到商业化和开源的演进历程,Seata社区当前进展和未来规划。 - -Seata是一款开源的分布式事务解决方案,旨在为现代化微服务架构下的分布式事务提供解决方案。Seata提供了完整的分布式事务解决方案,包括AT、TCC、Saga和XA事务模式,可支持多种编程语言和数据存储方案。Seata还提供了简便易用的API,以及丰富的文档和示例,方便企业在应用Seata时进行快速开发和部署。 - -**Seata的优势在于具有高可用性、高性能、高扩展性等特点,同时在进行横向扩展时也无需做额外的复杂操作。** 目前Seata已在阿里云上几千家客户业务系统中使用,其可靠性得到了业内各大厂商的认可和应用。 - -作为一个开源项目,Seata的社区也在不断扩大,现已成为开发者交流、分享和学习的重要平台,也得到了越来越多企业的支持和关注。 - -今天我主要针对以下三个小议题对Seata进行分享: -- **从TXC/GTS 到 Seata** -- **Seata 社区最新进展** -- **Seata 社区未来规划** -
-### 从TXC/GTS 到Seata -#### 分布式事务的缘起 -![产品矩阵](/img/blog/产品矩阵.jpg) -Seata 在阿里内部的产品代号叫TXC(taobao transaction constructor),这个名字有非常浓厚的组织架构色彩。TXC 起源于阿里五彩石项目,五彩石是上古神话中女娲补天所用的石子,项目名喻意为打破关键技术壁垒,象征着阿里在从单体架构向分布式架构的演进过程中的重要里程碑。在这个项目的过程中演进出一批划时代的互联网中间件,包括我们常说的三大件: -- **HSF 服务调用框架** -解决单体应用到服务化后的服务通信调用问题。 -- **TDDL 分库分表框架** -解决规模化后单库存储容量和连接数问题。 -- **MetaQ 消息框架** -解决异步调用问题。 - -三大件的诞生满足了微服务化业务开发的基本需求,但是微服务化后的数据一致性问题并未得到妥善解决,缺少统一的解决方案。应用微服务化后出现数据一致性问题概率远大于单体应用,从进程内调用到网络调用这种复杂的环境加剧了异常场景的产生,服务跳数的增多使得在出现业务处理异常时无法协同上下游服务同时进行数据回滚。TXC的诞生正是为了解决应用架构层数据一致性的痛点问题,TXC 核心要解决的数据一致性场景包括: -- **跨服务的一致性。** 应对系统异常如调用超时和业务异常时协调上下游服务节点回滚。 -- **分库分表的数据一致性。** 应对业务层逻辑SQL操作的数据在不同数据分片上,保证其分库分表操作的内部事务。 -- **消息发送的数据一致性。** 应对数据操作和消息发送成功的不一致性问题。 - -为了克服以上通用场景遇到的问题,TXC与三大件做了无缝集成。业务使用三大件开发时,完全感知不到背后TXC的存在,业务不需要考虑数据一致性的设计问题,数据一致性保证交给了框架托管,业务更加聚焦于业务本身的开发,极大的提升了开发的效率。 -
- -![GTS架构](/img/blog/GTS架构.jpg) - -TXC已在阿里集团内部广泛应用多年,经过双11等大型活动的洪荒流量洗礼,TXC极大提高了业务的开发效率,保证了数据的正确性,消除了数据不一致导致的资损和商誉问题。随着架构的不断演进,**标准的三节点集群已可以承载接近10W TPS的峰值和毫秒级事务处理。在可用性和性能方面都达到了4个9的SLA保证,即使在无值守状态下也能保证全年无故障。** -
- -#### 分布式事务的演进 -新事物的诞生总是会伴随着质疑的声音。中间件层来保证数据一致性到底可靠吗?TXC最初的诞生只是一种模糊的理论,缺乏理论模型和工程实践。在我们进行MVP(最小可行产品)模型测试并推广业务上线后,经常出现故障,常常需要在深夜起床处理问题,睡觉时要佩戴手环来应对紧急响应,这也是我接管这个团队在技术上过的最痛苦的几年。 - -![分布式事务演进](/img/blog/分布式事务演进.jpg) - -随后,我们进行了广泛的讨论和系统梳理。我们首先需要定义一致性问题,我们是要像RAFT一样实现多数共识一致性,还是要像Google Spanner一样解决数据库一致性问题,还是其他方式?从应用节点自上而下的分层结构来看,主要包括开发框架、服务调用框架、数据中间件、数据库Driver和数据库。我们需要决定在哪一层解决数据一致性问题。我们比较了解决不同层次数据一致性问题所面临的一致性要求、通用性、实现复杂度和业务接入成本。最后,我们权衡利弊,把实现复杂度留给我们,作为一个一致性组件,我们需要确保较高的一致性,但又不能锁定到具体数据库的实现上,确保场景的通用性和业务接入成本足够低以便更容易实现业务,这也是TXC最初采用AT模式的原因。 - -**分布式事务它不仅仅是一个框架,它是一个体系。** 我们在理论上定义了一致性问题,概念上抽象出了模式、角色、动作和隔离性等。从工程实践的角度,我们定义了编程模型,包括低侵入的注解、简单的方法模板和灵活的API ,定义了事务的基础能力和增强能力(例如如何以低成本支持大量活动),以及运维、安全、性能、可观测性和高可用等方面的能力。 - -![事务逻辑模型](/img/blog/事务逻辑模型.jpg) -分布式事务解决了哪些问题呢?一个经典且具有体感的例子就是转账场景。转账过程包括减去余额和增加余额两个步骤,我们如何保证操作的原子性?在没有任何干预的情况下,这两个步骤可能会遇到各种问题,例如B账户已销户或出现服务调用超时等情况。 - -**超时问题一直是分布式应用中比较难解决的问题**,我们无法准确知晓B服务是否执行以及其执行顺序。从数据的角度来看,这意味着B 账户的钱未必会被成功加起来。在服务化改造之后,每个节点仅获知部分信息,而事务本身需要全局协调所有节点,因此需要一个拥有上帝视角、能够获取全部信息的中心化角色,这个角色就是**TC(transaction coordinator)**,它用于全局协调事务的状态。**TM(Transaction Manager)** 则是驱动事务生成提议的角色。但是,即使上帝也有打瞌睡的时候,他的判断也并不总是正确的,因此需要一个**RM(resource manager)** 角色作为灵魂的代表来验证事务的真实性。这就是TXC 最基本的哲学模型。我们从方法论上验证了它的数据一致性是非常完备的,当然,我们的认知是有边界的。也许未来会证明我们是火鸡工程师,但在当前情况下,它的模型已经足以解决大部分现有问题。 - -![分布式事务性能](/img/blog/分布式事务性能.jpg) -**经过多年的架构演进,从事务的单链路耗时角度来看,TXC在事务开始时的处理平均时间约为0.2毫秒,分支注册的平均时间约为0.4毫秒,整个事务额外的耗时在毫秒级别之内。这也是我们推算出的极限理论值。在吞吐量方面,单节点的TPS达到3万次/秒,标准集群的TPS接近10万次/秒。** -
-#### Seata 开源 -为什么要做开源?这是很多人问过我的问题。2017年我们做了商业化的 GTS(Global Transaction Service )产品产品在阿里云上售卖,有公有云和专有云两种形态。此时集团内发展的顺利,但是在我们商业化的过程中并不顺利,我们遇到了各种各样的问题,问题总结起来主要包括两类:**一是开发者对于分布式事务的理论相当匮乏,** 大多数人连本地事务都没搞明白是怎么回事更何况是分布式事务。 **二是产品成熟度上存在问题,** 经常遇到稀奇古怪的场景问题,导致了支持交付成本的急剧上升,研发变成了售后客服。 - -我们反思为什么遇到如此多的问题,这里主要的问题是在阿里集团内部是统一语言栈和统一技术栈的,我们对特定场景的打磨是非常成熟的,服务阿里一家公司和服务云上成千上万家企业有本质的区,这也启示我们产品的场景生态做的不够好。在GitHub 80%以上的开源软件是基础软件,基础软件首要解决的是场景通用性问题,因此它不能被有一家企业Lock In,比如像Linux,它有非常多的社区分发版本。因此,为了让我们的产品变得更好,我们选择了开源,与开发者们共建,普及更多的企业用户。 - -![阿里开源](/img/blog/阿里开源.jpg) -阿里的开源经历了三个主要阶段。**第一个阶段是Dubbo所处的阶段,开发者用爱发电,** Dubbo开源了有10几年的时间,时间充分证明了Dubbo是非常优秀的开源软件,它的微内核插件化的扩展性设计也是我最初开源Seata 的重要参考。做软件设计的时候我们要思考扩展性和性能权衡起来哪个会更重要一些,我们到底是要做一个三年的设计,五年的设计亦或是满足业务发展的十年设计。我们在做0-1服务调用问题的解决方案的同时,能否预测到1-100规模化后的治理问题。 - -**第二个阶段是开源和商业化的闭环,商业化反哺于开源社区,促进了开源社区的发展。** 我认为云厂商更容易做好开源的原因如下: - -- 首先,云是一个规模化的经济,必然要建立在稳定成熟的内核基础上,在上面去包装其产品化能力包括高可用、免运维和弹性能力。不稳定的内核必然导致过高的交付支持成本,研发团队的支持答疑穿透过高,过高的交付成本无法实现大规模的复制,穿透率过高无法使产品快速的演进迭代。 -- 其次,商业产品是更懂业务需求的。我们内部团队做技术的经常是站在研发的视角YY 需求,做出来的东西没有人使用,也就不会形成价值的转换。商业化收集到的都是真实的业务需求,因此,它的开源内核也必须会朝着这个方向演进。如果不朝着这个方向去演进必然导致两边架构上的分裂,增加团队的维护成本。 -- 最后,开源和商业化闭环,能促进双方更好的发展。如果开源内核经常出现各种问题,你是否愿意相信的它的商业化产品是足够优秀的。 - -**第三个阶段是体系化和标准化。** 首先,体系化是开源解决方案的基础。阿里的开源项目大多是基于内部电商场景的实践而诞生的。例如Higress,它用于打通蚂蚁集团的网关;Nacos承载着服务的百万实例和千万连接;Sentinel 提供大促时的降级和限流等高可用性能力;而Seata负责保障交易数据的一致性。这套体系化的开源解决方案是基于阿里电商生态的最佳实践而设计的。其次,标准化是另一个重要的特点。以OpenSergo为例,它既是一个标准,又是一个实现。在过去几年里,国内开源项目数量呈爆发式增长。然而,各个开源产品的能力差异很大,彼此集成时会遇到许多兼容性问题。因此,像OpenSergo这样的开源项目能够定义一些标准化的能力和接口,并提供一些实现,这将为整个开源生态系统的发展提供极大的帮助。 -
- -### Seata 社区最新进展 -#### Seata 社区简介 -![社区简介](/img/blog/社区简介.jpg) -**目前,Seata已经开源了4种事务模式,包括AT、TCC、Saga和XA,并在积极探索其他可行的事务解决方案。** Seata已经与10多个主流的RPC框架和关系数据库进行了集成,同时与20 多个社区存在集成和被集成的关系。此外,我们还在多语言体系上探索除Java之外的语言,如Golang、PHP、Python和JS。 - -Seata已经被几千家客户应用到业务系统中。Seata的应用已经变得越来越成熟,在金融业务场景中信银行和光大银行与社区做了很好的合作,并成功将其纳入到核心账务系统中。在金融场景对微服务体系的落地是非常严苛的,这也标志着Seata的内核成熟度迈上了一个新台阶。 -
-#### Seata 扩展生态 -![扩展生态](/img/blog/扩展生态.jpg) -**Seata采用了微内核和插件化的设计,它在API、注册配置中心、存储模式、锁控制、SQL解析器、负载均衡、传输、协议编解码、可观察性等方面暴露了丰富的扩展点。** 这使得业务可以方便地进行灵活的扩展和技术组件的选择。 -
- -#### Seata 应用案例 -![应用案例](/img/blog/应用案例.jpg) -**案例1:中航信航旅纵横项目** -中航信航旅纵横项目在Seata 0.2版本中引入Seata解决机票和优惠券业务的数据一致性问题,大大提高了开发效率、减少了数据不一致造成的资损并提升了用户交互体验。 - -**案例2:滴滴出行二轮车事业部** -滴滴出行二轮车事业部在Seata 0.6.1版本中引入Seata,解决了小蓝单车、电动车、资产等业务流程的数据一致性问题,优化了用户使用体验并减少了资产的损失。 - -**案例3:美团基础架构** -美团基础架构团队基于开源的Seata项目开发了内部分布式事务解决方案Swan,被用于解决美团内部各业务的分布式事务问题。 - -**场景4:盒马小镇** -盒马小镇在游戏互动中使用Seata控制偷花的流程,开发周期大大缩短,从20天缩短到了5天,有效降低了开发成本。 -
- -#### Seata 事务模式的演进 -![模式演进](/img/blog/模式演进.jpg) -
- -#### Seata 当前进展 -- 支持 Oracle和 Postgresql 多主键。 -- 支持 Dubbo3 -- 支持 Spring Boot3 -- 支持 JDK 17 -- 支持 ARM64 镜像 -- 支持多注册模型 -- 扩展了多种SQL语法 -- 支持 GraalVM Native Image -- 支持 Redis lua 存储模式 -
- -### Seata 2.x 发展规划 -![发展规划](/img/blog/发展规划.jpg) - -主要包括下面几个方面: -- **存储/协议/特性** -存储模式上探索存算不分离的Raft集群模式;更好的体验,统一当前4种事务模式的API;兼容GTS协议;支持Saga注解;支持分布式锁的控制;支持以数据视角的洞察和治理。 -- **生态** -融合支持更多的数据库,更多的服务框架,同时探索国产化信创生态的支持;支持MQ生态;进一步完善APM的支持。 -- **解决方案** -解决方案上除了支持微服务生态探索多云方案;更贴近云原生的解决方案;增加安全和流量防护能力;实现架构上核心组件的自闭环收敛。 -- **多语言生态** -多语言生态中Java最成熟,其他已支持的编程语言继续完善,同时探索与语言无关的Transaction Mesh方案。 -- **研发效能/体验** -提升测试的覆盖率,优先保证质量、兼容性和稳定性;重构官网文档结构,提升文档搜索的命中率;在体验上简化运维部署,实现一键安装和配置元数据简化;控制台支持事务控制和在线分析能力。 - -一句话总结2.x 的规划:**更大的场景,更大的生态,从可用到好用。** -
- -### Seata 社区联系方式 -![联系方式](/img/blog/联系方式.jpg) \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-datasource-proxy.md b/blog/seata-datasource-proxy.md index a9b31b5416..e872b67af1 100644 --- a/blog/seata-datasource-proxy.md +++ b/blog/seata-datasource-proxy.md @@ -1,726 +1 @@ ---- -title: Seata数据源代理解析 -keywords: [Seata、数据源、数据源代理、多数据源] -description: 本文主要介绍Seata数据源代理实现原理及使用时可能会遇到的问题 -author: 罗小勇 -date: 2020/10/16 ---- - - -# 数据源代理 -在Seata1.3.0版本中,数据源自动代理和手动代理一定不能混合使用,否则会导致多层代理,从而导致以下问题: -1. 单数据源情况下:导致分支事务提交时,undo_log本身也被代理,即`为 undo_log 生成了 undo_log, 假设为undo_log2`,此时undo_log将被当作分支事务来处理;分支事务回滚时,因为`undo_log2`生成的有问题,在`undo_log对应的事务分支`回滚时会将`业务表关联的undo_log`也一起删除,从而导致`业务表对应的事务分支`回滚时发现undo_log不存在,从而又多生成一条状态为1的undo_log。这时候整体逻辑已经乱了,很严重的问题 -2. 多数据源和`逻辑数据源被代理`情况下:除了单数据源情况下会出现的问题,还可能会造成死锁问题。死锁的原因就是针对undo_log的操作,本该在一个事务中执行的`select for update` 和 `delete` 操作,被分散在多个事务中执行,导致一个事务在执行完`select for update`后一直不提交,一个事务在执行`delete`时一直等待锁,直到超时 - - -## 代理描述 -即对DataSource代理一层,重写一些方法。比如`getConnection`方法,这时不直接返回一个`Connection`,而是返回`ConnectionProxy`,其它的以此类推 -```java -// DataSourceProxy - -public DataSourceProxy(DataSource targetDataSource) { - this(targetDataSource, DEFAULT_RESOURCE_GROUP_ID); -} - -private void init(DataSource dataSource, String resourceGroupId) { - DefaultResourceManager.get().registerResource(this); -} - -public Connection getPlainConnection() throws SQLException { - return targetDataSource.getConnection(); -} - -@Override -public ConnectionProxy getConnection() throws SQLException { - Connection targetConnection = targetDataSource.getConnection(); - return new ConnectionProxy(this, targetConnection); -} -``` - -## 手动代理 -即手动注入一个`DataSourceProxy`,如下 -``` -@Bean -public DataSource druidDataSource() { - return new DruidDataSource() -} - -@Primary -@Bean("dataSource") -public DataSourceProxy dataSource(DataSource druidDataSource) { - return new DataSourceProxy(druidDataSource); -} -``` - -## 自动代理 -针对`DataSource`创建一个代理类,在代理类里面基于`DataSource`获取`DataSourceProxy(如果没有就创建)`,然后调用`DataSourceProxy`的相关方法。核心逻辑在`SeataAutoDataSourceProxyCreator`中 -```java -public class SeataAutoDataSourceProxyCreator extends AbstractAutoProxyCreator { - private static final Logger LOGGER = LoggerFactory.getLogger(SeataAutoDataSourceProxyCreator.class); - private final String[] excludes; - private final Advisor advisor = new DefaultIntroductionAdvisor(new SeataAutoDataSourceProxyAdvice()); - - public SeataAutoDataSourceProxyCreator(boolean useJdkProxy, String[] excludes) { - this.excludes = excludes; - setProxyTargetClass(!useJdkProxy); - } - - @Override - protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource customTargetSource) throws BeansException { - if (LOGGER.isInfoEnabled()) { - LOGGER.info("Auto proxy of [{}]", beanName); - } - return new Object[]{advisor}; - } - - @Override - protected boolean shouldSkip(Class beanClass, String beanName) { - return SeataProxy.class.isAssignableFrom(beanClass) || - DataSourceProxy.class.isAssignableFrom(beanClass) || - !DataSource.class.isAssignableFrom(beanClass) || - Arrays.asList(excludes).contains(beanClass.getName()); - } -} - -public class SeataAutoDataSourceProxyAdvice implements MethodInterceptor, IntroductionInfo { - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - DataSourceProxy dataSourceProxy = DataSourceProxyHolder.get().putDataSource((DataSource) invocation.getThis()); - Method method = invocation.getMethod(); - Object[] args = invocation.getArguments(); - Method m = BeanUtils.findDeclaredMethod(DataSourceProxy.class, method.getName(), method.getParameterTypes()); - if (m != null) { - return m.invoke(dataSourceProxy, args); - } else { - return invocation.proceed(); - } - } - - @Override - public Class[] getInterfaces() { - return new Class[]{SeataProxy.class}; - } -} -``` - - -## 数据源多层代理 -``` -@Bean -@DependsOn("strangeAdapter") -public DataSource druidDataSource(StrangeAdapter strangeAdapter) { - doxx - return new DruidDataSource() -} - -@Primary -@Bean("dataSource") -public DataSourceProxy dataSource(DataSource druidDataSource) { - return new DataSourceProxy(druidDataSource); -} -``` -1. 首先我们在配置类里面注入了两个`DataSource`,分别为: `DruidDataSource`和`DataSourceProxy`, 其中`DruidDataSource 作为 DataSourceProxy 的 targetDataSource 属性`,并且`DataSourceProxy`为使用了`@Primary`注解声明 -2. 应用默认开启了数据源自动代理,所以在调用`DruidDataSource`相关方法时,又会为为`DruidDataSource`创建一个对应的数据源代理`DataSourceProxy2` -3. 当我们在程序中想获取一个Connection时会发生什么? - 1. 先获取一个`DataSource`,因为`DataSourceProxy`为`Primary`,所以此时拿到的是`DataSourceProxy` - 2. 基于`DataSource`获取一个`Connection`,即通过`DataSourceProxy`获取`Connection`。此时会先调用`targetDataSource 即 DruidDataSource 的 getConnection 方法`,但因为切面会对`DruidDataSource`进行拦截,根据步骤2的拦截逻辑可以知道,此时会自动创建一个`DataSourceProxy2`,然后调用`DataSourceProxy2#getConnection`,然后再调用`DruidDataSource#getConnection`。最终形成了双层代理, 返回的`Connection`也是一个双层的`ConnectionProxy` - -![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9ac0b91bd8fc4c48aa68afd5c58a42d5~tplv-k3u1fbpfcp-watermark.image) - -上面其实是改造之后的代理逻辑,Seata默认的自动代理会对`DataSourceProxy`再次进行代理,后果就是代理多了一层此时对应的图如下 - -![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/837aa0462d994e9a8614707c6a50b5ae~tplv-k3u1fbpfcp-watermark.image) - -数据源多层代理会导致的两个问题在文章开头处已经总结了,下面会有案例介绍。 - - - -# 分支事务提交 -通过`ConnectionProxy`中执行对应的方法,会发生什么?以update操作涉及到的一个分支事务提交为例: -1. 执行`ConnectionProxy#prepareStatement`, 返回一个`PreparedStatementProxy` -2. 执行`PreparedStatementProxy#executeUpdate`,`PreparedStatementProxy#executeUpdate`大概会帮做两件事情: 执行业务SQL和提交undo_log - -## 提交业务SQL -```java -// ExecuteTemplate#execute -if (sqlRecognizers.size() == 1) { - SQLRecognizer sqlRecognizer = sqlRecognizers.get(0); - switch (sqlRecognizer.getSQLType()) { - case INSERT: - executor = EnhancedServiceLoader.load(InsertExecutor.class, dbType, - new Class[]{StatementProxy.class, StatementCallback.class, SQLRecognizer.class}, - new Object[]{statementProxy, statementCallback, sqlRecognizer}); - break; - case UPDATE: - executor = new UpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer); - break; - case DELETE: - executor = new DeleteExecutor<>(statementProxy, statementCallback, sqlRecognizer); - break; - case SELECT_FOR_UPDATE: - executor = new SelectForUpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer); - break; - default: - executor = new PlainExecutor<>(statementProxy, statementCallback); - break; - } -} else { - executor = new MultiExecutor<>(statementProxy, statementCallback, sqlRecognizers); -} -``` - -主要流程就是: 先执行业务SQL,然后执行ConnectionProxy的commit方法,在这个方法中,会先帮我们执行对应的 undo_log SQL,然后提交事务 -``` -PreparedStatementProxy#executeUpdate => -ExecuteTemplate#execute => -BaseTransactionalExecutor#execute => -AbstractDMLBaseExecutor#doExecute => -AbstractDMLBaseExecutor#executeAutoCommitTrue => -AbstractDMLBaseExecutor#executeAutoCommitFalse => 在这一步操中,会触发statementCallback#execute方法,即调用调用原生PreparedStatement#executeUpdate方法 -ConnectionProxy#commit -ConnectionProxy#processGlobalTransactionCommit -``` - -## UNDO_LOG插入 -```java -// ConnectionProxy#processGlobalTransactionCommit -private void processGlobalTransactionCommit() throws SQLException { - try { - // 注册分支事务,简单理解向server发一个请求,然后server在branch_table表里插入一条记录,不关注 - register(); - } catch (TransactionException e) { - // 如果没有for update 的sql,会直接在commit之前做注册,此时不止插入一条branch记录,而会附带锁信息进行竞争,下方的异常一般就是在注册时没拿到锁抛出,一般就是纯update语句的并发下会触发竞争锁失败的异常 @FUNKYE - recognizeLockKeyConflictException(e, context.buildLockKeys()); - } - try { - // undo_log处理,期望用 targetConnection 处理 @1 - UndoLogManagerFactory.getUndoLogManager(this.getDbType()).flushUndoLogs(this); - - // 提交本地事务,期望用 targetConnection 处理 @2 - targetConnection.commit(); - } catch (Throwable ex) { - LOGGER.error("process connectionProxy commit error: {}", ex.getMessage(), ex); - report(false); - throw new SQLException(ex); - } - if (IS_REPORT_SUCCESS_ENABLE) { - report(true); - } - context.reset(); -} -``` -1. undo_log处理@1,解析当前事务分支涉及到的`undo_log`,然后使用`TargetConnection`, 写到数据库 -```java -public void flushUndoLogs(ConnectionProxy cp) throws SQLException { - ConnectionContext connectionContext = cp.getContext(); - if (!connectionContext.hasUndoLog()) { - return; - } - - String xid = connectionContext.getXid(); - long branchId = connectionContext.getBranchId(); - - BranchUndoLog branchUndoLog = new BranchUndoLog(); - branchUndoLog.setXid(xid); - branchUndoLog.setBranchId(branchId); - branchUndoLog.setSqlUndoLogs(connectionContext.getUndoItems()); - - UndoLogParser parser = UndoLogParserFactory.getInstance(); - byte[] undoLogContent = parser.encode(branchUndoLog); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Flushing UNDO LOG: {}", new String(undoLogContent, Constants.DEFAULT_CHARSET)); - } - - insertUndoLogWithNormal(xid, branchId, buildContext(parser.getName()), undoLogContent,cp.getTargetConnection()); -} -``` -2. 提交本地事务@2,即通过`TargetConnection`提交事务。即 `务SQL执行`、`undo_log写入`、`即事务提交` 用的都是同一个`TargetConnection` - ->lcn的内置数据库方案,lcn是将undolog写到他内嵌的h2(忘了是不是这个来着了)数据库上,此时会变成2个本地事务,一个是h2的undolog插入事务,一个是业务数据库的事务,如果在h2插入后,业务数据库异常,lcn的方案就会出现数据冗余,回滚数据的时候也是一样,删除undolog跟回滚业务数据不是一个本地事务. -但是lcn这样的好处就是入侵小,不需要另外添加undolog表。 感谢@FUNKYE大佬给的建议,对lcn不太了解,有机会好好研究一下 - - -# 分支事务回滚 -1. Server端向Client端发送回滚请求 -2. Client端接收到Server发过来的请求,经过一系列处理,最终会到`DataSourceManager#branchRollback`方法 -3. 先根据resourceId从`DataSourceManager.dataSourceCache`中获取对应的`DataSourceProxy`,此时为`masterSlaveProxy`(回滚阶段我们就不考代理数据源问题,简单直接一些,反正最终拿到的都是`TragetConnection`) -4. 根据Server端发过来的xid和branchId查找对应的undo_log并解析其`rollback_info`属性,每条undo_log可能会解析出多条`SQLUndoLog`,每个`SQLUndoLog`可以理解成是一个操作。比如一个分支事务先更新A表,再更新B表,这时候针对该分支事务生成的undo_log就包含两个`SQLUndoLog`:第一个`SQLUndoLog`对应的是更新A表的前后快照;第二个`SQLUndoLog`对应的是更新B表的前后快照 -5. 针对每条`SQLUndoLog`执行对应的回滚操作,比如一个`SQLUndoLog`对应的操作是`INSERT`,则其对应的回滚操作就是`DELETE` -6. 根据xid和branchId删除该undo_log - - -```java -// AbstractUndoLogManager#undo 删除了部分非关键代码 - -public void undo(DataSourceProxy dataSourceProxy, String xid, long branchId) throws TransactionException { - Connection conn = null; - ResultSet rs = null; - PreparedStatement selectPST = null; - boolean originalAutoCommit = true; - - for (; ; ) { - try { - // 获取原生数据源的Connection, 回滚阶段我们不管代理数据源问题,最终拿到的都是 TargetConnection - conn = dataSourceProxy.getPlainConnection(); - - // 将回滚操作放在一个本地事务中,手动提交,确保最终业务SQL操作和undo_log删除操作一起提交 - if (originalAutoCommit = conn.getAutoCommit()) { - conn.setAutoCommit(false); - } - - // 根据xid 和 branchId 查询 undo_log,注意此时的SQL语句 SELECT * FROM undo_log WHERE branch_id = ? AND xid = ? FOR UPDATE - selectPST = conn.prepareStatement(SELECT_UNDO_LOG_SQL); - selectPST.setLong(1, branchId); - selectPST.setString(2, xid); - rs = selectPST.executeQuery(); - - boolean exists = false; - while (rs.next()) { - exists = true; - // status == 1 undo_log不处理,和防悬挂相关 - if (!canUndo(state)) { - return; - } - - // 解析undo_log - byte[] rollbackInfo = getRollbackInfo(rs); - BranchUndoLog branchUndoLog = UndoLogParserFactory.getInstance(serializer).decode(rollbackInfo); - try { - setCurrentSerializer(parser.getName()); - List sqlUndoLogs = branchUndoLog.getSqlUndoLogs(); - if (sqlUndoLogs.size() > 1) { - Collections.reverse(sqlUndoLogs); - } - for (SQLUndoLog sqlUndoLog : sqlUndoLogs) { - AbstractUndoExecutor undoExecutor = UndoExecutorFactory.getUndoExecutor(dataSourceProxy.getDbType(), sqlUndoLog); - // 执行对应的回滚操作 - undoExecutor.executeOn(conn); - } - } - } - - // - if (exists) { - LOGGER.error("\n delete from undo_log where xid={} AND branchId={} \n", xid, branchId); - deleteUndoLog(xid, branchId, conn); - conn.commit(); - // 和防悬挂相关 如果根据 xid和branchId 没有查到undo_log,说明这个分支事务有异常:例如业务处理超时,导致全局事务回滚,但这时候业务undo_log并没有插入 - } else { - LOGGER.error("\n insert into undo_log xid={},branchId={} \n", xid, branchId); - insertUndoLogWithGlobalFinished(xid, branchId, UndoLogParserFactory.getInstance(), conn); - conn.commit(); - } - return; - } catch (Throwable e) { - throw new BranchTransactionException(BranchRollbackFailed_Retriable, String - .format("Branch session rollback failed and try again later xid = %s branchId = %s %s", xid,branchId, e.getMessage()), e); - } - } -} -``` -有以下几个注意点: -1. 回滚时不考虑数据源代理问题,最终都是使用`TargetConnection` -2. 设置atuoCommit为false,即需要手动提交事务 -3. 根据xid和branchId查询undo_log时加了`for update`,也就是说,这个事务会持有这条undo_log的锁直到所有回滚操作都完成,因为完成之后才会 - - -# 多层代理问题 -数据源多层代理会导致的几个问题在文章开头的时候已经提到过了,重点分析一下为什么会造成以上问题: - - -## 对分支事务提交的影响 -先分析一下,如果使用双层代理会发生什么?我们从两个方面来分析:`业务SQL`和`undo_log` -1. 业务SQL -``` -PreparedStatementProxy1.executeUpdate => -statementCallback#executeUpdate(PreparedStatementProxy2#executeUpdate) => -PreparedStatement#executeUpdate -``` -好像没啥影响,就是多绕了一圈,最终还是通过`PreparedStatement`执行 - - -2. undo_log -``` -ConnectionProxy1#getTargetConnection -> -ConnectionProxy2#prepareStatement -> -PreparedStatementProxy2#executeUpdate -> -PreparedStatement#executeUpdate(原生的undo_log写入,在此之前会对为该 undo_log 生成 undo_log2(即 undo_log 的 undo_log)) -> -ConnectionProxy2#commit -> -ConnectionProxy2#processGlobalTransactionCommit(写入undo_log2) -> -ConnectionProxy2#getTargetConnection -> -TargetConnection#prepareStatement -> -PreparedStatement#executeUpdate -``` - - -## 对分支事务回滚的影响 ->在事务回滚之后,为何undo_log没有被删除呢? - -其实并不是没有被删除。前面已经说过,双层代理会导致`undo_log`被当作分支事务来处理,所以也会为该 `undo_log`生成一个undo_log(假设为`undo_log2`),而`undo_log2`生成的有问题(其实也没问题,就应该这样生成),从而导致回滚时会将`业务表关联的undo_log`也一起删除,最终导致`业务表对应的事务分支`回滚时发现undo_log不存在,从而又多生成一条状态为为1的undo_log - -**回滚之前** -``` -// undo_log -84 59734070967644161 172.16.120.59:23004:59734061438185472 serializer=jackson 1.1KB 0 -85 59734075254222849 172.16.120.59:23004:59734061438185472 serializer=jackson 4.0KB 0 - -// branch_table -59734070967644161 172.16.120.59:23004:59734061438185472 jdbc:mysql://172.16.248.10:3306/tuya_middleware -59734075254222849 172.16.120.59:23004:59734061438185472 jdbc:mysql://172.16.248.10:3306/tuya_middleware - -// lock_table -jdbc:mysql://xx^^^seata_storage^^^1 59734070967644161 jdbc:mysql://172.16.248.10:3306/tuya_middleware seata_storage 1 -jdbc:mysql://xx^^^undo_log^^^84 59734075254222849 jdbc:mysql://172.16.248.10:3306/tuya_middleware undo_log 84 -``` - -**回滚之后** -``` -// 生成了一条状态为1的undo_log,对应的日志为: undo_log added with GlobalFinished -86 59734070967644161 172.16.120.59:23004:59734061438185472 serializer=jackson 1.0Byte 1 -``` - - -### 问题分析 -1. 根据xid和branchId找到对应的undo_log日志 -2. 对undo_log进行解析,主要就是解析它的`rollback_info`字段,`rollback_info`解析出来就是一个`SQLUndoLog集合`,每条`SQLUndoLog`对应着一个操作,里面包含了该操作的前后的快照,然后执行对应的回滚 -3. 根据xid和branchId删除undo_log日志 - -因为双层代理问题,导致一条undo_log变成了一个分支事务,所以发生回滚时,我们也需要对undo_log分支事务进行回滚: -1、首先根据xid和branchId找到对应的`undo_log`并解析其`rollback_info`属性,这里解析出来的rollback_info包含了两条`SQLUndoLog`。为什么有两条? ->仔细想想也可以可以理解,第一层代理针对`seata_storage`的操作,放到缓存中,本来执行完之后是需要清掉的,但因为这里是双层代理,所以这时候这个流程并没有结束。轮到第二层代理对`undo_log`操作时,将该操作放到缓存中,此时缓存中有两个操作,分别为`seata_storage的UPDATE` 和 `undo_log的INSERT`。所以这也就很好理解为什么针对`undo_log操作`的那条undo_log格外大(4KB),因为它的`rollback_info`包含了两个操作。 - -有一点需要注意的是,第一条`SQLUndoLog`对应的after快照,里面的branchId=`59734070967644161` pk=`84`, 即 `seata_storage分支对应的branchId` 和 `seata_storage对应的undo_log PK`。也就是说,undo_log回滚时候 把`seata_storage对应的undo_log`删掉了。 -那undo_log本身对应的undo_log 如何删除呢?在接下来的逻辑中会根据xid和branchId删除 - -2、解析第一条`SQLUndoLog`,此时对应的是`undo_log的INSERT`操作,所以其对应的回滚操作是`DELETE`。因为`undo_log`此时被当作了业务表。所以这一步会将`59734075254222849`对应的undo_log删除,**但这个其实是业务表对应的对应的`undo_log`** - -3、解析第二条`SQLUndoLog`,此时对应的是`seata_storage的UPDATE`操作,这时会通过快照将`seata_storage`对应的记录恢复 - -4、根据xid和branchId删除undo_log日志,这里删除的是`undo_log 的 undo_log , 即 undo_log2`。所以,执行到这里,两条undo_log就已经被删除了 - -5、接下来回滚`seata_storage`,因为这时候它对应的undo_log已经在步骤2删掉了,所以此时查不到undo_log,然后重新生成一条`status == 1 的 undo_log` - - -# 案例分析 - -## 背景 -1、配置了三个数据源: 两个物理数据源、一个逻辑数据源,但是两个物理数据源对应的连接地址是一样的。这样做有意思吗? -``` -@Bean("dsMaster") -DynamicDataSource dsMaster() { - return new DynamicDataSource(masterDsRoute); -} - -@Bean("dsSlave") -DynamicDataSource dsSlave() { - return new DynamicDataSource(slaveDsRoute); -} - -@Primary -@Bean("masterSlave") -DataSource masterSlave(@Qualifier("dsMaster") DataSource dataSourceMaster, - @Qualifier("dsSlave") DataSource dataSourceSlave) throws SQLException { - Map dataSourceMap = new HashMap<>(2); - //主库 - dataSourceMap.put("dsMaster", dataSourceMaster); - //从库 - dataSourceMap.put("dsSlave", dataSourceSlave); - // 配置读写分离规则 - MasterSlaveRuleConfiguration masterSlaveRuleConfig = new MasterSlaveRuleConfiguration( - "masterSlave", "dsMaster", Lists.newArrayList("dsSlave") - ); - Properties shardingProperties = new Properties(); - shardingProperties.setProperty("sql.show", "true"); - shardingProperties.setProperty("sql.simple", "true"); - // 获取数据源对象 - DataSource dataSource = MasterSlaveDataSourceFactory.createDataSource(dataSourceMap, masterSlaveRuleConfig, shardingProperties); - log.info("datasource initialized!"); - return dataSource;˚ -} -``` -![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d3e05c8fc0294a8caf4d0883a4676750~tplv-k3u1fbpfcp-watermark.image) - -2、开启seata的数据源动态代理,根据seata的数据源代理逻辑可以知道,最终会生成三个代理数据源,原生数据源和代理数据源的关系缓存在`DataSourceProxyHolder.dataSourceProxyMap`中,假如原生数据源和代理数据源对应的关系如下: -``` -dsMaster(DynamicDataSource) => dsMasterProxy(DataSourceProxy) -dsSlave(DynamicDataSource) => dsSlaveProxy(DataSourceProxy) -masterSlave(MasterSlaveDataSource) => masterSlaveProxy(DataSourceProxy) -``` -所以,最终在IOC容器中存在的数据源是这三个: dsMasterProxy 、 dsSlaveProxy 、 masterSlaveProxy 。根据@Primary的特性可以知道,当我们从容器中获取一个DataSource的时候,默认返回的就是代理数据源 masterSlaveProxy - - ->对shardingjdbc没有具体的研究过,只是根据debug时看到的代码猜测它的工作机制,又不对的地方,还请大佬指出来 - -`masterSlaveProxy`可以看成是`被 DataSourceProxy 包装后的 MasterSlaveDataSource`。我们可以大胆的猜测`MasterSlaveDataSource`并不是一个物理数据源,而是一个逻辑数据源,可以简单的认为里面包含了路由的逻辑。当我们获取一个连接时,会通过里面的路由规则选择到具体的物理数据源,然后通过该物理数据源获取一个真实的连接。 -路由规则应该可以自己定义,根据debug时观察到的现象,默认的路由规则应该是: - 1. 针对select 读操作,会路由到从库,即我们的 dsSlave - 2. 针对update 写操作,会路由到主库,即我们的 dsMaster - -3、每个DataSourceProxy在初始化的时候,会解析该真实DataSource的连接地址,然后将该`连接地址和DataSourceProxy本身`维护`DataSourceManager.dataSourceCache`中。`DataSourceManager.dataSourceCache`有一个作用是用于回滚:回滚时根据连接地址找到对应的`DataSourceProxy`,然后基于该`DataSourceProxy`做回滚操作。 -但我们可以发现这个问题,这三个数据源解析出来的连接地址是一样的,也就是key重复了,所以在`DataSourceManager.dataSourceCache`中中,当连接地相同时,后注册的数据源会覆盖已存在的。即: `DataSourceManager.dataSourceCache`最终存在的是`masterSlaveProxy`,也就是说,最终会通过`masterSlaveProxy`进行回滚,这点很重要。 - -4、涉及到的表:很简单,我们期待的就一个业务表`seata_account`,但因为重复代理问题,导致seata将undo_log也当成了一个业务表 - 1. seata_account - 2. undo_log - -好了,这里简单介绍一下背景,接下来进入Seata环节 - - -## 需求 -我们的需求很简单,就是在分支事务里面执行一条简单的update操作,更新`seata_account`的count值。在更新完之后,手动抛出一个异常,触发全局事务的回滚。 -为了更便于排查问题,减少干扰,我们全局事务中就使用一个分支事务,没有其它分支事务了。SQL如下: -``` -update seata_account set count = count - 1 where id = 100; -``` - -## 问题现象 - -Client:在控制台日志中,不断重复打印以下日志 -1. 以上日志打印的间隔为20s,而我查看了数据库的`innodb_lock_wait_timeout`属性值,刚好就是20,说明每次回滚请求过来的时候,都因为获取锁超时(20)而回滚失败 -2. 为什么会没过20s打印一次?因为Server端会有定时处理回滚请求 - -``` -// 分支事务开始回滚 -Branch rollback start: 172.16.120.59:23004:59991911632711680 59991915571163137 jdbc:mysql://172.16.248.10:3306/tuya_middleware - -// undo_log事务分支 原始操作对应是 insert, 所以其回滚为 delete -undoSQL undoSQL=DELETE FROM undo_log WHERE id = ? , PK=[[id,139]] -// 因为第一层代理对应的操作也在上下文中,undo_log分支事务 提交时候, 对应的undo_log包含两个操作 -undoSQL undoSQL=UPDATE seata_account SET money = ? WHERE id = ? , PK=[[id,1]] - -// 该分支事务回滚完成之后,再删除该分支事务的对应的 undo_log -delete from undo_log where xid=172.16.120.59:23004:59991911632711680 AND branchId=59991915571163137 - -// 抛出异常,提示回滚失败,失败原因是`Lock wait timeout exceeded`, 在根据xid和branchId删除undo_log时失败,失败原因是获取锁超时,说明此时有另一个操作持有该记录的锁没有释放 -branchRollback failed. branchType:[AT], xid:[172.16.120.59:23004:59991911632711680], branchId:[59991915571163137], resourceId:[jdbc:mysql://172.16.248.10:3306/tuya_middleware], applicationData:[null]. reason:[Branch session rollback failed and try again later xid = 172.16.120.59:23004:59991911632711680 branchId = 59991915571163137 Lock wait timeout exceeded; try restarting transaction] -``` - -Server:每20s打印以下日志,说明server在不断的重试发送回滚请求 -``` -Rollback branch transaction fail and will retry, xid = 172.16.120.59:23004:59991911632711680 branchId = 59991915571163137 -``` - -在该过程中,涉及到的SQL大概如下: -```sql -1. SELECT * FROM undo_log WHERE branch_id = ? AND xid = ? FOR UPDATE slaveDS -2. SELECT * FROM undo_log WHERE (id ) in ( (?) ) slaveDS -3. DELETE FROM undo_log WHERE id = ? masterDS -4. SELECT * FROM seata_account WHERE (id ) in ( (?) ) masterDS -5. UPDATE seata_account SET money = ? WHERE id = ? masterDS -6. DELETE FROM undo_log WHERE branch_id = ? AND xid = ? masterDS -``` - - -此时查看数据库的 事务情况、锁情况 、锁等待关系 -1、查当前正在执行的事务 -```sql -SELECT * FROM information_schema.INNODB_TRX; -``` -![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1d9852b91a9949f781e1f90bffe95fbf~tplv-k3u1fbpfcp-watermark.image) - -2、查当前锁情况 -```sql -SELECT * FROM information_schema.INNODB_LOCKs; -``` -![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1a29748a3af34e7c90e3aa7cb78564bc~tplv-k3u1fbpfcp-watermark.image) - -3、查当前锁等待关系 -```sql -SELECT * FROM information_schema.INNODB_LOCK_waits; - -SELECT - block_trx.trx_mysql_thread_id AS 已经持有锁的sessionID, - request_trx.trx_mysql_thread_id AS 正在申请锁的sessionID, - block_trx.trx_query AS 已经持有锁的SQL语句, - request_trx.trx_query AS 正在申请锁的SQL语句, - waits.blocking_trx_id AS 已经持有锁的事务ID, - waits.requesting_trx_id AS 正在申请锁的事务ID, - waits.requested_lock_id AS 锁对象的ID, - locks.lock_table AS lock_table, -- 锁对象所锁定的表 - locks.lock_type AS lock_type, -- 锁类型 - locks.lock_mode AS lock_mode -- 锁模式 -FROM - information_schema.innodb_lock_waits AS waits - INNER JOIN information_schema.innodb_trx AS block_trx ON waits.blocking_trx_id = block_trx.trx_id - INNER JOIN information_schema.innodb_trx AS request_trx ON waits.requesting_trx_id = request_trx.trx_id - INNER JOIN information_schema.innodb_locks AS locks ON waits.requested_lock_id = locks.lock_id; -``` -![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4ca5b50cab534a69a49c3e470518e3b6~tplv-k3u1fbpfcp-watermark.image) - - - -1. 涉及到到记录为 `branch_id = 59991915571163137 AND xid = 172.16.120.59:23004:59991911632711680` -2. 事务ID`1539483284`持有该记录的锁,但是它对应的SQL为空,那应该是在等待commit -3. 事务ID`1539483286`在尝试获取该记录的锁,但从日志可以发现,它一直锁等待超时 - -大概可以猜测是 `select for update` 和 `delete from undo ...` 发生了冲突。根据代码中的逻辑,这两个操作应该是放在一个事务中提交了,为什么被分开到两个事务了? - - -## 问题分析 -结合上面的介绍的回滚流程看看我们这个例子在回滚时会发生什么 -1. 先获取数据源,此时dataSourceProxy.getPlainConnection()获取到的是`MasterSlaveDataSource`数据源 -2. 在`select for update`操作的时候,通过`MasterSlaveDataSource`获取一个`Connection`,前面说到过`MasterSlaveDataSource`是一个逻辑数据源,里面有路由逻辑,根据上面介绍的,这时候拿到的是`dsSlave`的`Connection` -3. 在执行`delete from undo ...`操作的时候,这时候拿到的是`dsMaster`的`Connection` -4. 虽然`dsSlave`和`dsMaster`对应的是相同的地址,但此时获取到的肯定是不同的连接,所以此时两个操作肯定是分布在两个事务中 -5. 执行`select for update`的事务,会一直等待直到删除undo_log完成才会提交 -6. 执行`delete from undo ...`的事务,会一直等待`select for update`的事务释放锁 -7. 典型的死锁问题 - -## 验证猜想 -我尝试用了两个方法验证这个问题: -1. 修改Seata代码,将`select for update`改成`select`,此时在查询undo_log就不需要持有该记录的锁,也就不会造成死锁 - - -2. 修改数据源代理逻辑,这才是问题的关键,该问题主要原因不是`select for update`。在此之前多层代理问题已经产生,然后才会造成死锁问题。从头到尾我们就不应该对`masterSlave`数据源进行代理。它只是一个逻辑数据源,为什么要对它进行代理呢?如果代理`masterSlave`,就不会造成多层代理问题,也就不会造成删除undo_log时的死锁问题 - - -## 最终实现 -`masterSlave`也是一个`DataSource`类型,该如何仅仅对`dsMaster` 和 `dsSlave` 代理而不对`masterSlave`代理呢?观察`SeataAutoDataSourceProxyCreator#shouldSkip`方法,我们可以通过EnableAutoDataSourceProxy注解的`excludes`属性解决这个问题 -``` -@Override -protected boolean shouldSkip(Class beanClass, String beanName) { - return SeataProxy.class.isAssignableFrom(beanClass) || - DataSourceProxy.class.isAssignableFrom(beanClass) || - !DataSource.class.isAssignableFrom(beanClass) || - Arrays.asList(excludes).contains(beanClass.getName()); -} -``` -即: 将数据源自动代理关闭,然后在启动类加上这个注解 -```` -@EnableAutoDataSourceProxy(excludes = {"org.apache.shardingsphere.shardingjdbc.jdbc.core.datasource.MasterSlaveDataSource"}) -```` - - -# 自动代理在新版本中的优化 -因为`Seata 1.4.0`还没有正式发布,我目前看的是`1.4.0-SNAPSHOT`版本的代码,即当前时间`ddevelop`分支最新的代码 - -## 代码改动 -主要改动如下,一些小的细节就不过多说明了: -1. `DataSourceProxyHolder`调整 -2. `DataSourceProxy`调整 -3. `SeataDataSourceBeanPostProcessor`新增 - -### DataSourceProxyHolder -在这个类改动中,最主要是其`putDataSource`方法的改动 -```java -public SeataDataSourceProxy putDataSource(DataSource dataSource, BranchType dataSourceProxyMode) { - DataSource originalDataSource; - if (dataSource instanceof SeataDataSourceProxy) { - SeataDataSourceProxy dataSourceProxy = (SeataDataSourceProxy) dataSource; - // 如果是代理数据源,并且和当前应用配置的数据源代理模式(AT/XA)一样, 则直接返回 - if (dataSourceProxyMode == dataSourceProxy.getBranchType()) { - return (SeataDataSourceProxy)dataSource; - } - - // 如果是代理数据源,和当前应用配置的数据源代理模式(AT/XA)不一样,则需要获取其TargetDataSource,然后为其创建一个代理数据源 - originalDataSource = dataSourceProxy.getTargetDataSource(); - } else { - originalDataSource = dataSource; - } - - // 如果有必要,基于 TargetDataSource 创建 代理数据源 - return this.dataSourceProxyMap.computeIfAbsent(originalDataSource, - BranchType.XA == dataSourceProxyMode ? DataSourceProxyXA::new : DataSourceProxy::new); -} -``` -`DataSourceProxyHolder#putDataSource`方法主要在两个地方被用到:一个是在`SeataAutoDataSourceProxyAdvice`切面中;一个是在`SeataDataSourceBeanPostProcessor`中。 -这段判断为我们解决了什么问题?数据源多层代理问题。在开启了数据源自动代理的前提下,思考以下场景: -1. 如果我们在项目中手动注入了一个`DataSourceProxy`,这时候在切面调用`DataSourceProxyHolder#putDataSource`方法时会直接返回该`DataSourceProxy`本身,而不会为其再创建一个`DataSourceProxy` -2. 如果我们在项目中手动注入了一个`DruidSource`,这时候在切面调用`DataSourceProxyHolder#putDataSource`方法时会为其再创建一个`DataSourceProxy`并返回 - -这样看好像问题已经解决了,有没有可能会有其它的问题呢?看看下面的代码 -```java -@Bean -public DataSourceProxy dsA(){ - return new DataSourceProxy(druidA) -} - -@Bean -public DataSourceProxy dsB(DataSourceProxy dsA){ - return new DataSourceProxy(dsA) -} -``` -1. 这样写肯定是不对,但如果他就要这样写你也没办法 -2. `dsA`没什么问题,但`dsB`还是会产生双层代理的问题,因为此时`dsB 的 TargetDataSource`是`dsA` -3. 这就涉及到`DataSourceProxy`的改动 - -### DataSourceProxy -```java -public DataSourceProxy(DataSource targetDataSource, String resourceGroupId) { - // 下面这个判断,保证了在我们传入一个DataSourceProxy的时候,也不会产生双层代理问题 - if (targetDataSource instanceof SeataDataSourceProxy) { - LOGGER.info("Unwrap the target data source, because the type is: {}", targetDataSource.getClass().getName()); - targetDataSource = ((SeataDataSourceProxy) targetDataSource).getTargetDataSource(); - } - this.targetDataSource = targetDataSource; - init(targetDataSource, resourceGroupId); -} -``` - -### SeataDataSourceBeanPostProcessor -```java -public class SeataDataSourceBeanPostProcessor implements BeanPostProcessor { - private static final Logger LOGGER = LoggerFactory.getLogger(SeataDataSourceBeanPostProcessor.class); - - ...... - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof DataSource) { - //When not in the excludes, put and init proxy. - if (!excludes.contains(bean.getClass().getName())) { - //Only put and init proxy, not return proxy. - DataSourceProxyHolder.get().putDataSource((DataSource) bean, dataSourceProxyMode); - } - - //If is SeataDataSourceProxy, return the original data source. - if (bean instanceof SeataDataSourceProxy) { - LOGGER.info("Unwrap the bean of the data source," + - " and return the original data source to replace the data source proxy."); - return ((SeataDataSourceProxy) bean).getTargetDataSource(); - } - } - return bean; - } -} -``` -1. `SeataDataSourceBeanPostProcessor`实现了`BeanPostProcessor`接口,在一个bean初始化后,会执行`BeanPostProcessor#postProcessAfterInitialization`方法。也就是说,在`postProcessAfterInitialization`方法中,这时候的bean已经是可用状态了 -2. 为什么要提供这么一个类呢?从它的代码上来看,仅仅是为了再bean初始化之后,为数据源初始化对应的`DataSourceProxy`,但为什么要这样做呢? ->因为有些数据源在应用启动之后,可能并不会初始化(即不会调用数据源的相关方法)。如果没有提供`SeataDataSourceBeanPostProcessor`类,那么就只有在`SeataAutoDataSourceProxyAdvice`切面中才会触发`DataSourceProxyHolder#putDataSource`方法。假如有一个客户端在回滚的时候宕机了,在重启之后,Server端通过定时任务向其派发回滚请求,这时候客户端需要先根据`rsourceId`(连接地址)找到对应的`DatasourceProxy`。但如果在此之前客户端还没有主动触发数据源的相关方法,就不会进入`SeataAutoDataSourceProxyAdvice`切面逻辑,也就不会为该数据源初始化对应的`DataSourceProxy`,从而导致回滚失败 - -## 多层代理总结 -通过上面的分析,我们大概已经知道了seata在避免多层代理上的一些优化,但其实还有一个问题需要注意:**逻辑数据源的代理** -![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6910095aadab436eaffe03a752e44240~tplv-k3u1fbpfcp-watermark.image) - -这时候的调用关系为: `masterSlaveProxy -> masterSlave -> masterproxy/slaveProxy -> master/slave` - -此时可以通过`excludes`属性排除逻辑数据源,从而不为其创建数据源代理。 - - -总结一下: -1. 在为`数据源`初始化对应的`DataSourceProxy`时,判断是否有必要为其创建对应的`DataSourceProxy`,如果本身就是`DataSourceProxy`,就直接返回 -2. 针对一些`数据源`手动注入的情况,为了避免一些人为误操作的导致的多层代理问题,在`DataSourceProxy`构造函数中添加了判断,`如果入参TragetDatasource本身就是一个DataSourceProxy, 则获取其target属性作为新DataSourceProxy的tragetDatasource` -3. 针对一些其它情况,比如**逻辑数据源代理问题**,通过`excludes`属性添加排除项,这样可以避免为逻辑数据源创建`DataSourceProxy` - - -# 全局事务和本地事务使用建议 -有一个问题,如果在一个方法里涉及到多个DB操作,比如涉及到3条update操作,我们需不需在这个方法使用spring中的`@Transactional`注解?针对这个问题,我们分别从两个角度考虑:不使用`@Transactional`注解 和 使用`@Transactional`注解 - - -## 不使用`@Transactional`注解 -1. 在提交阶段,因为该分支事务有3条update操作,每次执行update操作的时候,都会通过数据代理向TC注册一个分支事务,并为其生成对应的undo_log,最终3个update操作被当作3个分支事务来处理 -2. 在回滚阶段,需要回滚3个分支事务 -3. 数据的一致性通过seata全局事务来保证 - -## 使用`@Transactional`注解 -1. 在提交阶段,3个update操作被当作一个分支事务来提交,所以最终只会注册一个分支事务 -2. 在回滚阶段,需要回滚1个分支事务 -3. 数据的一致性:这3个update的操作通过本地事务的一致性保证;全局一致性由seata全局事务来保证。此时3个update仅仅是一个分支事务而已 - -## 结论 -通过上面的对比,答案是显而易见的,合理的使用本地事务,可以大大的提升全局事务的处理速度。上面仅仅是3个DB操作,如果一个方法里面涉及到的DB操作更多呢,这时候两种方式的差别是不是更大呢? - - - -最后,感谢@FUNKYE大佬为我解答了很多问题并提供了宝贵建议! +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-dsproxy-deadlock.md b/blog/seata-dsproxy-deadlock.md index c684e39d06..e872b67af1 100644 --- a/blog/seata-dsproxy-deadlock.md +++ b/blog/seata-dsproxy-deadlock.md @@ -1,320 +1 @@ ---- -title: ConcurrentHashMap导致的Seata死锁问题 -keywords: [Seata、动态数据源、DataSource、ConcurrentHashMap、computeIfAbsent] -description: 本文主要介绍了一个线上问题,因ConcurrentHashMap的Bug而导致的Seata动态数据源代理死锁 -author: 罗小勇 -date: 2021/03/13 ---- - - -# 背景介绍 -1. seata版本:1.4.0,但1.4以下的所有版本也都有这个问题 -2. 问题描述:在一个全局事务中,一个分支事务上的纯查询操作突然卡住了,没有任何反馈(日志/异常),直到消费端RPC超时 - -![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/03a7f737b56e45b4b74e662033ec74f6~tplv-k3u1fbpfcp-watermark.image) - -# 问题排查 -1. 整个流程在一个全局事务中,消费者和提供者可以看成是全局事务中的两个分支事务,消费者 --> 提供者 -2. 消费者先执行本地的一些逻辑,然后向提供者发送RPC请求,确定消费者发出了请求已经并且提供者接到了请求 -3. 提供者先打印一条日志,然后执行一条纯查询SQL,如果SQL正常执行会打印日志,但目前的现象是只打印了执行SQL前的那条日志,而没有打印任何SQL相关的日志。找DBA查SQL日志,发现该SQL没有执行 -4. 确定了该操作只是全局事务下的一个纯查询操作,在该操作之前,全局事务中的整体流程完全正常 -5. 其实到这里现象已经很明显了,不过当时想法没转变过来,一直关注那条查询SQL,总在想就算查询超时等原因也应该抛出异常啊,不应该什么都没有。DBA都找不到查询记录,那是不是说明SQL可能根本就没执行啊,而是在执行SQL前就出问题了,比如代理? -6. 借助arthas的watch命令,一直没有东西输出。第一条日志的输出代表这个方法一定执行了,迟迟没有结果输出说明当前请求卡住了,为什么卡住了呢? -7. 借助arthas的thread命令 `thread -b` 、`thread -n`,就是要找出当前最忙的线程。这个效果很好,有一个线程CPU使用率`92%`,并且因为该线程导致其它20多个Dubbo线程`BLOCKED`了。堆栈信息如下 -8. 分析堆栈信息,已经可以很明显的发现和seata相关的接口了,估计和seata的数据源代理有关;同时发现CPU占用最高的那个线程卡在了`ConcurrentHashMap#computeIfAbsent`方法中。难道`ConcurrentHashMap#computeIfAbsent`方法有bug? -9. 到这里,问题的具体原因我们还不知道,但应该和seata的数据源代理有点关系,具体原因我们需要分析业务代码和seata代码 - -![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/faac0be0982e45a7a43b335e8f8b44bf~tplv-k3u1fbpfcp-watermark.image) - -# 问题分析 - -### ConcurrentHashMap#computeIfAbsent -这个方法确实有可能出问题:如果两个key的hascode相同,并且在对应的mappingFunction中又进行了computeIfAbsent操作,则会导致死循环,具体分析参考这篇文章:https://juejin.cn/post/6844904191077384200 - -### Seata数据源自动代理 -相关内容之前有分析过,我们重点来看看以下几个核心的类: -1. SeataDataSourceBeanPostProcessor -2. SeataAutoDataSourceProxyAdvice -3. DataSourceProxyHolder - -##### SeataDataSourceBeanPostProcessor -`SeataDataSourceBeanPostProcessor`是`BeanPostProcessor`实现类,在`postProcessAfterInitialization`方法(即Bean初始化之后)中,会为业务方配置的数据源创建对应的`seata代理数据源` - -```java -public class SeataDataSourceBeanPostProcessor implements BeanPostProcessor { - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof DataSource) { - //When not in the excludes, put and init proxy. - if (!excludes.contains(bean.getClass().getName())) { - //Only put and init proxy, not return proxy. - DataSourceProxyHolder.get().putDataSource((DataSource) bean, dataSourceProxyMode); - } - //If is SeataDataSourceProxy, return the original data source. - if (bean instanceof SeataDataSourceProxy) { - LOGGER.info("Unwrap the bean of the data source," + - " and return the original data source to replace the data source proxy."); - return ((SeataDataSourceProxy) bean).getTargetDataSource(); - } - } - return bean; - } -} -``` - -##### SeataAutoDataSourceProxyAdvice -`SeataAutoDataSourceProxyAdvice`是一个MethodInterceptor,seata中的`SeataAutoDataSourceProxyCreator`会针对`DataSource类型的Bean`创建动态代理对象,代理逻辑就是`SeataAutoDataSourceProxyAdvice#invoke`逻辑。即:执行`数据源AOP代理对象`的相关方法时候,会经过其`invoke`方法,在`invoke`方法中再根据当原生数据源,找到对应的`seata代理数据源`,最终达到执行`seata代理数据源`逻辑的目的 -```java -public class SeataAutoDataSourceProxyAdvice implements MethodInterceptor, IntroductionInfo { - ...... - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - if (!RootContext.requireGlobalLock() && dataSourceProxyMode != RootContext.getBranchType()) { - return invocation.proceed(); - } - Method method = invocation.getMethod(); - Object[] args = invocation.getArguments(); - Method m = BeanUtils.findDeclaredMethod(dataSourceProxyClazz, method.getName(), method.getParameterTypes()); - if (m != null) { - SeataDataSourceProxy dataSourceProxy = DataSourceProxyHolder.get().putDataSource((DataSource) invocation.getThis(), dataSourceProxyMode); - return m.invoke(dataSourceProxy, args); - } else { - return invocation.proceed(); - } - } -} -``` - -##### DataSourceProxyHolder -流程上我们已经清楚了,现在还有一个问题,如何维护`原生数据源`和`seata代理数据源`之间的关系?通过`DataSourceProxyHolder`维护,这是一个单例对象,该对象中通过一个ConcurrentHashMap维护两者的关系:`原生数据源`为key --> `seata代理数据源` 为value - -```java -public class DataSourceProxyHolder { - public SeataDataSourceProxy putDataSource(DataSource dataSource, BranchType dataSourceProxyMode) { - DataSource originalDataSource = dataSource; - ...... - return CollectionUtils.computeIfAbsent(this.dataSourceProxyMap, originalDataSource, - BranchType.XA == dataSourceProxyMode ? DataSourceProxyXA::new : DataSourceProxy::new); - } -} - - -// CollectionUtils.java -public static V computeIfAbsent(Map map, K key, Function mappingFunction) { - V value = map.get(key); - if (value != null) { - return value; - } - return map.computeIfAbsent(key, mappingFunction); -} -``` - -### 客户端数据源配置 -1. 配置了两个数据源:`DynamicDataSource`、`P6DataSource` -2. `P6DataSource`可以看成是对`DynamicDataSource`的一层包装 -3. 我们暂时不去管这个配置合不合理,现在只是单纯的基于这个数据源配置分析问题 - -```java -@Qualifier("dsMaster") -@Bean("dsMaster") -DynamicDataSource dsMaster() { - return new DynamicDataSource(masterDsRoute); -} - -@Primary -@Qualifier("p6DataSource") -@Bean("p6DataSource") -P6DataSource p6DataSource(@Qualifier("dsMaster") DataSource dataSource) { - P6DataSource p6DataSource = new P6DataSource(dsMaster()); - return p6DataSource; -} -``` - -### 分析过程 - -`假设现在大家都已经知道了 ConcurrentHashMap#computeIfAbsent 可能会产生的问题`,已知现在产生了这个问题,结合堆栈信息,我们可以知道大概哪里产生了这个问题。 - -1、`ConcurrentHashMap#computeIfAbsent`会产生这个问题的前提条件是:`两个key的hashcode相同`;`mappingFunction中对应了一个put操作`。结合我们seata的使用场景,mappingFunction对应的是`DataSourceProxy::new`,说明在DataSourceProxy的构造方法中可能会触发put操作 - -![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/00d8e13f71644c63b3bbb58c93b30e0c~tplv-k3u1fbpfcp-watermark.image) -```java -执行AOP代理数据源相关方法 => -进入SeataAutoDataSourceProxyAdvice切面逻辑 => -执行DataSourceProxyHolder#putDataSource方法 => -执行DataSourceProxy::new => -AOP代理数据源的getConnection方法 => -原生数据源的getConnection方法 => -进入SeataAutoDataSourceProxyAdvice切面逻辑 => -执行DataSourceProxyHolder#putDataSource方法 => -执行DataSourceProxy::new => -DuridDataSource的getConnection方法 -``` - -2、步骤1中说的`AOP代理数据源`和`原生数据源`分别是什么?看下面这张图 -![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3579631f58df4d17bfcd6f28ccc3fd79~tplv-k3u1fbpfcp-watermark.image) - -3、上面还说到了产生这个问题还有一个条件`两个key的hashcode相同`,但我看这两个数据源对象都没有重写`hashcode`方法,所以按理来说,这两个对象的hashcode一定是不同的。后面又再看了一遍ConcurrentHashMap这个问题,感觉`两个key的hashcode相同`这个说法是不对的,`两个key会产生hash冲突`更合理一些,这样就能解释两个hashcode不同的对象为啥会遇上这个问题了。为了证明这个,下面我给了一个例子 -```java -public class Test { - public static void main(String[] args) { - ConcurrentHashMap map = new ConcurrentHashMap(8); - Num n1 = new Num(3); - Num n2 = new Num(19); - Num n3 = new Num(20); - -// map.computeIfAbsent(n1, k1 -> map.computeIfAbsent(n3, k2 -> 200)); // 这行代码不会导致程序死循环 - map.computeIfAbsent(n1, k1 -> map.computeIfAbsent(n2, k2 -> 200)); // 这行代码会导致程序死循环 - } - - static class Num{ - private int i; - public Num(int i){ - this.i = i; - } - - @Override - public int hashCode() { - return i; - } - } -} -``` -1. 为了方便重现问题,我们重写了`Num#hashCode`方法,保证构造函数入参就是hashcode的值 -2. 创建一个ConcurrentHashMap对象,initialCapacity为8,sizeCtl计算出来的值为16,即该map中数组长度默认为16 -3. 创建对象`n1`,入参为3,即hashcode为3,计算得出其对应的数组下标为3 -4. 创建对象`n2`,入参为19,即hashcode为19,计算得出其对应的数组下标为3,此时我们可以认为`n1和n2产生了hash冲突` -5. 创建对象`n3`,入参为20,即hashcode为20,计算得出其对应的数组下标为4 -6. 执行`map.computeIfAbsent(n1, k1 -> map.computeIfAbsent(n3, k2 -> 200))`,程序正常退出:`因为n1和n3没有hash冲突,所以正常结束` -7. 执行`map.computeIfAbsent(n1, k1 -> map.computeIfAbsent(n2, k2 -> 200))`,程序正常退出:`因为n1和n2产生了hash冲突,所以陷入死循环` - - -4、在对象初始化的时候,`SeataDataSourceBeanPostProcessor`不是已经将对象对应的数据源代理初始化好了吗?为什么在`SeataAutoDataSourceProxyAdvice`中还是会创建对应的数据源代理呢? -1. 首先,`SeataDataSourceBeanPostProcessor`执行时期是晚于AOP代理对象创建的,所以在执行`SeataDataSourceBeanPostProcessor`相关方法的时候,`SeataAutoDataSourceProxyAdvice`其实应生效了 -2. `SeataDataSourceBeanPostProcessor`中向map中添加元素时,key为`AOP代理数据源`;`SeataAutoDataSourceProxyAdvice`中的`invocation.getThis()`中拿到的是`原生数据源`,所以key不相同 - -![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/747b664a0b6c4f58843947576dd0856e~tplv-k3u1fbpfcp-watermark.image) - -5、还有一个问题,`SeataAutoDataSourceProxyAdvic#invoke`方法中并没有过滤`toString、hashCode`等方法,cglib创建的代理对象默认会重写这几个方法,如果在向map中put元素的时候触发了代理对象的这些方法,此时又会重新进入`SeataAutoDataSourceProxyAdvic#invoke`切面,直到线程堆栈益处 - - - -# 问题总结 -1. 在两个key会产生hash冲突的时候,会触发`ConcurrentHashMap#computeIfAbsent`BUG,该BUG的表现就是让当前线程陷入死循环 -2. 业务反馈,该问题是偶现的,偶现的原因有两种:首先,该应用是多节点部署,但线上只有一个节点触发了该BUG(hashcode冲突),所以只有当请求打到这个节点的时候才有可能会触发该BUG;其次,因为每次重启对象地址(hashcode)都是不确定的,所以并不是每次应用重启之后都会触发,但如果一旦触发,该节点就会一直存在这个问题。有一个线程一直在死循环,并将其它尝试从map中获取代理数据源的线程阻塞了,这种现象在业务上的反馈就是请求卡住了。如果连续请求都是这样,此时业务方可能会重启服务,然后`因为重启后hash冲突不一定存在,可能重启后业务表现就正常了,但也有可能在下次重启的时候又会触发了这个BUG` -3. 当遇到这个问题时,从整个问题上来看,确实就是死锁了,因为那个死循环的线程占者锁一直不释放,导致其它操作该map的线程被BLOCK了 -4. 本质上还是因为`ConcurrentHashMap#computeIfAbsent方法可能会触发BUG`,而seata的使用场景刚好就触发了该BUG -5. 下面这个demo其实就完整的模拟了线上出问题时的场景,如下: - -```java -public class Test { - public static void main(String[] args) { - - ConcurrentHashMap map = new ConcurrentHashMap(8); - - Num n1 = new Num(3); - Num n2 = new Num(19); - - for(int i = 0; i< 20; i++){ - new Thread(()-> { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - map.computeIfAbsent(n1, k-> 200); - }).start(); - } - map.computeIfAbsent(n1, k1 -> map.computeIfAbsent(n2, k2 -> 200)); - } - - - static class Num{ - private int i; - - public Num(int i){ - this.i = i; - } - @Override - public int hashCode() { - return i; - } - } -} -``` - -![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d6134c6498fa49c4a68b2745ba0895e3~tplv-k3u1fbpfcp-watermark.image) - - -### 解决问题 -可以从两方面解决这个问题: -1. 业务方改动:P6DataSource 和 DynamicDataSource 没必要都被代理,直接代理P6DataSource就可以了,DynamicDataSource没有必要声明成一个Bean;或者通过excluds属性排除P6DataSource,这样就不会存在重复代理问题 -2. Seata完善:完善数据源代理相关逻辑 - -##### 业务方改动 -1、数据源相关的配置改成如下即可: -```java -@Primary -@Qualifier("p6DataSource") -@Bean("p6DataSource") -P6DataSource p6DataSource(@Qualifier("dsMaster") DataSource dataSource) { - P6DataSource p6DataSource = new P6DataSource(new TuYaDynamicDataSource(masterDsRoute)); - logger.warn("dsMaster={}, hashcode={}",p6DataSource, p6DataSource.hashCode()); - return p6DataSource; -} -``` - -2、或者不改变目前的数据源配置,添加excluds属性 -```java -@EnableAutoDataSourceProxy(excludes={"P6DataSource"}) -``` - -##### Seata完善 - -1、`ConcurrentHashMap#computeIfAbsent`方法改成双重检查,如下: -```java -SeataDataSourceProxy dsProxy = dataSourceProxyMap.get(originalDataSource); -if (dsProxy == null) { - synchronized (dataSourceProxyMap) { - dsProxy = dataSourceProxyMap.get(originalDataSource); - if (dsProxy == null) { - dsProxy = createDsProxyByMode(dataSourceProxyMode, originalDataSource); - dataSourceProxyMap.put(originalDataSource, dsProxy); - } - } -} -return dsProxy; -``` - -之前我想直接改`CollectionUtils#computeIfAbsent`方法,群里大佬反馈这样可能会导致数据源多次创建,确实有这个问题:如下 -```java -public static V computeIfAbsent(Map map, K key, Function mappingFunction) { - V value = map.get(key); - if (value != null) { - return value; - } - value = mappingFunction.apply(key); - return map.computeIfAbsent(key, value); -} -``` - -2、SeataAutoDataSourceProxyAdvice切面逻辑中添加一些过滤 -```java -Method m = BeanUtils.findDeclaredMethod(dataSourceProxyClazz, method.getName(), method.getParameterTypes()); -if (m != null && DataSource.class.isAssignableFrom(method.getDeclaringClass())) { - SeataDataSourceProxy dataSourceProxy = DataSourceProxyHolder.get().putDataSource((DataSource) invocation.getThis(), dataSourceProxyMode); - return m.invoke(dataSourceProxy, args); -} else { - return invocation.proceed(); -} -``` - -### 遗留问题 -在`SeataDataSourceBeanPostProcessor`和`SeataAutoDataSourceProxyAdvice`对应方法中,向map中初始化`seata数据源代理`时对应的key根本就不同,`SeataDataSourceBeanPostProcessor`中对应的key是`AOP代理数据源`;`SeataAutoDataSourceProxyAdvice`中对应的key是原生对象,此时就造成了不必要的`seata数据源代理`对象的创建。 - -针对这个问题,大家有什么好的建议?能不能为`SeataDataSourceBeanPostProcessor`指定一个order,让其在创建AOP代理对象之前生效 - - -# 原文链接 - -https://juejin.cn/post/6939041336964153352/ \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-dynamic-config-and-dynamic-disable.md b/blog/seata-dynamic-config-and-dynamic-disable.md index 4919a18ca4..e872b67af1 100644 --- a/blog/seata-dynamic-config-and-dynamic-disable.md +++ b/blog/seata-dynamic-config-and-dynamic-disable.md @@ -1,148 +1 @@ ---- -title: Seata 动态配置订阅与降级实现原理 -author: 张乘辉 -keywords: [Seata、Dynamic、Config] -description: 讲述了 Seata 支持的多个配置中心是如何适配不同的动态配置订阅以及如何实现降级功能。 -date: 2019/12/17 ---- - - -# 前言 -Seata 的动态降级需要结合配置中心的动态配置订阅功能。动态配置订阅,即通过配置中心监听订阅,根据需要读取已更新的缓存值,ZK、Apollo、Nacos 等第三方配置中心都有现成的监听器可实现动态刷新配置;动态降级,即通过动态更新指定配置参数值,使得 Seata 能够在运行过程中动态控制全局事务失效(目前只有 AT 模式有这个功能)。 - -那么 Seata 支持的多个配置中心是如何适配不同的动态配置订阅以及如何实现降级的呢?下面从源码的层面详细给大家讲解一番。 - - - -# 动态配置订阅 - -Seata 配置中心有一个监听器基准接口,它主要有一个抽象方法和 default 方法,如下: - -io.seata.config.ConfigurationChangeListener - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191216212442.png) - -该监听器基准接口主要有两个实现类型: - -1. 实现注册配置订阅事件监听器:用于实现各种功能的动态配置订阅,比如 GlobalTransactionalInterceptor 实现了 ConfigurationChangeListener,根据动态配置订阅实现的动态降级功能; -2. 实现配置中心动态订阅功能与适配:对于目前还没有动态订阅功能的 file 类型默认配置中心,可以实现该基准接口来实现动态配置订阅功能;对于阻塞订阅需要另起一个线程去执行,这时候可以实现该基准接口进行适配,还可以复用该基准接口的线程池;以及还有异步订阅,有订阅单个 key,有订阅多个 key 等等,我们都可以实现该基准接口以适配各个配置中心。 - -## Nacos 动态订阅实现 - -Nacos 有自己内部实现的监听器,因此直接直接继承它内部抽象监听器 AbstractSharedListener,实现如下: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191223212237.png) - -如上, - -- dataId:为订阅的配置属性; -- listener:配置订阅事件监听器,用于将外部传入的 listener 作为一个 wrapper,执行真正的变更逻辑。 - -值得一提的是,nacos 并没有使用 ConfigurationChangeListener 实现自己的监听配置,一方面是因为 Nacos 本身已有监听订阅功能,不需要自己再去实现;另一方面因为 nacos 属于非阻塞式订阅,不需要复用 ConfigurationChangeListener 的线程池,即无需进行适配。 - -添加订阅: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191223213347.png) - -Nacos 配置中心为某个 dataId 添加订阅的逻辑很简单,用 dataId 和 listener 创建一个 NacosListener 调用 configService#addListener 方法,把 NacosListener 作为 dataId 的监听器,dataId 就实现了动态配置订阅功能。 - -## file 动态订阅实现 - -以它的实现类 FileListener 举例子,它的实现逻辑如下: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191215151642.png) - -如上, - -- dataId:为订阅的配置属性; - -- listener:配置订阅事件监听器,用于将外部传入的 listener 作为一个 wrapper,执行真正的变更逻辑,这里特别需要注意的是,**该监听器与 FileListener 同样实现了 ConfigurationChangeListener 接口,只不过 FileListener 是用于给 file 提供动态配置订阅功能,而 listener 用于执行配置订阅事件**; - -- executor:用于处理配置变更逻辑的线程池,在 ConfigurationChangeListener#onProcessEvent 方法中用到。 - -**FileListener#onChangeEvent 方法的实现让 file 具备了动态配置订阅的功能**,它的逻辑如下: - -无限循环获取订阅的配置属性当前的值,从缓存中获取旧的值,判断是否有变更,如果有变更就执行外部传入 listener 的逻辑。 - -ConfigurationChangeEvent 用于保存配置变更的事件类,它的成员属性如下: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191215175232.png) - - - -这里的 getConfig 方法是如何感知 file 配置的变更呢?我们点进去,发现它最终的逻辑如下: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191215162713.png) - -发现它是创建一个 future 类,然后包装成一个 Runnable 放入线程池中异步执行,最后调用 get 方法阻塞获取值,那么我们继续往下看: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191215170908.png) - -allowDynamicRefresh:动态刷新配置开关; - -targetFileLastModified:file 最后更改的时间缓存。 - -以上逻辑: - -获取 file 最后更新的时间值 tempLastModified,然后对比对比缓存值 targetFileLastModified,如果 tempLastModified > targetFileLastModified,说明期间配置有更改过,这时就重新加载 file 实例,替换掉旧的 fileConfig,使得后面的操作能够获取到最新的配置值。 - -添加一个配置属性监听器的逻辑如下: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191215161103.png) - -configListenersMap 为 FileConfiguration 的配置监听器缓存,它的数据结构如下: - -```java -ConcurrentMap> configListenersMap -``` - -从数据结构上可看出,每个配置属性可关联多个事件监听器。 - -最终执行 onProcessEvent 方法,这个是监听器基准接口里面的 default 方法,它会调用 onChangeEvent 方法,即最终会调用 FileListener 中的实现。 - - - -# 动态降级 - -有了以上的动态配置订阅功能,我们只需要实现 ConfigurationChangeListener 监听器,就可以做各种各种的功能,目前 Seata 只有动态降级有用到动态配置订阅的功能。 - -在「[Seata AT 模式启动源码分析](https://mp.weixin.qq.com/s/n9MHk47zSsFQmV-gBq_P1A)」这篇文章中讲到,Spring 集成 Seata 的项目中,在 AT 模式启动时,会用 用GlobalTransactionalInterceptor 代替了被 GlobalTransactional 和 GlobalLock 注解的方法,GlobalTransactionalInterceptor 实现了 MethodInterceptor,最终会执行 invoker 方法,那么想要实现动态降级,就可以在这里做手脚。 - -- 在 GlobalTransactionalInterceptor 中加入一个成员变量: - -```java -private volatile boolean disable; -``` - -在构造函数中进行初始化赋值: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191215173221.png) - -ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION(service.disableGlobalTransaction)这个参数目前有两个功能: - -1. 在启动时决定是否开启全局事务; -2. 在开启全局事务后,决定是否降级。 - -- 实现 ConfigurationChangeListener: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191215173358.png) - -这里的逻辑简单,就是判断监听事件是否属于 ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION 配置属性,如果是,直接更新 disable 值。 - -- 接下来在 GlobalTransactionalInterceptor#invoke 中做点手脚 - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191215174155.png) - -如上,disable = true 时,不执行全局事务与全局锁。 - -- 配置中心订阅降级监听器 - -io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20191215174409.png) - -在 Spring AOP 进行 wrap 逻辑过程中,当前配置中心将订阅降级事件监听器。 - -# 作者简介 - -张乘辉,目前就职于中通科技信息中心技术平台部,担任 Java 工程师,主要负责中通消息平台与全链路压测项目的研发,热爱分享技术,微信公众号「后端进阶」作者,技术博客([https://objcoding.com/](https://objcoding.com/))博主,Seata Contributor,GitHub ID:objcoding。 \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-feature-undo-log-compress.md b/blog/seata-feature-undo-log-compress.md index 618f67f41a..e872b67af1 100644 --- a/blog/seata-feature-undo-log-compress.md +++ b/blog/seata-feature-undo-log-compress.md @@ -1,128 +1 @@ ---- -title: Seata新特性支持 -- undo_log压缩 -author: chd -keywords: [Seata, undo_log, compress] -date: 2021/05/07 ---- - -# Seata新特性支持 -- undo_log压缩 - -### 现状 & 痛点 - -对于Seata而言,是通过记录DML操作的前后的数据用于进行后续可能的回滚操作的,并且把这些数据保存到数据库的一个blob的字段里面。对于批量插入,更新,删除等操作,其影响的行数可能会比较多,拼接成一个大的字段插入到数据库,可能会带来以下问题: - -1. 超出数据库单次操作的最大写入限制(比如MySQL的max_allowed_package参数); -2. 较大的数据量带来的网络IO和数据库磁盘IO开销比较大。 - - - -### 头脑风暴 - -对于第1点的问题,可以根据业务的实际情况,调大max_allowed_package参数的限制,从而避免出现query is too large的问题;对于第2点,可以通过提高带宽和选用高性能的SSD作为数据库的存储介质。 - -以上都是通过外部方案或者加钱方案去解决的。那么有没有框架层面解决方案以解决上面的痛点? - -此时结合到以上的痛点出现的根源,在于生成的数据字段过大。为此,如果可以把对应的数据进行业务方压缩之后,再进行数据传输以及落库,理论上也可以解决上面的问题。 - - - -### 可行性分析 - -结合以上头脑风暴的内容,考虑在实际开发中,当需要进行大批量操作的时候,大多会选在较少用户操作,并发相对较低的时间点执行,此时CPU,内存等资源可以相对占用多一点以快速完成对应的操作。因此,可以通过消耗CPU资源和内存资源,来对对应的回滚的数据进行压缩,从而缩小数据传输和存储的大小。 - -此时,还需要证明以下两件事: - -1. 经过压缩之后,可以减少网络IO和数据库磁盘IO的压力,这里可以采用数据压缩+落库完成的总时间作为侧面参考指标。 -2. 经过压缩之后,数据大小跟原来比较的压缩效率有多高,这里使用压缩前后的数据大小来作为指标。 - -压缩网络用时指标测试: - -![image](https://user-images.githubusercontent.com/22959373/95567752-f55ddf80-0a55-11eb-8092-1f1d99855bdd.png) - -压缩比测试: - -![image](https://user-images.githubusercontent.com/22959373/95567834-0ad30980-0a56-11eb-9d7e-48b74babbea4.png) - -通过以上的测试结果,可以明显的看出,使用gzip或zip进行压缩的情况下,可以较大程度的减少数据库的压力和网络传输的压力,同时也可以较大幅度的减少保存的数据的大小。 - - - -### 实现 - -#### 实现思路 - -![压缩](https://user-images.githubusercontent.com/22959373/116281711-8f039900-a7bc-11eb-91f8-82afdbb9f932.png) - -#### 部分代码 - -properties配置: - -```properties -# 是否开启undo_log压缩,默认为true -seata.client.undo.compress.enable=true -# 压缩器类型,默认为zip,一般建议都是zip -seata.client.undo.compress.type=zip -# 启动压缩的阈值,默认为64k -seata.client.undo.compress.threshold=64k -``` - -判断是否开启了undo_log压缩功能以及是否达到压缩的阈值: - -```java -protected boolean needCompress(byte[] undoLogContent) { - // 1. 判断是否开启了undo_log压缩功能(1.4.2默认开启) - // 2. 判断是否达到了压缩的阈值(默认64k) - // 如果都满足返回需要对对应的undoLogContent进行压缩 - return ROLLBACK_INFO_COMPRESS_ENABLE - && undoLogContent.length > ROLLBACK_INFO_COMPRESS_THRESHOLD; -} -``` - -确定需要压缩后,对undo_log进行压缩: - -```java -// 如果需要压缩,对undo_log进行压缩 -if (needCompress(undoLogContent)) { - // 获取压缩类型,默认zip - compressorType = ROLLBACK_INFO_COMPRESS_TYPE; - // 获取对应的压缩器,并且进行压缩 - undoLogContent = CompressorFactory.getCompressor(compressorType.getCode()).compress(undoLogContent); -} -// else 不需要压缩就不需要做任何操作 -``` - -将压缩类型同步保存到数据库,供回滚时使用: - -```java -protected String buildContext(String serializer, CompressorType compressorType) { - Map map = new HashMap<>(); - map.put(UndoLogConstants.SERIALIZER_KEY, serializer); - // 保存压缩类型到数据库 - map.put(UndoLogConstants.COMPRESSOR_TYPE_KEY, compressorType.name()); - return CollectionUtils.encodeMap(map); -} -``` - -回滚时解压缩对应的信息: - -```java -protected byte[] getRollbackInfo(ResultSet rs) throws SQLException { - // 获取保存到数据库的回滚信息的字节数组 - byte[] rollbackInfo = rs.getBytes(ClientTableColumnsName.UNDO_LOG_ROLLBACK_INFO); - // 获取压缩类型 - // getOrDefault使用默认值CompressorType.NONE来兼容1.4.2之前的版本直接升级1.4.2+ - String rollbackInfoContext = rs.getString(ClientTableColumnsName.UNDO_LOG_CONTEXT); - Map context = CollectionUtils.decodeMap(rollbackInfoContext); - CompressorType compressorType = CompressorType.getByName(context.getOrDefault(UndoLogConstants.COMPRESSOR_TYPE_KEY, - CompressorType.NONE.name())); - // 获取对应的压缩器,并且解压缩 - return CompressorFactory.getCompressor(compressorType.getCode()) - .decompress(rollbackInfo); -} -``` - - - -### 结语 - -通过对undo_log的压缩,在框架层面,进一步提高Seata在处理数据量较大的时候的性能。同时,也提供了对应的开关和相对合理的默认值,既方便用户进行开箱即用,也方便用户根据实际需求进行一定的调整,使得对应的功能更适合实际使用场景。 +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-go-1.2.0.md b/blog/seata-go-1.2.0.md index a022d73950..e872b67af1 100644 --- a/blog/seata-go-1.2.0.md +++ b/blog/seata-go-1.2.0.md @@ -1,79 +1 @@ ---- -title: 生产环境可用的 seata-go 1.2.0 来了!!! -author: Seata社区 -keywords: [seata、分布式事务、golang、1.2.0] -description: 生产环境可用的 seata-go 1.2.0 来了!!! -date: 2023/06/08 ---- - -## 生产环境可用的 seata-go 1.2.0 来了!!! - -Seata 是一款开源的分布式事务解决方案,提供高性能和简单易用的分布式事务服务。 - -### 发布概览 -Seata-go 1.2.0 版本支持 XA 模式。XA 协议是由 X/Open 组织提出的分布式事务处理规范,其优点是对业务代码无侵入。当前 Seata-go 的 XA 模式支持 MySQL 数据库。至此,seata-go 已经集齐 AT、TCC、Saga 和 XA 四种事务模式,完成了与 Seata Java 的功能对齐。 XA 模式的主要功能: -- 支持了 XA 数据源代理 -- 支持了 XA 事务模式 -XA 相关的 samples 可以参考示例:https://github.com/seata/seata-go-samples/tree/main/xa - -### feature: - -- [[#467](https://github.com/seata/seata-go/pull/467)] 实现 XA 模式支持 MySQL -- [[#534](https://github.com/seata/seata-go/pull/534)] 支持 session 的负载均衡 - -### bugfix: - -- [[#540](https://github.com/seata/seata-go/pull/540)] 修复初始化 xa 模式的 bug -- [[#545](https://github.com/seata/seata-go/pull/545)] 修复 xa 模式获取 db 版本号的 bug -- [[#548](https://github.com/seata/seata-go/pull/548)] 修复启动 xa 会失败的 bug -- [[#556](https://github.com/seata/seata-go/pull/556)] 修复 xa 数据源的 bug -- [[#562](https://github.com/seata/seata-go/pull/562)] 修复提交 xa 全局事务的 bug -- [[#564](https://github.com/seata/seata-go/pull/564)] 修复提交 xa 分支事务的 bug -- [[#566](https://github.com/seata/seata-go/pull/566)] 修复使用 xa 数据源执行本地事务的 bug - -### optimize: - -- [[#523](https://github.com/seata/seata-go/pull/523)] 优化 CI 流程 -- [[#525](https://github.com/seata/seata-go/pull/456)] 将 jackson 序列化重命名为 json -- [[#532](https://github.com/seata/seata-go/pull/532)] 移除重复的代码 -- [[#536](https://github.com/seata/seata-go/pull/536)] 优化 go import 代码格式 -- [[#554](https://github.com/seata/seata-go/pull/554)] 优化 xa 模式的性能 -- [[#561](https://github.com/seata/seata-go/pull/561)] 优化 xa 模式的日志输出 - -### test: - -- [[#535](https://github.com/seata/seata-go/pull/535)] 添加集成测试 - -### doc: -- [[#550](https://github.com/seata/seata-go/pull/550)] 添加 1.2.0 版本的改动日志 - - -### contributors: - -Thanks to these contributors for their code commits. Please report an unintended omission. - -- [georgehao](https://github.com/georgehao) -- [luky116](https://github.com/luky116) -- [jasondeng1997](https://github.com/jasondeng1997) -- [106umao](https://github.com/106umao) -- [wang1309](https://github.com/wang1309) -- [iSuperCoder](https://github.com/iSuperCoder) -- [Charlie17Li](https://github.com/Charlie17Li) -- [Code-Fight](https://github.com/Code-Fight) -- [Kirhaku](https://github.com/Kirhaku) -- [Vaderkai](https://github.com/VaderKai) - -同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 - - -#### Link - -- http://github.com/seata/seata -- https://github.com/seata/seata-php -- https://github.com/seata/seata-js -- https://github.com/seata/seata-go - **Samples:** -- https://github.com/seata/seata-samples -- https://github.com/seata/seata-go-samples - **官网:** -- https://seata.io/ \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-golang-communication-mode.md b/blog/seata-golang-communication-mode.md index 89a9e39c90..e872b67af1 100644 --- a/blog/seata-golang-communication-mode.md +++ b/blog/seata-golang-communication-mode.md @@ -1,716 +1 @@ ---- -title: seata-golang 通信模型详解 -keywords: [seata,seata-golang,seata-go,getty,分布式事务] -description: 本文详细讲述 seata-golang 底层 rpc 通信的实现原理 -author: 刘晓敏 -date: 2021/01/04 ---- - -# 基于 getty 的 seata-golang 通信模型详解 - - - -作者 | 刘晓敏 于雨 - -## 一、简介 - - -Java 的世界里,大家广泛使用的一个高性能网络通信框架 netty,很多 RPC 框架都是基于 netty 来实现的。在 golang 的世界里,[getty](https://github.com/AlexStocks/getty) 也是一个类似 netty 的高性能网络通信库。getty 最初由 dubbogo 项目负责人于雨开发,作为底层通信库在 [dubbo-go](https://github.com/apache/dubbo-go) 中使用。随着 dubbo-go 捐献给 apache 基金会,在社区小伙伴的共同努力下,getty 也最终进入到 apache 这个大家庭,并改名 [dubbo-getty](https://github.com/apache/dubbo-getty) 。 - - -18 年的时候,我在公司里实践微服务,当时遇到最大的问题就是分布式事务问题。同年,阿里在社区开源他们的分布式事务解决方案,我也很快关注到这个项目,起初还叫 fescar,后来更名 seata。由于我对开源技术很感兴趣,加了很多社区群,当时也很关注 dubbo-go 这个项目,在里面默默潜水。随着对 seata 的了解,逐渐萌生了做一个 go 版本的分布式事务框架的想法。 - - -要做一个 golang 版的分布式事务框架,首要的一个问题就是如何实现 RPC 通信。dubbo-go 就是很好的一个例子摆在眼前,遂开始研究 dubbo-go 的底层 getty。 - - -## 二、如何基于 getty 实现 RPC 通信 - - -getty 框架的整体模型图如下: - - -![image.png]( https://img.alicdn.com/imgextra/i1/O1CN011TIcL01jY4JaweOfV_!!6000000004559-2-tps-954-853.png) - - -下面结合相关代码,详述 seata-golang 的 RPC 通信过程。 - - -### 1. 建立连接 - - -实现 RPC 通信,首先要建立网络连接吧,我们从 [client.go](https://github.com/apache/dubbo-getty/blob/master/client.go) 开始看起。 - - -```go -func (c *client) connect() { - var ( - err error - ss Session - ) - - for { - // 建立一个 session 连接 - ss = c.dial() - if ss == nil { - // client has been closed - break - } - err = c.newSession(ss) - if err == nil { - // 收发报文 - ss.(*session).run() - // 此处省略部分代码 - - break - } - // don't distinguish between tcp connection and websocket connection. Because - // gorilla/websocket/conn.go:(Conn)Close also invoke net.Conn.Close() - ss.Conn().Close() - } -} -``` - - -`connect()` 方法通过 `dial()` 方法得到了一个 session 连接,进入 dial() 方法: - - -``` -func (c *client) dial() Session { - switch c.endPointType { - case TCP_CLIENT: - return c.dialTCP() - case UDP_CLIENT: - return c.dialUDP() - case WS_CLIENT: - return c.dialWS() - case WSS_CLIENT: - return c.dialWSS() - } - - return nil -} -``` - - -我们关注的是 TCP 连接,所以继续进入 `c.dialTCP()` 方法: - - -```go -func (c *client) dialTCP() Session { - var ( - err error - conn net.Conn - ) - - for { - if c.IsClosed() { - return nil - } - if c.sslEnabled { - if sslConfig, err := c.tlsConfigBuilder.BuildTlsConfig(); err == nil && sslConfig != nil { - d := &net.Dialer{Timeout: connectTimeout} - // 建立加密连接 - conn, err = tls.DialWithDialer(d, "tcp", c.addr, sslConfig) - } - } else { - // 建立 tcp 连接 - conn, err = net.DialTimeout("tcp", c.addr, connectTimeout) - } - if err == nil && gxnet.IsSameAddr(conn.RemoteAddr(), conn.LocalAddr()) { - conn.Close() - err = errSelfConnect - } - if err == nil { - // 返回一个 TCPSession - return newTCPSession(conn, c) - } - - log.Infof("net.DialTimeout(addr:%s, timeout:%v) = error:%+v", c.addr, connectTimeout, perrors.WithStack(err)) - <-wheel.After(connectInterval) - } -} -``` - - -至此,我们知道了 getty 如何建立 TCP 连接,并返回 TCPSession。 - - -### 2. 收发报文 - - -那它是怎么收发报文的呢,我们回到 connection 方法接着往下看,有这样一行 `ss.(*session).run()`,在这行代码之后代码都是很简单的操作,我们猜测这行代码运行的逻辑里面一定包含收发报文的逻辑,接着进入 `run()` 方法: - - -```go -func (s *session) run() { - // 省略部分代码 - - go s.handleLoop() - go s.handlePackage() -} -``` - - -这里起了两个 goroutine,`handleLoop` 和 `handlePackage`,看字面意思符合我们的猜想,进入 `handleLoop()` 方法: - - -```go -func (s *session) handleLoop() { - // 省略部分代码 - - for { - // A select blocks until one of its cases is ready to run. - // It choose one at random if multiple are ready. Otherwise it choose default branch if none is ready. - select { - // 省略部分代码 - - case outPkg, ok = <-s.wQ: - // 省略部分代码 - - iovec = iovec[:0] - for idx := 0; idx < maxIovecNum; idx++ { - // 通过 s.writer 将 interface{} 类型的 outPkg 编码成二进制的比特 - pkgBytes, err = s.writer.Write(s, outPkg) - // 省略部分代码 - - iovec = append(iovec, pkgBytes) - - //省略部分代码 - } - // 将这些二进制比特发送出去 - err = s.WriteBytesArray(iovec[:]...) - if err != nil { - log.Errorf("%s, [session.handleLoop]s.WriteBytesArray(iovec len:%d) = error:%+v", - s.sessionToken(), len(iovec), perrors.WithStack(err)) - s.stop() - // break LOOP - flag = false - } - - case <-wheel.After(s.period): - if flag { - if wsFlag { - err := wsConn.writePing() - if err != nil { - log.Warnf("wsConn.writePing() = error:%+v", perrors.WithStack(err)) - } - } - // 定时执行的逻辑,心跳等 - s.listener.OnCron(s) - } - } - } -} -``` - - -通过上面的代码,我们不难发现,`handleLoop()` 方法处理的是发送报文的逻辑,RPC 需要发送的消息首先由 `s.writer` 编码成二进制比特,然后通过建立的 TCP 连接发送出去。这个 `s.writer` 对应的 Writer 接口是 RPC 框架必须要实现的一个接口。 - - -继续看 `handlePackage()` 方法: - - -```go -func (s *session) handlePackage() { - // 省略部分代码 - - if _, ok := s.Connection.(*gettyTCPConn); ok { - if s.reader == nil { - errStr := fmt.Sprintf("session{name:%s, conn:%#v, reader:%#v}", s.name, s.Connection, s.reader) - log.Error(errStr) - panic(errStr) - } - - err = s.handleTCPPackage() - } else if _, ok := s.Connection.(*gettyWSConn); ok { - err = s.handleWSPackage() - } else if _, ok := s.Connection.(*gettyUDPConn); ok { - err = s.handleUDPPackage() - } else { - panic(fmt.Sprintf("unknown type session{%#v}", s)) - } -} -``` - - -进入 `handleTCPPackage()` 方法: - - -```go -func (s *session) handleTCPPackage() error { - // 省略部分代码 - - conn = s.Connection.(*gettyTCPConn) - for { - // 省略部分代码 - - bufLen = 0 - for { - // for clause for the network timeout condition check - // s.conn.SetReadTimeout(time.Now().Add(s.rTimeout)) - // 从 TCP 连接中收到报文 - bufLen, err = conn.recv(buf) - // 省略部分代码 - - break - } - // 省略部分代码 - - // 将收到的报文二进制比特写入 pkgBuf - pktBuf.Write(buf[:bufLen]) - for { - if pktBuf.Len() <= 0 { - break - } - // 通过 s.reader 将收到的报文解码成 RPC 消息 - pkg, pkgLen, err = s.reader.Read(s, pktBuf.Bytes()) - // 省略部分代码 - - s.UpdateActive() - // 将收到的消息放入 TaskQueue 供 RPC 消费端消费 - s.addTask(pkg) - pktBuf.Next(pkgLen) - // continue to handle case 5 - } - if exit { - break - } - } - - return perrors.WithStack(err) -} -``` - - -从上面的代码逻辑我们分析出,RPC 消费端需要将从 TCP 连接收到的二进制比特报文解码成 RPC 能消费的消息,这个工作由 s.reader 实现,所以,我们要构建 RPC 通信层也需要实现 s.reader 对应的 Reader 接口。 - - -### 3. 底层处理网络报文的逻辑如何与业务逻辑解耦 - - -我们都知道,netty 通过 boss 线程和 worker 线程实现了底层网络逻辑和业务逻辑的解耦。那么,getty 是如何实现的呢? - - -在 `handlePackage()` 方法最后,我们看到,收到的消息被放入了 `s.addTask(pkg)` 这个方法,接着往下分析: - - -```go -func (s *session) addTask(pkg interface{}) { - f := func() { - s.listener.OnMessage(s, pkg) - s.incReadPkgNum() - } - if taskPool := s.EndPoint().GetTaskPool(); taskPool != nil { - taskPool.AddTaskAlways(f) - return - } - f() -} -``` - - -`pkg` 参数传递到了一个匿名方法,这个方法最终放入了 `taskPool`。这个方法很关键,在我后来写 seata-golang 代码的时候,就遇到了一个坑,这个坑后面分析。 - - -接着我们看一下 [taskPool](https://github.com/dubbogo/gost/blob/master/sync/task_pool.go) 的定义: - - -```go -// NewTaskPoolSimple build a simple task pool -func NewTaskPoolSimple(size int) GenericTaskPool { - if size < 1 { - size = runtime.NumCPU() * 100 - } - return &taskPoolSimple{ - work: make(chan task), - sem: make(chan struct{}, size), - done: make(chan struct{}), - } -} -``` - - -构建了一个缓冲大小为 size (默认为  `runtime.NumCPU() * 100`) 的 channel `sem`。再看方法 `AddTaskAlways(t task)`: - - -```go -func (p *taskPoolSimple) AddTaskAlways(t task) { - select { - case <-p.done: - return - default: - } - - select { - case p.work <- t: - return - default: - } - select { - case p.work <- t: - case p.sem <- struct{}{}: - p.wg.Add(1) - go p.worker(t) - default: - goSafely(t) - } -} -``` - - -加入的任务,会先由 len(p.sem) 个 goroutine 去消费,如果没有 goroutine 空闲,则会启动一个临时的 goroutine 去运行 t()。相当于有  len(p.sem) 个 goroutine 组成了 goroutine pool,pool 中的 goroutine 去处理业务逻辑,而不是由处理网络报文的 goroutine 去运行业务逻辑,从而实现了解耦。写 seata-golang 时遇到的一个坑,就是忘记设置 taskPool 造成了处理业务逻辑和处理底层网络报文逻辑的 goroutine 是同一个,我在业务逻辑中阻塞等待一个任务完成时,阻塞了整个 goroutine,使得阻塞期间收不到任何报文。 - - -### 4. 具体实现 - - -下面的代码见 [getty.go](https://github.com/apache/dubbo-getty/blob/master/getty.go): - - -```go -// Reader is used to unmarshal a complete pkg from buffer -type Reader interface { - Read(Session, []byte) (interface{}, int, error) -} - -// Writer is used to marshal pkg and write to session -type Writer interface { - // if @Session is udpGettySession, the second parameter is UDPContext. - Write(Session, interface{}) ([]byte, error) -} - -// ReadWriter interface use for handle application packages -type ReadWriter interface { - Reader - Writer -} -``` - - -```go -// EventListener is used to process pkg that received from remote session -type EventListener interface { - // invoked when session opened - // If the return error is not nil, @Session will be closed. - OnOpen(Session) error - - // invoked when session closed. - OnClose(Session) - - // invoked when got error. - OnError(Session, error) - - // invoked periodically, its period can be set by (Session)SetCronPeriod - OnCron(Session) - - // invoked when getty received a package. Pls attention that do not handle long time - // logic processing in this func. You'd better set the package's maximum length. - // If the message's length is greater than it, u should should return err in - // Reader{Read} and getty will close this connection soon. - // - // If ur logic processing in this func will take a long time, u should start a goroutine - // pool(like working thread pool in cpp) to handle the processing asynchronously. Or u - // can do the logic processing in other asynchronous way. - // !!!In short, ur OnMessage callback func should return asap. - // - // If this is a udp event listener, the second parameter type is UDPContext. - OnMessage(Session, interface{}) -} -``` - - -通过对整个 getty 代码的分析,我们只要实现  `ReadWriter` 来对 RPC  消息编解码,再实现 `EventListener` 来处理 RPC 消息的对应的具体逻辑,将 `ReadWriter` 实现和 `EventLister` 实现注入到 RPC 的 Client 和 Server 端,则可实现 RPC 通信。 - - -#### 4.1 编解码协议实现 - - -下面是 seata 协议的定义: -![image-20201205214556457.png](https://cdn.nlark.com/yuque/0/2020/png/737378/1607180799872-5f96afb6-680d-4e69-8c95-b8fd1ac4c3a7.png#align=left&display=inline&height=209&margin=%5Bobject%20Object%5D&name=image-20201205214556457.png&originHeight=209&originWidth=690&size=18407&status=done&style=none&width=690) - - -在 ReadWriter 接口的实现 [`RpcPackageHandler`](https://github.com/opentrx/seata-golang) 中,调用 Codec 方法对消息体按照上面的格式编解码: - - -```go -// 消息编码为二进制比特 -func MessageEncoder(codecType byte, in interface{}) []byte { - switch codecType { - case SEATA: - return SeataEncoder(in) - default: - log.Errorf("not support codecType, %s", codecType) - return nil - } -} - -// 二进制比特解码为消息体 -func MessageDecoder(codecType byte, in []byte) (interface{}, int) { - switch codecType { - case SEATA: - return SeataDecoder(in) - default: - log.Errorf("not support codecType, %s", codecType) - return nil, 0 - } -} -``` - - -#### 4.2 Client 端实现 - - -再来看 client 端 `EventListener` 的实现 [`RpcRemotingClient`](https://github.com/opentrx/seata-golang/blob/dev/pkg/client/rpc_remoting_client.go): - - -```go -func (client *RpcRemoteClient) OnOpen(session getty.Session) error { - go func() - request := protocal.RegisterTMRequest{AbstractIdentifyRequest: protocal.AbstractIdentifyRequest{ - ApplicationId: client.conf.ApplicationId, - TransactionServiceGroup: client.conf.TransactionServiceGroup, - }} - // 建立连接后向 Transaction Coordinator 发起注册 TransactionManager 的请求 - _, err := client.sendAsyncRequestWithResponse(session, request, RPC_REQUEST_TIMEOUT) - if err == nil { - // 将与 Transaction Coordinator 建立的连接保存在连接池供后续使用 - clientSessionManager.RegisterGettySession(session) - client.GettySessionOnOpenChannel <- session.RemoteAddr() - } - }() - - return nil -} - -// OnError ... -func (client *RpcRemoteClient) OnError(session getty.Session, err error) { - clientSessionManager.ReleaseGettySession(session) -} - -// OnClose ... -func (client *RpcRemoteClient) OnClose(session getty.Session) { - clientSessionManager.ReleaseGettySession(session) -} - -// OnMessage ... -func (client *RpcRemoteClient) OnMessage(session getty.Session, pkg interface{}) { - log.Info("received message:{%v}", pkg) - rpcMessage, ok := pkg.(protocal.RpcMessage) - if ok { - heartBeat, isHeartBeat := rpcMessage.Body.(protocal.HeartBeatMessage) - if isHeartBeat && heartBeat == protocal.HeartBeatMessagePong { - log.Debugf("received PONG from %s", session.RemoteAddr()) - } - } - - if rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST || - rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST_ONEWAY { - log.Debugf("msgId:%s, body:%v", rpcMessage.Id, rpcMessage.Body) - - // 处理事务消息,提交 or 回滚 - client.onMessage(rpcMessage, session.RemoteAddr()) - } else { - resp, loaded := client.futures.Load(rpcMessage.Id) - if loaded { - response := resp.(*getty2.MessageFuture) - response.Response = rpcMessage.Body - response.Done <- true - client.futures.Delete(rpcMessage.Id) - } - } -} - -// OnCron ... -func (client *RpcRemoteClient) OnCron(session getty.Session) { - // 发送心跳 - client.defaultSendRequest(session, protocal.HeartBeatMessagePing) -} -``` - - -`clientSessionManager.RegisterGettySession(session)` 的逻辑 4.4 小节分析。 - - -#### 4.3 Server 端 Transaction Coordinator 实现 - - -代码见 [`DefaultCoordinator`](https://github.com/opentrx/seata-golang/blob/dev/tc/server/default_coordinator_event_listener.go): - - -```go -func (coordinator *DefaultCoordinator) OnOpen(session getty.Session) error { - log.Infof("got getty_session:%s", session.Stat()) - return nil -} - -func (coordinator *DefaultCoordinator) OnError(session getty.Session, err error) { - // 释放 TCP 连接 - SessionManager.ReleaseGettySession(session) - session.Close() - log.Errorf("getty_session{%s} got error{%v}, will be closed.", session.Stat(), err) -} - -func (coordinator *DefaultCoordinator) OnClose(session getty.Session) { - log.Info("getty_session{%s} is closing......", session.Stat()) -} - -func (coordinator *DefaultCoordinator) OnMessage(session getty.Session, pkg interface{}) { - log.Debugf("received message:{%v}", pkg) - rpcMessage, ok := pkg.(protocal.RpcMessage) - if ok { - _, isRegTM := rpcMessage.Body.(protocal.RegisterTMRequest) - if isRegTM { - // 将 TransactionManager 信息和 TCP 连接建立映射关系 - coordinator.OnRegTmMessage(rpcMessage, session) - return - } - - heartBeat, isHeartBeat := rpcMessage.Body.(protocal.HeartBeatMessage) - if isHeartBeat && heartBeat == protocal.HeartBeatMessagePing { - coordinator.OnCheckMessage(rpcMessage, session) - return - } - - if rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST || - rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST_ONEWAY { - log.Debugf("msgId:%s, body:%v", rpcMessage.Id, rpcMessage.Body) - _, isRegRM := rpcMessage.Body.(protocal.RegisterRMRequest) - if isRegRM { - // 将 ResourceManager 信息和 TCP 连接建立映射关系 - coordinator.OnRegRmMessage(rpcMessage, session) - } else { - if SessionManager.IsRegistered(session) { - defer func() { - if err := recover(); err != nil { - log.Errorf("Catch Exception while do RPC, request: %v,err: %w", rpcMessage, err) - } - }() - // 处理事务消息,全局事务注册、分支事务注册、分支事务提交、全局事务回滚等 - coordinator.OnTrxMessage(rpcMessage, session) - } else { - session.Close() - log.Infof("close a unhandled connection! [%v]", session) - } - } - } else { - resp, loaded := coordinator.futures.Load(rpcMessage.Id) - if loaded { - response := resp.(*getty2.MessageFuture) - response.Response = rpcMessage.Body - response.Done <- true - coordinator.futures.Delete(rpcMessage.Id) - } - } - } -} - -func (coordinator *DefaultCoordinator) OnCron(session getty.Session) { - -} -``` - - -`coordinator.OnRegTmMessage(rpcMessage, session)` 注册 Transaction Manager,`coordinator.OnRegRmMessage(rpcMessage, session)` 注册 Resource Manager。具体逻辑分析见 4.4 小节。 -消息进入 `coordinator.OnTrxMessage(rpcMessage, session)` 方法,将按照消息的类型码路由到具体的逻辑当中: - - -```go - switch msg.GetTypeCode() { - case protocal.TypeGlobalBegin: - req := msg.(protocal.GlobalBeginRequest) - resp := coordinator.doGlobalBegin(req, ctx) - return resp - case protocal.TypeGlobalStatus: - req := msg.(protocal.GlobalStatusRequest) - resp := coordinator.doGlobalStatus(req, ctx) - return resp - case protocal.TypeGlobalReport: - req := msg.(protocal.GlobalReportRequest) - resp := coordinator.doGlobalReport(req, ctx) - return resp - case protocal.TypeGlobalCommit: - req := msg.(protocal.GlobalCommitRequest) - resp := coordinator.doGlobalCommit(req, ctx) - return resp - case protocal.TypeGlobalRollback: - req := msg.(protocal.GlobalRollbackRequest) - resp := coordinator.doGlobalRollback(req, ctx) - return resp - case protocal.TypeBranchRegister: - req := msg.(protocal.BranchRegisterRequest) - resp := coordinator.doBranchRegister(req, ctx) - return resp - case protocal.TypeBranchStatusReport: - req := msg.(protocal.BranchReportRequest) - resp := coordinator.doBranchReport(req, ctx) - return resp - default: - return nil - } -``` - - -#### 4.4 session manager 分析 -Client 端同 Transaction Coordinator 建立连接起连接后,通过 `clientSessionManager.RegisterGettySession(session)` 将连接保存在 `serverSessions = sync.Map{}` 这个 map 中。map 的 key 为从 session 中获取的 RemoteAddress 即 Transaction Coordinator 的地址,value 为 session。这样,Client 端就可以通过 map 中的一个 session 来向 Transaction Coordinator 注册 Transaction Manager 和 Resource Manager 了。具体代码见 [`getty_client_session_manager.go`。](https://github.com/opentrx/seata-golang/blob/dev/pkg/client/getty_client_session_manager.go) -Transaction Manager 和 Resource Manager 注册到 Transaction Coordinator 后,一个连接既有可能用来发送 TM 消息也有可能用来发送 RM 消息。我们通过 RpcContext 来标识一个连接信息: -```go -type RpcContext struct { - Version string - TransactionServiceGroup string - ClientRole meta.TransactionRole - ApplicationId string - ClientId string - ResourceSets *model.Set - Session getty.Session -} -``` -当收到事务消息时,我们需要构造这样一个 RpcContext 供后续事务处理逻辑使用。所以,我们会构造下列 map 来缓存映射关系: -```go -var ( - // session -> transactionRole - // TM will register before RM, if a session is not the TM registered, - // it will be the RM registered - session_transactionroles = sync.Map{} - - // session -> applicationId - identified_sessions = sync.Map{} - - // applicationId -> ip -> port -> session - client_sessions = sync.Map{} - - // applicationId -> resourceIds - client_resources = sync.Map{} -) -``` -这样,Transaction Manager 和 Resource Manager 分别通过 `coordinator.OnRegTmMessage(rpcMessage, session)` 和 `coordinator.OnRegRmMessage(rpcMessage, session)` 注册到 Transaction Coordinator 时,会在上述 client_sessions map 中缓存 applicationId、ip、port 与 session 的关系,在 client_resources map 中缓存 applicationId 与 resourceIds(一个应用可能存在多个 Resource Manager) 的关系。在需要时,我们就可以通过上述映射关系构造一个 RpcContext。这部分的实现和 java 版 seata 有很大的不同,感兴趣的可以深入了解一下。具体代码见 [`getty_session_manager.go`。](https://github.com/opentrx/seata-golang/blob/dev/tc/server/getty_session_manager.go) -至此,我们就分析完了 [seata-golang](https://github.com/opentrx/seata-golang) 整个 RPC 通信模型的机制。 - -## 三、seata-golang 的未来 - -[seata-golang](https://github.com/opentrx/seata-golang)  从今年 4 月份开始开发,到 8 月份基本实现和 java 版 [seata 1.2](https://github.com/seata/seata) 协议的互通,对 mysql 数据库实现了 AT 模式(自动协调分布式事务的提交回滚),实现了 TCC 模式,TC 端使用 mysql 存储数据,使 TC 变成一个无状态应用支持高可用部署。下图展示了 AT 模式的原理:![image20201205-232516.png]( https://img.alicdn.com/imgextra/i3/O1CN01alqsQS1G2oQecFYIs_!!6000000000565-2-tps-1025-573.png) - - -后续,还有许多工作可以做,比如:对注册中心的支持、对配置中心的支持、和 java 版 seata 1.4 的协议互通、其他数据库的支持、raft transaction coordinator 的实现等,希望对分布式事务问题感兴趣的开发者可以加入进来一起来打造一个完善的 golang 的分布式事务框架。 - -如果你有任何疑问,欢迎钉钉扫码加入交流群【钉钉群号 33069364】: - - -### **作者简介** - -刘晓敏 (GitHubID dk-lockdown),目前就职于 h3c 成都分公司,擅长使用 Java/Go 语言,在云原生和微服务相关技术方向均有涉猎,目前专攻分布式事务。 -于雨(github @AlexStocks),dubbo-go 项目和社区负责人,一个有十多年服务端基础架构研发一线工作经验的程序员,陆续参与改进过 Muduo/Pika/Dubbo/Sentinel-go 等知名项目,目前在蚂蚁金服可信原生部从事容器编排和 service mesh 工作。 - -#### 参考资料 - - -seata 官方:[https://seata.io](https://seata.io) - - -java 版 seata:[https://github.com/seata/seata](https://github.com/seata/seata) - - -seata-golang 项目地址:[https://github.com/opentrx/seata-golang](https://github.com/transaction-wg/seata-golang) - - -seata-golang go 夜读 b站分享:[https://www.bilibili.com/video/BV1oz411e72T](https://www.bilibili.com/video/BV1oz411e72T) +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-ha-practice.md b/blog/seata-ha-practice.md index af6234925b..e872b67af1 100644 --- a/blog/seata-ha-practice.md +++ b/blog/seata-ha-practice.md @@ -1,405 +1 @@ ---- -hidden: true -title: Seata 高可用部署实践 -keywords: [kubernetes,ops] -description: Seata 高可用部署实践 -author: helloworlde -date: 2020-04-10 ---- - -# Seata 高可用部署实践 - -使用配置中心和数据库来实现 Seata 的高可用,以 Nacos 和 MySQL 为例,将[cloud-seata-nacos](https://github.com/helloworlde/spring-cloud-alibaba-component/blob/master/cloud-seata-nacos/)应用部署到 Kubernetes 集群中 - -该应用使用 Nacos 作为配置和注册中心,总共有三个服务: order-service, pay-service, storage-service, 其中 order-service 对外提供下单接口,当余额和库存充足时,下单成功,会提交事务,当不足时会抛出异常,下单失败,回滚事务 - -## 准备工作 - -需要准备可用的注册中心、配置中心 Nacos 和 MySQL,通常情况下,注册中心、配置中心和数据库都是已有的,不需要特别配置,在这个实践中,为了简单,只部署单机的注册中心、配置中心和数据库,假设他们是可靠的 - -- 部署 Nacos - -在服务器部署 Nacos,开放 8848 端口,用于 seata-server 注册,服务器地址为 `192.168.199.2` - -```bash -docker run --name nacos -p 8848:8848 -e MODE=standalone nacos/nacos-server -``` - -- 部署 MySQL - -部署一台MySQL 数据库,用于保存事务数据,服务器地址为 `192.168.199.2` - -```bash -docker run --name mysql -p 30060:3306-e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7.17 -``` - -## 部署 seata-server - -- 创建seata-server需要的表 - -具体的 SQL 参考 [script/server/db](https://github.com/seata/seata/tree/develop/script/server/db),这里使用的是 MySQL 的脚本,数据库名称为 `seata` - -同时,也需要创建 undo_log 表, 可以参考 [script/client/at/db/](https://github.com/seata/seata/blob/develop/script/client/at/db/) - -- 修改seata-server配置 - -将以下配置添加到 Nacos 配置中心,具体添加方法可以参考 [script/config-center](https://github.com/seata/seata/tree/develop/script/config-center) - -``` -service.vgroupMapping.my_test_tx_group=default -store.mode=db -store.db.datasource=druid -store.db.dbType=mysql -store.db.driverClassName=com.mysql.jdbc.Driver -store.db.url=jdbc:mysql://192.168.199.2:30060/seata?useUnicode=true -store.db.user=root -store.db.password=123456 -``` - -### 部署 seata-server 到 Kubernetes - -- seata-server.yaml - -需要将 ConfigMap 的注册中心和配置中心地址改成相应的地址 - -```yaml -apiVersion: v1 -kind: Service -metadata: - name: seata-ha-server - namespace: default - labels: - app.kubernetes.io/name: seata-ha-server -spec: - type: ClusterIP - ports: - - port: 8091 - protocol: TCP - name: http - selector: - app.kubernetes.io/name: seata-ha-server - ---- - -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: seata-ha-server - namespace: default - labels: - app.kubernetes.io/name: seata-ha-server -spec: - serviceName: seata-ha-server - replicas: 3 - selector: - matchLabels: - app.kubernetes.io/name: seata-ha-server - template: - metadata: - labels: - app.kubernetes.io/name: seata-ha-server - spec: - containers: - - name: seata-ha-server - image: docker.io/seataio/seata-server:latest - imagePullPolicy: IfNotPresent - env: - - name: SEATA_CONFIG_NAME - value: file:/root/seata-config/registry - ports: - - name: http - containerPort: 8091 - protocol: TCP - volumeMounts: - - name: seata-config - mountPath: /root/seata-config - volumes: - - name: seata-config - configMap: - name: seata-ha-server-config - - ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: seata-ha-server-config -data: - registry.conf: | - registry { - type = "nacos" - nacos { - application = "seata-server" - serverAddr = "192.168.199.2" - } - } - config { - type = "nacos" - nacos { - serverAddr = "192.168.199.2" - group = "SEATA_GROUP" - } - } -``` - -- 部署 - -```bash -kubectl apply -f seata-server.yaml -``` - -部署完成后,会有三个 pod - -```bash -kubectl get pod | grep seata-ha-server - -seata-ha-server-645844b8b6-9qh5j 1/1 Running 0 3m14s -seata-ha-server-645844b8b6-pzczs 1/1 Running 0 3m14s -seata-ha-server-645844b8b6-wkpw8 1/1 Running 0 3m14s -``` - -待启动完成后,可以在 Nacos 的服务列表中发现三个 seata-server 的实例,至此,已经完成 seata-server 的高可用部署 - -- 查看服务日志 - -```bash -kubelet logs -f seata-ha-server-645844b8b6-9qh5j -``` - -```java -[0.012s][info ][gc] Using Serial -2020-04-15 00:55:09.880 INFO [main]io.seata.server.ParameterParser.init:90 -The server is running in container. -2020-04-15 00:55:10.013 INFO [main]io.seata.config.FileConfiguration.:110 -The configuration file used is file:/root/seata-config/registry.conf -2020-04-15 00:55:12.426 INFO [main]com.alibaba.druid.pool.DruidDataSource.init:947 -{dataSource-1} inited -2020-04-15 00:55:13.127 INFO [main]io.seata.core.rpc.netty.RpcServerBootstrap.start:155 -Server started -``` - -其中`{dataSource-1} `说明使用了数据库,并正常初始化完成 - -- 查看注册中心,此时seata-serve 这个服务会有三个实例 - -![seata-ha-nacos-list.png](/img/blog/seata-ha-nacos-list.png) - - -## 部署业务服务 - -- 创建业务表并初始化数据 - -具体的业务表可以参考 [cloud-seata-nacos/README.md](https://github.com/helloworlde/spring-cloud-alibaba-component/blob/master/cloud-seata-nacos/README.md) - -- 添加 Nacos 配置 - -在 public 的命名空间下,分别创建 data-id 为 `order-service.properties`, `pay-service.properties`, `storage-service.properties` 的配置,内容相同,需要修改数据库的地址、用户名和密码 - -``` -# MySQL -spring.datasource.url=jdbc:mysql://192.168.199.2:30060/seata?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false -spring.datasource.username=root -spring.datasource.password=123456 -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -# Seata -spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group -``` - -- 部署服务 - -通过 application.yaml 配置文件部署服务,需要注意的是修改 ConfigMap 的 `NACOS_ADDR`为自己的 Nacos 地址 - -```yaml -apiVersion: v1 -kind: Service -metadata: - namespace: default - name: seata-ha-service - labels: - app.kubernetes.io/name: seata-ha-service -spec: - type: NodePort - ports: - - port: 8081 - nodePort: 30081 - protocol: TCP - name: http - selector: - app.kubernetes.io/name: seata-ha-service - ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: seata-ha-service-config -data: - NACOS_ADDR: 192.168.199.2:8848 - ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: seata-ha-account - namespace: default - ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: ClusterRoleBinding -metadata: - name: seata-ha-account -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cluster-admin -subjects: - - kind: ServiceAccount - name: seata-ha-account - namespace: default - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - namespace: default - name: seata-ha-service - labels: - app.kubernetes.io/name: seata-ha-service -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: seata-ha-service - template: - metadata: - labels: - app.kubernetes.io/name: seata-ha-service - spec: - serviceAccountName: seata-ha-account - containers: - - name: seata-ha-order-service - image: "registry.cn-qingdao.aliyuncs.com/hellowoodes/seata-ha-order-service:1.1" - imagePullPolicy: IfNotPresent - env: - - name: NACOS_ADDR - valueFrom: - configMapKeyRef: - key: NACOS_ADDR - name: seata-ha-service-config - ports: - - name: http - containerPort: 8081 - protocol: TCP - - name: seata-ha-pay-service - image: "registry.cn-qingdao.aliyuncs.com/hellowoodes/seata-ha-pay-service:1.1" - imagePullPolicy: IfNotPresent - env: - - name: NACOS_ADDR - valueFrom: - configMapKeyRef: - key: NACOS_ADDR - name: seata-ha-service-config - ports: - - name: http - containerPort: 8082 - protocol: TCP - - name: seata-ha-storage-service - image: "registry.cn-qingdao.aliyuncs.com/hellowoodes/seata-ha-storage-service:1.1" - imagePullPolicy: IfNotPresent - env: - - name: NACOS_ADDR - valueFrom: - configMapKeyRef: - key: NACOS_ADDR - name: seata-ha-service-config - ports: - - name: http - containerPort: 8083 - protocol: TCP -``` - -通过以下命令,将应用部署到集群中 - -```bash -kubectl apply -f application.yaml -``` - -然后查看创建的 pod,seata-ha-service 这个服务下有三个 pod - -```bash -kubectl get pod | grep seata-ha-service - -seata-ha-service-7dbdc6894b-5r8q4 3/3 Running 0 12m -``` - -待应用启动后,在 Nacos 的服务列表中,会有相应的服务 - -![seata-ha-service-list.png](/img/blog/seata-ha-service-list.png) - -此时查看服务的日志,会看到服务向每一个 TC 都注册了 - -```bash -kubectl logs -f seata-ha-service-7dbdc6894b-5r8q4 seata-ha-order-service -``` - -![seata-ha-service-register.png](/img/blog/seata-ha-service-register.png) - -查看任意的 TC 日志,会发现每一个服务都向 TC 注册了 - -```bash -kubelet logs -f seata-ha-server-645844b8b6-9qh5j -``` - -![seata-ha-tc-register.png](/img/blog/seata-ha-tc-register.png) - - -## 测试 - - -### 测试成功场景 - -调用下单接口,将 price 设置为 1,因为初始化的余额为 10,可以下单成功 - -```bash -curl -X POST \ - http://192.168.199.2:30081/order/placeOrder \ - -H 'Content-Type: application/json' \ - -d '{ - "userId": 1, - "productId": 1, - "price": 1 -}' -``` - -此时返回结果为: - -```json -{"success":true,"message":null,"data":null} -``` - -查看TC 的日志,事务成功提交: - -![seata-ha-commit-tc-success.png](/img/blog/seata-ha-commit-tc-success.png) - -查看 order-service 服务日志 -![seata-ha-commit-success.png](/img/blog/seata-ha-commit-service-success.png) - - -### 测试失败场景 - -设置 price 为 100,此时余额不足,会下单失败抛出异常,事务会回滚 - -```bash -curl -X POST \ - http://192.168.199.2:30081/order/placeOrder \ - -H 'Content-Type: application/json' \ - -d '{ - "userId": 1, - "productId": 1, - "price": 100 -}' -``` - -查看 TC 的日志: -![seata-ha-commit-tc-rollback.png](/img/blog/seata-ha-commit-tc-rollback.png) - -查看服务的日志 : -![seata-ha-commit-service-rollback.png](/img/blog/seata-ha-commit-service-rollback.png) - -多次调用查看服务日志,发现会随机的向其中某台TC发起事务注册,当扩容或缩容后,有相应的 TC 参与或退出,证明高可用部署生效 - +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-meetup-hangzhou.md b/blog/seata-meetup-hangzhou.md index 5671ad3e96..e872b67af1 100644 --- a/blog/seata-meetup-hangzhou.md +++ b/blog/seata-meetup-hangzhou.md @@ -1,50 +1 @@ ---- -title: Seata Community Meetup·杭州站 -keywords: [Seata, 杭州, meetup] -description: Seata Community Meetup·杭州站,于12月21号在杭州市梦想小镇浙江青年众创空间完美举办 ---- - -# Seata Meetup·杭州站成功举办 - -![](https://img.alicdn.com/tfs/TB1qH2YwVP7gK0jSZFjXXc5aXXa-2002-901.jpg) - -### 活动介绍 - -### 亮点解读 - -- Seata 开源项目发起人带来《Seata 过去、现在和未来》以及 Seata 1.0 的新特性。 -- Seata 核心贡献者详解 Seata AT, TCC, Saga 模式。 -- Seata 落地互联网医疗,滴滴出行实践剖析。 - -- [回放福利(开发者社区)](https://developer.aliyun.com/live/1760) -- [加入 Seata 千人钉钉群](http://w2wz.com/h2nb) - -### 分享嘉宾 - -- 季敏(清铭) 《Seata 的过去、现在和未来》 [slides](https://github.com/a364176773/awesome-seata/blob/master/slides/meetup/201912%40hangzhou/%E5%AD%A3%E6%95%8F%EF%BC%88%E6%B8%85%E9%93%AD%EF%BC%89%E3%80%8ASeata%20%E7%9A%84%E8%BF%87%E5%8E%BB%E3%80%81%E7%8E%B0%E5%9C%A8%E5%92%8C%E6%9C%AA%E6%9D%A5%E3%80%8B.pdf) - - ![](https://img.alicdn.com/tfs/TB1BALWw4z1gK0jSZSgXXavwpXa-6720-4480.jpg) - -- 吴江坷《我与SEATA的开源之路以及SEATA在互联网医疗系统中的应用》 [slides](https://github.com/seata/awesome-seata/blob/master/slides/meetup/201912%40hangzhou/%E5%AD%A3%E6%95%8F%EF%BC%88%E6%B8%85%E9%93%AD%EF%BC%89%E3%80%8ASeata%20%E7%9A%84%E8%BF%87%E5%8E%BB%E3%80%81%E7%8E%B0%E5%9C%A8%E5%92%8C%E6%9C%AA%E6%9D%A5%E3%80%8B.pdf) - - ![1577282651](https://img.alicdn.com/tfs/TB1Xzz1w4v1gK0jSZFFXXb0sXXa-6720-4480.jpg) - -- 申海强(煊檍)《带你读透 Seata AT 模式的精髓》 [slides](https://github.com/seata/awesome-seata/tree/master/slides/meetup/201912%40hangzhou) - - ![1577282652](https://img.alicdn.com/tfs/TB1UK22w7T2gK0jSZPcXXcKkpXa-6720-4480.jpg) - -- 张森 《分布式事务Seata之TCC模式详解》 - - ![1577282653](https://img.alicdn.com/tfs/TB1fCPZw.T1gK0jSZFhXXaAtVXa-6720-4480.jpg) - -- 陈龙(屹远)《Seata 长事务解决方案 Saga 模式》 - - ![1577282654](https://img.alicdn.com/tfs/TB1zLv3wYj1gK0jSZFuXXcrHpXa-6720-4480.jpg) - -- 陈鹏志《Seata%20在滴滴两轮车业务的实践》 [slides](https://github.com/seata/awesome-seata/blob/master/slides/meetup/201912%40hangzhou/%E9%99%88%E9%B9%8F%E5%BF%97%E3%80%8ASeata%20%E5%9C%A8%E6%BB%B4%E6%BB%B4%E4%B8%A4%E8%BD%AE%E8%BD%A6%E4%B8%9A%E5%8A%A1%E7%9A%84%E5%AE%9E%E8%B7%B5%E3%80%8B.pdf) - - ![1577282655](https://img.alicdn.com/tfs/TB1phvYw4n1gK0jSZKPXXXvUXXa-6720-4480.jpg) - -### 特别嘉奖 - -![](https://img.alicdn.com/tfs/TB1khDVw.z1gK0jSZLeXXb9kVXa-6720-4480.jpg) \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-mybatisplus-analysis.md b/blog/seata-mybatisplus-analysis.md index f85bd6fa90..e872b67af1 100644 --- a/blog/seata-mybatisplus-analysis.md +++ b/blog/seata-mybatisplus-analysis.md @@ -1,540 +1 @@ ---- -title: 透过源码解决SeataAT模式整合Mybatis-Plus失去MP特性的问题 -keywords: [Seata,Mybatis-Plus,分布式事务] -description: 本文讲述如何透过源码解决Seata整合Mybatis-Plus失去MP特性的问题 -author: FUNKYE -date: 2019/11/30 - ---- - -# 透过源码解决SeataAT模式整合Mybatis-Plus失去MP特性的问题 - -项目地址:https://gitee.com/itCjb/springboot-dubbo-mybatisplus-seata - -本文作者:FUNKYE(陈健斌),杭州某互联网公司主程。 - -# 介绍 - -Mybatis-Plus:[MyBatis-Plus](https://github.com/baomidou/mybatis-plus)(简称 MP)是一个 [MyBatis](http://www.mybatis.org/mybatis-3/) 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 - -MP配置: - -```xml - - - -``` - -Seata:Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。 - -AT模式机制: - -- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。 -- 二阶段: - - 提交异步化,非常快速地完成。 - - 回滚通过一阶段的回滚日志进行反向补偿。 - -## 分析原因 - -​ 1.首先我们通过介绍,可以看到,mp是需要注册sqlSessionFactory,注入数据源,而Seata是通过代理数据源来保证事务的正常回滚跟提交。 - -​ 2.我们来看基于seata的官方demo提供的SeataAutoConfig的代码 - -```java -package org.test.config; - -import javax.sql.DataSource; - -import org.apache.ibatis.session.SqlSessionFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; - -import com.alibaba.druid.pool.DruidDataSource; -import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; - -import io.seata.rm.datasource.DataSourceProxy; -import io.seata.spring.annotation.GlobalTransactionScanner; - -@Configuration -public class SeataAutoConfig { - @Autowired(required = true) - private DataSourceProperties dataSourceProperties; - private final static Logger logger = LoggerFactory.getLogger(SeataAutoConfig.class); - - @Bean(name = "dataSource") // 声明其为Bean实例 - @Primary // 在同样的DataSource中,首先使用被标注的DataSource - public DataSource druidDataSource() { - DruidDataSource druidDataSource = new DruidDataSource(); - logger.info("dataSourceProperties.getUrl():{}",dataSourceProperties.getUrl()); - druidDataSource.setUrl(dataSourceProperties.getUrl()); - druidDataSource.setUsername(dataSourceProperties.getUsername()); - druidDataSource.setPassword(dataSourceProperties.getPassword()); - druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName()); - druidDataSource.setInitialSize(0); - druidDataSource.setMaxActive(180); - druidDataSource.setMaxWait(60000); - druidDataSource.setMinIdle(0); - druidDataSource.setValidationQuery("Select 1 from DUAL"); - druidDataSource.setTestOnBorrow(false); - druidDataSource.setTestOnReturn(false); - druidDataSource.setTestWhileIdle(true); - druidDataSource.setTimeBetweenEvictionRunsMillis(60000); - druidDataSource.setMinEvictableIdleTimeMillis(25200000); - druidDataSource.setRemoveAbandoned(true); - druidDataSource.setRemoveAbandonedTimeout(1800); - druidDataSource.setLogAbandoned(true); - logger.info("装载dataSource........"); - return druidDataSource; - } - - /** - * init datasource proxy - * - * @Param: druidDataSource datasource bean instance - * @Return: DataSourceProxy datasource proxy - */ - @Bean - public DataSourceProxy dataSourceProxy(DataSource dataSource) { - logger.info("代理dataSource........"); - return new DataSourceProxy(dataSource); - } - - @Bean - public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception { - MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean(); - factory.setDataSource(dataSourceProxy); - factory.setMapperLocations(new PathMatchingResourcePatternResolver() - .getResources("classpath*:/mapper/*.xml")); - return factory.getObject(); - } - - /** - * init global transaction scanner - * - * @Return: GlobalTransactionScanner - */ - @Bean - public GlobalTransactionScanner globalTransactionScanner() { - logger.info("配置seata........"); - return new GlobalTransactionScanner("test-service", "test-group"); - } -} - -``` - -首先看到我们的seata配置数据源的类里,我们配置了一个数据源,然后又配置了一个seata代理datasource的bean,这时候. - -然后我们如果直接启动mp整合seata的项目会发现,分页之类的插件会直接失效,连扫描mapper都得从代码上写,这是为什么呢? - -通过阅读以上代码,是因为我们另外的配置了一个sqlSessionFactory,导致mp的sqlSessionFactory失效了,这时候我们发现了问题的所在了,即使我们不配置sqlSessionFactoryl,也会因为mp所使用的数据源不是被seata代理过后的数据源,导致分布式事务失效.但是如何解决这个问题呢? - -这时候我们需要去阅读mp的源码,找到他的启动类,一看便知 - -```java -/* - * Copyright (c) 2011-2020, baomidou (jobob@qq.com). - *

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.baomidou.mybatisplus.autoconfigure; - - -import com.baomidou.mybatisplus.core.MybatisConfiguration; -import com.baomidou.mybatisplus.core.config.GlobalConfig; -import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; -import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; -import com.baomidou.mybatisplus.core.injector.ISqlInjector; -import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.mapping.DatabaseIdProvider; -import org.apache.ibatis.plugin.Interceptor; -import org.apache.ibatis.scripting.LanguageDriver; -import org.apache.ibatis.session.ExecutorType; -import org.apache.ibatis.session.SqlSessionFactory; -import org.apache.ibatis.type.TypeHandler; -import org.mybatis.spring.SqlSessionFactoryBean; -import org.mybatis.spring.SqlSessionTemplate; -import org.mybatis.spring.mapper.MapperFactoryBean; -import org.mybatis.spring.mapper.MapperScannerConfigurer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeanWrapper; -import org.springframework.beans.BeanWrapperImpl; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.boot.autoconfigure.AutoConfigurationPackages; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; - -import javax.sql.DataSource; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; - -/** - * {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a - * {@link SqlSessionFactory} and a {@link SqlSessionTemplate}. - *

- * If {@link org.mybatis.spring.annotation.MapperScan} is used, or a - * configuration file is specified as a property, those will be considered, - * otherwise this auto-configuration will attempt to register mappers based on - * the interface definitions in or under the root auto-configuration package. - *

- *

copy from {@link org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration}

- * - * @author Eddú Meléndez - * @author Josh Long - * @author Kazuki Shimizu - * @author Eduardo Macarrón - */ -@Configuration -@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) -@ConditionalOnSingleCandidate(DataSource.class) -@EnableConfigurationProperties(MybatisPlusProperties.class) -@AutoConfigureAfter(DataSourceAutoConfiguration.class) -public class MybatisPlusAutoConfiguration implements InitializingBean { - - private static final Logger logger = LoggerFactory.getLogger(MybatisPlusAutoConfiguration.class); - - private final MybatisPlusProperties properties; - - private final Interceptor[] interceptors; - - private final TypeHandler[] typeHandlers; - - private final LanguageDriver[] languageDrivers; - - private final ResourceLoader resourceLoader; - - private final DatabaseIdProvider databaseIdProvider; - - private final List configurationCustomizers; - - private final List mybatisPlusPropertiesCustomizers; - - private final ApplicationContext applicationContext; - - - public MybatisPlusAutoConfiguration(MybatisPlusProperties properties, - ObjectProvider interceptorsProvider, - ObjectProvider typeHandlersProvider, - ObjectProvider languageDriversProvider, - ResourceLoader resourceLoader, - ObjectProvider databaseIdProvider, - ObjectProvider> configurationCustomizersProvider, - ObjectProvider> mybatisPlusPropertiesCustomizerProvider, - ApplicationContext applicationContext) { - this.properties = properties; - this.interceptors = interceptorsProvider.getIfAvailable(); - this.typeHandlers = typeHandlersProvider.getIfAvailable(); - this.languageDrivers = languageDriversProvider.getIfAvailable(); - this.resourceLoader = resourceLoader; - this.databaseIdProvider = databaseIdProvider.getIfAvailable(); - this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable(); - this.mybatisPlusPropertiesCustomizers = mybatisPlusPropertiesCustomizerProvider.getIfAvailable(); - this.applicationContext = applicationContext; - } - - @Override - public void afterPropertiesSet() { - if (!CollectionUtils.isEmpty(mybatisPlusPropertiesCustomizers)) { - mybatisPlusPropertiesCustomizers.forEach(i -> i.customize(properties)); - } - checkConfigFileExists(); - } - - private void checkConfigFileExists() { - if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) { - Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation()); - Assert.state(resource.exists(), - "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)"); - } - } - - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") - @Bean - @ConditionalOnMissingBean - public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { - // TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean - MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean(); - factory.setDataSource(dataSource); - factory.setVfs(SpringBootVFS.class); - if (StringUtils.hasText(this.properties.getConfigLocation())) { - factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); - } - applyConfiguration(factory); - if (this.properties.getConfigurationProperties() != null) { - factory.setConfigurationProperties(this.properties.getConfigurationProperties()); - } - if (!ObjectUtils.isEmpty(this.interceptors)) { - factory.setPlugins(this.interceptors); - } - if (this.databaseIdProvider != null) { - factory.setDatabaseIdProvider(this.databaseIdProvider); - } - if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { - factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); - } - if (this.properties.getTypeAliasesSuperType() != null) { - factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType()); - } - if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { - factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); - } - if (!ObjectUtils.isEmpty(this.typeHandlers)) { - factory.setTypeHandlers(this.typeHandlers); - } - if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { - factory.setMapperLocations(this.properties.resolveMapperLocations()); - } - - // TODO 对源码做了一定的修改(因为源码适配了老旧的mybatis版本,但我们不需要适配) - Class defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver(); - if (!ObjectUtils.isEmpty(this.languageDrivers)) { - factory.setScriptingLanguageDrivers(this.languageDrivers); - } - Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver); - - // TODO 自定义枚举包 - if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) { - factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage()); - } - // TODO 此处必为非 NULL - GlobalConfig globalConfig = this.properties.getGlobalConfig(); - // TODO 注入填充器 - if (this.applicationContext.getBeanNamesForType(MetaObjectHandler.class, - false, false).length > 0) { - MetaObjectHandler metaObjectHandler = this.applicationContext.getBean(MetaObjectHandler.class); - globalConfig.setMetaObjectHandler(metaObjectHandler); - } - // TODO 注入主键生成器 - if (this.applicationContext.getBeanNamesForType(IKeyGenerator.class, false, - false).length > 0) { - IKeyGenerator keyGenerator = this.applicationContext.getBean(IKeyGenerator.class); - globalConfig.getDbConfig().setKeyGenerator(keyGenerator); - } - // TODO 注入sql注入器 - if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false, - false).length > 0) { - ISqlInjector iSqlInjector = this.applicationContext.getBean(ISqlInjector.class); - globalConfig.setSqlInjector(iSqlInjector); - } - // TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean - factory.setGlobalConfig(globalConfig); - return factory.getObject(); - } - - // TODO 入参使用 MybatisSqlSessionFactoryBean - private void applyConfiguration(MybatisSqlSessionFactoryBean factory) { - // TODO 使用 MybatisConfiguration - MybatisConfiguration configuration = this.properties.getConfiguration(); - if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) { - configuration = new MybatisConfiguration(); - } - if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { - for (ConfigurationCustomizer customizer : this.configurationCustomizers) { - customizer.customize(configuration); - } - } - factory.setConfiguration(configuration); - } - - @Bean - @ConditionalOnMissingBean - public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { - ExecutorType executorType = this.properties.getExecutorType(); - if (executorType != null) { - return new SqlSessionTemplate(sqlSessionFactory, executorType); - } else { - return new SqlSessionTemplate(sqlSessionFactory); - } - } - - /** - * This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use - * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box, - * similar to using Spring Data JPA repositories. - */ - public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar { - - private BeanFactory beanFactory; - - @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { - - if (!AutoConfigurationPackages.has(this.beanFactory)) { - logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled."); - return; - } - - logger.debug("Searching for mappers annotated with @Mapper"); - - List packages = AutoConfigurationPackages.get(this.beanFactory); - if (logger.isDebugEnabled()) { - packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg)); - } - - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); - builder.addPropertyValue("processPropertyPlaceHolders", true); - builder.addPropertyValue("annotationClass", Mapper.class); - builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages)); - BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class); - Stream.of(beanWrapper.getPropertyDescriptors()) - // Need to mybatis-spring 2.0.2+ - .filter(x -> x.getName().equals("lazyInitialization")).findAny() - .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}")); - registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - } - - /** - * If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan - * mappers based on the same component-scanning path as Spring Boot itself. - */ - @Configuration - @Import(AutoConfiguredMapperScannerRegistrar.class) - @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class}) - public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { - - @Override - public void afterPropertiesSet() { - logger.debug( - "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer."); - } - } -} - -``` - -看到mp启动类里的sqlSessionFactory方法了吗,他也是一样的注入一个数据源,这时候大家应该都知道解决方法了吧? - -没错,就是把被代理过的数据源给放到mp的sqlSessionFactory中. - -很简单,我们需要稍微改动一下我们的seata配置类就行了 - -```java -package org.test.config; - -import javax.sql.DataSource; - -import org.mybatis.spring.annotation.MapperScan; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; - -import com.alibaba.druid.pool.DruidDataSource; - -import io.seata.rm.datasource.DataSourceProxy; -import io.seata.spring.annotation.GlobalTransactionScanner; - -@Configuration -@MapperScan("com.baomidou.springboot.mapper*") -public class SeataAutoConfig { - @Autowired(required = true) - private DataSourceProperties dataSourceProperties; - private final static Logger logger = LoggerFactory.getLogger(SeataAutoConfig.class); - private DataSourceProxy dataSourceProxy; - - @Bean(name = "dataSource") // 声明其为Bean实例 - @Primary // 在同样的DataSource中,首先使用被标注的DataSource - public DataSource druidDataSource() { - DruidDataSource druidDataSource = new DruidDataSource(); - logger.info("dataSourceProperties.getUrl():{}", dataSourceProperties.getUrl()); - druidDataSource.setUrl(dataSourceProperties.getUrl()); - druidDataSource.setUsername(dataSourceProperties.getUsername()); - druidDataSource.setPassword(dataSourceProperties.getPassword()); - druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName()); - druidDataSource.setInitialSize(0); - druidDataSource.setMaxActive(180); - druidDataSource.setMaxWait(60000); - druidDataSource.setMinIdle(0); - druidDataSource.setValidationQuery("Select 1 from DUAL"); - druidDataSource.setTestOnBorrow(false); - druidDataSource.setTestOnReturn(false); - druidDataSource.setTestWhileIdle(true); - druidDataSource.setTimeBetweenEvictionRunsMillis(60000); - druidDataSource.setMinEvictableIdleTimeMillis(25200000); - druidDataSource.setRemoveAbandoned(true); - druidDataSource.setRemoveAbandonedTimeout(1800); - druidDataSource.setLogAbandoned(true); - logger.info("装载dataSource........"); - dataSourceProxy = new DataSourceProxy(druidDataSource); - return dataSourceProxy; - } - - /** - * init datasource proxy - * - * @Param: druidDataSource datasource bean instance - * @Return: DataSourceProxy datasource proxy - */ - @Bean - public DataSourceProxy dataSourceProxy() { - logger.info("代理dataSource........"); - return dataSourceProxy; - } - - /** - * init global transaction scanner - * - * @Return: GlobalTransactionScanner - */ - @Bean - public GlobalTransactionScanner globalTransactionScanner() { - logger.info("配置seata........"); - return new GlobalTransactionScanner("test-service", "test-group"); - } -} - -``` - -看代码,我们去掉了自己配置的sqlSessionFactory,直接让DataSource bean返回的是一个被代理过的bean,并且我们加入了@Primary,导致mp优先使用我们配置的数据源,这样就解决了mp因为seata代理了数据源跟创建了新的sqlSessionFactory,导致mp的插件,组件失效的bug了! - -# 总结 - -踩到坑不可怕,主要又耐心的顺着每个组件实现的原理,再去思考,查找对应冲突的代码块,你一定能找到个兼容二者的方法。 +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-nacos-analysis.md b/blog/seata-nacos-analysis.md index 4654b50ba0..e872b67af1 100644 --- a/blog/seata-nacos-analysis.md +++ b/blog/seata-nacos-analysis.md @@ -1,435 +1 @@ ---- -title: Seata分布式事务启用Nacos做配置中心 -keywords: [Seata,Nacos,分布式事务] -description: 本文讲述如何使用Seata整合Nacos配置 -author: FUNKYE -date: 2019/12/02 ---- - -# Seata分布式事务启用Nacos做配置中心 - -[项目地址](https://gitee.com/itCjb/springboot-dubbo-mybatisplus-seata ) - -本文作者:FUNKYE(陈健斌),杭州某互联网公司主程。 - -# 前言 - -上次发布了直连方式的seata配置,详细可以看这篇[博客](http://seata.io/zh-cn/blog/springboot-dubbo-mybatisplus-seata.html) - -我们接着上一篇的基础上去配置nacos做配置中心跟dubbo注册中心. - -## 准备工作 - -​ 1.首先去nacos的github上下载[最新版本](https://github.com/alibaba/nacos/releases/tag/1.1.4) - -​ ![](/img/blog/20191202203649.png) - -​ 2.下载好了后,很简单,解压后到bin目录下去启动就好了,看到如图所示就成了: - -![](/img/blog/20191202203943.png) - -​ 3.启动完毕后访问:http://127.0.0.1:8848/nacos/#/login - -![](/img/blog/20191202204101.png) - -是不是看到这样的界面了?输入nacos(账号密码相同),先进去看看吧. - -这时候可以发现没有任何服务注册 - -![20191202204147](/img/blog/20191202204147.png) - -别急我们马上让seata服务连接进来. - -## Seata配置 - -​ 1.进入seata的conf文件夹看到这个木有? - -![](/img/blog/20191202204259.png) - -就是它,编辑它: - -![20191202204353](/img/blog/20191202204353.png) - -![20191202204437](/img/blog/20191202204437.png) - -​ 2.然后记得保存哦!接着我们把registry.conf文件打开编辑: - -``` -registry { - # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa - type = "nacos" - - nacos { - serverAddr = "localhost" - namespace = "" - cluster = "default" - } - eureka { - serviceUrl = "http://localhost:8761/eureka" - application = "default" - weight = "1" - } - redis { - serverAddr = "localhost:6379" - db = "0" - } - zk { - cluster = "default" - serverAddr = "127.0.0.1:2181" - session.timeout = 6000 - connect.timeout = 2000 - } - consul { - cluster = "default" - serverAddr = "127.0.0.1:8500" - } - etcd3 { - cluster = "default" - serverAddr = "http://localhost:2379" - } - sofa { - serverAddr = "127.0.0.1:9603" - application = "default" - region = "DEFAULT_ZONE" - datacenter = "DefaultDataCenter" - cluster = "default" - group = "SEATA_GROUP" - addressWaitTime = "3000" - } - file { - name = "file.conf" - } -} - -config { - # file、nacos 、apollo、zk、consul、etcd3 - type = "nacos" - - nacos { - serverAddr = "localhost" - namespace = "" - } - consul { - serverAddr = "127.0.0.1:8500" - } - apollo { - app.id = "seata-server" - apollo.meta = "http://192.168.1.204:8801" - } - zk { - serverAddr = "127.0.0.1:2181" - session.timeout = 6000 - connect.timeout = 2000 - } - etcd3 { - serverAddr = "http://localhost:2379" - } - file { - name = "file.conf" - } -} - -``` - -都编辑好了后,我们运行nacos-config.sh,这时候我们配置的nacos-config.txt的内容已经被发送到nacos中了详细如图: - -![20191202205743](/img/blog/20191202205743.png) - -出现以上类似的代码就是说明成功了,接着我们登录nacos配置中心,查看配置列表,出现如图列表说明配置成功了: - -![20191202205912](/img/blog/20191202205912.png) - -看到了吧,你的配置已经全部都提交上去了,如果再git工具内运行sh不行的话,试着把编辑sh文件,试试改成如下操作 - -```shell -for line in $(cat nacos-config.txt) - -do - -key=${line%%=*} -value=${line#*=} -echo "\r\n set "${key}" = "${value} - -result=`curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=$key&group=SEATA_GROUP&content=$value"` - -if [ "$result"x == "true"x ]; then - - echo "\033[42;37m $result \033[0m" - -else - - echo "\033[41;37 $result \033[0m" - let error++ - -fi - -done - - -if [ $error -eq 0 ]; then - -echo "\r\n\033[42;37m init nacos config finished, please start seata-server. \033[0m" - -else - -echo "\r\n\033[41;33m init nacos config fail. \033[0m" - -fi -``` - -​ 3.目前我们的准备工作全部完成,我们去seata-service/bin去运行seata服务吧,如图所示就成功啦! - -![20191202210112](/img/blog/20191202210112.png) - -# 进行调试 - -​ 1.首先把springboot-dubbo-mybatsiplus-seata项目的pom的依赖更改,去除掉zk这些配置,因为我们使用nacos做注册中心了. - -```java - - 3.1 - UTF-8 - UTF-8 - 1.8 - 1.8 - 3.2.0 - 3.2.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.1.8.RELEASE - - - - com.alibaba.nacos - nacos-client - 1.1.4 - - - org.apache.dubbo - dubbo-registry-nacos - 2.7.4.1 - - - org.apache.dubbo - dubbo-spring-boot-starter - 2.7.4.1 - - - org.apache.commons - commons-lang3 - - - com.alibaba - fastjson - 1.2.60 - - - - io.springfox - springfox-swagger2 - 2.9.2 - - - io.springfox - springfox-swagger-ui - 2.9.2 - - - - - com.baomidou - mybatis-plus-boot-starter - ${mybatis-plus-boot-starter.version} - - - - - org.projectlombok - lombok - provided - - - io.seata - seata-all - 0.9.0.1 - - - - - - - org.freemarker - freemarker - - - - com.alibaba - druid-spring-boot-starter - 1.1.20 - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - - - org.springframework.boot - spring-boot-starter-log4j2 - - - - mysql - mysql-connector-java - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - - org.slf4j - slf4j-log4j12 - - - - - org.springframework.boot - spring-boot-starter-aop - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - -``` - -​ 2.然后更改test-service的目录结构,删除zk的配置并更改application.yml文件,目录结构与代码: - -```yaml -server: - port: 38888 -spring: - application: - name: test-service - datasource: - type: com.alibaba.druid.pool.DruidDataSource - url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC - driver-class-name: com.mysql.cj.jdbc.Driver - username: root - password: 123456 -dubbo: - protocol: - loadbalance: leastactive - threadpool: cached - scan: - base-packages: org。test.service - application: - qos-enable: false - name: testserver - registry: - id: my-registry - address: nacos://127.0.0.1:8848 -mybatis-plus: - mapper-locations: classpath:/mapper/*Mapper.xml - typeAliasesPackage: org.test.entity - global-config: - db-config: - field-strategy: not-empty - id-type: auto - db-type: mysql - configuration: - map-underscore-to-camel-case: true - cache-enabled: true - auto-mapping-unknown-column-behavior: none -``` - -20191202211833 - -​ 3.再更改registry.conf文件,如果你的nacos是其它服务器,请改成对应都ip跟端口 - -```java -registry { - type = "nacos" - file { - name = "file.conf" - } - zk { - cluster = "default" - serverAddr = "127.0.0.1:2181" - session.timeout = 6000 - connect.timeout = 2000 - } - nacos { - serverAddr = "localhost" - namespace = "" - cluster = "default" - } -} -config { - type = "nacos" - file { - name = "file.conf" - } - zk { - serverAddr = "127.0.0.1:2181" - session.timeout = 6000 - connect.timeout = 2000 - } - nacos { - serverAddr = "localhost" - namespace = "" - cluster = "default" - } -} -``` - -​ 4.接着我们运行provideApplication - -![20191202212000](/img/blog/20191202212000.png) - -启动成功啦,我们再去看seata的日志: - -![20191202212028](/img/blog/20191202212028.png) - -成功了,这下我们一样,去修改test-client的内容,首先一样application.yml,把zk换成nacos,这里就不详细描述了,把test-service内的registry.conf,复制到client项目的resources中覆盖原来的registry.conf. - -然后我们可以运行clientApplication: - -![20191202212114](/img/blog/20191202212114.png) - -​ 5.确认服务已经被发布并测试事务运行是否正常 - -![20191202212203](/img/blog/20191202212203.png) - -服务成功发布出来,也被成功消费了.这下我们再去swagger中去测试回滚是否一切正常,访问http://127.0.0.1:28888/swagger-ui.html - -![20191202212240](/img/blog/20191202212240.png) - -恭喜你,看到这一定跟我一样成功了! - -# 总结 - -关于nacos的使用跟seata的简单搭建已经完成了,更详细的内容希望希望大家访问以下地址阅读详细文档 - -[nacos官网](https://nacos.io/zh-cn/index.html) - -[dubbo官网](http://dubbo.apache.org/en-us/) - -[seata官网](http://seata.io/zh-cn/) +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-nacos-docker.md b/blog/seata-nacos-docker.md index 984953f429..e872b67af1 100644 --- a/blog/seata-nacos-docker.md +++ b/blog/seata-nacos-docker.md @@ -1,576 +1 @@ ---- -title: Docker部署Seata与Nacos整合 -keywords: [Seata,Nacos,分布式事务] -description: 本文讲述如何使用Seata整合Nacos配置的Docker部署 -author: FUNKYE -date: 2019/12/03 ---- - -# Docker部署Seata与Nacos整合 - -运行所使用的demo[项目地址](https://gitee.com/itCjb/springboot-dubbo-mybatisplus-seata ) - -本文作者:FUNKYE(陈健斌),杭州某互联网公司主程。 - -# 前言 - -直连方式的Seata配置[博客](http://seata.io/zh-cn/blog/springboot-dubbo-mybatisplus-seata.html) - -Seata整合Nacos配置[博客](http://seata.io/zh-cn/blog/seata-nacos-analysis.html) - -我们接着前几篇篇的基础上去配置nacos做配置中心跟dubbo注册中心. - -## 准备工作 - -​ 1.安装docker - -```shell -yum -y install docker -``` - -​ 2.创建nacos与seata的数据库 - -```mysql -/******************************************/ -/* 数据库全名 = nacos */ -/* 表名称 = config_info */ -/******************************************/ -CREATE TABLE `config_info` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', - `data_id` varchar(255) NOT NULL COMMENT 'data_id', - `group_id` varchar(255) DEFAULT NULL, - `content` longtext NOT NULL COMMENT 'content', - `md5` varchar(32) DEFAULT NULL COMMENT 'md5', - `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间', - `src_user` text COMMENT 'source user', - `src_ip` varchar(20) DEFAULT NULL COMMENT 'source ip', - `app_name` varchar(128) DEFAULT NULL, - `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', - `c_desc` varchar(256) DEFAULT NULL, - `c_use` varchar(64) DEFAULT NULL, - `effect` varchar(64) DEFAULT NULL, - `type` varchar(64) DEFAULT NULL, - `c_schema` text, - PRIMARY KEY (`id`), - UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info'; - -/******************************************/ -/* 数据库全名 = nacos_config */ -/* 表名称 = config_info_aggr */ -/******************************************/ -CREATE TABLE `config_info_aggr` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', - `data_id` varchar(255) NOT NULL COMMENT 'data_id', - `group_id` varchar(255) NOT NULL COMMENT 'group_id', - `datum_id` varchar(255) NOT NULL COMMENT 'datum_id', - `content` longtext NOT NULL COMMENT '内容', - `gmt_modified` datetime NOT NULL COMMENT '修改时间', - `app_name` varchar(128) DEFAULT NULL, - `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段'; - - -/******************************************/ -/* 数据库全名 = nacos_config */ -/* 表名称 = config_info_beta */ -/******************************************/ -CREATE TABLE `config_info_beta` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', - `data_id` varchar(255) NOT NULL COMMENT 'data_id', - `group_id` varchar(128) NOT NULL COMMENT 'group_id', - `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', - `content` longtext NOT NULL COMMENT 'content', - `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps', - `md5` varchar(32) DEFAULT NULL COMMENT 'md5', - `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间', - `src_user` text COMMENT 'source user', - `src_ip` varchar(20) DEFAULT NULL COMMENT 'source ip', - `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta'; - -/******************************************/ -/* 数据库全名 = nacos_config */ -/* 表名称 = config_info_tag */ -/******************************************/ -CREATE TABLE `config_info_tag` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', - `data_id` varchar(255) NOT NULL COMMENT 'data_id', - `group_id` varchar(128) NOT NULL COMMENT 'group_id', - `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', - `tag_id` varchar(128) NOT NULL COMMENT 'tag_id', - `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', - `content` longtext NOT NULL COMMENT 'content', - `md5` varchar(32) DEFAULT NULL COMMENT 'md5', - `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间', - `src_user` text COMMENT 'source user', - `src_ip` varchar(20) DEFAULT NULL COMMENT 'source ip', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag'; - -/******************************************/ -/* 数据库全名 = nacos_config */ -/* 表名称 = config_tags_relation */ -/******************************************/ -CREATE TABLE `config_tags_relation` ( - `id` bigint(20) NOT NULL COMMENT 'id', - `tag_name` varchar(128) NOT NULL COMMENT 'tag_name', - `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type', - `data_id` varchar(255) NOT NULL COMMENT 'data_id', - `group_id` varchar(128) NOT NULL COMMENT 'group_id', - `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', - `nid` bigint(20) NOT NULL AUTO_INCREMENT, - PRIMARY KEY (`nid`), - UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`), - KEY `idx_tenant_id` (`tenant_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation'; - -/******************************************/ -/* 数据库全名 = nacos_config */ -/* 表名称 = group_capacity */ -/******************************************/ -CREATE TABLE `group_capacity` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群', - `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', - `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', - `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', - `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值', - `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', - `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', - `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_group_id` (`group_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表'; - -/******************************************/ -/* 数据库全名 = nacos_config */ -/* 表名称 = his_config_info */ -/******************************************/ -CREATE TABLE `his_config_info` ( - `id` bigint(64) unsigned NOT NULL, - `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `data_id` varchar(255) NOT NULL, - `group_id` varchar(128) NOT NULL, - `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', - `content` longtext NOT NULL, - `md5` varchar(32) DEFAULT NULL, - `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00', - `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00', - `src_user` text, - `src_ip` varchar(20) DEFAULT NULL, - `op_type` char(10) DEFAULT NULL, - `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', - PRIMARY KEY (`nid`), - KEY `idx_gmt_create` (`gmt_create`), - KEY `idx_gmt_modified` (`gmt_modified`), - KEY `idx_did` (`data_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造'; - - -/******************************************/ -/* 数据库全名 = nacos_config */ -/* 表名称 = tenant_capacity */ -/******************************************/ -CREATE TABLE `tenant_capacity` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID', - `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', - `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', - `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', - `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数', - `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', - `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', - `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_tenant_id` (`tenant_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表'; - - -CREATE TABLE `tenant_info` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', - `kp` varchar(128) NOT NULL COMMENT 'kp', - `tenant_id` varchar(128) default '' COMMENT 'tenant_id', - `tenant_name` varchar(128) default '' COMMENT 'tenant_name', - `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc', - `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source', - `gmt_create` bigint(20) NOT NULL COMMENT '创建时间', - `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`), - KEY `idx_tenant_id` (`tenant_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info'; - -CREATE TABLE users ( - username varchar(50) NOT NULL PRIMARY KEY, - password varchar(500) NOT NULL, - enabled boolean NOT NULL -); - -CREATE TABLE roles ( - username varchar(50) NOT NULL, - role varchar(50) NOT NULL -); - -INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE); - -INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN'); - -``` - -```mysql --- the table to store GlobalSession data -CREATE TABLE IF NOT EXISTS `global_table` -( - `xid` VARCHAR(128) NOT NULL, - `transaction_id` BIGINT, - `status` TINYINT NOT NULL, - `application_id` VARCHAR(32), - `transaction_service_group` VARCHAR(32), - `transaction_name` VARCHAR(128), - `timeout` INT, - `begin_time` BIGINT, - `application_data` VARCHAR(2000), - `gmt_create` DATETIME, - `gmt_modified` DATETIME, - PRIMARY KEY (`xid`), - KEY `idx_gmt_modified_status` (`gmt_modified`, `status`), - KEY `idx_transaction_id` (`transaction_id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8; - --- the table to store BranchSession data -CREATE TABLE IF NOT EXISTS `branch_table` -( - `branch_id` BIGINT NOT NULL, - `xid` VARCHAR(128) NOT NULL, - `transaction_id` BIGINT, - `resource_group_id` VARCHAR(32), - `resource_id` VARCHAR(256), - `branch_type` VARCHAR(8), - `status` TINYINT, - `client_id` VARCHAR(64), - `application_data` VARCHAR(2000), - `gmt_create` DATETIME(6), - `gmt_modified` DATETIME(6), - PRIMARY KEY (`branch_id`), - KEY `idx_xid` (`xid`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8; - --- the table to store lock data -CREATE TABLE IF NOT EXISTS `lock_table` -( - `row_key` VARCHAR(128) NOT NULL, - `xid` VARCHAR(128), - `transaction_id` BIGINT, - `branch_id` BIGINT NOT NULL, - `resource_id` VARCHAR(256), - `table_name` VARCHAR(32), - `pk` VARCHAR(36), - `gmt_create` DATETIME, - `gmt_modified` DATETIME, - PRIMARY KEY (`row_key`), - KEY `idx_branch_id` (`branch_id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8; - -``` - -​ 3.拉取nacos以及seata镜像并运行 - -```shell -docker run -d --name nacos -p 8848:8848 -e MODE=standalone -e MYSQL_MASTER_SERVICE_HOST=你的mysql所在ip -e MYSQL_MASTER_SERVICE_DB_NAME=nacos -e MYSQL_MASTER_SERVICE_USER=root -e MYSQL_MASTER_SERVICE_PASSWORD=mysql密码 -e MYSQL_SLAVE_SERVICE_HOST=你的mysql所在ip -e SPRING_DATASOURCE_PLATFORM=mysql -e MYSQL_DATABASE_NUM=1 nacos/nacos-server:latest -``` - -```shell -docker run -d --name seata -p 8091:8091 -e SEATA_IP=你想指定的ip -e SEATA_PORT=8091 seataio/seata-server:1.4.2 -``` - -## Seata配置 - -​ 1.由于seata容器内没有内置vim,我们可以直接将要文件夹cp到宿主机外来编辑好了,再cp回去 - -``` -docker cp 容器id:seata-server/resources 你想放置的目录 -``` - -​ 2.使用如下代码获取两个容器的ip地址 - -``` -docker inspect --format='{{.NetworkSettings.IPAddress}}' ID/NAMES -``` - -​ 3.nacos-config.txt编辑为如下内容 - -``` -transport.type=TCP -transport.server=NIO -transport.heartbeat=true -transport.enableClientBatchSendRequest=false -transport.threadFactory.bossThreadPrefix=NettyBoss -transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker -transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler -transport.threadFactory.shareBossWorker=false -transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector -transport.threadFactory.clientSelectorThreadSize=1 -transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread -transport.threadFactory.bossThreadSize=1 -transport.threadFactory.workerThreadSize=default -transport.shutdown.wait=3 -service.vgroupMapping.你的事务组名=default -service.default.grouplist=127.0.0.1:8091 -service.enableDegrade=false -service.disableGlobalTransaction=false -client.rm.asyncCommitBufferLimit=10000 -client.rm.lock.retryInterval=10 -client.rm.lock.retryTimes=30 -client.rm.lock.retryPolicyBranchRollbackOnConflict=true -client.rm.reportRetryCount=5 -client.rm.tableMetaCheckEnable=false -client.rm.tableMetaCheckerInterval=60000 -client.rm.sqlParserType=druid -client.rm.reportSuccessEnable=false -client.rm.sagaBranchRegisterEnable=false -client.tm.commitRetryCount=5 -client.tm.rollbackRetryCount=5 -client.tm.defaultGlobalTransactionTimeout=60000 -client.tm.degradeCheck=false -client.tm.degradeCheckAllowTimes=10 -client.tm.degradeCheckPeriod=2000 -store.mode=file -store.publicKey= -store.file.dir=file_store/data -store.file.maxBranchSessionSize=16384 -store.file.maxGlobalSessionSize=512 -store.file.fileWriteBufferCacheSize=16384 -store.file.flushDiskMode=async -store.file.sessionReloadReadSize=100 -store.db.datasource=druid -store.db.dbType=mysql -store.db.driverClassName=com.mysql.jdbc.Driver -store.db.url=jdbc:mysql://你的mysql所在ip:3306/seata?useUnicode=true&rewriteBatchedStatements=true -store.db.user=mysql账号 -store.db.password=mysql密码 -store.db.minConn=5 -store.db.maxConn=30 -store.db.globalTable=global_table -store.db.branchTable=branch_table -store.db.queryLimit=100 -store.db.lockTable=lock_table -store.db.maxWait=5000 -server.recovery.committingRetryPeriod=1000 -server.recovery.asynCommittingRetryPeriod=1000 -server.recovery.rollbackingRetryPeriod=1000 -server.recovery.timeoutRetryPeriod=1000 -server.maxCommitRetryTimeout=-1 -server.maxRollbackRetryTimeout=-1 -server.rollbackRetryTimeoutUnlockEnable=false -client.undo.dataValidation=true -client.undo.logSerialization=jackson -client.undo.onlyCareUpdateColumns=true -server.undo.logSaveDays=7 -server.undo.logDeletePeriod=86400000 -client.undo.logTable=undo_log -client.undo.compress.enable=true -client.undo.compress.type=zip -client.undo.compress.threshold=64k -log.exceptionRate=100 -transport.serialization=seata -transport.compressor=none -metrics.enabled=false -metrics.registryType=compact -metrics.exporterList=prometheus -metrics.exporterPrometheusPort=9898 -``` - -详细参数配置请点[此处](http://seata.io/zh-cn/docs/user/configurations.html) - -​ 4.registry.conf编辑为如下内容 - -``` -registry { - # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa - type = "nacos" - - nacos { - serverAddr = "nacos容器ip:8848" - namespace = "" - cluster = "default" - } -} - -config { - # file、nacos 、apollo、zk、consul、etcd3 - type = "nacos" - - nacos { - serverAddr = "nacos容器ip:8848" - namespace = "" - } -} -``` - -​ 5.配置完成后使用如下命令,把修改完成的registry.conf复制到容器中,并重启查看日志运行 - -```shell -docker cp /home/seata/resources/registry.conf seata:seata-server/resources/ -docker restart seata -docker logs -f seata -``` - -​ 6.运行nacos-config.sh导入Nacos配置 - -eg: sh ${SEATAPATH}/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u username -w password - -具体参数释义参考:[配置导入说明](https://github.com/seata/seata/blob/1.4.2/script/config-center/README.md) - - -​ 7.登录nacos控制中心查看 - -![20191202205912](/img/blog/20191202205912.png) - -​ 如图所示便是成功了. - -# 进行调试 - -​ 1.拉取博文中所示的项目,修改test-service的application.yml与registry.conf - -``` -registry { - type = "nacos" - nacos { - serverAddr = "宿主机ip:8848" - namespace = "" - cluster = "default" - } -} -config { - type = "nacos" - nacos { - serverAddr = "宿主机ip:8848" - namespace = "" - cluster = "default" - } -} - -``` - -``` -server: - port: 38888 -spring: - application: - name: test-service - datasource: - type: com.alibaba.druid.pool.DruidDataSource - url: jdbc:mysql://mysqlip:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC - driver-class-name: com.mysql.cj.jdbc.Driver - username: root - password: 123456 -dubbo: - protocol: - threadpool: cached - scan: - base-packages: com.example - application: - qos-enable: false - name: testserver - registry: - id: my-registry - address: nacos://宿主机ip:8848 -mybatis-plus: - mapper-locations: classpath:/mapper/*Mapper.xml - typeAliasesPackage: org.test.entity - global-config: - db-config: - field-strategy: not-empty - id-type: auto - db-type: mysql - configuration: - map-underscore-to-camel-case: true - cache-enabled: true - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl - auto-mapping-unknown-column-behavior: none -``` - -​ 2.把修改完成的registry.conf复制到test-client-resources中,并修改application - -``` -spring: - application: - name: test - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://mysqlIp:3306/test?userSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai - username: root - password: 123456 - mvc: - servlet: - load-on-startup: 1 - http: - encoding: - force: true - charset: utf-8 - enabled: true - multipart: - max-file-size: 10MB - max-request-size: 10MB -dubbo: - registry: - id: my-registry - address: nacos://宿主机ip:8848 - application: - name: dubbo-demo-client - qos-enable: false -server: - port: 28888 - max-http-header-size: 8192 - address: 0.0.0.0 - tomcat: - max-http-post-size: 104857600 -``` -​ 4. 在每个所涉及的 db 执行 undo_log 脚本. -```sql -CREATE TABLE IF NOT EXISTS `undo_log` -( - `branch_id` BIGINT NOT NULL COMMENT 'branch transaction id', - `xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id', - `context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization', - `rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info', - `log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status', - `log_created` DATETIME(6) NOT NULL COMMENT 'create datetime', - `log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime', - UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) -) ENGINE = InnoDB - AUTO_INCREMENT = 1 - DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table'; -``` - -​ 5.依次运行test-service,test-client. - -​ 6.查看nacos中服务列表是否如下图所示 - -![20191203132351](/img/blog/20191203132351.png) - -# 总结 - -关于nacos与seata的docker部署已经完成了,更详细的内容希望希望大家访问以下地址阅读详细文档 - -[nacos官网](https://nacos.io/zh-cn/index.html) - -[dubbo官网](http://dubbo.apache.org/en-us/) - -[seata官网](http://seata.io/zh-cn/) - -[docker官网](https://www.docker.com/) \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-observable-practice.md b/blog/seata-observable-practice.md index 31397eccb9..e872b67af1 100644 --- a/blog/seata-observable-practice.md +++ b/blog/seata-observable-practice.md @@ -1,141 +1 @@ ---- -title: Seata的可观测实践 -keywords: [Seata、分布式事务、数据一致性、微服务、可观测] -description: 本文介绍Seata在可观测领域的探索和实践 -author: 刘戎-Seata -date: 2023/06/25 ---- - -## Seata简介 -Seata的前身是阿里巴巴集团内大规模使用保证分布式事务一致性的中间件,Seata是其开源产品,由社区维护。在介绍Seata前,先与大家讨论下我们业务发展过程中经常遇到的一些问题场景。 -### 业务场景 -我们业务在发展的过程中,基本上都是从一个简单的应用,逐渐过渡到规模庞大、业务复杂的应用。这些复杂的场景难免遇到分布式事务管理问题,Seata的出现正是解决这些分布式场景下的事务管理问题。介绍下其中几个经典的场景: -#### 场景一:分库分表场景下的分布式事务 -![image.png](/img/blog/metrics-分库分表场景下的分布式事务.png) -起初我们的业务规模小、轻量化,单一数据库就能保障我们的数据链路。但随着业务规模不断扩大、业务不断复杂化,通常单一数据库在容量、性能上会遭遇瓶颈。通常的解决方案是向分库、分表的架构演进。此时,即引入了**分库分表场景下**的分布式事务场景。 -#### 场景二:跨服务场景下的分布式事务 -![image.png](/img/blog/metrics-跨服务场景下的分布式事务.png) -降低单体应用复杂度的方案:应用微服务化拆分。拆分后,我们的产品由多个功能各异的微服务组件构成,每个微服务都使用独立的数据库资源。在涉及到跨服务调用的数据一致性场景时,就引入了**跨服务场景下**的分布式事务。 -### Seata架构 -![image.png](/img/blog/metrics-Seata架构.png) -其核心组件主要如下: - -- **Transaction Coordinator(TC)** - -事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。 - -- **Transaction Manager(TM)** - -控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议,TM定义全局事务的边界。 - -- **Resource Manager(RM)** - -控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。RM负责定义分支事务的边界和行为。 -## Seata的可观测实践 -### 为什么需要可观测? - -- **分布式事务消息链路较复杂** - -Seata在解决了用户易用性和分布式事务一致性这些问题的同时,需要多次TC与TM、RM之间的交互,尤其当微服务的链路变复杂时,Seata的交互链路也会呈正相关性增加。这种情况下,其实我们就需要引入可观测的能力来观察、分析事物链路。 - -- **异常链路、故障排查难定位,性能优化无从下手** - -在排查Seata的异常事务链路时,传统的方法需要看日志,这样检索起来比较麻烦。在引入可观测能力后,帮助我们直观的分析链路,快速定位问题;为优化耗时的事务链路提供依据。 - -- **可视化、数据可量化** - -可视化能力可让用户对事务执行情况有直观的感受;借助可量化的数据,可帮助用户评估资源消耗、规划预算。 -### 可观测能力概览 -| **可观测维度** | **seata期望的能力** | **技术选型参考** | -| --- | --- | --- | -| Metrics | 功能层面:可按业务分组隔离,采集事务总量、耗时等重要指标 -性能层面:高度量性能,插件按需加载 -架构层面:减少第三方依赖,服务端、客户端能够采用统一的架构,减少技术复杂度 -兼容性层面:至少兼容Prometheus生态 | Prometheus:指标存储和查询等领域有着业界领先的地位 -OpenTelemetry:可观测数据采集和规范的事实标准。但自身并不负责数据的存储,展示和分析 | -| Tracing | 功能层面:全链路追踪分布式事务生命周期,反应分布式事务执行性能消耗 -易用性方面:对使用seata的用户而言简单易接入 | SkyWalking:利用Java的Agent探针技术,效率高,简单易用。 | -| Logging | 功能层面:记录服务端、客户端全部生命周期信息 -易用性层面:能根据XID快速匹配全局事务对应链路日志 | Alibaba Cloud Service -ELK | - -### Metrics维度 -#### 设计思路 - -1. Seata作为一个被集成的数据一致性框架,Metrics模块将尽可能少的使用第三方依赖以降低发生冲突的风险 -2. Metrics模块将竭力争取更高的度量性能和更低的资源开销,尽可能降低开启后带来的副作用 -3. 配置时,Metrics是否激活、数据如何发布,取决于对应的配置;开启配置则自动启用,并默认将度量数据通过prometheusexporter的形式发布 -4. 不使用Spring,使用SPI(Service Provider Interface)加载扩展 -#### 模块设计 -![图片 1.png](/img/blog/metrics-模块设计.png) - -- seata-metrics-core:Metrics核心模块,根据配置组织(加载)1个Registry和N个Exporter; -- seata-metrics-api:定义了Meter指标接口,Registry指标注册中心接口; -- seata-metrics-exporter-prometheus:内置的prometheus-exporter实现; -- seata-metrics-registry-compact:内置的Registry实现,并轻量级实现了Gauge、Counter、Summay、Timer指标; -#### metrics模块工作流 -![图片 1.png](/img/blog/metrics-模块工作流.png) -上图是metrics模块的工作流,其工作流程如下: - -1. 利用SPI机制,根据配置加载Exporter和Registry的实现类; -2. 基于消息订阅与通知机制,监听所有全局事务的状态变更事件,并publish到EventBus; -3. 事件订阅者消费事件,并将生成的metrics写入Registry; -4. 监控系统(如prometheus)从Exporter中拉取数据。 -#### TC核心指标 -![image.png](/img/blog/metrics-TC核心指标.png) -#### TM核心指标 -![image.png](/img/blog/metrics-TM核心指标.png) -#### RM核心指标 -![image.png](/img/blog/metrics-RM核心指标.png) -#### 大盘展示 -![lQLPJxZhZlqESU3NBpjNBp6w8zYK6VbMgzYCoKVrWEDWAA_1694_1688.png](/img/blog/metrics-大盘展示.png) -### Tracing维度 -#### Seata为什么需要tracing? - -1. 对业务侧而言,引入Seata后,对业务性能会带来多大损耗?主要时间消耗在什么地方?如何针对性的优化业务逻辑?这些都是未知的。 -2. Seata的所有消息记录都通过日志持久化落盘,但对不了解Seata的用户而言,日志非常不友好。能否通过接入Tracing,提升事务链路排查效率? -3. 对于新手用户,可通过Tracing记录,快速了解seata的工作原理,降低seata使用门槛。 -#### Seata的tracing解决方案 - -- Seata在自定义的RPC消息协议中定义了Header信息; -- SkyWalking拦截指定的RPC消息,并注入tracing相关的span信息; -- 以RPC消息的发出&接收为临界点,定义了span的生命周期范围。 - -基于上述的方式,Seata实现了事务全链路的tracing,具体接入可参考[为[Seata应用 | Seata-server]接入Skywalking](https://seata.io/zh-cn/docs/user/apm/skywalking.html)。 -#### tracing效果 - -- 基于的demo场景: -1. 用户请求交易服务 -2. 交易服务锁定库存 -3. 交易服务创建账单 -4. 账单服务进行扣款 - -![image.png](/img/blog/metrics-tracing效果-业务逻辑图.png) - -- GlobalCommit成功的事务链路(事例) - -![image.png](/img/blog/metrics-tracing效果-tracing链1.png) -![image.png](/img/blog/metrics-tracing效果-tracing链2.png) -![image.png](/img/blog/metrics-tracing效果-tracing链3.png) -### Logging维度 -#### 设计思路 -![image.png](/img/blog/metrics-logging设计思路.png) -Logging这一块其实承担的是可观测这几个维度当中的兜底角色。放在最底层的,其实就是我们日志格式的设计,只有好日志格式,我们才能对它进行更好的采集、模块化的存储和展示。在其之上,是日志的采集、存储、监控、告警、数据可视化,这些模块更多的是有现成的工具,比如阿里的SLS日志服务、还有ELK的一套技术栈,我们更多是将开销成本、接入复杂度、生态繁荣度等作为考量。 -#### 日志格式设计 -这里拿Seata-Server的一个日志格式作为案例: -![image.png](/img/blog/metrics-logging日志效果.png) - -- 线程池规范命名:当线程池、线程比较多时,规范的线程命名能将无序执行的线程执行次序清晰展示。 -- 方法全类名可追溯:快速定位到具体的代码块。 -- 重点运行时信息透出:重点突出关键日志,不关键的日志不打印,减少日志冗余。 -- 消息格式可扩展:通过扩展消息类的输出格式,减少日志的代码修改量。 -## 总结&展望 -#### Metrics -总结:基本实现分布式事务的可量化、可观测。 -展望:更细粒度的指标、更广阔的生态兼容。 -#### Tracing -总结:分布式事务全链路的可追溯。 -展望:根据xid追溯事务链路,异常链路根因快速定位。 -#### Logging -总结:结构化的日志格式。 -展望:日志可观测体系演进。 +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-quick-start.md b/blog/seata-quick-start.md index 1a06b25649..e872b67af1 100644 --- a/blog/seata-quick-start.md +++ b/blog/seata-quick-start.md @@ -1,340 +1 @@ ---- -title: Seata 极简入门 -description: 从 0 开始入门 Seata,搭建 Seata 服务,并接入 Java 项目中实现分布式事务 -keywords: [fescar、seata、分布式事务] -author: 芋道源码 -date: 2020/04/19 ---- - -- [1. 概述](#) -- [2. 部署单机 TC Server](#) -- [3. 部署集群 TC Server](#) -- [4. 接入 Java 应用](#) - -# 1. 概述 - -[Seata](https://github.com/seata/seata) 是**阿里**开源的一款开源的**分布式事务**解决方案,致力于提供高性能和简单易用的分布式事务服务。 - -## 1.1 四种事务模式 - -Seata 目标打造**一站式**的分布事务的解决方案,最终会提供四种事务模式: -* AT 模式:参见[《Seata AT 模式》](https://seata.io/zh-cn/docs/dev/mode/at-mode.html)文档 -* TCC 模式:参见[《Seata TCC 模式》](https://seata.io/zh-cn/docs/dev/mode/tcc-mode.html)文档 -* Saga 模式:参见[《SEATA Saga 模式》](https://seata.io/zh-cn/docs/dev/mode/saga-mode.html)文档 -* XA 模式:正在开发中... - -目前使用的**流行度**情况是:AT > TCC > Saga。因此,我们在学习 Seata 的时候,可以花更多精力在 **AT 模式**上,最好搞懂背后的实现原理,毕竟分布式事务涉及到数据的正确性,出问题需要快速排查定位并解决。 - -> 友情提示:具体的流行度,胖友可以选择看看 [Wanted: who's using Seata](https://github.com/seata/seata/issues/1246) 每个公司登记的使用方式。 - -## 1.2 三种角色 - -在 Seata 的架构中,一共有三个角色: - -![三个角色](http://www.iocoder.cn/images/Seata/2017-01-01/02.png) - -* **TC** (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动**全局事务**提交或回滚。 -* **TM** (Transaction Manager) - 事务管理器:定义**全局事务**的范围,开始全局事务、提交或回滚全局事务。 -* **RM** ( Resource Manager ) - 资源管理器:管理**分支事务**处理的资源( Resource ),与 TC 交谈以注册分支事务和报告分支事务的状态,并驱动**分支事务**提交或回滚。 - -其中,TC 为单独部署的 **Server** 服务端,TM 和 RM 为嵌入到应用中的 **Client** 客户端。 - -在 Seata 中,一个分布式事务的**生命周期**如下: - -![架构图](http://www.iocoder.cn/images/Seata/2017-01-01/01.png) - -> 友情提示:看下艿艿添加的红色小勾。 - -* TM 请求 TC 开启一个全局事务。TC 会生成一个 **XID** 作为该全局事务的编号。 - > **XID**,会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起。 - -* RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的 **XID** 进行关联。 -* TM 请求 TC 告诉 **XID** 对应的全局事务是进行提交还是回滚。 -* TC 驱动 RM 们将 **XID** 对应的自己的本地事务进行提交还是回滚。 - -## 1.3 框架支持情况 - -Seata 目前提供了对主流的**微服务框架**的支持: -* Dubbo - > 通过 [`seata-dubbo`](https://github.com/seata/seata/blob/develop/integration/dubbo/) 集成 - -* SOFA-RPC - > 通过 [`seata-sofa-rpc`](https://github.com/seata/seata/blob/develop/integration/sofa-rpc/) 集成 - -* Motan - > 通过 [`seata-motan`](https://github.com/seata/seata/blob/develop/integration/motan/) 集成 - -* gRPC - > 通过 [`seata-grpc`](https://github.com/seata/seata/blob/develop/integration/gprc/) 集成 - -* Apache HttpClient - > 通过 [`seata-http`](https://github.com/seata/seata/blob/develop/integration/http/) 集成 - -* Spring Cloud OpenFeign - > 通过 [`spring-cloud-starter-alibaba-seata`](https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/) 的 [`feign`](https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/feign/) 模块 - -* Spring RestTemplate - > 通过 [`spring-cloud-starter-alibaba-seata`](https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/feign/SeataBeanPostProcessor.java) 的 [`rest`](https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/rest/) 模块 - -同时方便我们集成到 Java 项目当中,Seata 也提供了相应的 Starter 库: -* [`seata-spring-boot-starter`](https://mvnrepository.com/artifact/io.seata/seata-spring-boot-starter) -* [`spring-cloud-starter-alibaba-seata`](https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-seata) - -因为 Seata 是基于 [DataSource](https://docs.oracle.com/javase/7/docs/api/javax/sql/DataSource.html) 数据源进行**代理**来拓展,所以天然对主流的 ORM 框架提供了非常好的支持: -* MyBatis、MyBatis-Plus -* JPA、Hibernate - -## 1.4 案例情况 - -从 [Wanted: who's using Seata](https://github.com/seata/seata/issues/1246) 的登记情况,Seata 已经在国内很多团队开始落地,其中不乏有滴滴、韵达等大型公司。可汇总如下图: - -![汇总图](http://www.iocoder.cn/images/Seata/2017-01-01/03.png) - -另外,在 [awesome-seata](https://github.com/seata/awesome-seata) 仓库中,艿艿看到了滴滴等等公司的落地时的技术分享,还是非常真实可靠的。如下图所示:![awesome-seata 滴滴](http://www.iocoder.cn/images/Seata/2017-01-01/04.png) - -从案例的情况来说,Seata 可能给是目前已知最可靠的分布式事务解决方案,至少对它进行技术投入是非常不错的选择。 - -# 2. 部署单机 TC Server - -本小节,我们来学习部署**单机** Seata **TC** Server,常用于学习或测试使用,不建议在生产环境中部署单机。 - -因为 TC 需要进行全局事务和分支事务的记录,所以需要对应的**存储**。目前,TC 有两种存储模式( `store.mode` ): - -* file 模式:适合**单机**模式,全局事务会话信息在**内存**中读写,并持久化本地文件 `root.data`,性能较高。 -* db 模式:适合**集群**模式,全局事务会话信息通过 **db** 共享,相对性能差点。 - -显然,我们将采用 file 模式,最终我们部署单机 TC Server 如下图所示:![单机 TC Server](http://www.iocoder.cn/images/Seata/2017-01-01/11.png) - -哔哔完这么多,我们开始正式部署单机 TC Server,这里艿艿使用 macOS 系统,和 Linux、Windows 是差不多的,胖友脑补翻译。 - -## 2.1 下载 Seata 软件包 - -打开 [Seata 下载页面](https://github.com/seata/seata/releases),选择想要的 Seata 版本。这里,我们选择 [v1.1.0](https://github.com/seata/seata/releases/tag/v1.1.0) 最新版本。 - -```Bash -# 创建目录 -$ mkdir -p /Users/yunai/Seata -$ cd /Users/yunai/Seata - -# 下载 -$ wget https://github.com/seata/seata/releases/download/v1.1.0/seata-server-1.1.0.tar.gz - -# 解压 -$ tar -zxvf seata-server-1.1.0.tar.gz - -# 查看目录 -$ cd seata -$ ls -ls -24 -rw-r--r-- 1 yunai staff 11365 May 13 2019 LICENSE - 0 drwxr-xr-x 4 yunai staff 128 Apr 2 07:46 bin # 执行脚本 - 0 drwxr-xr-x 9 yunai staff 288 Feb 19 23:49 conf # 配置文件 - 0 drwxr-xr-x 138 yunai staff 4416 Apr 2 07:46 lib # seata-*.jar + 依赖库 -``` - -## 2.2 启动 TC Server - -执行 `nohup sh bin/seata-server.sh &` 命令,启动 TC Server 在后台。在 `nohup.out` 文件中,我们看到如下日志,说明启动成功: - -```Java -# 使用 File 存储器 -2020-04-02 08:36:01.302 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load TransactionStoreManager[FILE] extension by class[io.seata.server.store.file.FileTransactionStoreManager] -2020-04-02 08:36:01.302 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load SessionManager[FILE] extension by class[io.seata.server.session.file.FileBasedSessionManager] -# 启动成功 -2020-04-02 08:36:01.597 INFO [main]io.seata.core.rpc.netty.RpcServerBootstrap.start:155 -Server started ... -``` -* 默认配置下,Seata TC Server 启动在 **8091** 端点。 - -因为我们使用 file 模式,所以可以看到用于持久化的本地文件 `root.data`。操作命令如下: - -```Bash -$ ls -ls sessionStore/ -total 0 -0 -rw-r--r-- 1 yunai staff 0 Apr 2 08:36 root.data -``` - -后续,胖友可以阅读[「4. 接入 Java 应用」](#)小节,开始使用 Seata 实现分布式事务。 - -# 3. 部署集群 TC Server - -本小节,我们来学习部署**集群** Seata **TC** Server,实现高可用,生产环境下必备。在集群时,多个 Seata TC Server 通过 **db** 数据库,实现全局事务会话信息的共享。 - -同时,每个 Seata TC Server 可以注册自己到注册中心上,方便应用从注册中心获得到他们。最终我们部署 集群 TC Server 如下图所示:![集群 TC Server](http://www.iocoder.cn/images/Seata/2017-01-01/21.png) - -Seata TC Server 对主流的注册中心都提供了集成,具体可见 [discovery](https://github.com/seata/seata/tree/develop/discovery) 目录。考虑到国内使用 Nacos 作为注册中心越来越流行,这里我们就采用它。 - -> 友情提示:如果对 Nacos 不了解的胖友,可以参考[《Nacos 安装部署》](http://www.iocoder.cn/Nacos/install/?self)文章。 - -哔哔完这么多,我们开始正式部署单机 TC Server,这里艿艿使用 macOS 系统,和 Linux、Windows 是差不多的,胖友脑补翻译。 - -## 3.1 下载 Seata 软件包 - -打开 [Seata 下载页面](https://github.com/seata/seata/releases),选择想要的 Seata 版本。这里,我们选择 [v1.1.0](https://github.com/seata/seata/releases/tag/v1.1.0) 最新版本。 - -```Bash -# 创建目录 -$ mkdir -p /Users/yunai/Seata -$ cd /Users/yunai/Seata - -# 下载 -$ wget https://github.com/seata/seata/releases/download/v1.1.0/seata-server-1.1.0.tar.gz - -# 解压 -$ tar -zxvf seata-server-1.1.0.tar.gz - -# 查看目录 -$ cd seata -$ ls -ls -24 -rw-r--r-- 1 yunai staff 11365 May 13 2019 LICENSE - 0 drwxr-xr-x 4 yunai staff 128 Apr 2 07:46 bin # 执行脚本 - 0 drwxr-xr-x 9 yunai staff 288 Feb 19 23:49 conf # 配置文件 - 0 drwxr-xr-x 138 yunai staff 4416 Apr 2 07:46 lib # seata-*.jar + 依赖库 -``` - -## 3.2 初始化数据库 - -① 使用 [`mysql.sql`](https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql) 脚本,初始化 Seata TC Server 的 db 数据库。脚本内容如下: - -```SQL --- -------------------------------- The script used when storeMode is 'db' -------------------------------- --- the table to store GlobalSession data -CREATE TABLE IF NOT EXISTS `global_table` -( - `xid` VARCHAR(128) NOT NULL, - `transaction_id` BIGINT, - `status` TINYINT NOT NULL, - `application_id` VARCHAR(32), - `transaction_service_group` VARCHAR(32), - `transaction_name` VARCHAR(128), - `timeout` INT, - `begin_time` BIGINT, - `application_data` VARCHAR(2000), - `gmt_create` DATETIME, - `gmt_modified` DATETIME, - PRIMARY KEY (`xid`), - KEY `idx_gmt_modified_status` (`gmt_modified`, `status`), - KEY `idx_transaction_id` (`transaction_id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8; - --- the table to store BranchSession data -CREATE TABLE IF NOT EXISTS `branch_table` -( - `branch_id` BIGINT NOT NULL, - `xid` VARCHAR(128) NOT NULL, - `transaction_id` BIGINT, - `resource_group_id` VARCHAR(32), - `resource_id` VARCHAR(256), - `branch_type` VARCHAR(8), - `status` TINYINT, - `client_id` VARCHAR(64), - `application_data` VARCHAR(2000), - `gmt_create` DATETIME(6), - `gmt_modified` DATETIME(6), - PRIMARY KEY (`branch_id`), - KEY `idx_xid` (`xid`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8; - --- the table to store lock data -CREATE TABLE IF NOT EXISTS `lock_table` -( - `row_key` VARCHAR(128) NOT NULL, - `xid` VARCHAR(96), - `transaction_id` BIGINT, - `branch_id` BIGINT NOT NULL, - `resource_id` VARCHAR(256), - `table_name` VARCHAR(32), - `pk` VARCHAR(36), - `gmt_create` DATETIME, - `gmt_modified` DATETIME, - PRIMARY KEY (`row_key`), - KEY `idx_branch_id` (`branch_id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8; -``` - -在 MySQL 中,创建 `seata` 数据库,并在该库下执行该脚本。最终结果如下图:![`seata` 数据库 - MySQL 5.X](http://www.iocoder.cn/images/Seata/2017-01-01/22.png) - -② 修改 `conf/file` 配置文件,修改使用 db 数据库,实现 Seata TC Server 的全局事务会话信息的共享。如下图所示:![`conf/file` 配置文件](http://www.iocoder.cn/images/Seata/2017-01-01/23.png) - -③ MySQL8 的支持 - -> 如果胖友使用的 MySQL 是 8.X 版本,则需要看该步骤。否则,可以直接跳过。 - -首先,需要下载 MySQL 8.X JDBC 驱动,命令行操作如下: - -```Bash -$ cd lib -$ wget https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.19/mysql-connector-java-8.0.19.jar -``` - -然后,修改 `conf/file` 配置文件,使用该 MySQL 8.X JDBC 驱动。如下图所示:![`seata` 数据库 - MySQL 8.X](http://www.iocoder.cn/images/Seata/2017-01-01/24.png) - -## 3.3 设置使用 Nacos 注册中心 - -修改 `conf/registry.conf` 配置文件,设置使用 Nacos 注册中心。如下图所示:![`conf/registry.conf` 配置文件](http://www.iocoder.cn/images/Seata/2017-01-01/25.png) - -## 3.4 启动 TC Server - -① 执行 `nohup sh bin/seata-server.sh -p 18091 -n 1 &` 命令,启动**第一个** TC Server 在后台。 -* `-p`:Seata TC Server 监听的端口。 -* `-n`:Server node。在多个 TC Server 时,需区分各自节点,用于生成不同区间的 transactionId 事务编号,以免冲突。 - -在 `nohup.out` 文件中,我们看到如下日志,说明启动成功: - -```Java -# 使用 DB 存储器 -2020-04-05 16:54:12.793 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load DataSourceGenerator[dbcp] extension by class[io.seata.server.store.db.DbcpDataSourceGenerator] -Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary. -2020-04-05 16:54:13.442 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load LogStore[DB] extension by class[io.seata.core.store.db.LogStoreDataBaseDAO] -2020-04-05 16:54:13.442 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load TransactionStoreManager[DB] extension by class[io.seata.server.store.db.DatabaseTransactionStoreManager] -2020-04-05 16:54:13.442 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load SessionManager[DB] extension by class[io.seata.server.session.db.DataBaseSessionManager] -# 启动成功 -2020-04-05 16:54:13.779 INFO [main]io.seata.core.rpc.netty.RpcServerBootstrap.start:155 -Server started ... -# 使用 Nacos 注册中心 -2020-04-05 16:54:13.788 INFO [main]io.seata.common.loader.EnhancedServiceLoader.loadFile:247 -load RegistryProvider[Nacos] extension by class[io.seata.discovery.registry.nacos.NacosRegistryProvider] -``` - -② 执行 `nohup sh bin/seata-server.sh -p 28091 -n 2 &` 命令,启动**第二个** TC Server 在后台。 - -③ 打开 Nacos 注册中心的控制台,我们可以看到有**两个** Seata TC Server 示例。如下图所示:![Nacos 控制台](http://www.iocoder.cn/images/Seata/2017-01-01/26.png) - -# 4. 接入 Java 应用 - -## 4.1 AT 模式 - -**① Spring Boot** - -1、[《芋道 Spring Boot 分布式事务 Seata 入门》](http://www.iocoder.cn/Spring-Boot/Seata/?self)的[「2. AT 模式 + 多数据源」](#)小节,实现单体 Spring Boot 项目在多数据源下的分布式事务。 - -![整体图](http://www.iocoder.cn/images/Spring-Boot/2020-10-01/01.png) - -2、[《芋道 Spring Boot 分布式事务 Seata 入门》](http://www.iocoder.cn/Spring-Boot/Seata/?self)的[「AT 模式 + HttpClient 远程调用」](#)小节,实现多个 Spring Boot 项目的分布事务。 - -![整体图](http://www.iocoder.cn/images/Spring-Boot/2020-10-01/21.png) - -**② Dubbo** - -[《Dubbo 分布式事务 Seata 入门》](http://www.iocoder.cn/Dubbo/Seata/?sef)的[「2. AT 模式」](#)小节,实现多个 Dubbo 服务下的分布式事务。 - -![整体图](http://www.iocoder.cn/images/Dubbo/2020-04-15/01.png) - -**③ Spring Cloud** - -[《芋道 Spring Cloud Alibaba 分布式事务 Seata 入门》](http://www.iocoder.cn/Spring-Cloud-Alibaba/Seata/?self)的[「3. AT 模式 + Feign」](#)小节,实现多个 Spring Cloud 服务下的分布式事务。 - -![整体图](http://www.iocoder.cn/images/Spring-Cloud/2020-07-15/01.png) - -## 4.2 TCC 模式 - -* 文档:[《Seata 文档 —— TCC 模式》](https://seata.io/zh-cn/docs/dev/mode/tcc-mode.html) -* 示例: - -## 4.3 Saga 模式 - -* 文档:[《Seata 文档 —— Saga 模式》](https://seata.io/zh-cn/docs/dev/mode/saga-mode.html) -* 示例: - -## 4.4 XA 模式 - -Seata 正在开发中... +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-rpc-refactor.md b/blog/seata-rpc-refactor.md index 59366cfaf2..e872b67af1 100644 --- a/blog/seata-rpc-refactor.md +++ b/blog/seata-rpc-refactor.md @@ -1,183 +1 @@ ---- -title: Seata RPC 模块的重构之路 -author: 张乘辉 -keywords: [Seata、RPC模块、重构 ] -date: 2020/07/13 ---- - -# 前言 - -RPC 模块是我最初研究 Seata 源码开始的地方,因此我对 Seata 的 RPC 模块有过一些深刻研究,在我研究了一番后,发现 RPC 模块中的代码需要进行优化,使得代码更加优雅,交互逻辑更加清晰易懂,本着 “**让天下没有难懂的 -RPC 通信代码**” 的初衷,我开始了 RPC 模块的重构之路。 - -这里建议想要深入了解 Seata 交互细节的,不妨从 RPC 模块的源码入手,RPC 模块相当于 Seata 的中枢,Seata 所有的交互逻辑在 RPC 模块中表现得淋漓尽致。 - -这次 RPC 模块的重构将会使得 Seata 的中枢变得更加健壮和易于解读。 - -# 重构继承关系 - -在 Seata 的旧版本中,RPC 模块的整体结构有点混乱,尤其是在各个类的继承关系上,主要体现在: - -1. 直接在 Remoting 类继承 Netty Handler,使得 Remoting 类与 Netty Handler 处理逻辑耦合在一起; -2. 客户端和服务端的 Reomting 类继承关系不统一; -3. RemotingClient 被 RpcClientBootstrap 实现,而 RemotingServer 却被 RpcServer 实现,没有一个独立的 ServerBootstrap,这个看起来关系非常混乱; -4. 有些接口没必要抽取出来,比如 ClientMessageSender、ClientMessageListener、ServerMessageSender 等接口,因这些接口会增加整体结构继承关系的复杂性。 - -针对上面发现的问题,在重构过程中我大致做了如下事情: - -1. 将 Netty Handler 抽象成一个内部类放在 Remoting 类中; -2. 将 RemotingClient 为客户端顶级接口,定义客户端与服务端交互的基本方法,抽象一层 AbstractNettyRemotingClient,下面分别有 - RmNettyRemotingClient、TmNettyRemotingClient;将 RemotingServer 为服务端顶级接口,定义服务端与客户端交互的基本方法,实现类 NettyRemotingServer; -3. 同时将 ClientMessageSender、ClientMessageListener、ServerMessageSender 等接口方法归入到 RemotingClient、RemotingServer 中,由 Reomting - 类实现 RemotingClient、RemotingServer,统一 Remoting 类继承关系; -4. 新建 RemotingBootstrap 接口,客户端和服务端分别实现 NettyClientBootstrap、NettyServerBootstrap,将引导类逻辑从 Reomting 类抽离出来。 - -在最新的 RPC 模块中的继承关系简单清晰,用如下类关系图表示: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20200711111637.png) - -1. AbstractNettyRemoting:Remoting 类的最顶层抽象,包含了客户端和服务端公用的成员变量与公用方法,拥有通用的请求方法(文章后面会讲到),Processor 处理器调用逻辑(文章后面会讲到); -2. RemotingClient:客户端最顶级接口,定义客户端与服务端交互的基本方法; -3. RemotingServer:服务端最顶级接口,定义服务端与客户端交互的基本方法; -4. AbstractNettyRemotingClient:客户端抽象类,继承 AbstractNettyRemoting 类并实现了 RemotingClient 接口; -5. NettyRemotingServer:服务端实现类,继承 AbstractNettyRemoting 类并实现了 RemotingServer 接口; -6. RmNettyRemotingClient:Rm 客户端实现类,继承 AbstractNettyRemotingClient 类; -7. TmNettyRemotingClient:Tm 客户端实现类,继承 AbstractNettyRemotingClient 类。 - -同时将客户端和服务端的引导类逻辑抽象出来,如下类关系图表示: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20200510225359.png) - -1. RemotingBootstrap:引导类接口,有 start 和 stop 两个抽象方法; -2. NettyClientBootstrap:客户端引导实现类; -3. NettyServerBootstrap:服务端引导实现类。 - -# 解耦处理逻辑 - -解耦处理逻辑即是将 RPC 交互的处理逻辑从 Netty Handler 中抽离出来,并将处理逻辑抽象成一个个 Processor,为什么要这么做呢?我大致讲下现在存在的一些问题: - -1. Netty Handler 与 处理逻辑是糅合在一起的,由于客户端与服务端都共用了一套处理逻辑,因此为了兼容更多的交互,在处理逻辑中你可以看到非常多难以理解的判断逻辑; -2. 在 Seata 的交互中有些请求是异步处理的,也有一些请求是同步处理的,但是在旧的处理代码逻辑中对同步异步处理的表达非常隐晦,而且难以看明白; -3. 无法从代码逻辑当中清晰地表达出请求消息类型与对应的处理逻辑关系; -4. 在 Seata 后面的更新迭代中,如果不将处理处理逻辑抽离出来,这部分代码想要增加新的交互逻辑,将会非常困难。 - -在将处理逻辑从 Netty Handler 进行抽离之前,我们先梳理一下 Seata 现有的交互逻辑: - -- RM 客户端请求服务端的交互逻辑: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/Xnip2020-05-12_21-41-45.png) - -- TM 客户端请求服务端的交互逻辑: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/Xnip2020-05-12_21-44-04.png) - -- 服务端请求 RM 客户端的交互逻辑: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20200513000620.png) - -从以上的交互图中可以清晰地看到了 Seata 的交互逻辑。 - -客户端总共接收服务端的消息: - -1)服务端请求消息 - -1. BranchCommitRequest、BranchRollbackRequest、UndoLogDeleteRequest - -2)服务端响应消息 - -1. RegisterRMResponse、BranchRegisterResponse、BranchReportResponse、GlobalLockQueryResponse -2. -RegisterTMResponse、GlobalBeginResponse、GlobalCommitResponse、GlobalRollbackResponse、GlobalStatusResponse、GlobalReportResponse -3. HeartbeatMessage(PONG) - -服务端总共接收客户端的消息: - -1)客户端请求消息: - -1. RegisterRMRequest、BranchRegisterRequest、BranchReportRequest、GlobalLockQueryRequest -2. -RegisterTMRequest、GlobalBeginRequest、GlobalCommitRequest、GlobalRollbackRequest、GlobalStatusRequest、GlobalReportRequest -3. HeartbeatMessage(PING) - -2)客户端响应消息: - -1. BranchCommitResponse、BranchRollbackResponse - -基于以上的交互逻辑分析,我们可以将处理消息的逻辑抽象成若干个 Processor,一个 Processor 可以处理一个或者多个消息类型的消息,只需在 Seata 启动时注册将消息类型注册到 ProcessorTable -中即可,形成一个映射关系,这样就可以根据消息类型调用对应的 Processor 对消息进行处理,用如下图表示: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/Xnip2020-05-12_22-09-17.png) - -在抽象 Remoting 类中定一个 processMessage 方法,方法逻辑是根据消息类型从 ProcessorTable 中拿到消息类型对应的 Processor。 - -这样就成功将处理逻辑从 Netty Handler 中彻底抽离出来了,Handler#channelRead 方法只需要调用 processMessage 方法即可,且还可以灵活根据消息类型动态注册 Processor 到 -ProcessorTable 中,处理逻辑的可扩展性得到了极大的提升。 - -以下是 Processor 的调用流程: - -1)客户端 - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20200510234047.png) - -1. RmBranchCommitProcessor:处理服务端全局提交请求; -2. RmBranchRollbackProcessor:处理服务端全局回滚请求; -3. RmUndoLogProcessor:处理服务端 undo log 删除请求; -4. ClientOnResponseProcessor:客户端处理服务端响应请求,如:BranchRegisterResponse、GlobalBeginResponse、GlobalCommitResponse 等; -5. ClientHeartbeatProcessor:处理服务端心跳响应。 - -2)服务端 - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20200510234016.png) - -1. RegRmProcessor:处理 RM 客户端注册请求; -2. RegTmProcessor:处理 TM 客户端注册请求; -3. ServerOnRequestProcessor:处理客户端相关请求,如:BranchRegisterRequest、GlobalBeginRequest、GlobalLockQueryRequest 等; -4. ServerOnResponseProcessor:处理客户端相关响应,如:BranchCommitResponse、BranchRollbackResponse 等; -5. ServerHeartbeatProcessor:处理客户端心跳响应。 - -下面我以 TM 发起全局事务提交请求为例子,让大家感受下 Processor 在整个交互中所处的位置: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20200514191842.png) - -# 重构请求方法 - -在 Seata 的旧版本当中,RPC 的请求方法也是欠缺优雅,主要体现在: - -1. 请求方法过于杂乱无章,没有层次感; -2. sendAsyncRequest 方法耦合的代码太多,逻辑过于混乱,客户端与服务端都共用了一套请求逻辑,方法中决定是否批量发送是根据参数 address 是否为 null 决定,决定是否同步请求是根据 timeout 是否大于 0 - 决定,显得极为不合理,且批量请求只有客户端有用到,服务端并没有批量请求,共用一套请求逻辑还会导致服务端异步请求也会创建 MessageFuture 放入 futures 中; -3. 请求方法名称风格不统一,比如客户端 sendMsgWithResponse,服务端却叫 sendSyncRequest; - -针对以上旧版本 RPC 请求方法的各种缺点,我作了以下改动: - -1. 将请求方法统一放入 RemotingClient、RemotingServer 接口当中,并作为顶级接口; -2. 分离客户端与服务端请求逻辑,将批量请求逻辑单独抽到客户端相关请求方法中,使得是否批量发送不再根据参数 address 是否为 null 决定; -3. 由于 Seata - 自身的逻辑特点,客户端服务端请求方法的参数无法统一,可通过抽取通用的同步/异步请求方法,客户端和服务端根据自身请求逻辑特点实现自身的同步/异步请求逻辑,最后再调用通用的同步/异步请求方法,使得同步/异步请求都有明确的方法,不再根据 - timeout 是否大于 0 决定; -4. 统一请求名称风格。 - -最终,Seata RPC 的请求方法终于看起来更加优雅且有层次感了。 - -同步请求: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20200513103838.png) - -异步请求: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20200513103904.png) - -# 其它 - -1. 类目录调整:RPC 模块目录中还有一个 netty 目录,也可以从目录结构中发现 Seata 的初衷是兼容多个 RPC 框架,目前只实现了 netty,但发现 netty 模块中有些类并不 ”netty“,且 RPC - 跟目录的类也并不通用,因此需要将相关类的位置进行调整; -2. 某些类重新命名,比如 netty 相关类包含 「netty」; - -最终 RPC 模块看起来是这样的: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20200711213204.png) - -# 作者简介 - -张乘辉,目前就职于蚂蚁集团,热爱分享技术,微信公众号「后端进阶」作者,技术博客([https://objcoding.com/](https://objcoding.com/))博主,Seata Contributor,GitHub -ID:objcoding。 \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-snowflake-explain.md b/blog/seata-snowflake-explain.md index c1a8bc14bf..e872b67af1 100644 --- a/blog/seata-snowflake-explain.md +++ b/blog/seata-snowflake-explain.md @@ -1,58 +1 @@ ---- -title: 关于新版雪花算法的答疑 -author: selfishlover -keywords: [Seata, snowflake, UUID, page split] -date: 2021/06/21 ---- - -# 关于新版雪花算法的答疑 - -在上一篇关于新版雪花算法的解析中,我们提到新版算法所做出的2点改变: -1. 时间戳不再时刻追随系统时钟。 -2. 节点ID和时间戳互换位置。由原版的: -![原版位分配策略](/img/blog/seata/uuid/before.png) -改成: -![改进版位分配策略](/img/blog/seata/uuid/after.png) - -有细心的同学提出了一个问题:新版算法在单节点内部确实是单调递增的,但是在多实例部署时,它就不再是全局单调递增了啊!因为显而易见,节点ID排在高位,那么节点ID大的,生成的ID一定大于节点ID小的,不管时间上谁先谁后。而原版算法,时间戳在高位,并且始终追随系统时钟,可以保证早生成的ID小于晚生成的ID,只有当2个节点恰好在同一时间戳生成ID时,2个ID的大小才由节点ID决定。这样看来,新版算法是不是错的? - -这是一个很好的问题!能提出这个问题的同学,说明已经深入思考了标准版雪花算法和新版雪花算法的本质区别,这点值得鼓励!在这里,我们先说结论:新版算法的确不具备全局的单调递增性,但这不影响我们的初衷(减少数据库的页分裂)。这个结论看起来有点违反直觉,但可以被证明。 - -在证明之前,我们先简单回顾一下数据库关于页分裂的知识。以经典的mysql innodb为例,innodb使用B+树索引,其中,主键索引的叶子节点还保存了数据行的完整记录,叶子节点之间以双向链表的形式串联起来。叶子节点的物理存储形式为数据页,一个数据页内最多可以存储N条行记录(N与行的大小成反比)。如图所示: -![数据页](/img/blog/seata/uuid/page1.png) -B+树的特性要求,左边的节点应小于右边的节点。如果此时要插入一条ID为25的记录,会怎样呢(假设每个数据页只够存放4条记录)?答案是会引起页分裂,如图: -![页分裂](/img/blog/seata/uuid/page2.png) -页分裂是IO不友好的,需要新建数据页,拷贝转移旧数据页的部分记录等,我们应尽量避免。 - -理想的情况下,主键ID最好是顺序递增的(例如把主键设置为auto_increment),这样就只会在当前数据页放满了的时候,才需要新建下一页,双向链表永远是顺序尾部增长的,不会有中间的节点发生分裂的情况。 - -最糟糕的情况下,主键ID是随机无序生成的(例如java中一个UUID字符串),这种情况下,新插入的记录会随机分配到任何一个数据页,如果该页已满,就会触发页分裂。 - -如果主键ID由标准版雪花算法生成,最好的情况下,是每个时间戳内只有一个节点在生成ID,这时候算法的效果等同于理想情况的顺序递增,即跟auto_increment无差。最坏的情况下,是每个时间戳内所有节点都在生成ID,这时候算法的效果接近于无序(但仍比UUID的完全无序要好得多,因为workerId只有10位决定了最多只有1024个节点)。实际生产中,算法的效果取决于业务流量,并发度越低,算法越接近理想情况。 - -那么,换成新版算法又会如何呢? -新版算法从全局角度来看,ID是无序的,但对于每一个workerId,它生成的ID都是严格单调递增的,又因为workerId是有限的,所以最多可划分出1024个子序列,每个子序列都是单调递增的。 -对于数据库而言,也许它初期接收的ID都是无序的,来自各个子序列的ID都混在一起,就像这样: -![初期](/img/blog/seata/uuid/page3.png) -如果这时候来了个worker1-seq2,显然会造成页分裂: -![首次分裂](/img/blog/seata/uuid/page4.png) -但分裂之后,有趣的事情发生了,对于worker1而言,后续的seq3,seq4不会再造成页分裂(因为还装得下),seq5也只需要像顺序增长那样新建页进行链接(区别是这个新页不是在双向链表的尾部)。注意,worker1的后续ID,不会排到worker2及之后的任意节点(因而不会造成后边节点的页分裂),因为它们总比worker2的ID小;也不会排到worker1当前节点的前边(因而不会造成前边节点的页分裂),因为worker1的子序列总是单调递增的。在这里,我们称worker1这样的子序列达到了稳态,意为这条子序列已经"稳定"了,它的后续增长只会出现在子序列的尾部,而不会造成其它节点的页分裂。 - -同样的事情,可以推广到各个子序列上。无论前期数据库接收到的ID有多乱,经过有限次的页分裂后,双向链表总能达到这样一个稳定的终态: -![终态](/img/blog/seata/uuid/page5.png) -到达终态后,后续的ID只会在该ID所属的子序列上进行顺序增长,而不会造成页分裂。该状态下的顺序增长与auto_increment的顺序增长的区别是,前者有1024个增长位点(各个子序列的尾部),后者只有尾部一个。 - -到这里,我们可以回答开头所提出的问题了:新算法从全局来看的确不是全局递增的,但该算法是**收敛**的,达到稳态后,新算法同样能达成像全局顺序递增一样的效果。 - -*** -## 扩展思考 - -以上只提到了序列不停增长的情况,而实践生产中,不光有新数据的插入,也有旧数据的删除。而数据的删除有可能会导致页合并(innodb若发现相邻2个数据页的空间利用率都不到50%,就会把它俩合并),这对新算法的影响如何呢? - -经过上面的流程,我们可以发现,新算法的本质是利用前期的页分裂,把不同的子序列逐渐分离开来,让算法不断收敛到稳态。而页合并则恰好相反,它有可能会把不同的子序列又合并回同一个数据页里,妨碍算法的收敛。尤其是在收敛的前期,频繁的页合并甚至可以让算法永远无法收敛(你刚分离出来我就又把它们合并回去,一夜回到解放前~)!但在收敛之后,只有在各个子序列的尾节点进行的页合并,才有可能破坏稳态(一个子序列的尾节点和下一个子序列的头节点进行合并)。而在子序列其余节点上的页合并,不影响稳态,因为子序列仍然是有序的,只不过长度变短了而已。 - -以seata的服务端为例,服务端那3张表的数据的生命周期都是比较短的,一个全局事务结束之后,它们就会被清除了,这对于新算法是不友好的,没有给时间它进行收敛。不过已经有延迟删除的PR在review中,搭配这个PR,效果会好很多。比如定期每周清理一次,前期就有足够的时间给算法进行收敛,其余的大部分时间,数据库就能从中受益了。到期清理时,最坏的结果也不过是表被清空,算法从头再来。 - -如果您希望把新算法应用到业务系统当中,请务必确保算法有时间进行收敛。比如用户表之类的,数据本就打算长期保存的,算法可以自然收敛。或者也做了延迟删除的机制,给算法足够的时间进行收敛。 - -如果您有更好的意见和建议,也欢迎跟seata社区联系! +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-sourcecode-client-bootstrap.md b/blog/seata-sourcecode-client-bootstrap.md index cceb29cd66..e872b67af1 100644 --- a/blog/seata-sourcecode-client-bootstrap.md +++ b/blog/seata-sourcecode-client-bootstrap.md @@ -1,304 +1 @@ ---- -title: 分布式事务Seata源码-Client端启动流程 -author: 杨晓兵|中原银行 -date: 2020/08/25 -keywords: [fescar、seata、分布式事务] ---- - -## 【分布式事务Seata源码解读二】Client端启动流程 - -本文从源码的角度分析一下AT模式下Client端启动流程,所谓的Client端,即业务应用方。分布式事务分为三个模块:TC、TM、RM。其中TC位于seata-server端,而TM、RM通过SDK的方式运行在client端。 - -下图展示了Seata官方Demo的一个分布式事务场景,分为如下几个微服务,共同实现了一个下订单、扣库存、扣余额的分布式事务。 -* **BusinessService:** 业务服务,下单服务的入口 -* **StorageService:** 库存微服务,用于扣减商品库存 -* **OrderService:** 订单微服务,创建订单 -* **AccountService:** 账户微服务,扣减用户账户的余额 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20200820184156758.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTE0NTg0OA==,size_16,color_FFFFFF,t_70#pic_center) - -从上图也可以看出,在AT模式下Seata Client端主要通过如下三个模块来实现分布式事务: -* **GlobalTransactionScanner:** GlobalTransactionScanner负责初始TM、RM模块,并为添加分布式事务注解的方法添加拦截器,拦截器负责全局事务的开启、提交或回滚 -* **DatasourceProxy:** DatasourceProxy为DataSource添加拦截,拦截器会拦截所有SQL执行,并作为RM事务参与方的角色参与分布式事务执行。 -* **Rpc Interceptor:** 在上一篇[分布式事务Seata源码解读一](https://blog.csdn.net/weixin_45145848/article/details/106930538)中有提到分布式事务的几个核心要点,其中有一个是分布式事务的跨服务实例传播。Rpc Interceptor的职责就是负责在多个微服务之间传播事务。 - -## seata-spring-boot-starter -引用seata分布式事务SDK有两种方式,依赖seata-all或者seata-spring-boot-starter,推荐使用seata-spring-boot-starter,因为该starter已经自动注入了上面提到的三个模块,用户只要添加相应的配置,在业务代码添加全局分布式事务注解即可。下面从seata-spring-boot-starter项目中的代码入手: - -如下图所示是seata-spring-boot-starter的项目结构: -![在这里插入图片描述](https://img-blog.csdnimg.cn/20200810204518853.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTE0NTg0OA==,size_16,color_FFFFFF,t_70) -主要分为以下几个模块: -* **properties:** properties目录下都是Springboot 适配seata的相关配置类,即可以通过SpringBoot的配置方式来Seata的相关参数 -* **provider:** provider目录下的类负责把Springboot、SpringCloud的配置适配到Seata配置中 -* **resources:** resources目录下主要有两个文件,spring.factories用于注册Springboot的自动装配类,ExtConfigurationProvider用于注册SpringbootConfigurationProvider类,该Provider类负责把SpringBoot的相关配置类适配到Seata中。 - -对于springboot-starter项目,我们先查看resources/META-INF/spring.factories文件: -```properties -# Auto Configure -org.springframework.boot.autoconfigure.EnableAutoConfiguration= -io.seata.spring.boot.autoconfigure.SeataAutoConfiguration -``` -可以看到在spring.factories中配置了自动装配类:SeataAutoConfiguration,在该装配类中主要注入了GlobalTransactionScanner和seataAutoDataSourceProxyCreator两个实例。代码如下: -```java -@ComponentScan(basePackages = "io.seata.spring.boot.autoconfigure.properties") -@ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = "enabled", - havingValue = "true", - matchIfMissing = true) -@Configuration -@EnableConfigurationProperties({SeataProperties.class}) -public class SeataAutoConfiguration { - - ... - - // GlobalTransactionScanner负责为添加GlobalTransaction注解的方法添加拦截器, - // 并且负责初始化RM、TM - @Bean - @DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER}) - @ConditionalOnMissingBean(GlobalTransactionScanner.class) - public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties, - FailureHandler failureHandler) { - if (LOGGER.isInfoEnabled()) { - LOGGER.info("Automatically configure Seata"); - } - return new GlobalTransactionScanner(seataProperties.getApplicationId(), - seataProperties.getTxServiceGroup(), - failureHandler); - } - - // SeataAutoDataSourceProxyCreator负责为Spring中的所有DataSource生成代理对象, - // 从而实现拦截所有SQL的执行 - @Bean(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR) - @ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = { - "enableAutoDataSourceProxy", "enable-auto" + - "-data-source-proxy"}, havingValue = "true", matchIfMissing = true) - @ConditionalOnMissingBean(SeataAutoDataSourceProxyCreator.class) - public SeataAutoDataSourceProxyCreator seataAutoDataSourceProxyCreator(SeataProperties seataProperties) { - return new SeataAutoDataSourceProxyCreator(seataProperties.isUseJdkProxy(), - seataProperties.getExcludesForAutoProxying()); - } -} -``` - -### GlobalTransactionScanner -GlobalTransactionScanner继承于AutoProxyCreator,AutoProxyCreator是Spring中实现AOP的一种方式,可以拦截Spring中的所有实例,判断是否需要进行代理。下面列出了GlobalTransactionScanner中一些比较重要的字段和拦截代理的核心方法: -```java -public class GlobalTransactionScanner extends AbstractAutoProxyCreator - implements InitializingBean, ApplicationContextAware, - DisposableBean { - ... - // interceptor字段是对应一个代理对象的拦截器, - // 可以认为是一个临时变量,有效期是一个被代理对象 - private MethodInterceptor interceptor; - - // globalTransactionalInterceptor是通用的Interceptor, - // 非TCC事务方式的都使用该Interceptor - private MethodInterceptor globalTransactionalInterceptor; - - // PROXYED_SET存储已经代理过的实例,防止重复处理 - private static final Set PROXYED_SET = new HashSet<>(); - - // applicationId是一个服务的唯一标识, - // 对应springcloud项目中的spring.application.name - private final String applicationId; - // 事务的分组标识,参考文章wiki:http://seata.io/zh-cn/docs/user/txgroup/transaction-group.html - private final String txServiceGroup; - - ... - - // 判断是否需要代理目标对象,如果需要代理,则生成拦截器赋值到类变量interceptor中 - @Override - protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { - // 判断是否禁用分布式事务 - if (disableGlobalTransaction) { - return bean; - } - try { - synchronized (PROXYED_SET) { - if (PROXYED_SET.contains(beanName)) { - return bean; - } - - // 每次处理一个被代理对象时先把interceptor置为null,所以interceptor的 - // 生命周期是一个被代理对象,由于是在另外一个方法getAdvicesAndAdvisorsForBean - // 中使用interceptor,所以该interceptor要定义为一个类变量 - interceptor = null; - - // 判断是否是TCC事务模式,判断的主要依据是方法上是否有TwoPhaseBusinessAction注解 - if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, - applicationContext)) { - // 创建一个TCC事务的拦截器 - interceptor = - new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName)); - } else { - // 获取待处理对象的class类型 - Class serviceInterface = SpringProxyUtils.findTargetClass(bean); - // 获取待处理对象继承的所有接口 - Class[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean); - - // 如果待处理对象的class或者继承的接口上有GlobalTransactional注解, - // 或者待处理对象的class的任一个方法上有GlobalTransactional或者 - // GlobalLock注解则返回true,即需要被代理 - if (!existsAnnotation(new Class[]{serviceInterface}) - && !existsAnnotation(interfacesIfJdk)) { - return bean; - } - - // 如果interceptor为null,即不是TCC模式, - // 则使用globalTransactionalInterceptor作为拦截器 - if (interceptor == null) { - // globalTransactionalInterceptor只会被创建一次 - if (globalTransactionalInterceptor == null) { - globalTransactionalInterceptor = - new GlobalTransactionalInterceptor(failureHandlerHook); - ConfigurationCache.addConfigListener( - ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, - (ConfigurationChangeListener) globalTransactionalInterceptor); - } - interceptor = globalTransactionalInterceptor; - } - } - - if (!AopUtils.isAopProxy(bean)) { - // 如果bean本身不是Proxy对象,则直接调用父类的wrapIfNecessary生成代理对象即可 - // 在父类中会调用getAdvicesAndAdvisorsForBean获取到上面定义的interceptor - bean = super.wrapIfNecessary(bean, beanName, cacheKey); - } else { - // 如果该bean已经是代理对象了,则直接在代理对象的拦截调用链AdvisedSupport - // 上直接添加新的interceptor即可。 - AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean); - Advisor[] advisor = buildAdvisors(beanName, - getAdvicesAndAdvisorsForBean(null, null, null)); - for (Advisor avr : advisor) { - advised.addAdvisor(0, avr); - } - } - // 标识该beanName已经处理过了 - PROXYED_SET.add(beanName); - return bean; - } - } catch (Exception exx) { - throw new RuntimeException(exx); - } - } - - // 返回wrapIfNecessary方法中计算出的interceptor对象 - @Override - protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, - TargetSource customTargetSource) - throws BeansException { - return new Object[]{interceptor}; - } -} -``` - -上面介绍了GlobalTransactionScanner是如何通过注解拦截全局事务的,具体拦截器实现为TccActionInterceptor和GlobalTransactionalInterceptor,对于AT模式来说我们主要关心GlobalTransactionalInterceptor,在后续的文章中会介绍GlobalTransactionalInterceptor的具体实现。 - -另外GloabalTransactionScanner还负责TM、RM的初始化工作,是在initClient方法中实现的: -```java -private void initClient() { - ... - - //初始化TM - TMClient.init(applicationId, txServiceGroup); - ... - - //初始化RM - RMClient.init(applicationId, txServiceGroup); - ... - - // 注册Spring shutdown的回调,用来释放资源 - registerSpringShutdownHook(); - - } -``` - -TMClient、RMClient都是Seata基于Netty实现的Rpc框架的客户端类,只是业务逻辑不同,由于TMClient相对来说更简单一些,我们以RMClient为例看一下源码: -```java -public class RMClient { - // RMClient的init是一个static方法,创建了一个RmNettyRemotingClient实例,并调用init方法 - public static void init(String applicationId, String transactionServiceGroup) { - RmNettyRemotingClient rmNettyRemotingClient = - RmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup); - rmNettyRemotingClient.setResourceManager(DefaultResourceManager.get()); - rmNettyRemotingClient.setTransactionMessageHandler(DefaultRMHandler.get()); - rmNettyRemotingClient.init(); - } -} -``` - -RmNettyRemotingClient的实现如下: -```java -@Sharable -public final class RmNettyRemotingClient extends AbstractNettyRemotingClient { - // ResourceManager负责处理事务参与方,支持AT、TCC、Saga三种模式 - private ResourceManager resourceManager; - // RmNettyRemotingClient单例 - private static volatile RmNettyRemotingClient instance; - private final AtomicBoolean initialized = new AtomicBoolean(false); - // 微服务的唯一标识 - private String applicationId; - // 分布式事务分组名称 - private String transactionServiceGroup; - - // RMClient中init方法会调用该init方法 - public void init() { - // 注册Seata自定义Rpc的Processor - registerProcessor(); - if (initialized.compareAndSet(false, true)) { - // 调用父类的init方法,在父类中负责Netty的初始化,与Seata-Server建立连接 - super.init(); - } - } - - // 注册Seata自定义Rpc的Processor - private void registerProcessor() { - // 1.注册Seata-Server发起branchCommit的处理Processor - RmBranchCommitProcessor rmBranchCommitProcessor = - new RmBranchCommitProcessor(getTransactionMessageHandler(), this); - super.registerProcessor(MessageType.TYPE_BRANCH_COMMIT, rmBranchCommitProcessor, - messageExecutor); - - // 2.注册Seata-Server发起branchRollback的处理Processor - RmBranchRollbackProcessor rmBranchRollbackProcessor = - new RmBranchRollbackProcessor(getTransactionMessageHandler(), this); - super.registerProcessor(MessageType.TYPE_BRANCH_ROLLBACK, rmBranchRollbackProcessor - , messageExecutor); - - // 3.注册Seata-Server发起删除undoLog的处理Processor - RmUndoLogProcessor rmUndoLogProcessor = - new RmUndoLogProcessor(getTransactionMessageHandler()); - super.registerProcessor(MessageType.TYPE_RM_DELETE_UNDOLOG, rmUndoLogProcessor, - messageExecutor); - - // 4.注册Seata-Server返回Response的处理Processor,ClientOnResponseProcessor - // 用于处理由Client主动发起Request,Seata-Server返回的Response。 - // ClientOnResponseProcessor负责把Client发送的Request和Seata-Server - // 返回的Response对应起来,从而实现Rpc - ClientOnResponseProcessor onResponseProcessor = - new ClientOnResponseProcessor(mergeMsgMap, super.getFutures(), - getTransactionMessageHandler()); - super.registerProcessor(MessageType.TYPE_SEATA_MERGE_RESULT, onResponseProcessor, - null); - super.registerProcessor(MessageType.TYPE_BRANCH_REGISTER_RESULT, - onResponseProcessor, null); - super.registerProcessor(MessageType.TYPE_BRANCH_STATUS_REPORT_RESULT, - onResponseProcessor, null); - super.registerProcessor(MessageType.TYPE_GLOBAL_LOCK_QUERY_RESULT, - onResponseProcessor, null); - super.registerProcessor(MessageType.TYPE_REG_RM_RESULT, onResponseProcessor, null); - - // 5. 处理Seata-Server返回的Pong消息 - ClientHeartbeatProcessor clientHeartbeatProcessor = new ClientHeartbeatProcessor(); - super.registerProcessor(MessageType.TYPE_HEARTBEAT_MSG, clientHeartbeatProcessor, - null); - } -} -``` - -上面的逻辑看起来比较复杂,相关类也比较多,如:各种Processor、各种MessageType、TransactionMessageHandler、ResourceManager。其实本质上就是Rpc调用,分为Rm主动调用和Seata主动调用。 -* **Rm主动调用方法:** 如:注册分支、汇报分支状态、申请全局锁等。Rm主动调用的方法都需要在ClientOnResponseProcessor中处理Seata-Server返回的Response -* **Seata-Server主动调用方法:** 如:提交分支事务、回滚分支事务、删除undolog日志。Seata-Server主动调用的方法,Client端分别对应不同的Processor来处理,并且处理结束后要返回给Seata-Server处理结果Response。而事务提交、回滚的核心实现逻辑都在TransactionMessageHandler、ResourceManager中。 - -关于TransactionMessageHandler、ResourceManager的具体实现也会在后续的章节中详细描述。 - -下一篇会介绍一下SeataAutoDataSourceProxyCreator、Rpc Interceptor是如何初始化以及拦截的。 \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-sourcecode-server-bootstrap.md b/blog/seata-sourcecode-server-bootstrap.md index 84ae1b0d7a..e872b67af1 100644 --- a/blog/seata-sourcecode-server-bootstrap.md +++ b/blog/seata-sourcecode-server-bootstrap.md @@ -1,575 +1 @@ ---- -title: 分布式事务Seata源码-Server端启动流程 -author: 杨晓兵|中原银行 -date: 2020/07/02 -keywords: [fescar、seata、分布式事务] ---- - -## 【分布式事务Seata源码解读一】Server端启动流程 - -### 实现分布式事务的核心要点: -1. 事务的持久化,事务所处的各种状态事务参与方的各种状态都需要持久化,当实例宕机时才能基于持久化的数据对事务回滚或提交,实现最终一致性 -2. 定时对超时未完成事务的处理(继续尝试提交或回滚),即通过重试机制实现事务的最终一致性 -3. 分布式事务的跨服务实例传播,当分布式事务跨多个实例时需要实现事务的传播,一般需要适配不同的rpc框架 -4. 事务的隔离级别:大多数分布式事务为了性能,默认的隔离级别是读未提交 -5. 幂等性:对于XA或者seata的AT这样的分布式事务来说,都已经默认实现了幂等性,而TCC、Saga这种接口级别实现的分布式事务都还需要业务开发者自己实现幂等性。 - -本片文章主要从seata-server的启动流程的角度介绍一下seata-server的源码,启动流程图如下: - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20200726213919467.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTE0NTg0OA==,size_16,color_FFFFFF,t_70) - - -#### 1. 启动类Server -seata-server的入口类在Server类中,源码如下: -```java -public static void main(String[] args) throws IOException { - // 从环境变量或运行时参数中获取监听端口,默认端口8091 - int port = PortHelper.getPort(args); - - // 把监听端口设置到SystemProperty中,Logback的LoggerContextListener实现类 - // SystemPropertyLoggerContextListener会把Port写入到Logback的Context中, - // 在logback.xml文件中会使用Port变量来构建日志文件名称。 - System.setProperty(ConfigurationKeys.SERVER_PORT, Integer.toString(port)); - - // 创建Logger - final Logger logger = LoggerFactory.getLogger(Server.class); - if (ContainerHelper.isRunningInContainer()) { - logger.info("The server is running in container."); - } - - // 解析启动以及配置文件的各种配置参数 - ParameterParser parameterParser = new ParameterParser(args); - - // metrics相关,这里是使用SPI机制获取Registry实例对象 - MetricsManager.get().init(); - - // 把从配置文件中读取到的storeMode写入SystemProperty中,方便其他类使用。 - System.setProperty(ConfigurationKeys.STORE_MODE, parameterParser.getStoreMode()); - - // 创建NettyRemotingServer实例,NettyRemotingServer是一个基于Netty实现的Rpc框架, - // 此时并没有初始化,NettyRemotingServer负责与客户端SDK中的TM、RM进行网络通信。 - nettyRemotingServer = new NettyRemotingServer(WORKING_THREADS); - - // 设置监听端口 - nettyRemotingServer.setListenPort(parameterParser.getPort()); - - // UUIDGenerator初始化,UUIDGenerator基于雪花算法实现, - // 用于生成全局事务、分支事务的id。 - // 多个Server实例配置不同的ServerNode,保证id的唯一性 - UUIDGenerator.init(parameterParser.getServerNode()); - - // SessionHodler负责事务日志(状态)的持久化存储, - // 当前支持file、db、redis三种存储模式,集群部署模式要使用db或redis模式 - SessionHolder.init(parameterParser.getStoreMode()); - - // 创建初始化DefaultCoordinator实例,DefaultCoordinator是TC的核心事务逻辑处理类, - // 底层包含了AT、TCC、SAGA等不同事务类型的逻辑处理。 - DefaultCoordinator coordinator = new DefaultCoordinator(nettyRemotingServer); - coordinator.init(); - nettyRemotingServer.setHandler(coordinator); - // register ShutdownHook - ShutdownHook.getInstance().addDisposable(coordinator); - ShutdownHook.getInstance().addDisposable(nettyRemotingServer); - - // 127.0.0.1 and 0.0.0.0 are not valid here. - if (NetUtil.isValidIp(parameterParser.getHost(), false)) { - XID.setIpAddress(parameterParser.getHost()); - } else { - XID.setIpAddress(NetUtil.getLocalIp()); - } - XID.setPort(nettyRemotingServer.getListenPort()); - - try { - // 初始化Netty,开始监听端口并阻塞在这里,等待程序关闭 - nettyRemotingServer.init(); - } catch (Throwable e) { - logger.error("nettyServer init error:{}", e.getMessage(), e); - System.exit(-1); - } - - System.exit(0); -} -``` - -#### 2. 解析配置 -参数解析的实现代码在ParameterParser类中,init方法源码如下: -```java -private void init(String[] args) { - try { - // 判断是否运行在容器中,如果运行在容器中则配置从环境变量中获取 - if (ContainerHelper.isRunningInContainer()) { - this.seataEnv = ContainerHelper.getEnv(); - this.host = ContainerHelper.getHost(); - this.port = ContainerHelper.getPort(); - this.serverNode = ContainerHelper.getServerNode(); - this.storeMode = ContainerHelper.getStoreMode(); - } else { - // 基于JCommander获取启动应用程序时配置的参数, - // JCommander通过注解、反射的方式把参数赋值到当前类的字段上。 - JCommander jCommander = JCommander.newBuilder().addObject(this).build(); - jCommander.parse(args); - if (help) { - jCommander.setProgramName(PROGRAM_NAME); - jCommander.usage(); - System.exit(0); - } - } - // serverNode用于雪花算中的实例的唯一标识,需要保证唯一。 - // 如果没有指定基于当前服务器的I随机生成一个 - if (this.serverNode == null) { - this.serverNode = IdWorker.initWorkerId(); - } - if (StringUtils.isNotBlank(seataEnv)) { - System.setProperty(ENV_PROPERTY_KEY, seataEnv); - } - if (StringUtils.isBlank(storeMode)) { - // 这里牵扯到一个重要的Configuration类,ParameterParser只负责获取ip、port、storeMode等核心参数, - // 其他的参数都是从Configuration中获取的。这里如果没有启动参数没有指定storeMode, - // 就从Configuration类中获取。 - storeMode = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_MODE, - SERVER_DEFAULT_STORE_MODE); - } - } catch (ParameterException e) { - printError(e); - } - -} -``` - -在ParameterParser的init方法中第一次调用了ConfigurationFactory.getInstance(),初始化了一个单例的Configuration对象,Configuration负责初始化所有的其他配置参数信息。从Seata Server端的源码中我们能看到两个配置文件file.conf、registry.conf。那么这两个配置文件的区别是什么,两个文件都是必须的吗?我们继续看代码。 - -ConfigurationFactory.getInstance方法其实就是获取一个单例对象,核心在buildConfiguration方法中,不过在buidlConfiguration方法前,ConfigurationFactory类有一段static代码块会先执行。 -```java -// 获取Configuration的单例对象 -public static Configuration getInstance() { - if (instance == null) { - synchronized (Configuration.class) { - if (instance == null) { - instance = buildConfiguration(); - } - } - } - return instance; -} - -// ConfigurationFactory的static代码块 -static { - // 获取配置文件的名称,默认为registry.conf - String seataConfigName = System.getProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME); - if (seataConfigName == null) { - seataConfigName = System.getenv(ENV_SEATA_CONFIG_NAME); - } - if (seataConfigName == null) { - seataConfigName = REGISTRY_CONF_PREFIX; - } - String envValue = System.getProperty(ENV_PROPERTY_KEY); - if (envValue == null) { - envValue = System.getenv(ENV_SYSTEM_KEY); - } - - // 读取registry.conf文件的配置,构建基础的Configuration对象 - Configuration configuration = (envValue == null) ? new FileConfiguration(seataConfigName + REGISTRY_CONF_SUFFIX, - false) : new FileConfiguration(seataConfigName + "-" + envValue + REGISTRY_CONF_SUFFIX, false); - Configuration extConfiguration = null; - try { - // ExtConfigurationProvider当前只有一个SpringBootConfigurationProvider实现类 - // 用于支持客户端SDK SpringBoot的配置文件方式,对于Server端来说这段逻辑可以忽略。 - extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); - if (LOGGER.isInfoEnabled()) { - LOGGER.info("load Configuration:{}", extConfiguration == null ? configuration.getClass().getSimpleName() - : extConfiguration.getClass().getSimpleName()); - } - } catch (EnhancedServiceNotFoundException ignore) { - - } catch (Exception e) { - LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e); - } - CURRENT_FILE_INSTANCE = extConfiguration == null ? configuration : extConfiguration; -} -``` - -ConfigurationFactory中的static代码块是从registry.conf中读取配置信息。registry.conf中主有两个配置信息,**注册中心**和**配置源**,**配置源**用来指定其他更详细的配置项是file.conf或者是apollo等其他配置源。所以registry.conf配置文件时必须的,registry.conf配置文件中指定其他详细配置的配置源,当前配置源支持file、zk、apollo、nacos、etcd3等。所以file.conf不是必须的,只有当设置配置源为file类型时才会读取file.conf文件中的内容。 - -接下来ConfigurationFactory中的buildConfiguration就是根据registry.conf中设置的配置源来加载更多的配置项。 - -```java -private static Configuration buildConfiguration() { - ConfigType configType; - String configTypeName; - try { - // 从registry.conf配置文件中读取config.type字段值,并解析为枚举ConfigType - configTypeName = CURRENT_FILE_INSTANCE.getConfig( - ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR - + ConfigurationKeys.FILE_ROOT_TYPE); - - if (StringUtils.isBlank(configTypeName)) { - throw new NotSupportYetException("config type can not be null"); - } - - configType = ConfigType.getType(configTypeName); - } catch (Exception e) { - throw e; - } - Configuration extConfiguration = null; - Configuration configuration; - if (ConfigType.File == configType) { - // 如果配置文件为file类型,则从registry.conf中读取config.file.name配置项, - // 即file类型配置文件的路径,示例中默认为file.conf - String pathDataId = String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, - ConfigurationKeys.FILE_ROOT_CONFIG, FILE_TYPE, NAME_KEY); - String name = CURRENT_FILE_INSTANCE.getConfig(pathDataId); - - // 根据file配置文件的路径构建FileConfuguration对象 - configuration = new FileConfiguration(name); - try { - // configuration的额外扩展,也是只对客户端SpringBoot的SDK才生效 - extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); - if (LOGGER.isInfoEnabled()) { - LOGGER.info("load Configuration:{}", extConfiguration == null - ? configuration.getClass().getSimpleName() : extConfiguration.getClass().getSimpleName()); - } - } catch (EnhancedServiceNotFoundException ignore) { - - } catch (Exception e) { - LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e); - } - } else { - // 如果配置文件的类型不是file,如:nacos、zk等, - // 则通过SPI的方式生成对应的ConfigurationProvider对象 - configuration = EnhancedServiceLoader - .load(ConfigurationProvider.class, Objects.requireNonNull(configType).name()).provide(); - } - try { - // ConfigurationCache是对Configuration做了一次层代理内存缓存,提升获取配置的性能 - Configuration configurationCache; - if (null != extConfiguration) { - configurationCache = ConfigurationCache.getInstance().proxy(extConfiguration); - } else { - configurationCache = ConfigurationCache.getInstance().proxy(configuration); - } - if (null != configurationCache) { - extConfiguration = configurationCache; - } - } catch (EnhancedServiceNotFoundException ignore) { - - } catch (Exception e) { - LOGGER.error("failed to load configurationCacheProvider:{}", e.getMessage(), e); - } - return null == extConfiguration ? configuration : extConfiguration; -} -``` - -#### 3. 初始化UUIDGenerator -UUIDGenertor初始化接收一个serverNode参数,UUIDGenertor当前是使用了雪花算法来生成唯一Id,该serverNode用来保证多个seata-server实例生成的唯一id不重复。 -```java -public class UUIDGenerator { - - /** - * Generate uuid long. - * - * @return the long - */ - public static long generateUUID() { - return IdWorker.getInstance().nextId(); - } - - /** - * Init. - * - * @param serverNode the server node id - */ - public static void init(Long serverNode) { - IdWorker.init(serverNode); - } -} -``` - -UUIDGenerator是对IdWorker做了封装,唯一id的核心实现逻辑在IdWoker类中,IdWorker是一个雪花算法实现的。此处的IdWorker又是一个单例 -```java -public class IdWorker -/** - * Constructor - * - * @param workerId就是上面提到的ServerNode, 取值范围在0·1023,也就是在64位的UUID中占10位 - */ - public IdWorker(long workerId) { - if (workerId > maxWorkerId || workerId < 0) { - throw new IllegalArgumentException( - String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); - } - this.workerId = workerId; - } - - /** - * Get the next ID (the method is thread-safe) - * - * @return SnowflakeId - */ - public long nextId() { - long timestamp = timeGen(); - - if (timestamp < lastTimestamp) { - throw new RuntimeException(String.format( - "clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); - } - - synchronized (this) { - if (lastTimestamp == timestamp) { - sequence = (sequence + 1) & sequenceMask; - if (sequence == 0) { - timestamp = tilNextMillis(lastTimestamp); - } - } else { - sequence = 0L; - } - lastTimestamp = timestamp; - } - //雪花算法64位唯一id组成:第一位0 + 41位时间戳 + 10位workerId + 12位自增序列化(同一时间戳内自增) - return ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence; - } -``` - -#### 4. SessionHolder初始化 -SessionHolder负责Session的持久化,一个Session对象对应一个事务,事务分为两种:全局事务(GlobalSession)和分支事务(BranchSession)。 -SessionHolder支持file和db两种持久化方式,其中db支持集群模式,推荐使用db。SessionHolder中最主要的四个字段如下: -```java -// ROOT_SESSION_MANAGER用于获取所有的Setssion,以及Session的创建、更新、删除等。 -private static SessionManager ROOT_SESSION_MANAGER; -// 用于获取、更新所有的异步commit的Session -private static SessionManager ASYNC_COMMITTING_SESSION_MANAGER; -// 用于获取、更新所有需要重试commit的Session -private static SessionManager RETRY_COMMITTING_SESSION_MANAGER; -// 用于获取、更新所有需要重试rollback的Session -private static SessionManager RETRY_ROLLBACKING_SESSION_MANAGER; -``` - -SessionHolder的init方法 -```java -public static void init(String mode) throws IOException { - if (StringUtils.isBlank(mode)) { - mode = CONFIG.getConfig(ConfigurationKeys.STORE_MODE); - } - StoreMode storeMode = StoreMode.get(mode); - if (StoreMode.DB.equals(storeMode)) { - // 这里又用到了SPI的方式加载SessionManager, - // 其实下面获取的四个SessionManager实例都是同一个类DataBaseSessionManager的不同实例, - // 只是给DataBaseSessionManager的构造函数传参不同。 - ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName()); - ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(), - new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME}); - RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(), - new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME}); - RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(), - new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME}); - } else if (StoreMode.FILE.equals(storeMode)) { - //file模式可以先不关心 - ... - } else { - throw new IllegalArgumentException("unknown store mode:" + mode); - } - // reload方法对于db模式可以忽略 - reload(); -} -``` - -上面看到SessionHolder中的四个SessionManager本质都是类DataBaseSessionManager的实例,只是给构造函数传参不同,看下DataBaseSessionManager的定义: -```java -public DataBaseSessionManager(String name) { - super(); - this.taskName = name; -} - -// 根据实例的taskName来决定allSessions返回的事务列表, -// 如taskName等于ASYNC_COMMITTING_SESSION_MANAGER_NAME的 -// 就返回所有状态为AsyncCommitting的事务。 -public Collection allSessions() { - // get by taskName - if (SessionHolder.ASYNC_COMMITTING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) { - return findGlobalSessions(new SessionCondition(GlobalStatus.AsyncCommitting)); - } else if (SessionHolder.RETRY_COMMITTING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) { - return findGlobalSessions(new SessionCondition(new GlobalStatus[] {GlobalStatus.CommitRetrying})); - } else if (SessionHolder.RETRY_ROLLBACKING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) { - return findGlobalSessions(new SessionCondition(new GlobalStatus[] {GlobalStatus.RollbackRetrying, - GlobalStatus.Rollbacking, GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying})); - } else { - // taskName为null,则对应ROOT_SESSION_MANAGER,即获取所有状态的事务 - return findGlobalSessions(new SessionCondition(new GlobalStatus[] { - GlobalStatus.UnKnown, GlobalStatus.Begin, - GlobalStatus.Committing, GlobalStatus.CommitRetrying, GlobalStatus.Rollbacking, - GlobalStatus.RollbackRetrying, - GlobalStatus.TimeoutRollbacking, - GlobalStatus.TimeoutRollbackRetrying, - GlobalStatus.AsyncCommitting})); - } -} -``` - -#### 5. 初始化DefaultCoordinator -DefaultCoordinator是事务协调器的核心,如:开启、提交、回滚全局事务,注册、提交、回滚分支事务都是由DefaultCoordinator负责协调处理的。DefaultCoordinato通过RpcServer与远程的TM、RM通信来实现分支事务的提交、回滚等。 -```java -public DefaultCoordinator(ServerMessageSender messageSender) { - // 接口messageSender的实现类就是上文提到的RpcServer - this.messageSender = messageSender; - - // DefaultCore封装了AT、TCC、Saga等分布式事务模式的具体实现类 - this.core = new DefaultCore(messageSender); -} - -// init方法初始化了5个定时器,主要用于分布式事务的重试机制, -// 因为分布式环境的不稳定性会造成事务处于中间状态, -// 所以要通过不断的重试机制来实现事务的最终一致性。 -// 下面的定时器除了undoLogDelete之外,其他的定时任务默认都是1秒执行一次。 -public void init() { - // 处理处于回滚状态可重试的事务 - retryRollbacking.scheduleAtFixedRate(() -> { - try { - handleRetryRollbacking(); - } catch (Exception e) { - LOGGER.info("Exception retry rollbacking ... ", e); - } - }, 0, ROLLBACKING_RETRY_PERIOD, TimeUnit.MILLISECONDS); - - // 处理二阶段可以重试提交的状态可重试的事务 - retryCommitting.scheduleAtFixedRate(() -> { - try { - handleRetryCommitting(); - } catch (Exception e) { - LOGGER.info("Exception retry committing ... ", e); - } - }, 0, COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS); - - // 处理异步提交的事务 - asyncCommitting.scheduleAtFixedRate(() -> { - try { - handleAsyncCommitting(); - } catch (Exception e) { - LOGGER.info("Exception async committing ... ", e); - } - }, 0, ASYNC_COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS); - - // 检查事务的第一阶段已经超时的事务,设置事务状态为TimeoutRollbacking, - // 该事务会由其他定时任务执行回滚操作 - timeoutCheck.scheduleAtFixedRate(() -> { - try { - timeoutCheck(); - } catch (Exception e) { - LOGGER.info("Exception timeout checking ... ", e); - } - }, 0, TIMEOUT_RETRY_PERIOD, TimeUnit.MILLISECONDS); - - // 根据unlog的保存天数调用RM删除unlog - undoLogDelete.scheduleAtFixedRate(() -> { - try { - undoLogDelete(); - } catch (Exception e) { - LOGGER.info("Exception undoLog deleting ... ", e); - } - }, UNDO_LOG_DELAY_DELETE_PERIOD, UNDO_LOG_DELETE_PERIOD, TimeUnit.MILLISECONDS); -} -``` - -#### 6. 初始化NettyRemotingServer -NettyRemotingServer是基于Netty实现的简化版的Rpc服务端,NettyRemotingServer初始化时主要做了两件事: -1. **registerProcessor**:注册与Client通信的Processor。 -2. **super.init()**:super.init()方法中负责初始化Netty,并把当前实例的IP端口注册到注册中心中 - -```java -public void init() { - // registry processor - registerProcessor(); - if (initialized.compareAndSet(false, true)) { - super.init(); - } -} - -private void registerProcessor() { - // 1. 注册核心的ServerOnRequestProcessor,即与事务处理相关的Processor, - // 如:全局事务开始、提交,分支事务注册、反馈当前状态等。 - // ServerOnRequestProcessor的构造函数中传入getHandler()返回的示例,这个handler - // 就是前面提到的DefaultCoordinator,DefaultCoordinator是分布式事务的核心处理类 - ServerOnRequestProcessor onRequestProcessor = - new ServerOnRequestProcessor(this, getHandler()); - super.registerProcessor(MessageType.TYPE_BRANCH_REGISTER, onRequestProcessor, messageExecutor); - super.registerProcessor(MessageType.TYPE_BRANCH_STATUS_REPORT, onRequestProcessor, messageExecutor); - super.registerProcessor(MessageType.TYPE_GLOBAL_BEGIN, onRequestProcessor, messageExecutor); - super.registerProcessor(MessageType.TYPE_GLOBAL_COMMIT, onRequestProcessor, messageExecutor); - super.registerProcessor(MessageType.TYPE_GLOBAL_LOCK_QUERY, onRequestProcessor, messageExecutor); - super.registerProcessor(MessageType.TYPE_GLOBAL_REPORT, onRequestProcessor, messageExecutor); - super.registerProcessor(MessageType.TYPE_GLOBAL_ROLLBACK, onRequestProcessor, messageExecutor); - super.registerProcessor(MessageType.TYPE_GLOBAL_STATUS, onRequestProcessor, messageExecutor); - super.registerProcessor(MessageType.TYPE_SEATA_MERGE, onRequestProcessor, messageExecutor); - - // 2.注册ResponseProcessor,ResponseProcessor用于处理当Server端主动发起请求时, - // Client端回复的消息,即Response。如:Server向Client端发送分支事务提交或者回滚的请求时, - // Client返回提交/回滚的结果 - ServerOnResponseProcessor onResponseProcessor = - new ServerOnResponseProcessor(getHandler(), getFutures()); - super.registerProcessor(MessageType.TYPE_BRANCH_COMMIT_RESULT, onResponseProcessor, messageExecutor); - super.registerProcessor(MessageType.TYPE_BRANCH_ROLLBACK_RESULT, onResponseProcessor, messageExecutor); - - // 3. Client端发起RM注册请求时对应的Processor - RegRmProcessor regRmProcessor = new RegRmProcessor(this); - super.registerProcessor(MessageType.TYPE_REG_RM, regRmProcessor, messageExecutor); - - // 4. Client端发起TM注册请求时对应的Processor - RegTmProcessor regTmProcessor = new RegTmProcessor(this); - super.registerProcessor(MessageType.TYPE_REG_CLT, regTmProcessor, null); - - // 5. Client端发送心跳请求时对应的Processor - ServerHeartbeatProcessor heartbeatMessageProcessor = new ServerHeartbeatProcessor(this); - super.registerProcessor(MessageType.TYPE_HEARTBEAT_MSG, heartbeatMessageProcessor, null); -} -``` - -在NettyRemotingServer中有调用基类AbstractNettyRemotingServer的init方法,代码如下: -```java -public void init() { - // super.init()方法中启动了一个定时清理超时Rpc请求的定时任务,3S执行一次。 - super.init(); - // 配置Netty Server端,开始监听端口。 - serverBootstrap.start(); -} - -// serverBootstrap.start(); -public void start() { - // Netty server端的常规配置,其中添加了两个ChannelHandler: - // ProtocolV1Decoder、ProtocolV1Encoder, - // 分别对应Seata自定义RPC协议的解码器和编码器 - this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupWorker) - .channel(NettyServerConfig.SERVER_CHANNEL_CLAZZ) - .option(ChannelOption.SO_BACKLOG, nettyServerConfig.getSoBackLogSize()) - .option(ChannelOption.SO_REUSEADDR, true) - .childOption(ChannelOption.SO_KEEPALIVE, true) - .childOption(ChannelOption.TCP_NODELAY, true) - .childOption(ChannelOption.SO_SNDBUF, nettyServerConfig.getServerSocketSendBufSize()) - .childOption(ChannelOption.SO_RCVBUF, nettyServerConfig.getServerSocketResvBufSize()) - .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, - new WriteBufferWaterMark(nettyServerConfig.getWriteBufferLowWaterMark(), - nettyServerConfig.getWriteBufferHighWaterMark())) - .localAddress(new InetSocketAddress(listenPort)) - .childHandler(new ChannelInitializer() { - @Override - public void initChannel(SocketChannel ch) { - ch.pipeline().addLast(new IdleStateHandler(nettyServerConfig.getChannelMaxReadIdleSeconds(), 0, 0)) - .addLast(new ProtocolV1Decoder()) - .addLast(new ProtocolV1Encoder()); - if (channelHandlers != null) { - addChannelPipelineLast(ch, channelHandlers); - } - - } - }); - - try { - // 开始监听配置的端口 - ChannelFuture future = this.serverBootstrap.bind(listenPort).sync(); - LOGGER.info("Server started, listen port: {}", listenPort); - // Netty启动成功之后把当前实例注册到registry.conf配置文件配置的注册中心上 - RegistryFactory.getInstance().register(new InetSocketAddress(XID.getIpAddress(), XID.getPort())); - initialized.set(true); - future.channel().closeFuture().sync(); - } catch (Exception exx) { - throw new RuntimeException(exx); - } -} -``` +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-spring-boot-aop-aspectj.md b/blog/seata-spring-boot-aop-aspectj.md index afc177dda1..e872b67af1 100644 --- a/blog/seata-spring-boot-aop-aspectj.md +++ b/blog/seata-spring-boot-aop-aspectj.md @@ -1,163 +1 @@ ---- -title: 通过AOP动态创建/关闭Seata分布式事务 -keywords: [Seata,Nacos,分布式事务,spring] -description: 本文讲述如何通过AOP动态创建/关闭Seata分布式事务 -author: FUNKYE -date: 2019/12/23 ---- - -# 通过AOP动态创建/关闭Seata分布式事务 - -本文作者:FUNKYE(陈健斌),杭州某互联网公司主程。 - -# 前言 - -通过GA大会上滴滴出行的高级研发工程陈鹏志的在滴滴两轮车业务中的实践,发现动态降级的必要性是非常的高,所以这边简单利用spring boot aop来简单的处理降级相关的处理,这边非常感谢陈鹏志的分享! - -可利用此demo[项目地址](https://gitee.com/itCjb/springboot-dubbo-mybatisplus-seata ) - -通过以下代码改造实践. - -## 准备工作 - -​ 1.创建测试用的TestAspect: - -```java -package org.test.config; - -import java.lang.reflect.Method; - -import org.apache.commons.lang3.StringUtils; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.AfterReturning; -import org.aspectj.lang.annotation.AfterThrowing; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; -import org.aspectj.lang.reflect.MethodSignature; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import io.seata.core.context.RootContext; -import io.seata.core.exception.TransactionException; -import io.seata.tm.api.GlobalTransaction; -import io.seata.tm.api.GlobalTransactionContext; - -@Aspect -@Component -public class TestAspect { - private final static Logger logger = LoggerFactory.getLogger(TestAspect.class); - - @Before("execution(* org.test.service.*.*(..))") - public void before(JoinPoint joinPoint) throws TransactionException { - MethodSignature signature = (MethodSignature)joinPoint.getSignature(); - Method method = signature.getMethod(); - logger.info("拦截到需要分布式事务的方法," + method.getName()); - // 此处可用redis或者定时任务来获取一个key判断是否需要关闭分布式事务 - // 模拟动态关闭分布式事务 - if ((int)(Math.random() * 100) % 2 == 0) { - GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate(); - tx.begin(300000, "test-client"); - } else { - logger.info("关闭分布式事务"); - } - } - - @AfterThrowing(throwing = "e", pointcut = "execution(* org.test.service.*.*(..))") - public void doRecoveryActions(Throwable e) throws TransactionException { - logger.info("方法执行异常:{}", e.getMessage()); - if (!StringUtils.isBlank(RootContext.getXID())) - GlobalTransactionContext.reload(RootContext.getXID()).rollback(); - } - - @AfterReturning(value = "execution(* org.test.service.*.*(..))", returning = "result") - public void afterReturning(JoinPoint point, Object result) throws TransactionException { - logger.info("方法执行结束:{}", result); - if ((Boolean)result) { - if (!StringUtils.isBlank(RootContext.getXID())) { - logger.info("分布式事务Id:{}", RootContext.getXID()); - GlobalTransactionContext.reload(RootContext.getXID()).commit(); - } - } - } - -} -``` - -请注意上面的包名可改为你自己的service包名: - -​ 2.改动service代码: - -```java - public Object seataCommit() { - testService.Commit(); - return true; - } -``` - -因为异常跟返回结果我们都会拦截,所以这边可以trycatch或者直接让他抛异常来拦截也行,或者直接判断返回结果,比如你的业务代码code=200为成功,那么就commit,反之在拦截返回值那段代码加上rollback; - -# 进行调试 - -​ 1.更改代码主动抛出异常 - -```java - public Object seataCommit() { - try { - testService.Commit(); - int i = 1 / 0; - return true; - } catch (Exception e) { - // TODO: handle exception - throw new RuntimeException(); - } - } -``` - -​ 查看日志: - -```java -2019-12-23 11:57:55.386 INFO 23952 --- [.0-28888-exec-7] org.test.controller.TestController : 拦截到需要分布式事务的方法,seataCommit -2019-12-23 11:57:55.489 INFO 23952 --- [.0-28888-exec-7] i.seata.tm.api.DefaultGlobalTransaction : Begin new global transaction [192.168.14.67:8092:2030765910] -2019-12-23 11:57:55.489 INFO 23952 --- [.0-28888-exec-7] org.test.controller.TestController : 创建分布式事务完毕192.168.14.67:8092:2030765910 -2019-12-23 11:57:55.709 INFO 23952 --- [.0-28888-exec-7] org.test.controller.TestController : 方法执行异常:null -2019-12-23 11:57:55.885 INFO 23952 --- [.0-28888-exec-7] i.seata.tm.api.DefaultGlobalTransaction : [192.168.14.67:8092:2030765910] rollback status: Rollbacked -2019-12-23 11:57:55.888 ERROR 23952 --- [.0-28888-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException] with root cause - -``` - -​ 可以看到已被拦截也触发了rollback了. - -​ 2.恢复代码调试正常情况: - -```java - public Object seataCommit() { - testService.Commit(); - return true; - } -``` - -​ 查看日志: - -``` -2019-12-23 12:00:20.876 INFO 23952 --- [.0-28888-exec-2] org.test.controller.TestController : 拦截到需要分布式事务的方法,seataCommit -2019-12-23 12:00:20.919 INFO 23952 --- [.0-28888-exec-2] i.seata.tm.api.DefaultGlobalTransaction : Begin new global transaction [192.168.14.67:8092:2030765926] -2019-12-23 12:00:20.920 INFO 23952 --- [.0-28888-exec-2] org.test.controller.TestController : 创建分布式事务完毕192.168.14.67:8092:2030765926 -2019-12-23 12:00:21.078 INFO 23952 --- [.0-28888-exec-2] org.test.controller.TestController : 方法执行结束:true -2019-12-23 12:00:21.078 INFO 23952 --- [.0-28888-exec-2] org.test.controller.TestController : 分布式事务Id:192.168.14.67:8092:2030765926 -2019-12-23 12:00:21.213 INFO 23952 --- [.0-28888-exec-2] i.seata.tm.api.DefaultGlobalTransaction : [192.168.14.67:8092:2030765926] commit status: Committed -``` - -​ 可以看到事务已经被提交了. - -# 总结 - -更详细的内容希望希望大家访问以下地址阅读详细文档 - -[nacos官网](https://nacos.io/zh-cn/index.html) - -[dubbo官网](http://dubbo.apache.org/en-us/) - -[seata官网](http://seata.io/zh-cn/) - -[docker官网](https://www.docker.com/) \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-tcc-fence.md b/blog/seata-tcc-fence.md index 1901a81456..e872b67af1 100644 --- a/blog/seata-tcc-fence.md +++ b/blog/seata-tcc-fence.md @@ -1,336 +1 @@ ---- -title: 阿里 Seata 新版本终于解决了 TCC 模式的幂等、悬挂和空回滚问题 - -author: 朱晋君 - -keywords: [Seata、TCC、幂等、悬挂、空回滚] - -description: Seata 在 1.5.1 版本解决了 TCC 模式的幂等、悬挂和空回滚问题,这篇文章主要讲解 Seata 是怎么解决的。 - -date: 2022/06/25 ---- -今天来聊一聊阿里巴巴 Seata 新版本(1.5.1)是怎么解决 TCC 模式下的幂等、悬挂和空回滚问题的。 - -## 1 TCC 回顾 - -TCC 模式是最经典的分布式事务解决方案,它将分布式事务分为两个阶段来执行,try 阶段对每个分支事务进行预留资源,如果所有分支事务都预留资源成功,则进入 commit 阶段提交全局事务,如果有一个节点预留资源失败则进入 cancel 阶段回滚全局事务。 - -以传统的订单、库存、账户服务为例,在 try 阶段尝试预留资源,插入订单、扣减库存、扣减金额,这三个服务都是要提交本地事务的,这里可以把资源转入中间表。在 commit 阶段,再把 try 阶段预留的资源转入最终表。而在 cancel 阶段,把 try 阶段预留的资源进行释放,比如把账户金额返回给客户的账户。 - -**注意:try 阶段必须是要提交本地事务的,比如扣减订单金额,必须把钱从客户账户扣掉,如果不扣掉,在 commit 阶段客户账户钱不够了,就会出问题。** - -### 1.1 try-commit - -try 阶段首先进行预留资源,然后在 commit 阶段扣除资源。如下图: - -![fence-try-commit](/img/blog/fence-try-commit.png) - - -### 1.2 try-cancel - -try 阶段首先进行预留资源,预留资源时扣减库存失败导致全局事务回滚,在 cancel 阶段释放资源。如下图: - -![fence-try-cancel](/img/blog/fence-try-cancel.png) - - -## 2 TCC 优势 - -TCC 模式最大的优势是效率高。TCC 模式在 try 阶段的锁定资源并不是真正意义上的锁定,而是真实提交了本地事务,将资源预留到中间态,并不需要阻塞等待,因此效率比其他模式要高。 - -同时 TCC 模式还可以进行如下优化: - -### 2.1 异步提交 - -try 阶段成功后,不立即进入 confirm/cancel 阶段,而是认为全局事务已经结束了,启动定时任务来异步执行 confirm/cancel,扣减或释放资源,这样会有很大的性能提升。 - -### 2.2 同库模式 - -TCC 模式中有三个角色: - -- TM:管理全局事务,包括开启全局事务,提交/回滚全局事务; -- RM:管理分支事务; -- TC: 管理全局事务和分支事务的状态。 - -下图来自 Seata 官网: - -![fence-fiffrent-db](/img/blog/fence-fiffrent-db.png) - -TM 开启全局事务时,RM 需要向 TC 发送注册消息,TC 保存分支事务的状态。TM 请求提交或回滚时,TC 需要向 RM 发送提交或回滚消息。这样包含两个个分支事务的分布式事务中,TC 和 RM 之间有四次 RPC。 - -优化后的流程如下图: - -![fence-same-db](/img/blog/fence-same-db.png) - - -TC 保存全局事务的状态。TM 开启全局事务时,RM 不再需要向 TC 发送注册消息,而是把分支事务状态保存在了本地。TM 向 TC 发送提交或回滚消息后,RM 异步线程首先查出本地保存的未提交分支事务,然后向 TC 发送消息获取(本地分支事务)所在的全局事务状态,以决定是提交还是回滚本地事务。 - -这样优化后,RPC 次数减少了 50%,性能大幅提升。 - -## 3 RM 代码示例 - -以库存服务为例,RM 库存服务接口代码如下: -```Java -@LocalTCC -public interface StorageService { - - /** - * 扣减库存 - * @param xid 全局xid - * @param productId 产品id - * @param count 数量 - * @return - */ - @TwoPhaseBusinessAction(name = "storageApi", commitMethod = "commit", rollbackMethod = "rollback", useTCCFence = true) - boolean decrease(String xid, Long productId, Integer count); - - /** - * 提交事务 - * @param actionContext - * @return - */ - boolean commit(BusinessActionContext actionContext); - - /** - * 回滚事务 - * @param actionContext - * @return - */ - boolean rollback(BusinessActionContext actionContext); -} -``` - -通过 @LocalTCC 这个注解,RM 初始化的时候会向 TC 注册一个分支事务。在 try 阶段的方法(decrease方法)上有一个 @TwoPhaseBusinessAction 注解,这里定义了分支事务的 resourceId,commit 方法和 cancel 方法,useTCCFence 这个属性下一节再讲。 - -## 4 TCC 存在问题 - -TCC 模式中存在的三大问题是幂等、悬挂和空回滚。在 Seata1.5.1 版本中,增加了一张事务控制表,表名是 tcc_fence_log 来解决这个问题。而在上一节 @TwoPhaseBusinessAction 注解中提到的属性 useTCCFence 就是来指定是否开启这个机制,这个属性值默认是 false。 - -tcc_fence_log 建表语句如下(MySQL 语法): -```SQL -CREATE TABLE IF NOT EXISTS `tcc_fence_log` -( - `xid` VARCHAR(128) NOT NULL COMMENT 'global id', - `branch_id` BIGINT NOT NULL COMMENT 'branch id', - `action_name` VARCHAR(64) NOT NULL COMMENT 'action name', - `status` TINYINT NOT NULL COMMENT 'status(tried:1;committed:2;rollbacked:3;suspended:4)', - `gmt_create` DATETIME(3) NOT NULL COMMENT 'create time', - `gmt_modified` DATETIME(3) NOT NULL COMMENT 'update time', - PRIMARY KEY (`xid`, `branch_id`), - KEY `idx_gmt_modified` (`gmt_modified`), - KEY `idx_status` (`status`) -) ENGINE = InnoDB -DEFAULT CHARSET = utf8mb4; -``` - -### 4.1 幂等 - -在 commit/cancel 阶段,因为 TC 没有收到分支事务的响应,需要进行重试,这就要分支事务支持幂等。 - -我们看一下新版本是怎么解决的。下面的代码在 TCCResourceManager 类: -```Java -@Override -public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId, - String applicationData) throws TransactionException { - TCCResource tccResource = (TCCResource)tccResourceCache.get(resourceId); - //省略判断 - Object targetTCCBean = tccResource.getTargetBean(); - Method commitMethod = tccResource.getCommitMethod(); - //省略判断 - try { - //BusinessActionContext - BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId, - applicationData); - Object[] args = this.getTwoPhaseCommitArgs(tccResource, businessActionContext); - Object ret; - boolean result; - //注解 useTCCFence 属性是否设置为 true - if (Boolean.TRUE.equals(businessActionContext.getActionContext(Constants.USE_TCC_FENCE))) { - try { - result = TCCFenceHandler.commitFence(commitMethod, targetTCCBean, xid, branchId, args); - } catch (SkipCallbackWrapperException | UndeclaredThrowableException e) { - throw e.getCause(); - } - } else { - //省略逻辑 - } - LOGGER.info("TCC resource commit result : {}, xid: {}, branchId: {}, resourceId: {}", result, xid, branchId, resourceId); - return result ? BranchStatus.PhaseTwo_Committed : BranchStatus.PhaseTwo_CommitFailed_Retryable; - } catch (Throwable t) { - //省略 - return BranchStatus.PhaseTwo_CommitFailed_Retryable; - } -} -``` -上面的代码可以看到,执行分支事务提交方法时,首先判断 useTCCFence 属性是否为 true,如果为 true,则走 TCCFenceHandler 类中的 commitFence 逻辑,否则走普通提交逻辑。 - -TCCFenceHandler 类中的 commitFence 方法调用了 TCCFenceHandler 类的 commitFence 方法,代码如下: -```Java -public static boolean commitFence(Method commitMethod, Object targetTCCBean, - String xid, Long branchId, Object[] args) { - return transactionTemplate.execute(status -> { - try { - Connection conn = DataSourceUtils.getConnection(dataSource); - TCCFenceDO tccFenceDO = TCC_FENCE_DAO.queryTCCFenceDO(conn, xid, branchId); - if (tccFenceDO == null) { - throw new TCCFenceException(String.format("TCC fence record not exists, commit fence method failed. xid= %s, branchId= %s", xid, branchId), - FrameworkErrorCode.RecordAlreadyExists); - } - if (TCCFenceConstant.STATUS_COMMITTED == tccFenceDO.getStatus()) { - LOGGER.info("Branch transaction has already committed before. idempotency rejected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus()); - return true; - } - if (TCCFenceConstant.STATUS_ROLLBACKED == tccFenceDO.getStatus() || TCCFenceConstant.STATUS_SUSPENDED == tccFenceDO.getStatus()) { - if (LOGGER.isWarnEnabled()) { - LOGGER.warn("Branch transaction status is unexpected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus()); - } - return false; - } - return updateStatusAndInvokeTargetMethod(conn, commitMethod, targetTCCBean, xid, branchId, TCCFenceConstant.STATUS_COMMITTED, status, args); - } catch (Throwable t) { - status.setRollbackOnly(); - throw new SkipCallbackWrapperException(t); - } - }); -} -``` - -从代码中可以看到,提交事务时首先会判断 tcc_fence_log 表中是否已经有记录,如果有记录,则判断事务执行状态并返回。这样如果判断到事务的状态已经是 STATUS_COMMITTED,就不会再次提交,保证了幂等。如果 tcc_fence_log 表中没有记录,则插入一条记录,供后面重试时判断。 - -Rollback 的逻辑跟 commit 类似,逻辑在类 TCCFenceHandler 的 rollbackFence 方法。 - -### 4.2 空回滚 - -如下图,账户服务是两个节点的集群,在 try 阶段账户服务 1 这个节点发生了故障,try 阶段在不考虑重试的情况下,全局事务必须要走向结束状态,这样就需要在账户服务上执行一次 cancel 操作。 - -![fence-empty-rollback](/img/blog/fence-empty-rollback.png) - - -Seata 的解决方案是在 try 阶段 往 tcc_fence_log 表插入一条记录,status 字段值是 STATUS_TRIED,在 Rollback 阶段判断记录是否存在,如果不存在,则不执行回滚操作。代码如下: -```Java -//TCCFenceHandler 类 -public static Object prepareFence(String xid, Long branchId, String actionName, Callback targetCallback) { - return transactionTemplate.execute(status -> { - try { - Connection conn = DataSourceUtils.getConnection(dataSource); - boolean result = insertTCCFenceLog(conn, xid, branchId, actionName, TCCFenceConstant.STATUS_TRIED); - LOGGER.info("TCC fence prepare result: {}. xid: {}, branchId: {}", result, xid, branchId); - if (result) { - return targetCallback.execute(); - } else { - throw new TCCFenceException(String.format("Insert tcc fence record error, prepare fence failed. xid= %s, branchId= %s", xid, branchId), - FrameworkErrorCode.InsertRecordError); - } - } catch (TCCFenceException e) { - //省略 - } catch (Throwable t) { - //省略 - } - }); -} -``` -在 Rollback 阶段的处理逻辑如下: -```Java -//TCCFenceHandler 类 -public static boolean rollbackFence(Method rollbackMethod, Object targetTCCBean, - String xid, Long branchId, Object[] args, String actionName) { - return transactionTemplate.execute(status -> { - try { - Connection conn = DataSourceUtils.getConnection(dataSource); - TCCFenceDO tccFenceDO = TCC_FENCE_DAO.queryTCCFenceDO(conn, xid, branchId); - // non_rollback - if (tccFenceDO == null) { - //不执行回滚逻辑 - return true; - } else { - if (TCCFenceConstant.STATUS_ROLLBACKED == tccFenceDO.getStatus() || TCCFenceConstant.STATUS_SUSPENDED == tccFenceDO.getStatus()) { - LOGGER.info("Branch transaction had already rollbacked before, idempotency rejected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus()); - return true; - } - if (TCCFenceConstant.STATUS_COMMITTED == tccFenceDO.getStatus()) { - if (LOGGER.isWarnEnabled()) { - LOGGER.warn("Branch transaction status is unexpected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus()); - } - return false; - } - } - return updateStatusAndInvokeTargetMethod(conn, rollbackMethod, targetTCCBean, xid, branchId, TCCFenceConstant.STATUS_ROLLBACKED, status, args); - } catch (Throwable t) { - status.setRollbackOnly(); - throw new SkipCallbackWrapperException(t); - } - }); -} -``` - -updateStatusAndInvokeTargetMethod 方法执行的 sql 如下: -```sql -update tcc_fence_log set status = ?, gmt_modified = ? - where xid = ? and branch_id = ? and status = ? ; -``` -可见就是把 tcc_fence_log 表记录的 status 字段值从 STATUS_TRIED 改为 STATUS_ROLLBACKED,如果更新成功,就执行回滚逻辑。 - -### 4.3 悬挂 - -悬挂是指因为网络问题,RM 开始没有收到 try 指令,但是执行了 Rollback 后 RM 又收到了 try 指令并且预留资源成功,这时全局事务已经结束,最终导致预留的资源不能释放。如下图: - - -![fence-suspend](/img/blog/fence-suspend.png) - - -Seata 解决这个问题的方法是执行 Rollback 方法时先判断 tcc_fence_log 是否存在当前 xid 的记录,如果没有则向 tcc_fence_log 表插入一条记录,状态是 STATUS_SUSPENDED,并且不再执行回滚操作。代码如下: -```Java -public static boolean rollbackFence(Method rollbackMethod, Object targetTCCBean, - String xid, Long branchId, Object[] args, String actionName) { - return transactionTemplate.execute(status -> { - try { - Connection conn = DataSourceUtils.getConnection(dataSource); - TCCFenceDO tccFenceDO = TCC_FENCE_DAO.queryTCCFenceDO(conn, xid, branchId); - // non_rollback - if (tccFenceDO == null) { - //插入防悬挂记录 - boolean result = insertTCCFenceLog(conn, xid, branchId, actionName, TCCFenceConstant.STATUS_SUSPENDED); - //省略逻辑 - return true; - } else { - //省略逻辑 - } - return updateStatusAndInvokeTargetMethod(conn, rollbackMethod, targetTCCBean, xid, branchId, TCCFenceConstant.STATUS_ROLLBACKED, status, args); - } catch (Throwable t) { - //省略逻辑 - } - }); -} -``` - -而后面执行 try 阶段方法时首先会向 tcc_fence_log 表插入一条当前 xid 的记录,这样就造成了主键冲突。代码如下: -```Java -//TCCFenceHandler 类 -public static Object prepareFence(String xid, Long branchId, String actionName, Callback targetCallback) { - return transactionTemplate.execute(status -> { - try { - Connection conn = DataSourceUtils.getConnection(dataSource); - boolean result = insertTCCFenceLog(conn, xid, branchId, actionName, TCCFenceConstant.STATUS_TRIED); - //省略逻辑 - } catch (TCCFenceException e) { - if (e.getErrcode() == FrameworkErrorCode.DuplicateKeyException) { - LOGGER.error("Branch transaction has already rollbacked before,prepare fence failed. xid= {},branchId = {}", xid, branchId); - addToLogCleanQueue(xid, branchId); - } - status.setRollbackOnly(); - throw new SkipCallbackWrapperException(e); - } catch (Throwable t) { - //省略 - } - }); -} -``` - -**注意:queryTCCFenceDO 方法 sql 中使用了 for update,这样就不用担心 Rollback 方法中获取不到 tcc_fence_log 表记录而无法判断 try 阶段本地事务的执行结果了。** - -## 5 总结 - -TCC 模式是分布式事务中非常重要的事务模式,但是幂等、悬挂和空回滚一直是 TCC 模式需要考虑的问题,Seata 框架在 1.5.1 版本完美解决了这些问题。 - -对 tcc_fence_log 表的操作也需要考虑事务的控制,Seata 使用了代理数据源,使 tcc_fence_log 表操作和 RM 业务操作在同一个本地事务中执行,这样就能保证本地操作和对 tcc_fence_log 的操作同时成功或失败。 - - +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-tcc.md b/blog/seata-tcc.md index 4ec4d3cef6..e872b67af1 100644 --- a/blog/seata-tcc.md +++ b/blog/seata-tcc.md @@ -1,325 +1 @@ ---- -title: 深度剖析 Seata TCC 模式(一) - -author: 张乘辉 - -keywords: [Seata、分布式事务、TCC] - -description: Seata 目前支持 AT 模式、XA 模式、TCC 模式和 SAGA 模式,之前文章更多谈及的是非侵入式的 AT 模式,今天带大家认识一下同样是二阶段提交的 TCC 模式。 - -date: 2022/01/18 - ---- - -# 前言 - -Seata 目前支持 AT 模式、XA 模式、TCC 模式和 SAGA 模式,之前文章更多谈及的是非侵入式的 AT 模式,今天带大家认识一下同样是二阶段提交的 TCC 模式。 - -# 什么是 TCC - -TCC 是分布式事务中的二阶段提交协议,它的全称为 Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),他们的具体含义如下: - -1. Try:对业务资源的检查并预留; -2. Confirm:对业务处理进行提交,即 commit 操作,只要 Try 成功,那么该步骤一定成功; -3. Cancel:对业务处理进行取消,即回滚操作,该步骤回对 Try 预留的资源进行释放。 - -TCC 是一种侵入式的分布式事务解决方案,以上三个操作都需要业务系统自行实现,对业务系统有着非常大的入侵性,设计相对复杂,但优点是 TCC -完全不依赖数据库,能够实现跨数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决了在各种复杂业务场景下的分布式事务问题。 - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20220116160157.png) - -# Seata TCC 模式 - -Seata TCC 模式跟通用型 TCC 模式原理一致,我们先来使用 Seata TCC 模式实现一个分布式事务: - -假设现有一个业务需要同时使用服务 A 和服务 B 完成一个事务操作,我们在服务 A 定义该服务的一个 TCC 接口: - -```java -public interface TccActionOne { - @TwoPhaseBusinessAction(name = "DubboTccActionOne", commitMethod = "commit", rollbackMethod = "rollback") - public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a); - - public boolean commit(BusinessActionContext actionContext); - - public boolean rollback(BusinessActionContext actionContext); -} -``` - -同样,在服务 B 定义该服务的一个 TCC 接口: - -```java -public interface TccActionTwo { - @TwoPhaseBusinessAction(name = "DubboTccActionTwo", commitMethod = "commit", rollbackMethod = "rollback") - public void prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "b") String b); - - public void commit(BusinessActionContext actionContext); - - public void rollback(BusinessActionContext actionContext); -} -``` - -在业务所在系统中开启全局事务并执行服务 A 和服务 B 的 TCC 预留资源方法: - -```java -@GlobalTransactional -public String doTransactionCommit(){ - //服务A事务参与者 - tccActionOne.prepare(null,"one"); - //服务B事务参与者 - tccActionTwo.prepare(null,"two"); -} -``` - -以上就是使用 Seata TCC 模式实现一个全局事务的例子,可以看出,TCC 模式同样使用 `@GlobalTransactional` 注解开启全局事务,而服务 A 和服务 B 的 TCC 接口为事务参与者,Seata 会把一个 TCC -接口当成一个 Resource,也叫 TCC Resource。 - -TCC 接口可以是 RPC,也可以是 JVM 内部调用,意味着一个 TCC 接口,会有发起方和调用方两个身份,以上例子,TCC 接口在服务 A 和服务 B 中是发起方,在业务所在系统中是调用方。如果该 TCC 接口为 Dubbo -RPC,那么调用方就是一个 dubbo:reference,发起方则是一个 dubbo:service。 - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20220116161933.png) - -Seata 启动时会对 TCC 接口进行扫描并解析,如果 TCC 接口是一个发布方,则在 Seata 启动时会向 TC 注册 TCC Resource,每个 TCC Resource 都有一个资源 ID;如果 TCC -接口时一个调用方,Seata 代理调用方,与 AT 模式一样,代理会拦截 TCC 接口的调用,即每次调用 Try 方法,会向 TC 注册一个分支事务,接着才执行原来的 RPC 调用。 - -当全局事务决议提交/回滚时,TC 会通过分支注册的的资源 ID 回调到对应参与者服务中执行 TCC Resource 的 Confirm/Cancel 方法。 - -# Seata 如何实现 TCC 模式 - -从上面的 Seata TCC 模型可以看出,TCC 模式在 Seata 中也是遵循 TC、TM、RM 三种角色模型的,如何在这三种角色模型中实现 TCC 模式呢?我将其主要实现归纳为资源解析、资源管理、事务处理。 - -## 资源解析 - -资源解析即是把 TCC 接口进行解析并注册,前面说过,TCC 接口可以是 RPC,也可以是 JVM 内部调用,在 Seata TCC 模块有中一个 remoting -模块,该模块专门用于解析具有 `TwoPhaseBusinessAction` 注解的 TCC 接口资源: - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20220116175059.png) - -`RemotingParser` 接口主要有 `isRemoting`、`isReference`、`isService`、`getServiceDesc` 等方法,默认的实现为 `DefaultRemotingParser`,其余各自的 -RPC 协议解析类都在 `DefaultRemotingParser` 中执行,Seata 目前已经实现了对 Dubbo、HSF、SofaRpc、LocalTCC 的 RPC 协议的解析,同时具备 SPI 可扩展性,未来欢迎大家为 -Seata 提供更多的 RPC 协议解析类。 - -在 Seata 启动过程中,有个 `GlobalTransactionScanner ` 注解进行扫描,会执行以下方法: - -`io.seata.spring.util.TCCBeanParserUtils#isTccAutoProxy` - -该方法目的是判断 bean 是否已被 TCC 代理,在过程中会先判断 bean 是否是一个 Remoting bean,如果是则调用 `getServiceDesc` 方法对 remoting bean -进行解析,同时判断如果是一个发起方,则对其进行资源注册: - -io.seata.rm.tcc.remoting.parser.DefaultRemotingParser#parserRemotingServiceInfo - -```java -public RemotingDesc parserRemotingServiceInfo(Object bean,String beanName,RemotingParser remotingParser){ - RemotingDesc remotingBeanDesc=remotingParser.getServiceDesc(bean,beanName); - if(remotingBeanDesc==null){ - return null; - } - remotingServiceMap.put(beanName,remotingBeanDesc); - - Class interfaceClass=remotingBeanDesc.getInterfaceClass(); - Method[]methods=interfaceClass.getMethods(); - if(remotingParser.isService(bean,beanName)){ - try{ - //service bean, registry resource - Object targetBean=remotingBeanDesc.getTargetBean(); - for(Method m:methods){ - TwoPhaseBusinessAction twoPhaseBusinessAction=m.getAnnotation(TwoPhaseBusinessAction.class); - if(twoPhaseBusinessAction!=null){ - TCCResource tccResource=new TCCResource(); - tccResource.setActionName(twoPhaseBusinessAction.name()); - tccResource.setTargetBean(targetBean); - tccResource.setPrepareMethod(m); - tccResource.setCommitMethodName(twoPhaseBusinessAction.commitMethod()); - tccResource.setCommitMethod(interfaceClass.getMethod(twoPhaseBusinessAction.commitMethod(), - twoPhaseBusinessAction.commitArgsClasses())); - tccResource.setRollbackMethodName(twoPhaseBusinessAction.rollbackMethod()); - tccResource.setRollbackMethod(interfaceClass.getMethod(twoPhaseBusinessAction.rollbackMethod(), - twoPhaseBusinessAction.rollbackArgsClasses())); - // set argsClasses - tccResource.setCommitArgsClasses(twoPhaseBusinessAction.commitArgsClasses()); - tccResource.setRollbackArgsClasses(twoPhaseBusinessAction.rollbackArgsClasses()); - // set phase two method's keys - tccResource.setPhaseTwoCommitKeys(this.getTwoPhaseArgs(tccResource.getCommitMethod(), - twoPhaseBusinessAction.commitArgsClasses())); - tccResource.setPhaseTwoRollbackKeys(this.getTwoPhaseArgs(tccResource.getRollbackMethod(), - twoPhaseBusinessAction.rollbackArgsClasses())); - //registry tcc resource - DefaultResourceManager.get().registerResource(tccResource); - } - } - }catch(Throwable t){ - throw new FrameworkException(t,"parser remoting service error"); - } - } - if(remotingParser.isReference(bean,beanName)){ - //reference bean, TCC proxy - remotingBeanDesc.setReference(true); - } - return remotingBeanDesc; - } -``` - -以上方法,先调用解析类 `getServiceDesc` 方法对 remoting bean 进行解析,并将解析后的 `remotingBeanDesc` 放入 本地缓存 `remotingServiceMap` -中,同时调用解析类 `isService` 方法判断是否为发起方,如果是发起方,则解析 `TwoPhaseBusinessAction` 注解内容生成一个 `TCCResource`,并对其进行资源注册。 - -## 资源管理 - -**1、资源注册** - -Seata TCC 模式的资源叫 `TCCResource`,其资源管理器叫 `TCCResourceManager`,前面讲过,当解析完 TCC 接口 RPC 资源后,如果是发起方,则会对其进行资源注册: - -io.seata.rm.tcc.TCCResourceManager#registerResource - -```java -public void registerResource(Resource resource){ - TCCResource tccResource=(TCCResource)resource; - tccResourceCache.put(tccResource.getResourceId(),tccResource); - super.registerResource(tccResource); - } -``` - -`TCCResource` 包含了 TCC 接口的相关信息,同时会在本地进行缓存。继续调用父类 `registerResource` 方法(封装了通信方法)向 TC 注册,TCC 资源的 resourceId 是 -actionName,actionName 就是 `@TwoParseBusinessAction` 注解中的 name。 - -**2、资源提交/回滚** - -io.seata.rm.tcc.TCCResourceManager#branchCommit - -```java -public BranchStatus branchCommit(BranchType branchType,String xid,long branchId,String resourceId, - String applicationData)throws TransactionException{ - TCCResource tccResource=(TCCResource)tccResourceCache.get(resourceId); - if(tccResource==null){ - throw new ShouldNeverHappenException(String.format("TCC resource is not exist, resourceId: %s",resourceId)); - } - Object targetTCCBean=tccResource.getTargetBean(); - Method commitMethod=tccResource.getCommitMethod(); - if(targetTCCBean==null||commitMethod==null){ - throw new ShouldNeverHappenException(String.format("TCC resource is not available, resourceId: %s",resourceId)); - } - try{ - //BusinessActionContext - BusinessActionContext businessActionContext=getBusinessActionContext(xid,branchId,resourceId, - applicationData); - // ... ... - ret=commitMethod.invoke(targetTCCBean,args); - // ... ... - return result?BranchStatus.PhaseTwo_Committed:BranchStatus.PhaseTwo_CommitFailed_Retryable; - }catch(Throwable t){ - String msg=String.format("commit TCC resource error, resourceId: %s, xid: %s.",resourceId,xid); - LOGGER.error(msg,t); - return BranchStatus.PhaseTwo_CommitFailed_Retryable; - } - } -``` - -当 TM 决议二阶段提交,TC 会通过分支注册的的资源 ID 回调到对应参与者(即 TCC 接口发起方)服务中执行 TCC Resource 的 Confirm/Cancel 方法。 - -资源管理器中会根据 resourceId 在本地缓存找到对应的 `TCCResource`,同时根据 xid、branchId、resourceId、applicationData 找到对应的 `BusinessActionContext` -上下文,执行的参数就在上下文中。最后,执行 `TCCResource` 中获取 `commit` 的方法进行二阶段提交。 - -二阶段回滚同理类似。 - -## 事务处理 - -前面讲过,如果 TCC 接口时一个调用方,则会使用 Seata TCC 代理对调用方进行拦截处理,并在处理调用真正的 RPC 方法前对分支进行注册。 - -执行方法`io.seata.spring.util.TCCBeanParserUtils#isTccAutoProxy`除了对 TCC 接口资源进行解析,还会判断 TCC 接口是否为调用方,如果是调用方则返回 true: - -io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20220116192544.png) - -如图,当 `GlobalTransactionalScanner` 扫描到 TCC 接口调用方(Reference)时,会使 `TccActionInterceptor` 对其进行代理拦截处理,`TccActionInterceptor` -实现 `MethodInterceptor`。 - -在 `TccActionInterceptor` 中还会调用 `ActionInterceptorHandler` 类型执行拦截处理逻辑,事务相关处理就在 `ActionInterceptorHandler#proceed` 方法中: - -```java -public Object proceed(Method method,Object[]arguments,String xid,TwoPhaseBusinessAction businessAction, - Callback targetCallback)throws Throwable{ - //Get action context from arguments, or create a new one and then reset to arguments - BusinessActionContext actionContext=getOrCreateActionContextAndResetToArguments(method.getParameterTypes(),arguments); - //Creating Branch Record - String branchId=doTccActionLogStore(method,arguments,businessAction,actionContext); - // ... ... - try{ - // ... ... - return targetCallback.execute(); - }finally{ - try{ - //to report business action context finally if the actionContext.getUpdated() is true - BusinessActionContextUtil.reportContext(actionContext); - }finally{ - // ... ... - } - } - } -``` - -以上,在执行 TCC 接口一阶段之前,会调用 `doTccActionLogStore` 方法分支注册,同时还会将 TCC 相关信息比如参数放置在上下文,上面讲的资源提交/回滚就会用到这个上下文。 - -# 如何控制异常 - -在 TCC 模型执行的过程中,还可能会出现各种异常,其中最为常见的有空回滚、幂等、悬挂等。下面我讲下 Seata 是如何处理这三种异常的。 - -## 如何处理空回滚 - -什么是空回滚? - -空回滚指的是在一个分布式事务中,在没有调用参与方的 Try 方法的情况下,TM 驱动二阶段回滚调用了参与方的 Cancel 方法。 - -那么空回滚是如何产生的呢? - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20220116201900.png) - -如上图所示,全局事务开启后,参与者 A 分支注册完成之后会执行参与者一阶段 RPC 方法,如果此时参与者 A 所在的机器发生宕机,网络异常,都会造成 RPC 调用失败,即参与者 A 一阶段方法未成功执行,但是此时全局事务已经开启,Seata -必须要推进到终态,在全局事务回滚时会调用参与者 A 的 Cancel 方法,从而造成空回滚。 - -要想防止空回滚,那么必须在 Cancel 方法中识别这是一个空回滚,Seata 是如何做的呢? - -Seata 的做法是新增一个 TCC 事务控制表,包含事务的 XID 和 BranchID 信息,在 Try 方法执行时插入一条记录,表示一阶段执行了,执行 Cancel 方法时读取这条记录,如果记录不存在,说明 Try 方法没有执行。 - -## 如何处理幂等 - -幂等问题指的是 TC 重复进行二阶段提交,因此 Confirm/Cancel 接口需要支持幂等处理,即不会产生资源重复提交或者重复释放。 - -那么幂等问题是如何产生的呢? - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20220116203816.png) - -如上图所示,参与者 A 执行完二阶段之后,由于网络抖动或者宕机问题,会造成 TC 收不到参与者 A 执行二阶段的返回结果,TC 会重复发起调用,直到二阶段执行结果成功。 - -Seata 是如何处理幂等问题的呢? - -同样的也是在 TCC 事务控制表中增加一个记录状态的字段 status,该字段有 3 个值,分别为: - -1. tried:1 -2. committed:2 -3. rollbacked:3 - -二阶段 Confirm/Cancel 方法执行后,将状态改为 committed 或 rollbacked 状态。当重复调用二阶段 Confirm/Cancel 方法时,判断事务状态即可解决幂等问题。 - -## 如何处理悬挂 - -悬挂指的是二阶段 Cancel 方法比 一阶段 Try 方法优先执行,由于允许空回滚的原因,在执行完二阶段 Cancel 方法之后直接空回滚返回成功,此时全局事务已结束,但是由于 Try 方法随后执行,这就会造成一阶段 Try -方法预留的资源永远无法提交和释放了。 - -那么悬挂是如何产生的呢? - -![](https://gitee.com/objcoding/md-picture/raw/master/img/20220116205241.png) - -如上图所示,在执行参与者 A 的一阶段 Try 方法时,出现网路拥堵,由于 Seata 全局事务有超时限制,执行 Try 方法超时后,TM 决议全局回滚,回滚完成后如果此时 RPC 请求才到达参与者 A,执行 Try -方法进行资源预留,从而造成悬挂。 - -Seata 是怎么处理悬挂的呢? - -在 TCC 事务控制表记录状态的字段 status 中增加一个状态: - -1. suspended:4 - -当执行二阶段 Cancel 方法时,如果发现 TCC 事务控制表没有相关记录,说明二阶段 Cancel 方法优先一阶段 Try 方法执行,因此插入一条 status=4 状态的记录,当一阶段 Try 方法后面执行时,判断 status=4 ,则说明有二阶段 Cancel 已执行,并返回 false 以阻止一阶段 Try 方法执行成功。 - -# 作者简介 - -张乘辉,目前就职于蚂蚁集团,热爱分享技术,微信公众号「后端进阶」作者,技术博客([https://objcoding.com/](https://objcoding.com/))博主,Seata Committer,GitHub -ID:objcoding。 +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/seata-xa-introduce.md b/blog/seata-xa-introduce.md index 6a84065020..e872b67af1 100644 --- a/blog/seata-xa-introduce.md +++ b/blog/seata-xa-introduce.md @@ -1,330 +1 @@ ---- -title: 分布式事务如何实现?深入解读 Seata 的 XA 模式 -keywords: [Seata,分布式事务,XA,AT] -description: 深入解读 Seata 的 XA 模式 -author: 煊檍 -date: 2020/04/28 ---- - -# 分布式事务如何实现?深入解读 Seata 的 XA 模式 - -作者简介:煊檍,GitHub ID:sharajava,阿里巴巴中件间 GTS 研发团队负责人,SEATA 开源项目发起人,曾在 Oracle 北京研发中心多年,从事 WebLogic 核心研发工作。长期专注于中间件,尤其是分布式事务领域的技术实践。 - -Seata 1.2.0 版本重磅发布新的事务模式:XA 模式,实现对 XA 协议的支持。 - -这里,我们从三个方面来深入解读这个新的特性: - -- 是什么(What):XA 模式是什么? -- 为什么(Why):为什么支持 XA? -- 怎么做(How):XA 模式是如何实现的,以及怎样使用? - -# 1. XA 模式是什么? - -这里有两个基本的前置概念: - -1. 什么是 XA? -2. 什么是 Seata 定义的所谓 事务模式? - -基于这两点,再来理解 XA 模式就很自然了。 - -## 1.1 什么是 XA? - -XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。 - -XA 规范 描述了全局的事务管理器与局部的资源管理器之间的接口。 XA规范 的目的是允许的多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使 ACID 属性跨越应用程序而保持有效。 - -XA 规范 使用两阶段提交(2PC,Two-Phase Commit)来保证所有资源同时提交或回滚任何特定的事务。 - -XA 规范 在上世纪 90 年代初就被提出。目前,几乎所有主流的数据库都对 XA 规范 提供了支持。 - -## 1.2 什么是 Seata 的事务模式? - -Seata 定义了全局事务的框架。 - -全局事务 定义为若干 分支事务 的整体协调: - -1. TM 向 TC 请求发起(Begin)、提交(Commit)、回滚(Rollback)全局事务。 -2. TM 把代表全局事务的 XID 绑定到分支事务上。 -3. RM 向 TC 注册,把分支事务关联到 XID 代表的全局事务中。 -4. RM 把分支事务的执行结果上报给 TC。(可选) -5. TC 发送分支提交(Branch Commit)或分支回滚(Branch Rollback)命令给 RM。 - -seata-mod - -Seata 的 全局事务 处理过程,分为两个阶段: - -- 执行阶段 :执行 分支事务,并 保证 执行结果满足是 *可回滚的(Rollbackable)* 和 *持久化的(Durable)*。 -- 完成阶段: 根据 执行阶段 结果形成的决议,应用通过 TM 发出的全局提交或回滚的请求给 TC,TC 命令 RM 驱动 分支事务 进行 Commit 或 Rollback。 - -Seata 的所谓 事务模式 是指:运行在 Seata 全局事务框架下的 分支事务 的行为模式。准确地讲,应该叫作 分支事务模式。 - -不同的 事务模式 区别在于 分支事务 使用不同的方式达到全局事务两个阶段的目标。即,回答以下两个问题: - -- 执行阶段 :如何执行并 保证 执行结果满足是 *可回滚的(Rollbackable)* 和 *持久化的(Durable)*。 -- 完成阶段: 收到 TC 的命令后,如何做到分支的提交或回滚? - -以我们 Seata 的 AT 模式和 TCC 模式为例来理解: - -AT 模式 - -at-mod - -- 执行阶段: - -- - 可回滚:根据 SQL 解析结果,记录回滚日志 - - 持久化:回滚日志和业务 SQL 在同一个本地事务中提交到数据库 - -- 完成阶段: - -- - 分支提交:异步删除回滚日志记录 - - 分支回滚:依据回滚日志进行反向补偿更新 - -TCC 模式 - -tcc-mod - -- 执行阶段: - -- - 调用业务定义的 Try 方法(完全由业务层面保证 *可回滚* 和 *持久化*) - -- 完成阶段: - -- - 分支提交:调用各事务分支定义的 Confirm 方法 - - 分支回滚:调用各事务分支定义的 Cancel 方法 - -## 1.3 什么是 Seata 的 XA 模式? - -XA 模式: - -在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。 - -xa-mod - -- 执行阶段: - -- - 可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证 *可回滚* - - 持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证 *持久化*(即,之后任何意外都不会造成无法回滚的情况) - -- 完成阶段: - -- - 分支提交:执行 XA 分支的 commit - - 分支回滚:执行 XA 分支的 rollback - -# 2. 为什么支持 XA? - -为什么要在 Seata 中增加 XA 模式呢?支持 XA 的意义在哪里呢? - -## 2.1 补偿型事务模式的问题 - -本质上,Seata 已经支持的 3 大事务模式:AT、TCC、Saga 都是 补偿型 的。 - -补偿型 事务处理机制构建在 事务资源 之上(要么在中间件层面,要么在应用层面),事务资源 本身对分布式事务是无感知的。 - -img - -事务资源 对分布式事务的无感知存在一个根本性的问题:无法做到真正的 全局一致性 。 - -比如,一条库存记录,处在 补偿型 事务处理过程中,由 100 扣减为 50。此时,仓库管理员连接数据库,查询统计库存,就看到当前的 50。之后,事务因为异外回滚,库存会被补偿回滚为 100。显然,仓库管理员查询统计到的 50 就是 脏 数据。 - -可以看到,补偿型 分布式事务机制因为不要求 事务资源 本身(如数据库)的机制参与,所以无法保证从事务框架之外的全局视角的数据一致性。 - -## 2.2 XA 的价值 - -与 补偿型 不同,XA 协议 要求 事务资源 本身提供对规范和协议的支持。 - -nct - -因为 事务资源 感知并参与分布式事务处理过程,所以 事务资源(如数据库)可以保障从任意视角对数据的访问有效隔离,满足全局数据一致性。 - -比如,上一节提到的库存更新场景,XA 事务处理过程中,中间态数据库存 50 由数据库本身保证,是不会仓库管理员的查询统计 *看* 到的。(当然隔离级别需要 读已提交 以上) - -除了 全局一致性 这个根本性的价值外,支持 XA 还有如下几个方面的好处: - -1. 业务无侵入:和 AT 一样,XA 模式将是业务无侵入的,不给应用设计和开发带来额外负担。 -2. 数据库的支持广泛:XA 协议被主流关系型数据库广泛支持,不需要额外的适配即可使用。 -3. 多语言支持容易:因为不涉及 SQL 解析,XA 模式对 Seata 的 RM 的要求比较少,为不同语言开发 SDK 较之 AT 模式将更 *薄*,更容易。 -4. 传统基于 XA 应用的迁移:传统的,基于 XA 协议的应用,迁移到 Seata 平台,使用 XA 模式将更平滑。 - -## 2.3 XA 广泛被质疑的问题 - -不存在某一种分布式事务机制可以完美适应所有场景,满足所有需求。 - -XA 规范早在上世纪 90 年代初就被提出,用以解决分布式事务处理这个领域的问题。 - -现在,无论 AT 模式、TCC 模式还是 Saga 模式,这些模式的提出,本质上都源自 XA 规范对某些场景需求的无法满足。 - -XA 规范定义的分布式事务处理机制存在一些被广泛质疑的问题,针对这些问题,我们是如何思考的呢? - -1. **数据锁定**:数据在整个事务处理过程结束前,都被锁定,读写都按隔离级别的定义约束起来。 - -> 思考: -> -> 数据锁定是获得更高隔离性和全局一致性所要付出的代价。 -> -> 补偿型 的事务处理机制,在 执行阶段 即完成分支(本地)事务的提交,(资源层面)不锁定数据。而这是以牺牲 隔离性 为代价的。 -> -> 另外,AT 模式使用 *全局锁* 保障基本的 *写隔离*,实际上也是锁定数据的,只不过锁在 TC 侧集中管理,解锁效率高且没有阻塞的问题。 - -2. **协议阻塞**:XA prepare 后,分支事务进入阻塞阶段,收到 XA commit 或 XA rollback 前必须阻塞等待。 - -> 思考: -> -> 协议的阻塞机制本身并不是问题,关键问题在于 协议阻塞 遇上 数据锁定。 -> -> 如果一个参与全局事务的资源 “失联” 了(收不到分支事务结束的命令),那么它锁定的数据,将一直被锁定。进而,甚至可能因此产生死锁。 -> -> 这是 XA 协议的核心痛点,也是 Seata 引入 XA 模式要重点解决的问题。 -> -> 基本思路是两个方面:避免 “失联” 和 增加 “自解锁” 机制。(这里涉及非常多技术细节,暂时不展开,在后续 XA 模式演进过程中,会专门拿出来讨论) - -3. **性能差**:性能的损耗主要来自两个方面:一方面,事务协调过程,增加单个事务的 RT;另一方面,并发事务数据的锁冲突,降低吞吐。 - -> 思考: -> -> 和不使用分布式事务支持的运行场景比较,性能肯定是下降的,这点毫无疑问。 -> -> 本质上,事务(无论是本地事务还是分布式事务)机制就是拿部分 性能的牺牲 ,换来 编程模型的简单 。 -> -> 与同为 业务无侵入 的 AT 模式比较: -> -> 首先,因为同样运行在 Seata 定义的分布式事务框架下,XA 模式并没有产生更多事务协调的通信开销。 -> -> 其次,并发事务间,如果数据存在热点,产生锁冲突,这种情况,在 AT 模式(默认使用全局锁)下同样存在的。 -> -> 所以,在影响性能的两个主要方面,XA 模式并不比 AT 模式有非常明显的劣势。 -> -> AT 模式性能优势主要在于:集中管理全局数据锁,锁的释放不需要 RM 参与,释放锁非常快;另外,全局提交的事务,完成阶段 异步化。 - -# 3. XA 模式如何实现以及怎样用? - -## 3.1 XA 模式的设计 - -### 3.1.1 设计目标 - -XA 模式的基本设计目标,两个主要方面: - -1. 从 场景 上,满足 全局一致性 的需求。 -2. 从 应用上,保持与 AT 模式一致的无侵入。 -3. 从 机制 上,适应分布式微服务架构的特点。 - -整体思路: - -1. 与 AT 模式相同的:以应用程序中 本地事务 的粒度,构建到 XA 模式的 分支事务。 -2. 通过数据源代理,在应用程序本地事务范围外,在框架层面包装 XA 协议的交互机制,把 XA 编程模型 透明化。 -3. 把 XA 的 2PC 拆开,在分支事务 执行阶段 的末尾就进行 XA prepare,把 XA 协议完美融合到 Seata 的事务框架,减少一轮 RPC 交互。 - -### 3.1.2 核心设计 - -#### 1. 整体运行机制 - -XA 模式 运行在 Seata 定义的事务框架内: - -xa-fw - -- 执行阶段(E xecute): - -- - XA start/XA end/XA prepare + SQL + 注册分支 - -- 完成阶段(F inish): - -- - XA commit/XA rollback - -#### 2. 数据源代理 - -XA 模式需要 XAConnection。 - -获取 XAConnection 两种方式: - -- 方式一:要求开发者配置 XADataSource -- 方式二:根据开发者的普通 DataSource 来创建 - -第一种方式,给开发者增加了认知负担,需要为 XA 模式专门去学习和使用 XA 数据源,与 透明化 XA 编程模型的设计目标相违背。 - -第二种方式,对开发者比较友好,和 AT 模式使用一样,开发者完全不必关心 XA 层面的任何问题,保持本地编程模型即可。 - -我们优先设计实现第二种方式:数据源代理根据普通数据源中获取的普通 JDBC 连接创建出相应的 XAConnection。 - -类比 AT 模式的数据源代理机制,如下: - -img - -但是,第二种方法有局限:无法保证兼容的正确性。 - -实际上,这种方法是在做数据库驱动程序要做的事情。不同的厂商、不同版本的数据库驱动实现机制是厂商私有的,我们只能保证在充分测试过的驱动程序上是正确的,开发者使用的驱动程序版本差异很可能造成机制的失效。 - -这点在 Oracle 上体现非常明显。参见 Druid issue:https://github.com/alibaba/druid/issues/3707 - -综合考虑,XA 模式的数据源代理设计需要同时支持第一种方式:基于 XA 数据源进行代理。 - -类比 AT 模式的数据源代理机制,如下: - -img - -#### 3. 分支注册 - -XA start 需要 Xid 参数。 - -这个 Xid 需要和 Seata 全局事务的 XID 和 BranchId 关联起来,以便由 TC 驱动 XA 分支的提交或回滚。 - -目前 Seata 的 BranchId 是在分支注册过程,由 TC 统一生成的,所以 XA 模式分支注册的时机需要在 XA start 之前。 - -将来一个可能的优化方向: - -把分支注册尽量延后。类似 AT 模式在本地事务提交之前才注册分支,避免分支执行失败情况下,没有意义的分支注册。 - -这个优化方向需要 BranchId 生成机制的变化来配合。BranchId 不通过分支注册过程生成,而是生成后再带着 BranchId 去注册分支。 - -#### 4. 小结 - -这里只通过几个重要的核心设计,说明 XA 模式的基本工作机制。 - -此外,还有包括 *连接保持*、*异常处理* 等重要方面,有兴趣可以从项目代码中进一步了解。 - -以后会陆续写出来和大家交流。 - -### 3.1.3 演进规划 - -XA 模式总体的演进规划如下: - -1. 第 1 步(已经完成):首个版本(1.2.0),把 XA 模式原型机制跑通。确保只增加,不修改,不给其他模式引入的新问题。 -2. 第 2 步(计划 5 月完成):与 AT 模式必要的融合、重构。 -3. 第 3 步(计划 7 月完成):完善异常处理机制,进行上生产所必需的打磨。 -4. 第 4 步(计划 8 月完成):性能优化。 -5. 第 5 步(计划 2020 年内完成):结合 Seata 项目正在进行的面向云原生的 Transaction Mesh 设计,打造云原生能力。 - -## 3.2 XA 模式的使用 - -从编程模型上,XA 模式与 AT 模式保持完全一致。 - -可以参考 Seata 官网的样例:seata-xa - -样例场景是 Seata 经典的,涉及库存、订单、账户 3 个微服务的商品订购业务。 - -在样例中,上层编程模型与 AT 模式完全相同。只需要修改数据源代理,即可实现 XA 模式与 AT 模式之间的切换。 - -``` - @Bean("dataSource") - public DataSource dataSource(DruidDataSource druidDataSource) { - // DataSourceProxy for AT mode - // return new DataSourceProxy(druidDataSource); - - // DataSourceProxyXA for XA mode - return new DataSourceProxyXA(druidDataSource); - } -``` - -# 4. 总结 - -在当前的技术发展阶段,不存一个分布式事务处理机制可以完美满足所有场景的需求。 - -一致性、可靠性、易用性、性能等诸多方面的系统设计约束,需要用不同的事务处理机制去满足。 - -Seata 项目最核心的价值在于:构建一个全面解决分布式事务问题的 标准化 平台。 - -基于 Seata,上层应用架构可以根据实际场景的需求,灵活选择合适的分布式事务解决方案。 - -img - -XA 模式的加入,补齐了 Seata 在 全局一致性 场景下的缺口,形成 AT、TCC、Saga、XA 四大 事务模式 的版图,基本可以满足所有场景的分布式事务处理诉求。 - -当然 XA 模式和 Seata 项目本身都还不尽完美,有很多需要改进和完善的地方。非常欢迎大家参与到项目的建设中,共同打造一个标准化的分布式事务平台。 \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/springboot-dubbo-mybatisplus-seata.md b/blog/springboot-dubbo-mybatisplus-seata.md index cc820797d5..e872b67af1 100644 --- a/blog/springboot-dubbo-mybatisplus-seata.md +++ b/blog/springboot-dubbo-mybatisplus-seata.md @@ -1,1450 +1 @@ ---- -title: SpringBoot+Dubbo+MybatisPlus整合seata分布式事务 -keywords: [Seata,dubbo,mybatis,分布式事务] -description: 本文讲述如何将springboot+dubbo+mybatisplus整合seata直连方式搭建 -author: FUNKYE -date: 2019/11/29 ---- - -# SpringBoot+Dubbo+MybatisPlus整合Seata分布式事务 - -[项目地址](https://gitee.com/itCjb/springboot-dubbo-mybatisplus-seata ) - -本文作者:FUNKYE(陈健斌),杭州某互联网公司主程。 - -# 前言 - -​ **事务**:事务是由一组操作构成的可靠的独立的工作单元,事务具备ACID的特性,即原子性、一致性、隔离性和持久性。 -​ **分布式事务**:当一个操作牵涉到多个服务,多台数据库协力完成时(比如分表分库后,业务拆分),多个服务中,本地的Transaction已经无法应对这个情况了,为了保证数据一致性,就需要用到分布式事务。 -​ **Seata** :是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。 -​ **本文目的**:现如今微服务越来越流行,而市面上的分布式事务的方案可谓不少,参差不齐,比较流行的以MQ代表的保证的是消息最终一致性的解决方案(消费确认,消息回查,消息补偿机制等),以及TX-LCN的LCN模式协调本地事务来保证事务统一提交或回滚(已经停止更新,对Dubbo2.7不兼容)。而MQ的分布式事务太过复杂,TX-LCN断更,这时候需要一个高效可靠及易上手的分布式事务解决方案,Seata脱颖而出,本文要介绍的就是如何快速搭建一个整合Seata的Demo项目,一起来吧! - -# 准备工作 - -1.首先安装mysql,eclipse之类常用的工具,这不展开了. - -2.访问seata下载中心[地址](http://seata.io/zh-cn/blog/download.html)我们使用的0.9.0版本 - -3.下载并解压seata-server - -## 建库建表 - -1.首先我们链接mysql创建一个名为seata的数据库,然后运行一下建表sql,这个在seata-server的conf文件夹内的db_store.sql就是我的所需要使用的sql了. - -```mysql -/* -Navicat MySQL Data Transfer -Source Server : mysql -Source Server Version : 50721 -Source Host : localhost:3306 -Source Database : seata -Target Server Type : MYSQL -Target Server Version : 50721 -File Encoding : 65001 -Date: 2019-11-23 22:03:18 -*/ - -SET FOREIGN_KEY_CHECKS=0; - --- ---------------------------- - --- Table structure for branch_table - --- ---------------------------- - -DROP TABLE IF EXISTS `branch_table`; -CREATE TABLE `branch_table` ( - `branch_id` bigint(20) NOT NULL, - `xid` varchar(128) NOT NULL, - `transaction_id` bigint(20) DEFAULT NULL, - `resource_group_id` varchar(32) DEFAULT NULL, - `resource_id` varchar(256) DEFAULT NULL, - `lock_key` varchar(128) DEFAULT NULL, - `branch_type` varchar(8) DEFAULT NULL, - `status` tinyint(4) DEFAULT NULL, - `client_id` varchar(64) DEFAULT NULL, - `application_data` varchar(2000) DEFAULT NULL, - `gmt_create` datetime DEFAULT NULL, - `gmt_modified` datetime DEFAULT NULL, - PRIMARY KEY (`branch_id`), - KEY `idx_xid` (`xid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ---------------------------- - --- Records of branch_table - --- ---------------------------- - --- ---------------------------- - --- Table structure for global_table - --- ---------------------------- - -DROP TABLE IF EXISTS `global_table`; -CREATE TABLE `global_table` ( - `xid` varchar(128) NOT NULL, - `transaction_id` bigint(20) DEFAULT NULL, - `status` tinyint(4) NOT NULL, - `application_id` varchar(32) DEFAULT NULL, - `transaction_service_group` varchar(32) DEFAULT NULL, - `transaction_name` varchar(128) DEFAULT NULL, - `timeout` int(11) DEFAULT NULL, - `begin_time` bigint(20) DEFAULT NULL, - `application_data` varchar(2000) DEFAULT NULL, - `gmt_create` datetime DEFAULT NULL, - `gmt_modified` datetime DEFAULT NULL, - PRIMARY KEY (`xid`), - KEY `idx_gmt_modified_status` (`gmt_modified`,`status`), - KEY `idx_transaction_id` (`transaction_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ---------------------------- - --- Records of global_table - --- ---------------------------- - --- ---------------------------- - --- Table structure for lock_table - --- ---------------------------- - -DROP TABLE IF EXISTS `lock_table`; -CREATE TABLE `lock_table` ( - `row_key` varchar(128) NOT NULL, - `xid` varchar(96) DEFAULT NULL, - `transaction_id` mediumtext, - `branch_id` mediumtext, - `resource_id` varchar(256) DEFAULT NULL, - `table_name` varchar(32) DEFAULT NULL, - `pk` varchar(36) DEFAULT NULL, - `gmt_create` datetime DEFAULT NULL, - `gmt_modified` datetime DEFAULT NULL, - PRIMARY KEY (`row_key`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- ---------------------------- - --- Records of lock_table - --- ---------------------------- - --- ---------------------------- - --- Table structure for undo_log - --- ---------------------------- - -DROP TABLE IF EXISTS `undo_log`; -CREATE TABLE `undo_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `branch_id` bigint(20) NOT NULL, - `xid` varchar(100) NOT NULL, - `context` varchar(128) NOT NULL, - `rollback_info` longblob NOT NULL, - `log_status` int(11) NOT NULL, - `log_created` datetime NOT NULL, - `log_modified` datetime NOT NULL, - `ext` varchar(100) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - --- ---------------------------- - --- Records of undo_log -``` - -2.运行完上面的seata所需要的数据库后,我们进行搭建我们所需要写的demo的库,创建一个名为test的数据库,然后执行以下sql代码: - -```mysql -/* -Navicat MySQL Data Transfer -Source Server : mysql -Source Server Version : 50721 -Source Host : localhost:3306 -Source Database : test -Target Server Type : MYSQL -Target Server Version : 50721 -File Encoding : 65001 -Date: 2019-11-23 22:03:24 -*/ - -SET FOREIGN_KEY_CHECKS=0; - --- ---------------------------- - --- Table structure for test - --- ---------------------------- - -DROP TABLE IF EXISTS `test`; -CREATE TABLE `test` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `one` varchar(255) DEFAULT NULL, - `two` varchar(255) DEFAULT NULL, - `createTime` datetime DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; - --- ---------------------------- - --- Records of test - --- ---------------------------- - -INSERT INTO `test` VALUES ('1', '1', '2', '2019-11-23 16:07:34'); - --- ---------------------------- - --- Table structure for undo_log - --- ---------------------------- - -DROP TABLE IF EXISTS `undo_log`; -CREATE TABLE `undo_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `branch_id` bigint(20) NOT NULL, - `xid` varchar(100) NOT NULL, - `context` varchar(128) NOT NULL, - `rollback_info` longblob NOT NULL, - `log_status` int(11) NOT NULL, - `log_created` datetime NOT NULL, - `log_modified` datetime NOT NULL, - `ext` varchar(100) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) -) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; - --- ---------------------------- - --- Records of undo_log -``` - - 3.我们找到seata-server/conf 文件夹内的file编辑它:![20191129132933](/img/blog/20191129132933.png) - - 4.再次找到其中的db配置方法块,更改方法如下图:![](/img/blog/20191129133111.png) - -好了,可以到bin目录去./seata-server.bat 运行看看了 - -# 创建项目 - -​ 首先我们使用的是eclipse,当然你也可以用idea之类的工具,详细请按下面步骤来运行 - -​ 1.创建一个新的maven项目,并删除多余文件夹:![20191129133354](/img/blog/20191129133354.png)20191129133441 - -​ 2.打开项目的pom.xml,加入以下依赖: - -```java - - 3.1 - UTF-8 - UTF-8 - 1.8 - 1.8 - 3.2.0 - 3.2.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.1.8.RELEASE - - - - org.apache.curator - curator-framework - 4.2.0 - - - org.apache.curator - curator-recipes - 4.2.0 - - - org.apache.dubbo - dubbo-spring-boot-starter - 2.7.4.1 - - - org.apache.commons - commons-lang3 - - - com.alibaba - fastjson - 1.2.60 - - - - io.springfox - springfox-swagger2 - 2.9.2 - - - io.springfox - springfox-swagger-ui - 2.9.2 - - - - - com.baomidou - mybatis-plus-boot-starter - ${mybatis-plus-boot-starter.version} - - - - - org.projectlombok - lombok - provided - - - io.seata - seata-all - 0.9.0.1 - - - - org.apache.zookeeper - zookeeper - 3.4.9 - - - org.slf4j - slf4j-log4j12 - - - - - - - - - org.freemarker - freemarker - - - - com.alibaba - druid-spring-boot-starter - 1.1.20 - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - - - org.springframework.boot - spring-boot-starter-log4j2 - - - - mysql - mysql-connector-java - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - - org.slf4j - slf4j-log4j12 - - - - - org.springframework.boot - spring-boot-starter-aop - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - -``` - -​ 3.再切换父项目为pom模式,还是pom文件,切换为 overview ,做如图操作:![20191129134127](/img/blog/20191129134127.png) - -4.创建我们的demo子项目,test-service:![20191129135935](/img/blog/20191129135935.png) - -​ 目录如下: - -20191129140048 - - 创建EmbeddedZooKeeper.java文件,跟 ProviderApplication.java,代码如下: - -```java -package org.test; - -import java.io.File; -import java.lang.reflect.Method; -import java.util.Properties; -import java.util.UUID; - -import org.apache.zookeeper.server.ServerConfig; -import org.apache.zookeeper.server.ZooKeeperServerMain; -import org.apache.zookeeper.server.quorum.QuorumPeerConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.SmartLifecycle; -import org.springframework.util.ErrorHandler; -import org.springframework.util.SocketUtils; - -/** - * from: - * https://github.com/spring-projects/spring-xd/blob/v1.3.1.RELEASE/spring-xd-dirt/src/main/java/org/springframework/xd/dirt/zookeeper/ZooKeeperUtils.java - * - * Helper class to start an embedded instance of standalone (non clustered) ZooKeeper. - * - * NOTE: at least an external standalone server (if not an ensemble) are recommended, even for - * {@link org.springframework.xd.dirt.server.singlenode.SingleNodeApplication} - * - * @author Patrick Peralta - * @author Mark Fisher - * @author David Turanski - */ -public class EmbeddedZooKeeper implements SmartLifecycle { - - /** - * Logger. - */ - private static final Logger logger = LoggerFactory.getLogger(EmbeddedZooKeeper.class); - - /** - * ZooKeeper client port. This will be determined dynamically upon startup. - */ - private final int clientPort; - - /** - * Whether to auto-start. Default is true. - */ - private boolean autoStartup = true; - - /** - * Lifecycle phase. Default is 0. - */ - private int phase = 0; - - /** - * Thread for running the ZooKeeper server. - */ - private volatile Thread zkServerThread; - - /** - * ZooKeeper server. - */ - private volatile ZooKeeperServerMain zkServer; - - /** - * {@link ErrorHandler} to be invoked if an Exception is thrown from the ZooKeeper server thread. - */ - private ErrorHandler errorHandler; - - private boolean daemon = true; - - /** - * Construct an EmbeddedZooKeeper with a random port. - */ - public EmbeddedZooKeeper() { - clientPort = SocketUtils.findAvailableTcpPort(); - } - - /** - * Construct an EmbeddedZooKeeper with the provided port. - * - * @param clientPort - * port for ZooKeeper server to bind to - */ - public EmbeddedZooKeeper(int clientPort, boolean daemon) { - this.clientPort = clientPort; - this.daemon = daemon; - } - - /** - * Returns the port that clients should use to connect to this embedded server. - * - * @return dynamically determined client port - */ - public int getClientPort() { - return this.clientPort; - } - - /** - * Specify whether to start automatically. Default is true. - * - * @param autoStartup - * whether to start automatically - */ - public void setAutoStartup(boolean autoStartup) { - this.autoStartup = autoStartup; - } - - /** - * {@inheritDoc} - */ - public boolean isAutoStartup() { - return this.autoStartup; - } - - /** - * Specify the lifecycle phase for the embedded server. - * - * @param phase - * the lifecycle phase - */ - public void setPhase(int phase) { - this.phase = phase; - } - - /** - * {@inheritDoc} - */ - public int getPhase() { - return this.phase; - } - - /** - * {@inheritDoc} - */ - public boolean isRunning() { - return (zkServerThread != null); - } - - /** - * Start the ZooKeeper server in a background thread. - *

- * Register an error handler via {@link #setErrorHandler} in order to handle any exceptions thrown during startup or - * execution. - */ - public synchronized void start() { - if (zkServerThread == null) { - zkServerThread = new Thread(new ServerRunnable(), "ZooKeeper Server Starter"); - zkServerThread.setDaemon(daemon); - zkServerThread.start(); - } - } - - /** - * Shutdown the ZooKeeper server. - */ - public synchronized void stop() { - if (zkServerThread != null) { - // The shutdown method is protected...thus this hack to invoke it. - // This will log an exception on shutdown; see - // https://issues.apache.org/jira/browse/ZOOKEEPER-1873 for details. - try { - Method shutdown = ZooKeeperServerMain.class.getDeclaredMethod("shutdown"); - shutdown.setAccessible(true); - shutdown.invoke(zkServer); - } - - catch (Exception e) { - throw new RuntimeException(e); - } - - // It is expected that the thread will exit after - // the server is shutdown; this will block until - // the shutdown is complete. - try { - zkServerThread.join(5000); - zkServerThread = null; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - logger.warn("Interrupted while waiting for embedded ZooKeeper to exit"); - // abandoning zk thread - zkServerThread = null; - } - } - } - - /** - * Stop the server if running and invoke the callback when complete. - */ - public void stop(Runnable callback) { - stop(); - callback.run(); - } - - /** - * Provide an {@link ErrorHandler} to be invoked if an Exception is thrown from the ZooKeeper server thread. If none - * is provided, only error-level logging will occur. - * - * @param errorHandler - * the {@link ErrorHandler} to be invoked - */ - public void setErrorHandler(ErrorHandler errorHandler) { - this.errorHandler = errorHandler; - } - - /** - * Runnable implementation that starts the ZooKeeper server. - */ - private class ServerRunnable implements Runnable { - - public void run() { - try { - Properties properties = new Properties(); - File file = new File(System.getProperty("java.io.tmpdir") + File.separator + UUID.randomUUID()); - file.deleteOnExit(); - properties.setProperty("dataDir", file.getAbsolutePath()); - properties.setProperty("clientPort", String.valueOf(clientPort)); - - QuorumPeerConfig quorumPeerConfig = new QuorumPeerConfig(); - quorumPeerConfig.parseProperties(properties); - - zkServer = new ZooKeeperServerMain(); - ServerConfig configuration = new ServerConfig(); - configuration.readFrom(quorumPeerConfig); - - zkServer.runFromConfig(configuration); - } catch (Exception e) { - if (errorHandler != null) { - errorHandler.handleError(e); - } else { - logger.error("Exception running embedded ZooKeeper", e); - } - } - } - } - -} - -``` - -```java -package org.test; - -import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -/** - * - * @author cjb - * @date 2019/10/24 - */ -@EnableTransactionManagement -@ComponentScan(basePackages = {"org.test.config", "org.test.service.impl"}) -@DubboComponentScan(basePackages = "org.test.service.impl") -@SpringBootApplication -public class ProviderApplication { - - public static void main(String[] args) { - new EmbeddedZooKeeper(2181, false).start(); - SpringApplication app = new SpringApplication(ProviderApplication.class); - app.run(args); - } - -} - -``` - - 创建实体包 org.test.entity以及创建实体类Test 用到了lombok,详细百度一下,eclipse装lombok插件 - -```java -package org.test.entity; - -import java.io.Serializable; -import java.time.LocalDateTime; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.experimental.Accessors; - -/** - *

- * 功能 - *

- * - * @author Funkye - * @since 2019-04-23 - */ -@Data -@EqualsAndHashCode(callSuper = false) -@Accessors(chain = true) -@ApiModel(value = "test对象", description = "功能") -public class Test implements Serializable { - - private static final long serialVersionUID = 1L; - - @ApiModelProperty(value = "主键") - @TableId(value = "id", type = IdType.AUTO) - private Integer id; - - @ApiModelProperty(value = "one") - @TableField("one") - private String one; - - @ApiModelProperty(value = "two") - @TableField("two") - private String two; - - @ApiModelProperty(value = "createTime") - @TableField("createTime") - private LocalDateTime createTime; - -} - -``` - -​ 创建service,service.impl,mapper等包,依次创建ITestservice,以及实现类,mapper - -```java -package org.test.service; - -import org.test.entity.Test; - -import com.baomidou.mybatisplus.extension.service.IService; - -/** - *

- * 功能 服务类 - *

- * - * @author Funkye - * @since 2019-04-10 - */ -public interface ITestService extends IService { - -} - -``` - -```java -package org.test.service.impl; - - - - -import org.apache.dubbo.config.annotation.Service; -import org.test.entity.Test; -import org.test.mapper.TestMapper; -import org.test.service.ITestService; - -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; - -@Service(version = "1.0.0",interfaceClass =ITestService.class ) -public class TestServiceImpl extends ServiceImpl implements ITestService { - -} - -``` - -```java -package org.test.mapper; - -import org.test.entity.Test; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -/** - *

- * 功能 Mapper 接口 - *

- * - * @author Funkye - * @since 2019-04-10 - */ -public interface TestMapper extends BaseMapper { - -} - -``` - - 创建org.test.config包,创建SeataAutoConfig.java,配置信息都在此处,主要作用为代理数据,连接事务服务分组 - -```java -package org.test.config; - -import javax.sql.DataSource; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; - -import com.alibaba.druid.pool.DruidDataSource; - -import io.seata.rm.datasource.DataSourceProxy; -import io.seata.spring.annotation.GlobalTransactionScanner; - -@Configuration -public class SeataAutoConfig { - @Autowired(required = true) - private DataSourceProperties dataSourceProperties; - private final static Logger logger = LoggerFactory.getLogger(SeataAutoConfig.class); - - @Bean(name = "druidDataSource") // 声明其为Bean实例 - public DataSource druidDataSource() { - DruidDataSource druidDataSource = new DruidDataSource(); - logger.info("dataSourceProperties.getUrl():{}", dataSourceProperties.getUrl()); - druidDataSource.setUrl(dataSourceProperties.getUrl()); - druidDataSource.setUsername(dataSourceProperties.getUsername()); - druidDataSource.setPassword(dataSourceProperties.getPassword()); - druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName()); - druidDataSource.setInitialSize(0); - druidDataSource.setMaxActive(180); - druidDataSource.setMaxWait(60000); - druidDataSource.setMinIdle(0); - druidDataSource.setValidationQuery("Select 1 from DUAL"); - druidDataSource.setTestOnBorrow(false); - druidDataSource.setTestOnReturn(false); - druidDataSource.setTestWhileIdle(true); - druidDataSource.setTimeBetweenEvictionRunsMillis(60000); - druidDataSource.setMinEvictableIdleTimeMillis(25200000); - druidDataSource.setRemoveAbandoned(true); - druidDataSource.setRemoveAbandonedTimeout(1800); - druidDataSource.setLogAbandoned(true); - logger.info("装载dataSource........"); - return druidDataSource; - } - - /** - * init datasource proxy - * - * @Param: druidDataSource datasource bean instance - * @Return: DataSourceProxy datasource proxy - */ - @Bean(name = "dataSource") - @Primary // 在同样的DataSource中,首先使用被标注的DataSource - public DataSourceProxy dataSourceProxy(@Qualifier(value = "druidDataSource") DruidDataSource druidDataSource) { - logger.info("代理dataSource........"); - return new DataSourceProxy(druidDataSource); - } - - /** - * init global transaction scanner - * - * @Return: GlobalTransactionScanner - */ - @Bean - public GlobalTransactionScanner globalTransactionScanner() { - logger.info("配置seata........"); - return new GlobalTransactionScanner("test-service", "test-group"); - } -} -``` - - 再创建mybatisplus所需的配置文件MybatisPlusConfig - -```java -package org.test.config; - -import java.util.ArrayList; -import java.util.List; - -import org.mybatis.spring.mapper.MapperScannerConfigurer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import com.baomidou.mybatisplus.core.parser.ISqlParser; -import com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser; -import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; - -@Configuration -// @MapperScan("com.baomidou.springboot.mapper*")//这个注解,作用相当于下面的@Bean -// MapperScannerConfigurer,2者配置1份即可 -public class MybatisPlusConfig { - - /** - * mybatis-plus分页插件
- * 文档:http://mp.baomidou.com
- */ - @Bean - public PaginationInterceptor paginationInterceptor() { - PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); - List sqlParserList = new ArrayList(); - // 攻击 SQL 阻断解析器、加入解析链 - sqlParserList.add(new BlockAttackSqlParser()); - paginationInterceptor.setSqlParserList(sqlParserList); - return paginationInterceptor; - } - - /** - * 相当于顶部的: {@code @MapperScan("com.baomidou.springboot.mapper*")} 这里可以扩展,比如使用配置文件来配置扫描Mapper的路径 - */ - - @Bean - public MapperScannerConfigurer mapperScannerConfigurer() { - MapperScannerConfigurer scannerConfigurer = new MapperScannerConfigurer(); - scannerConfigurer.setBasePackage("org.test.mapper"); - return scannerConfigurer; - } - -} - -``` - -​ 再创建**resources目录,创建mapper文件夹,application.yml等文件** - -```yaml -server: - port: 38888 -spring: - application: - name: test-service - datasource: - type: com.alibaba.druid.pool.DruidDataSource - url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC - driver-class-name: com.mysql.cj.jdbc.Driver - username: root - password: 123456 -dubbo: - protocol: - loadbalance: leastactive - threadpool: cached - scan: - base-packages: org。test.service - application: - qos-enable: false - name: testserver - registry: - id: my-registry - address: zookeeper://127.0.0.1:2181?client=curator -mybatis-plus: - mapper-locations: classpath:/mapper/*Mapper.xml - typeAliasesPackage: org.test.entity - global-config: - db-config: - field-strategy: not-empty - id-type: auto - db-type: mysql - configuration: - map-underscore-to-camel-case: true - cache-enabled: true - auto-mapping-unknown-column-behavior: none - -``` - -​ 创建file.conf,此处的service 内的vgroup_mapping.你的事务分组,比如上**面SeataAutoConfig内配置了test-group,那么这里也要改为test-group**,然后下面ip端口都是seata运行的ip跟端口就行了 - -```java -transport { - type = "TCP" - server = "NIO" - heartbeat = true - thread-factory { - boss-thread-prefix = "NettyBoss" - worker-thread-prefix = "NettyServerNIOWorker" - server-executor-thread-prefix = "NettyServerBizHandler" - share-boss-worker = false - client-selector-thread-prefix = "NettyClientSelector" - client-selector-thread-size = 1 - client-worker-thread-prefix = "NettyClientWorkerThread" - boss-thread-size = 1 - worker-thread-size = 8 - } - shutdown { - wait = 3 - } - serialization = "seata" - compressor = "none" -} -service { - vgroup_mapping.test-group = "default" - default.grouplist = "127.0.0.1:8091" - enableDegrade = false - disable = false - max.commit.retry.timeout = "-1" - max.rollback.retry.timeout = "-1" -} - -client { - async.commit.buffer.limit = 10000 - lock { - retry.internal = 10 - retry.times = 30 - } - report.retry.count = 5 - tm.commit.retry.count = 1 - tm.rollback.retry.count = 1 - undo.log.table = "undo_log" -} - -recovery { - committing-retry-period = 1000 - asyn-committing-retry-period = 1000 - rollbacking-retry-period = 1000 - timeout-retry-period = 1000 -} - -transaction { - undo.data.validation = true - undo.log.serialization = "jackson" - undo.log.save.days = 7 - undo.log.delete.period = 86400000 - undo.log.table = "undo_log" -} - -metrics { - enabled = false - registry-type = "compact" - exporter-list = "prometheus" - exporter-prometheus-port = 9898 -} - -support { - spring { - datasource.autoproxy = false - } -} - -``` - - 创建registry.conf,来指定file,zk的ip端口之类的配置 - -```java -registry { - type = "file" - file { - name = "file.conf" - } -} -config { - type = "file" - file { - name = "file.conf" - } - zk { - serverAddr = "127.0.0.1:2181" - session.timeout = 6000 - connect.timeout = 2000 - } -} - -``` - -​ 大功告成,可以直接运行啦,这时候观察seata-server![20191129142115](/img/blog/20191129142115.png) - -​ 接下来我们创建test-client项目项目,这里就不赘述了,跟上面的test-service一样的创建方式 - -​ 接下来我们复制test-service内的service跟实体过去,当然你嫌麻烦,可以单独搞个子项目放通用的service跟实体,一些工具类等等,我这边为了快速搭建这个demo,就选择复制黏贴的方式了. - -目录结构:![](/img/blog/20191129142349.png) - - 然后我们创建ClientApplication: - -```java -package org.test; - -import java.util.TimeZone; -import java.util.concurrent.Executor; - -import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; - -import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; - -@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, MybatisPlusAutoConfiguration.class}) -@EnableScheduling -@EnableAsync -@Configuration -@EnableDubbo(scanBasePackages = {"org.test.service"}) -@ComponentScan(basePackages = {"org.test.service", "org.test.controller", "org.test.config"}) -public class ClientApplication { - public static void main(String[] args) { - TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); - SpringApplication app = new SpringApplication(ClientApplication.class); - app.run(args); - } - - @Bean(name = "threadPoolTaskExecutor") - public Executor threadPoolTaskExecutor() { - return new ThreadPoolTaskExecutor(); - } -} - -``` - - 再到config包内创建SwaggerConfig : - -```java -package org.test.config; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import springfox.documentation.builders.ApiInfoBuilder; -import springfox.documentation.builders.PathSelectors; -import springfox.documentation.builders.RequestHandlerSelectors; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.service.Contact; -import springfox.documentation.service.Parameter; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; - -@Configuration -@EnableSwagger2 -public class SwaggerConfig { - // swagger2的配置文件,这里可以配置swagger2的一些基本的内容,比如扫描的包等等 - @Bean - public Docket createRestApi() { - List pars = new ArrayList(); - return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select() - // 为当前包路径 - .apis(RequestHandlerSelectors.basePackage("org.test.controller")).paths(PathSelectors.any()).build() - .globalOperationParameters(pars); - } - - // 构建 api文档的详细信息函数,注意这里的注解引用的是哪个 - private ApiInfo apiInfo() { - return new ApiInfoBuilder() - // 页面标题 - .title("项目接口") - // 创建人 - .contact(new Contact("FUNKYE", "", "")) - // 版本号 - .version("1.0") - // 描述 - .description("API 描述").build(); - } -} - -``` - -​ 再创建SpringMvcConfigure,再里面放入seata的配置,我为了偷懒直接集成在mvc配置的类里了,大家规范点可以另外创建个配置seata的类,大家可以发现下面还是有个组名称,我把两个项目都分配到一个组去,貌似另外取一个也没事的. - -```java -package org.test.config; - -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.apache.dubbo.config.annotation.Reference; -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.StringHttpMessageConverter; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.filter.CorsFilter; -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.web.servlet.view.InternalResourceViewResolver; - -import com.alibaba.fastjson.serializer.SerializerFeature; -import com.alibaba.fastjson.support.config.FastJsonConfig; -import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; -import com.google.common.collect.Maps; - -import io.seata.spring.annotation.GlobalTransactionScanner; - -@Configuration -public class SpringMvcConfigure implements WebMvcConfigurer { - - @Bean - public FilterRegistrationBean corsFilter() { - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - CorsConfiguration config = new CorsConfiguration(); - config.setAllowCredentials(true); - config.addAllowedOrigin("*"); - config.addAllowedHeader(CorsConfiguration.ALL); - config.addAllowedMethod(CorsConfiguration.ALL); - source.registerCorsConfiguration("/**", config); - FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new CorsFilter(source)); - filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); - filterRegistrationBean.setOrder(1); - filterRegistrationBean.setEnabled(true); - filterRegistrationBean.addUrlPatterns("/**"); - Map initParameters = Maps.newHashMap(); - initParameters.put("excludes", "/favicon.ico,/img/*,/js/*,/css/*"); - initParameters.put("isIncludeRichText", "true"); - filterRegistrationBean.setInitParameters(initParameters); - return filterRegistrationBean; - } - - @Bean - public InternalResourceViewResolver viewResolver() { - InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); - viewResolver.setPrefix("/WEB-INF/jsp/"); - viewResolver.setSuffix(".jsp"); - // viewResolver.setViewClass(JstlView.class); - // 这个属性通常并不需要手动配置,高版本的Spring会自动检测 - return viewResolver; - } - - - - /** - * 替换框架json为fastjson - */ - @Override - public void configureMessageConverters(List> converters) { - FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); - FastJsonConfig fastJsonConfig = new FastJsonConfig(); - fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue, - SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.DisableCircularReferenceDetect); - // 处理中文乱码问题 - List fastMediaTypes = new ArrayList<>(); - fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); - fastConverter.setSupportedMediaTypes(fastMediaTypes); - fastConverter.setFastJsonConfig(fastJsonConfig); - // 处理字符串, 避免直接返回字符串的时候被添加了引号 - StringHttpMessageConverter smc = new StringHttpMessageConverter(Charset.forName("UTF-8")); - converters.add(smc); - converters.add(fastConverter); - } - - @Bean - public GlobalTransactionScanner globalTransactionScanner() { - return new GlobalTransactionScanner("test-client", "test-group"); - } - -} - -``` - - 再创建c**ontroller包,再包下创建TestController** : - -```java -package org.test.controller; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.test.service.DemoService; - -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; - -/** - *

- * 文件表 前端控制器 - *

- * - * @author funkye - * @since 2019-03-20 - */ -@RestController -@RequestMapping("/test") -@Api(tags = "测试接口") -public class TestController { - - private final static Logger logger = LoggerFactory.getLogger(TestController.class); - @Autowired - @Lazy - DemoService demoService; - - @GetMapping(value = "testSeataOne") - @ApiOperation(value = "测试手动回滚分布式事务接口") - public Object testSeataOne() { - return demoService.One(); - } - - @GetMapping(value = "testSeataTwo") - @ApiOperation(value = "测试异常回滚分布式事务接口") - public Object testSeataTwo() { - return demoService.Two(); - } - -} - -``` - - 再到service去创建需要依赖的DemoService - -```java -package org.test.service; - -import java.time.LocalDateTime; - -import org.apache.dubbo.config.annotation.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; -import org.test.controller.TestController; -import org.test.entity.Test; - -import io.seata.core.context.RootContext; -import io.seata.core.exception.TransactionException; -import io.seata.spring.annotation.GlobalTransactional; -import io.seata.tm.api.GlobalTransactionContext; - -@Service -public class DemoService { - @Reference(version = "1.0.0", timeout = 60000) - private ITestService testService; - private final static Logger logger = LoggerFactory.getLogger(DemoService.class); - - /** - * 手动回滚示例 - * - * @return - */ - @GlobalTransactional - public Object One() { - logger.info("seata分布式事务Id:{}", RootContext.getXID()); - Test t = new Test(); - t.setOne("1"); - t.setTwo("2"); - t.setCreateTime(LocalDateTime.now()); - testService.save(t); - try { - int i = 1 / 0; - return true; - } catch (Exception e) { - // TODO: handle exception - try { - logger.info("载入事务id进行回滚"); - GlobalTransactionContext.reload(RootContext.getXID()).rollback(); - } catch (TransactionException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - } - return false; - } - - /** - * 抛出异常进行回滚示例 - * - * @return - */ - @GlobalTransactional - public Object Two() { - logger.info("seata分布式事务Id:{}", RootContext.getXID()); - Test t = new Test(); - t.setOne("1"); - t.setTwo("2"); - t.setCreateTime(LocalDateTime.now()); - testService.save(t); - try { - int i = 1 / 0; - return true; - } catch (Exception e) { - // TODO: handle exception - throw new RuntimeException(); - } - } -} - -``` - - 一样创建resources文件夹,先创建常用的**application.yml** - -```java -spring: - application: - name: test - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://127.0.0.1:3306/test?userSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai - username: root - password: 123456 - mvc: - servlet: - load-on-startup: 1 - http: - encoding: - force: true - charset: utf-8 - enabled: true - multipart: - max-file-size: 10MB - max-request-size: 10MB -dubbo: - registry: - id: my-registry - address: zookeeper://127.0.0.1:2181?client=curator -# address: zookeeper://127.0.0.1:2181?client=curator - application: - name: dubbo-demo-client - qos-enable: false -server: - port: 28888 - max-http-header-size: 8192 - address: 0.0.0.0 - tomcat: - max-http-post-size: 104857600 - -``` - - 再把之前service配置好的file跟registry文件复制来,如果你的client组名称再配置类里修改了,那么这里的file文件内的组名称一样需要更改. - -![](/img/blog/20191129142851.png) - - 完整的目录结构如上,这时候可以启动test-service后,再启动test-client,到swagger里测试咯 - -​ 4.访问127.0.0.1:28888/swagger-ui.html做最后的收尾 ![](/img/blog/20191129143041.png) - -![20191129143124](/img/blog/20191129143124.png) - - 这里数据我已经存了一条记录了,我们看看会不会成功回滚: - -![20191129143252](/img/blog/20191129143252.png) - - 刷新数据库,发现还是只有一条数据: - -![20191129143124](/img/blog/20191129143124.png) - - 再查看日志: - -![20191129143407](/img/blog/20191129143407.png) - - 显示已经回滚,我们再看看seata-server的日志: - - - - 显示回滚成功,事务id也是一致的,这下我们的分布式事务就跑通咯,通过打断点方式,大家可以查看undo_log,会发现再事务提交前,会存入一条事务信息的数据,如果回滚成功,该信息就会被删除. - -# 总结 - -seata的整合还是比较简单易入手,稍微用心一些你肯定写的比我更好! - -欢迎大家也多去阅读seata,dubbo之类的源代码,能解决业务中遇到的大量的坑哦! \ No newline at end of file +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/tcc-mode-applicable-scenario-analysis.md b/blog/tcc-mode-applicable-scenario-analysis.md index 3f4bf6b859..e872b67af1 100644 --- a/blog/tcc-mode-applicable-scenario-analysis.md +++ b/blog/tcc-mode-applicable-scenario-analysis.md @@ -1,150 +1 @@ ---- -title: TCC适用模型与适用场景分析 -author: zhangthen -date: 2019/03/27 -keywords: [seata、分布式事务、TCC、roadmap] ---- - -# TCC 适用模型与适用场景分析 - -Fescar 0.4.0 版本发布了 TCC 模式,由蚂蚁金服团队贡献,欢迎大家试用,文末也提供了项目后续的 Roadmap,欢迎关注。 - - -## 前言:基于 TCC 模型的应用场景 - 
-![1.png](/img/blog/TCC1.png)
- -TCC 分布式事务模型直接作用于服务层。不与具体的服务框架耦合,与底层 RPC 协议无关,与底层存储介质无关,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。 - - - - -## 一、TCC 模型优势 - -对于 TCC 分布式事务模型,笔者认为其在业务场景应用上,有两方面的意义。 - - - -### 1.1 跨服务的分布式事务 - -服务的拆分,也可以认为是资源的横向扩展,只不过方向不同而已。 - -横向扩展可能沿着两个方向发展: - -1. 功能扩展,根据功能对数据进行分组,并将不同的功能组分布在多个不同的数据库上,这实际上就是 SOA 架构下的服务化。 -1. 数据分片,在功能组内部将数据拆分到多个数据库上,为横向扩展增加一个新的维度。 - -下图简要阐释了横向数据扩展策略: - -![2.png](/img/blog/TCC2.png) - -因此,TCC 的其中一个作用就是在按照功能横向扩展资源时,保证多资源访问的事务属性。 - - - -### 1.2 两阶段拆分 - -TCC 另一个作用就是把两阶段拆分成了两个独立的阶段,通过资源业务锁定的方式进行关联。资源业务锁定方式的好处在于,既不会阻塞其他事务在第一阶段对于相同资源的继续使用,也不会影响本事务第二阶段的正确执行。 - -**传统模型的并发事务:**
-![3.png](/img/blog/TCC3.png) - -**TCC 模型的并发事务:**
-![4.png](/img/blog/TCC4.png) - -这对业务有什么好处呢?拿支付宝的担保交易场景来说,简化情况下,只需要涉及两个服务,交易服务和账务服务。交易作为主业务服务,账务作为从业务服务,提供 Try、Commit、Cancel 接口: - -1. Try 接口扣除用户可用资金,转移到预冻结资金。预冻结资金就是业务锁定方案,每个事务第二阶段只能使用本事务的预冻结资金,在第一阶段执行结束后,其他并发事务也可以继续处理用户的可用资金。 -1. Commit 接口扣除预冻结资金,增加中间账户可用资金(担保交易不能立即把钱打给商户,需要有一个中间账户来暂存)。 - -假设只有一个中间账户的情况下,每次调用支付服务的 Commit 接口,都会锁定中间账户,中间账户存在热点性能问题。 但是,在担保交易场景中,七天以后才需要将资金从中间账户划拨给商户,中间账户并不需要对外展示。因此,在执行完支付服务的第一阶段后,就可以认为本次交易的支付环节已经完成,并向用户和商户返回支付成功的结果,并不需要马上执行支付服务二阶段的 Commit 接口,等到低锋期时,再慢慢消化,异步地执行。
-![5.png](/img/blog/TCC5.png) - -这就是 TCC 分布式事务模型的二阶段异步化功能,从业务服务的第一阶段执行成功,主业务服务就可以提交完成,然后再由框架异步的执行各从业务服务的第二阶段。 - - - -## 二、通用型 TCC 解决方案 - -通用型 TCC 解决方案就是最典型的 TCC 分布式事务模型实现,所有从业务服务都需要参与到主业务服务的决策当中。
-![6.png](/img/blog/TCC6.png)
  - - -### 适用场景 - -由于从业务服务是同步调用,其结果会影响到主业务服务的决策,因此通用型 TCC 分布式事务解决方案适用于执行时间确定且较短的业务,比如互联网金融企业最核心的三个服务:交易、支付、账务:
-![7.png](/img/blog/TCC7.png)
 
当用户发起一笔交易时,首先访问交易服务,创建交易订单;然后交易服务调用支付服务为该交易创建支付订单,执行收款动作,最后支付服务调用账务服务记录账户流水和记账。 - -为了保证三个服务一起完成一笔交易,要么同时成功,要么同时失败,可以使用通用型 TCC 解决方案,将这三个服务放在一个分布式事务中,交易作为主业务服务,支付作为从业务服务,账务作为支付服务的嵌套从业务服务,由 TCC 模型保证事务的原子性。
-![8.png](/img/blog/TCC8.png) - -支付服务的 Try 接口创建支付订单,开启嵌套分布式事务,并调用账务服务的 Try 接口;账务服务在 Try 接口中冻结买家资金。一阶段调用完成后,交易完成,提交本地事务,由 TCC 框架完成分布式事务各从业务服务二阶段的调用。 - -支付服务二阶段先调用账务服务的 Confirm 接口,扣除买家冻结资金;增加卖家可用资金。调用成功后,支付服务修改支付订单为完成状态,完成支付。 - -当支付和账务服务二阶段都调用完成后,整个分布式事务结束。 - - - -## 三、异步确保型 TCC 解决方案 - -异步确保型 TCC 解决方案的直接从业务服务是可靠消息服务,而真正的从业务服务则通过消息服务解耦,作为消息服务的消费端,异步地执行。
-![9.png](/img/blog/TCC9.png)
 
可靠消息服务需要提供 Try,Confirm,Cancel 三个接口。Try 接口预发送,只负责持久化存储消息数据;Confirm 接口确认发送,这时才开始真正的投递消息;Cancel 接口取消发送,删除消息数据。 - -消息服务的消息数据独立存储,独立伸缩,降低从业务服务与消息系统间的耦合,在消息服务可靠的前提下,实现分布式事务的最终一致性。 - -此解决方案虽然增加了消息服务的维护成本,但由于消息服务代替从业务服务实现了 TCC 接口,从业务服务不需要任何改造,接入成本非常低。 - - - -### 适用场景 - -由于从业务服务消费消息是一个异步的过程,执行时间不确定,可能会导致不一致时间窗口增加。因此,异步确保性 TCC 分布式事务解决方案只适用于对最终一致性时间敏感度较低的一些被动型业务(从业务服务的处理结果不影响主业务服务的决策,只被动的接收主业务服务的决策结果)。比如会员注册服务和邮件发送服务:
-![10.png](/img/blog/TCC10.png)
 
当用户注册会员成功,需要给用户发送一封邮件,告诉用户注册成功,并提示用户激活该会员。但要注意两点: - -1. 如果用户注册成功,一定要给用户发送一封邮件; -1. 如果用户注册失败,一定不能给用户发送邮件。 - -因此,这同样需要会员服务和邮件服务保证原子性,要么都执行,要么都不执行。不一样的是,邮件服务只是一种被动型的业务,并不影响用户是否能够注册成功,它只需要在用户注册成功以后发送邮件给用户即可,邮件服务不需要参与到会员服务的活动决策中。 - -对于此种业务场景,可以使用异步确保型TCC分布式事务解决方案,如下:
-![11.png](/img/blog/TCC11.png)
 
 
由可靠消息服务来解耦会员和邮件服务,会员服务与消息服务组成 TCC 事务模型,保证事务原子性。然后通过消息服务的可靠特性,确保消息一定能够被邮件服务消费,从而使得会员与邮件服务在同一个分布式事务中。同时,邮件服务也不会影响会员服务的执行过程,只在会员服务执行成功后被动接收发送邮件的请求。 - - - -## 四、补偿型 TCC 解决方案 - -补偿型 TCC 解决方案与通用型 TCC 解决方案的结构相似,其从业务服务也需要参与到主业务服务的活动决策当中。但不一样的是,前者的从业务服务只需要提供 Do 和 Compensate 两个接口,而后者需要提供三个接口。
-![12.png](/img/blog/TCC12.png)
 
Do 接口直接执行真正的完整业务逻辑,完成业务处理,业务执行结果外部可见;Compensate 操作用于业务补偿,抵消或部分抵消正向业务操作的业务结果,Compensate操作需满足幂等性。
与通用型解决方案相比,补偿型解决方案的从业务服务不需要改造原有业务逻辑,只需要额外增加一个补偿回滚逻辑即可,业务改造量较小。但要注意的是,业务在一阶段就执行完整个业务逻辑,无法做到有效的事务隔离,当需要回滚时,可能存在补偿失败的情况,还需要额外的异常处理机制,比如人工介入。 - - - -### 适用场景 - -由于存在回滚补偿失败的情况,补偿型 TCC 分布式事务解决方案只适用于一些并发冲突较少或者需要与外部交互的业务,这些外部业务不属于被动型业务,其执行结果会影响主业务服务的决策,比如机票代理商的机票预订服务:
-![13.png](/img/blog/TCC13.png)
 
该机票服务提供多程机票预订服务,可以同时预订多趟行程航班机票,比如从北京到圣彼得堡,需要第一程从北京到莫斯科,以及第二程从莫斯科到圣彼得堡。 - -当用户预订机票时,肯定希望能同时预订这两趟航班的机票,只预订一趟航班对用户来说没有意义。因此,对于这样的业务服务同样提出了原子性要求,如果其中一趟航班的机票预订失败,另外一趟需要能够取消预订。 - -但是,由于航空公司相对于机票代理商来说属于外部业务,只提供订票接口和取消预订接口,想要推动航空公司改造是极其困难的。因此,对于此类业务服务,可以使用补偿型 TCC 分布式事务解决方案,如下:
-![14.png](/img/blog/TCC14.png) - -网关服务在原有逻辑基础上增加 Compensate 接口,负责调用对应航空公司的取消预订接口。 - -在用户发起机票预订请求时,机票服务先通过网关 Do 接口,调用各航空公司的预订接口,如果所有航班都预订成功,则整个分布式事务直接执行成功;一旦某趟航班机票预订失败,则分布式事务回滚,由 TCC 事务框架调用各网关的 Compensate 补偿接口,其再调用对应航空公司的取消预订接口。通过这种方式,也可以保证多程机票预订服务的原子性。 - - - -## 五. 总结 - -对于现在的互联网应用来说,资源横向扩展提供了更多的灵活性,是一种比较容易实现的向外扩展方案,但是同时也明显增加了复杂度,引入一些新的挑战,比如资源之间的数据一致性问题。 - -横向数据扩展既可以按数据分片扩展,也可以按功能扩展。TCC 模型能在功能横向扩展资源的同时,保证多资源访问的事务属性。 - -TCC 模型除了跨服务的分布式事务这一层作用之外,还具有两阶段划分的功能,通过业务资源锁定,允许第二阶段的异步执行,而异步化思想正是解决热点数据并发性能问题的利器之一。
  - - -## Roadmap - -当前已经发布到 0.4.0,后续我们会发布 0.5 ~ 1.0 版本,继续对 AT、TCC 模式进行功能完善和和丰富,并解决服务端高可用问题,在 1.0 版本之后,本开源产品将达到生产环境使用的标准。


![图片1.png](/img/blog/roadmap.png)
- +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/blog/tcc-mode-design-principle.md b/blog/tcc-mode-design-principle.md index 127d9bca75..e872b67af1 100644 --- a/blog/tcc-mode-design-principle.md +++ b/blog/tcc-mode-design-principle.md @@ -1,105 +1 @@ ---- -title: TCC 理论及设计实现指南介绍 -author: zhangthen -date: 2019/03/26 -keywords: [fescar、分布式事务、TCC、roadmap] ---- - -# TCC 理论及设计实现指南介绍 - -Fescar 0.4.0 版本发布了 TCC 模式,由蚂蚁金服团队贡献,欢迎大家试用,
Sample 地址:[https://github.com/fescar-group/fescar-samples/tree/master/tcc](https://github.com/fescar-group/fescar-samples/tree/master/tcc),
文末也提供了项目后续的 Roadmap,欢迎关注。 - - - -### 一、TCC 简介 - -在两阶段提交协议(2PC,Two Phase Commitment Protocol)中,资源管理器(RM, resource manager)需要提供“准备”、“提交”和“回滚” 3 个操作;而事务管理器(TM, transaction manager)分 2 阶段协调所有资源管理器,在第一阶段询问所有资源管理器“准备”是否成功,如果所有资源均“准备”成功则在第二阶段执行所有资源的“提交”操作,否则在第二阶段执行所有资源的“回滚”操作,保证所有资源的最终状态是一致的,要么全部提交要么全部回滚。 - -资源管理器有很多实现方式,其中 TCC(Try-Confirm-Cancel)是资源管理器的一种服务化的实现;TCC 是一种比较成熟的分布式事务解决方案,可用于解决跨数据库、跨服务业务操作的数据一致性问题;TCC 其 Try、Confirm、Cancel 3 个方法均由业务编码实现,故 TCC 可以被称为是服务化的资源管理器。 - -TCC 的 Try 操作作为一阶段,负责资源的检查和预留;Confirm 操作作为二阶段提交操作,执行真正的业务;Cancel 是二阶段回滚操作,执行预留资源的取消,使资源回到初始状态。 - -如下图所示,用户实现 TCC 服务之后,该 TCC 服务将作为分布式事务的其中一个资源,参与到整个分布式事务中;事务管理器分 2 阶段协调 TCC 服务,在第一阶段调用所有 TCC 服务的 Try 方法,在第二阶段执行所有 TCC 服务的 Confirm 或者 Cancel 方法;最终所有 TCC 服务要么全部都是提交的,要么全部都是回滚的。 - -![image.png](/img/blog/tcc.png) - - - -### 二、TCC 设计 - -用户在接入 TCC 时,大部分工作都集中在如何实现 TCC 服务上,经过蚂蚁金服多年的 TCC 应用,总结如下主要的TCC 设计和实现主要事项: - - - -#### 1、**业务操作分两阶段完成** - -接入 TCC 前,业务操作只需要一步就能完成,但是在接入 TCC 之后,需要考虑如何将其分成 2 阶段完成,把资源的检查和预留放在一阶段的 Try 操作中进行,把真正的业务操作的执行放在二阶段的 Confirm 操作中进行。 - -以下举例说明业务模式如何分成两阶段进行设计,举例场景:“账户A的余额中有 100 元,需要扣除其中 30 元”; - -在接入 TCC 之前,用户编写 SQL:“update 账户表 set 余额 = 余额 - 30 where 账户 = A”,便能一步完成扣款操作。 - -在接入 TCC 之后,就需要考虑如何将扣款操作分成 2 步完成: - -* Try 操作:资源的检查和预留; - -在扣款场景,Try 操作要做的事情就是先检查 A 账户余额是否足够,再冻结要扣款的 30 元(预留资源);此阶段不会发生真正的扣款。 - -* Confirm 操作:执行真正业务的提交; - -在扣款场景下,Confirm 阶段走的事情就是发生真正的扣款,把A账户中已经冻结的 30 元钱扣掉。 - -* Cancel 操作:预留资源的是否释放; - -在扣款场景下,扣款取消,Cancel 操作执行的任务是释放 Try 操作冻结的 30 元钱,是 A 账户回到初始状态。 - -![image.png](/img/blog/tow_step.png) - - - - -#### 2、**并发控制** - -用户在实现 TCC 时,应当考虑并发性问题,将锁的粒度降到最低,以最大限度的提高分布式事务的并发性。 - -以下还是以A账户扣款为例,“账户 A 上有 100 元,事务 T1 要扣除其中的 30 元,事务 T2 也要扣除 30 元,出现并发”。 - -在一阶段 Try 操作中,分布式事务 T1 和分布式事务 T2 分别冻结资金的那一部分资金,相互之间无干扰;这样在分布式事务的二阶段,无论 T1 是提交还是回滚,都不会对 T2 产生影响,这样 T1 和 T2 在同一笔业务数据上并行执行。 - -![image.png](/img/blog/conc.png)
- - - -#### 3、**允许空回滚** - -如下图所示,事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会出现因为丢包而导致的网络超时,此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,而 Cancel 操作调用未出现超时。 - -TCC 服务在未收到 Try 请求的情况下收到 Cancel 请求,这种场景被称为空回滚;空回滚在生产环境经常出现,用户在实现TCC服务时,应允许允许空回滚的执行,即收到空回滚时返回成功。 - -![image.png](/img/blog/empty_rollback.png) - - - -#### 4、防悬挂控制 - -如下图所示,事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会出现因网络拥堵而导致的超时,此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,Cancel 调用未超时;在此之后,拥堵在网络上的一阶段 Try 数据包被 TCC 服务收到,出现了二阶段 Cancel 请求比一阶段 Try 请求先执行的情况,此 TCC 服务在执行晚到的 Try 之后,将永远不会再收到二阶段的 Confirm 或者 Cancel ,造成 TCC 服务悬挂。 - -用户在实现  TCC 服务时,要允许空回滚,但是要拒绝执行空回滚之后 Try 请求,要避免出现悬挂。 - -![image.png](/img/blog/susp.png) - - - - -#### 5、幂等控制 - -无论是网络数据包重传,还是异常事务的补偿执行,都会导致 TCC 服务的 Try、Confirm 或者 Cancel 操作被重复执行;用户在实现 TCC 服务时,需要考虑幂等控制,即 Try、Confirm、Cancel 执行一次和执行多次的业务结果是一样的。
![image.png](/img/blog/miden.png)

- - - -### Roadmap - -当前已经发布到 0.4.0 版本,后续我们会发布 0.5 ~ 1.0 版本,继续对 AT、TCC 模式进行功能完善和和丰富,并解决服务端高可用问题,在 1.0 版本之后,本开源产品将达到生产环境使用的标准。 - - -![图片1.png](/img/blog/roadmap.png) +Placeholder. DO NOT DELETE. \ No newline at end of file diff --git a/docs/intro.md b/docs/intro.md deleted file mode 100644 index 8a2e69d95f..0000000000 --- a/docs/intro.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Tutorial Intro - -Let's discover **Docusaurus in less than 5 minutes**. - -## Getting Started - -Get started by **creating a new site**. - -Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**. - -### What you'll need - -- [Node.js](https://nodejs.org/en/download/) version 16.14 or above: - - When installing Node.js, you are recommended to check all checkboxes related to dependencies. - -## Generate a new site - -Generate a new Docusaurus site using the **classic template**. - -The classic template will automatically be added to your project after you run the command: - -```bash -npm init docusaurus@latest my-website classic -``` - -You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor. - -The command also installs all necessary dependencies you need to run Docusaurus. - -## Start your site - -Run the development server: - -```bash -cd my-website -npm run start -``` - -The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there. - -The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/. - -Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes. diff --git a/docs/tutorial-basics/_category_.json b/docs/tutorial-basics/_category_.json deleted file mode 100644 index 2e6db55b1e..0000000000 --- a/docs/tutorial-basics/_category_.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "Tutorial - Basics", - "position": 2, - "link": { - "type": "generated-index", - "description": "5 minutes to learn the most important Docusaurus concepts." - } -} diff --git a/docs/tutorial-basics/congratulations.md b/docs/tutorial-basics/congratulations.md deleted file mode 100644 index 04771a00b7..0000000000 --- a/docs/tutorial-basics/congratulations.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Congratulations! - -You have just learned the **basics of Docusaurus** and made some changes to the **initial template**. - -Docusaurus has **much more to offer**! - -Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**. - -Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610) - -## What's next? - -- Read the [official documentation](https://docusaurus.io/) -- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config) -- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration) -- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout) -- Add a [search bar](https://docusaurus.io/docs/search) -- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase) -- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support) diff --git a/docs/tutorial-basics/create-a-blog-post.md b/docs/tutorial-basics/create-a-blog-post.md deleted file mode 100644 index ea472bbaf8..0000000000 --- a/docs/tutorial-basics/create-a-blog-post.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Create a Blog Post - -Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed... - -## Create your first Post - -Create a file at `blog/2021-02-28-greetings.md`: - -```md title="blog/2021-02-28-greetings.md" ---- -slug: greetings -title: Greetings! -authors: - - name: Joel Marcey - title: Co-creator of Docusaurus 1 - url: https://github.com/JoelMarcey - image_url: https://github.com/JoelMarcey.png - - name: Sébastien Lorber - title: Docusaurus maintainer - url: https://sebastienlorber.com - image_url: https://github.com/slorber.png -tags: [greetings] ---- - -Congratulations, you have made your first post! - -Feel free to play around and edit this post as much you like. -``` - -A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings). diff --git a/docs/tutorial-basics/create-a-document.md b/docs/tutorial-basics/create-a-document.md deleted file mode 100644 index ffddfa8eb8..0000000000 --- a/docs/tutorial-basics/create-a-document.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Create a Document - -Documents are **groups of pages** connected through: - -- a **sidebar** -- **previous/next navigation** -- **versioning** - -## Create your first Doc - -Create a Markdown file at `docs/hello.md`: - -```md title="docs/hello.md" -# Hello - -This is my **first Docusaurus document**! -``` - -A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello). - -## Configure the Sidebar - -Docusaurus automatically **creates a sidebar** from the `docs` folder. - -Add metadata to customize the sidebar label and position: - -```md title="docs/hello.md" {1-4} ---- -sidebar_label: 'Hi!' -sidebar_position: 3 ---- - -# Hello - -This is my **first Docusaurus document**! -``` - -It is also possible to create your sidebar explicitly in `sidebars.js`: - -```js title="sidebars.js" -module.exports = { - tutorialSidebar: [ - 'intro', - // highlight-next-line - 'hello', - { - type: 'category', - label: 'Tutorial', - items: ['tutorial-basics/create-a-document'], - }, - ], -}; -``` diff --git a/docs/tutorial-basics/create-a-page.md b/docs/tutorial-basics/create-a-page.md deleted file mode 100644 index 20e2ac3005..0000000000 --- a/docs/tutorial-basics/create-a-page.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Create a Page - -Add **Markdown or React** files to `src/pages` to create a **standalone page**: - -- `src/pages/index.js` → `localhost:3000/` -- `src/pages/foo.md` → `localhost:3000/foo` -- `src/pages/foo/bar.js` → `localhost:3000/foo/bar` - -## Create your first React Page - -Create a file at `src/pages/my-react-page.js`: - -```jsx title="src/pages/my-react-page.js" -import React from 'react'; -import Layout from '@theme/Layout'; - -export default function MyReactPage() { - return ( - -

My React page

-

This is a React page

-
- ); -} -``` - -A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page). - -## Create your first Markdown Page - -Create a file at `src/pages/my-markdown-page.md`: - -```mdx title="src/pages/my-markdown-page.md" -# My Markdown page - -This is a Markdown page -``` - -A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page). diff --git a/docs/tutorial-basics/deploy-your-site.md b/docs/tutorial-basics/deploy-your-site.md deleted file mode 100644 index 1c50ee063e..0000000000 --- a/docs/tutorial-basics/deploy-your-site.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -sidebar_position: 5 ---- - -# Deploy your site - -Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**). - -It builds your site as simple **static HTML, JavaScript and CSS files**. - -## Build your site - -Build your site **for production**: - -```bash -npm run build -``` - -The static files are generated in the `build` folder. - -## Deploy your site - -Test your production build locally: - -```bash -npm run serve -``` - -The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/). - -You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**). diff --git a/docs/tutorial-basics/markdown-features.mdx b/docs/tutorial-basics/markdown-features.mdx deleted file mode 100644 index 0337f34d6a..0000000000 --- a/docs/tutorial-basics/markdown-features.mdx +++ /dev/null @@ -1,150 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Markdown Features - -Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**. - -## Front Matter - -Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/): - -```text title="my-doc.md" -// highlight-start ---- -id: my-doc-id -title: My document title -description: My document description -slug: /my-custom-url ---- -// highlight-end - -## Markdown heading - -Markdown text with [links](./hello.md) -``` - -## Links - -Regular Markdown links are supported, using url paths or relative file paths. - -```md -Let's see how to [Create a page](/create-a-page). -``` - -```md -Let's see how to [Create a page](./create-a-page.md). -``` - -**Result:** Let's see how to [Create a page](./create-a-page.md). - -## Images - -Regular Markdown images are supported. - -You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`): - -```md -![Docusaurus logo](/img/docusaurus.png) -``` - -![Docusaurus logo](/img/docusaurus.png) - -You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them: - -```md -![Docusaurus logo](./img/docusaurus.png) -``` - -## Code Blocks - -Markdown code blocks are supported with Syntax highlighting. - - ```jsx title="src/components/HelloDocusaurus.js" - function HelloDocusaurus() { - return ( -

Hello, Docusaurus!

- ) - } - ``` - -```jsx title="src/components/HelloDocusaurus.js" -function HelloDocusaurus() { - return

Hello, Docusaurus!

; -} -``` - -## Admonitions - -Docusaurus has a special syntax to create admonitions and callouts: - - :::tip My tip - - Use this awesome feature option - - ::: - - :::danger Take care - - This action is dangerous - - ::: - -:::tip My tip - -Use this awesome feature option - -::: - -:::danger Take care - -This action is dangerous - -::: - -## MDX and React Components - -[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**: - -```jsx -export const Highlight = ({children, color}) => ( - { - alert(`You clicked the color ${color} with label ${children}`) - }}> - {children} - -); - -This is Docusaurus green ! - -This is Facebook blue ! -``` - -export const Highlight = ({children, color}) => ( - { - alert(`You clicked the color ${color} with label ${children}`); - }}> - {children} - -); - -This is Docusaurus green ! - -This is Facebook blue ! diff --git a/docs/tutorial-extras/_category_.json b/docs/tutorial-extras/_category_.json deleted file mode 100644 index a8ffcc1930..0000000000 --- a/docs/tutorial-extras/_category_.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "label": "Tutorial - Extras", - "position": 3, - "link": { - "type": "generated-index" - } -} diff --git a/docs/tutorial-extras/img/docsVersionDropdown.png b/docs/tutorial-extras/img/docsVersionDropdown.png deleted file mode 100644 index 97e4164618..0000000000 Binary files a/docs/tutorial-extras/img/docsVersionDropdown.png and /dev/null differ diff --git a/docs/tutorial-extras/img/localeDropdown.png b/docs/tutorial-extras/img/localeDropdown.png deleted file mode 100644 index e257edc1f9..0000000000 Binary files a/docs/tutorial-extras/img/localeDropdown.png and /dev/null differ diff --git a/docs/tutorial-extras/manage-docs-versions.md b/docs/tutorial-extras/manage-docs-versions.md deleted file mode 100644 index e12c3f3444..0000000000 --- a/docs/tutorial-extras/manage-docs-versions.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Manage Docs Versions - -Docusaurus can manage multiple versions of your docs. - -## Create a docs version - -Release a version 1.0 of your project: - -```bash -npm run docusaurus docs:version 1.0 -``` - -The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created. - -Your docs now have 2 versions: - -- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs -- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs** - -## Add a Version Dropdown - -To navigate seamlessly across versions, add a version dropdown. - -Modify the `docusaurus.config.js` file: - -```js title="docusaurus.config.js" -module.exports = { - themeConfig: { - navbar: { - items: [ - // highlight-start - { - type: 'docsVersionDropdown', - }, - // highlight-end - ], - }, - }, -}; -``` - -The docs version dropdown appears in your navbar: - -![Docs Version Dropdown](./img/docsVersionDropdown.png) - -## Update an existing version - -It is possible to edit versioned docs in their respective folder: - -- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello` -- `docs/hello.md` updates `http://localhost:3000/docs/next/hello` diff --git a/docs/tutorial-extras/translate-your-site.md b/docs/tutorial-extras/translate-your-site.md deleted file mode 100644 index caeaffb055..0000000000 --- a/docs/tutorial-extras/translate-your-site.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Translate your site - -Let's translate `docs/intro.md` to French. - -## Configure i18n - -Modify `docusaurus.config.js` to add support for the `fr` locale: - -```js title="docusaurus.config.js" -module.exports = { - i18n: { - defaultLocale: 'en', - locales: ['en', 'fr'], - }, -}; -``` - -## Translate a doc - -Copy the `docs/intro.md` file to the `i18n/fr` folder: - -```bash -mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/ - -cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md -``` - -Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French. - -## Start your localized site - -Start your site on the French locale: - -```bash -npm run start -- --locale fr -``` - -Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated. - -:::caution - -In development, you can only use one locale at a same time. - -::: - -## Add a Locale Dropdown - -To navigate seamlessly across languages, add a locale dropdown. - -Modify the `docusaurus.config.js` file: - -```js title="docusaurus.config.js" -module.exports = { - themeConfig: { - navbar: { - items: [ - // highlight-start - { - type: 'localeDropdown', - }, - // highlight-end - ], - }, - }, -}; -``` - -The locale dropdown now appears in your navbar: - -![Locale Dropdown](./img/localeDropdown.png) - -## Build your localized site - -Build your site for a specific locale: - -```bash -npm run build -- --locale fr -``` - -Or build your site to include all the locales at once: - -```bash -npm run build -``` diff --git a/i18n/en/docusaurus-plugin-content-blog/download.md b/i18n/en/docusaurus-plugin-content-blog/download.md deleted file mode 100644 index c9045f58ea..0000000000 --- a/i18n/en/docusaurus-plugin-content-blog/download.md +++ /dev/null @@ -1,2854 +0,0 @@ ---- -title: Downloads -keywords: [Seata, Downloads, Version] -description: This article will introduce you how to understand the details of each version and upgrade matters needing attention. ---- - - -# Downloads - -# Seata - -> GitHub: https://github.com/seata/seata -> -> Release Notes: https://github.com/seata/seata/releases - -### 1.7.0 (2023-07-11) - -[source](https://github.com/seata/seata/archive/v1.7.0.zip) | -[binary](https://github.com/seata/seata/releases/download/v1.7.0/seata-server-1.7.0.zip) - -
- Release notes - - -### Seata 1.7.0 - -Seata 1.7.0 Released - -Seata is an easy-to-use, high-performance, open source distributed transaction solution. - -The version is updated as follows: - -### feature: -- [[#5476](https://github.com/seata/seata/pull/5476)] first support `native-image` for `seata-client` -- [[#5495](https://github.com/seata/seata/pull/5495)] console integration saga-statemachine-designer -- [[#5668](https://github.com/seata/seata/pull/5668)] compatible with file.conf and registry.conf configurations in version 1.4.2 and below - -### bugfix: -- [[#5682](https://github.com/seata/seata/pull/5682)] fix saga mode replay context lost startParams -- [[#5671](https://github.com/seata/seata/pull/5671)] fix saga mode serviceTask inputParams json autoType convert exception -- [[#5194](https://github.com/seata/seata/pull/5194)] fix wrong keyword order for oracle when creating a table -- [[#5021](https://github.com/seata/seata/pull/5201)] fix JDK Reflection for Spring origin proxy failed in JDK17 -- [[#5023](https://github.com/seata/seata/pull/5203)] fix `seata-core` dependency transitive conflict in `seata-dubbo` -- [[#5224](https://github.com/seata/seata/pull/5224)] fix oracle initialize script index_name is duplicate -- [[#5233](https://github.com/seata/seata/pull/5233)] fix the inconsistent configuration item names related to LoadBalance -- [[#5266](https://github.com/seata/seata/pull/5265)] fix server console has queried the released lock -- [[#5245](https://github.com/seata/seata/pull/5245)] fix the incomplete dependency of distribution module -- [[#5239](https://github.com/seata/seata/pull/5239)] fix `getConfig` throw `ClassCastException` when use JDK proxy -- [[#5281](https://github.com/seata/seata/pull/5281)] parallel request handle throw IndexOutOfBoundsException -- [[#5288](https://github.com/seata/seata/pull/5288)] fix auto-increment of pk columns in Oracle in AT mode -- [[#5287](https://github.com/seata/seata/pull/5287)] fix auto-increment of pk columns in PostgreSQL in AT mode -- [[#5299](https://github.com/seata/seata/pull/5299)] fix GlobalSession deletion when retry rollback or retry commit timeout -- [[#5307](https://github.com/seata/seata/pull/5307)] fix that keywords don't add escaped characters -- [[#5311](https://github.com/seata/seata/pull/5311)] remove RollbackRetryTimeout sessions during in file storage recover -- [[#4734](https://github.com/seata/seata/pull/4734)] check if table meta cache should be refreshed in AT mode -- [[#5316](https://github.com/seata/seata/pull/5316)] fix G1 jvm parameter in jdk8 -- [[#5321](https://github.com/seata/seata/pull/5321)] fix When the rollback logic on the TC side returns RollbackFailed, the custom FailureHandler is not executed -- [[#5332](https://github.com/seata/seata/pull/5332)] fix bugs found in unit tests -- [[#5145](https://github.com/seata/seata/pull/5145)] fix global session is always begin in saga mode -- [[#5413](https://github.com/seata/seata/pull/5413)] fix bad service configuration file and compilation failure -- [[#5415](https://github.com/seata/seata/pull/5415)] fix transaction timeout on client side not execute hook and failureHandler -- [[#5447](https://github.com/seata/seata/pull/5447)] fix oracle xa mode cannnot be used By same database -- [[#5472](https://github.com/seata/seata/pull/5472)] fix if using `@GlobalTransactional` in RM, `ShouldNeverHappenException` will be thrown -- [[#5535](https://github.com/seata/seata/pull/5535)] fix the log file path was loaded incorrectly -- [[#5538](https://github.com/seata/seata/pull/5538)] fix finished transaction swallows exception when committing -- [[#5539](https://github.com/seata/seata/pull/5539)] fix the full table scan issue with 'setDate' condition in Oracle 10g -- [[#5540](https://github.com/seata/seata/pull/5540)] fix GlobalStatus=9 can't be cleared in DB storage mode -- [[#5552](https://github.com/seata/seata/pull/5552)] fix mariadb rollback failed -- [[#5583](https://github.com/seata/seata/pull/5583)] fix grpc interceptor xid unbinding problem -- [[#5602](https://github.com/seata/seata/pull/5602)] fix log in participant transaction role -- [[#5645](https://github.com/seata/seata/pull/5645)] fix oracle insert undolog failed -- [[#5659](https://github.com/seata/seata/pull/5659)] fix the issue of case sensitivity enforcement on the database after adding escape characters to keywords -- [[#5663](https://github.com/seata/seata/pull/5663)] fix the timeout is null when the connectionProxyXA connection is reused -- [[#5675](https://github.com/seata/seata/pull/5675)] fix compatibility between xxx.grouplist and grouplist.xxx configuration items -- [[#5690](https://github.com/seata/seata/pull/5690)] fix console print `unauthorized error` -- [[#5711](https://github.com/seata/seata/pull/5711)] fix get configuration item contains underlined error - -### optimize: -- [[#5208](https://github.com/seata/seata/pull/5208)] optimize throwable getCause once more -- [[#5212](https://github.com/seata/seata/pull/5212)] optimize log message level -- [[#5237](https://github.com/seata/seata/pull/5237)] optimize exception log message print(EnhancedServiceLoader.loadFile#cahtch) -- [[#5089](https://github.com/seata/seata/pull/5089)] optimize the check of the delay value of the TCC fence log clean task -- [[#5243](https://github.com/seata/seata/pull/5243)] optimize kryo 5.4.0 optimize compatibility with jdk17 -- [[#5153](https://github.com/seata/seata/pull/5153)] Only AT mode try to get channel with other app -- [[#5177](https://github.com/seata/seata/pull/5177)] If `server.session.enable-branch-async-remove` is true, delete the branch asynchronously and unlock it synchronously. -- [[#5273](https://github.com/seata/seata/pull/5273)] optimize the compilation configuration of the `protobuf-maven-plugin` plug-in to solve the problem of too long command lines in higher versions. -- [[#5303](https://github.com/seata/seata/pull/5303)] remove startup script the -Xmn configuration -- [[#5325](https://github.com/seata/seata/pull/5325)] add store mode,config type and registry type log info -- [[#5315](https://github.com/seata/seata/pull/5315)] optimize the log of SPI -- [[#5323](https://github.com/seata/seata/pull/5323)] add time info for global transaction timeout log -- [[#5414](https://github.com/seata/seata/pull/5414)] optimize transaction fail handler -- [[#5537](https://github.com/seata/seata/pull/5537)] optimize transaction log on client side -- [[#5541](https://github.com/seata/seata/pull/5541)] optimize server log output -- [[#5548](https://github.com/seata/seata/pull/5548)] update expire gpg key and publish workflow -- [[#5638](https://github.com/seata/seata/pull/5638)] optimize: set server's transaction level to READ_COMMITTED -- [[#5646](https://github.com/seata/seata/pull/5646)] refactor ColumnUtils and EscapeHandler -- [[#5648](https://github.com/seata/seata/pull/5648)] optimize server logs print -- [[#5647](https://github.com/seata/seata/pull/5647)] support case-sensitive attributes for table and column metadata -- [[#5678](https://github.com/seata/seata/pull/5678)] optimize escape character for case of columnNames -- [[#5684](https://github.com/seata/seata/pull/5684)] optimize github actions for CodeQL, skywalking-eyes and checkout -- [[#5700](https://github.com/seata/seata/pull/5700)] optimize distributed lock log - - -### security: -- [[#5172](https://github.com/seata/seata/pull/5172)] fix some security vulnerabilities -- [[#5683](https://github.com/seata/seata/pull/5683)] add Hessian Serializer WhiteDenyList -- [[#5696](https://github.com/seata/seata/pull/5696)] fix several node.js security vulnerabilities - -### test: -- [[#5380](https://github.com/seata/seata/pull/5380)] fix UpdateExecutorTest failed -- [[#5382](https://github.com/seata/seata/pull/5382)] fix multi spring version test failed - -Thanks to these contributors for their code commits. Please report an unintended omission. - - -- [slievrly](https://github.com/slievrly) -- [xssdpgy](https://github.com/xssdpgy) -- [albumenj](https://github.com/albumenj) -- [PeppaO](https://github.com/PeppaO) -- [yuruixin](https://github.com/yuruixin) -- [dmego](https://github.com/dmego) -- [CrazyLionLi](https://github.com/JavaLionLi) -- [xingfudeshi](https://github.com/xingfudeshi) -- [Bughue](https://github.com/Bughue) -- [pengten](https://github.com/pengten) -- [wangliang181230](https://github.com/wangliang181230) -- [GoodBoyCoder](https://github.com/GoodBoyCoder) -- [a364176773](https://github.com/a364176773) -- [isharpever](https://github.com/isharpever) -- [ZhangShiYeChina](https://github.com/ZhangShiYeChina) -- [mxsm](https://github.com/mxsm) -- [l81893521](https://github.com/l81893521) -- [liuqiufeng](https://github.com/liuqiufeng) -- [yixia](https://github.com/wt-better) -- [jumtp](https://github.com/jumtp) - - -Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - -#### Link - -- **Seata:** https://github.com/seata/seata -- **Seata-Samples:** https://github.com/seata/seata-samples -- **Release:** https://github.com/seata/seata/releases -- **WebSite:** https://seata.io - -
- - -### 1.6.1 (2022-12-21) - -[source](https://github.com/seata/seata/archive/v1.6.1.zip) | -[binary](https://github.com/seata/seata/releases/download/v1.6.1/seata-server-1.6.1.zip) - -
- Release notes - - -### Seata 1.6.1 - -Seata 1.6.1 Released - -Seata is an easy-to-use, high-performance, open source distributed transaction solution. - -The version is updated as follows: - -### feature: -- [[#5115](https://github.com/seata/seata/pull/5115)] support for `spring-boot:3.x` - -### bugfix: -- [[#5179](https://github.com/seata/seata/pull/5179)] fix ClassNotFoundException when server starts using Eureka - -### optimize: -- [[#5120](https://github.com/seata/seata/pull/5120)] unify the format of configuration items in yml files -- [[#5180](https://github.com/seata/seata/pull/5180)] GlobalTransactionScanner,SeataAutoDataSourceProxyCreator declare @bean methods as static -- [[#5182](https://github.com/seata/seata/pull/5182)] fix some security vulnerabilities in GGEditor -- [[#5183](https://github.com/seata/seata/pull/5183)] optimize the default values for some switches - -Thanks to these contributors for their code commits. Please report an unintended omission. - - -- [slievrly](https://github.com/slievrly) -- [wangliang181230](https://github.com/wangliang181230) -- [xingfudeshi](https://github.com/xingfudeshi) -- [whxxxxx](https://github.com/whxxxxx) -- [xssdpgy](https://github.com/xssdpgy) - -Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - -#### Link - -- **Seata:** https://github.com/seata/seata -- **Seata-Samples:** https://github.com/seata/seata-samples -- **Release:** https://github.com/seata/seata/releases -- **WebSite:** https://seata.io - -
- -### 1.6.0 (2022-12-17) - -[source](https://github.com/seata/seata/archive/v1.6.0.zip) | -[binary](https://github.com/seata/seata/releases/download/v1.6.0/seata-server-1.6.0.zip) - -
- Release notes - - -### Seata 1.6.0 - -Seata 1.6.0 Released - -Seata is an easy-to-use, high-performance, open source distributed transaction solution. - -The version is updated as follows: - -### feature: -- [[#4863](https://github.com/seata/seata/pull/4863)] support oracle and postgresql multi primary key -- [[#4649](https://github.com/seata/seata/pull/4649)] seata-server support multiple registry -- [[#4779](https://github.com/seata/seata/pull/4779)] support Apache Dubbo3 -- [[#4479](https://github.com/seata/seata/pull/4479)] TCC mode supports tcc annotation marked on both interface and implementation class -- [[#4877](https://github.com/seata/seata/pull/4877)] seata client support jdk17 -- [[#4914](https://github.com/seata/seata/pull/4914)] support mysql update join sql -- [[#4542](https://github.com/seata/seata/pull/4542)] support oracle timestamp types -- [[#5111](https://github.com/seata/seata/pull/5111)] support Nacos contextPath -- [[#4802](https://github.com/seata/seata/pull/4802)] dockerfile support arm64 - - -### bugfix: -- [[#4780](https://github.com/seata/seata/pull/4780)] fix can't post TimeoutRollbacked event after a successful timeout rollback -- [[#4954](https://github.com/seata/seata/pull/4954)] fix output expression incorrectly throws npe -- [[#4817](https://github.com/seata/seata/pull/4817)] fix in high version springboot property not Standard -- [[#4838](https://github.com/seata/seata/pull/4838)] fix when use Statement.executeBatch() can not generate undo log -- [[#4533](https://github.com/seata/seata/pull/4533)] fix rollback event repeated and some event status not correct -- [[#4912](https://github.com/seata/seata/pull/4912)] fix mysql InsertOnDuplicateUpdate column case is different and cannot be matched -- [[#4543](https://github.com/seata/seata/pull/4543)] fix support Oracle nclob types -- [[#4915](https://github.com/seata/seata/pull/4915)] fix failed to get server recovery properties -- [[#4919](https://github.com/seata/seata/pull/4919)] fix XID port and address null:0 before coordinator.init -- [[#4928](https://github.com/seata/seata/pull/4928)] fix rpcContext.getClientRMHolderMap NPE -- [[#4953](https://github.com/seata/seata/pull/4953)] fix InsertOnDuplicateUpdate bypass modify pk -- [[#4978](https://github.com/seata/seata/pull/4978)] fix kryo support circular reference -- [[#4874](https://github.com/seata/seata/pull/4874)] fix startup failure by using OpenJDK 11 -- [[#5018](https://github.com/seata/seata/pull/5018)] fix loader path in startup scripts -- [[#5004](https://github.com/seata/seata/pull/5004)] fix duplicate image row for update join -- [[#5032](https://github.com/seata/seata/pull/5032)] fix mysql InsertOnDuplicateUpdate sql query error caused by placeholder index calculation error -- [[#5033](https://github.com/seata/seata/pull/5033)] fix null exception when sql columns is empty for insert on duplicate -- [[#5038](https://github.com/seata/seata/pull/5038)] remove @EnableConfigurationProperties({SagaAsyncThreadPoolProperties.class}) -- [[#5050](https://github.com/seata/seata/pull/5050)] fix global session is not change to Committed in saga mode -- [[#5052](https://github.com/seata/seata/pull/5052)] fix update join condition placeholder param error -- [[#5031](https://github.com/seata/seata/pull/5031)] fix mysql InsertOnDuplicateUpdate should not use null index value as image sql query condition -- [[#5075](https://github.com/seata/seata/pull/5075)] fix InsertOnDuplicateUpdateExecutor could not intercept the sql which has no primary and unique key -- [[#5093](https://github.com/seata/seata/pull/5093)] fix access key loss after seata server restart -- [[#5092](https://github.com/seata/seata/pull/5092)] fix when seata and jpa are used together, their AutoConfiguration order is incorrect -- [[#5109](https://github.com/seata/seata/pull/5109)] fix NPE caused when there is no @GlobalTransactional annotation on the RM side -- [[#5098](https://github.com/seata/seata/pull/5098)] Druid disable oracle implicit cache -- [[#4860](https://github.com/seata/seata/pull/4860)] fix metrics tags coverage in the seata-server side -- [[#5028](https://github.com/seata/seata/pull/5028)] fix insert value null parsed as string in insert on duplicate SQL -- [[#5078](https://github.com/seata/seata/pull/5078)] fix could not intercept the sql witch has no primary and unique key -- [[#5097](https://github.com/seata/seata/pull/5097)] fix access key loss after server restart -- [[#5131](https://github.com/seata/seata/pull/5131)] fix rollback xa connection active state -- [[#5134](https://github.com/seata/seata/pull/5134)] fix hikari datasource auto proxy fail -- [[#5163](https://github.com/seata/seata/pull/5163)] fix bad service configuration file and compilation failure - -### optimize: -- [[#4774](https://github.com/seata/seata/pull/4774)] optimize mysql8 dependencies for seataio/seata-server image -- [[#4790](https://github.com/seata/seata/pull/4790)] Add a github action to publish Seata to OSSRH -- [[#4765](https://github.com/seata/seata/pull/4765)] mysql 8.0.29 not should be hold for connection -- [[#4750](https://github.com/seata/seata/pull/4750)] optimize unBranchLock romove xid -- [[#4797](https://github.com/seata/seata/pull/4797)] optimize the github actions -- [[#4800](https://github.com/seata/seata/pull/4800)] Add NOTICE as Apache License V2 -- [[#4681](https://github.com/seata/seata/pull/4681)] optimize the check lock during global transaction -- [[#4761](https://github.com/seata/seata/pull/4761)] use hget replace hmget because only one field -- [[#4414](https://github.com/seata/seata/pull/4414)] exclude log4j dependencies -- [[#4836](https://github.com/seata/seata/pull/4836)] optimize BaseTransactionalExecutor#buildLockKey(TableRecords rowsIncludingPK) method more readable -- [[#4865](https://github.com/seata/seata/pull/4865)] fix some security vulnerabilities in GGEditor -- [[#4590](https://github.com/seata/seata/pull/4590)] auto degrade enable to dynamic configure -- [[#4490](https://github.com/seata/seata/pull/4490)] tccfence log table delete by index -- [[#4911](https://github.com/seata/seata/pull/4911)] add license checker workflow -- [[#4917](https://github.com/seata/seata/pull/4917)] upgrade package-lock.json fix vulnerabilities -- [[#4924](https://github.com/seata/seata/pull/4924)] optimize pom dependencies -- [[#4932](https://github.com/seata/seata/pull/4932)] extract the default values for some properties -- [[#4925](https://github.com/seata/seata/pull/4925)] optimize java doc warning -- [[#4921](https://github.com/seata/seata/pull/4921)] fix some vulnerabilities in console and upgrade skywalking-eyes -- [[#4936](https://github.com/seata/seata/pull/4936)] optimize read of storage configuration -- [[#4946](https://github.com/seata/seata/pull/4946)] pass the sqlexception to client when get lock -- [[#4962](https://github.com/seata/seata/pull/4962)] optimize build and fix the base image -- [[#4974](https://github.com/seata/seata/pull/4974)] optimize cancel the limit on the number of globalStatus queries in Redis mode -- [[#4981](https://github.com/seata/seata/pull/4981)] optimize tcc fence record not exists errMessage -- [[#4985](https://github.com/seata/seata/pull/4985)] fix undo_log id repeat -- [[#4995](https://github.com/seata/seata/pull/4995)] fix mysql InsertOnDuplicateUpdate duplicate pk condition in after image query sql -- [[#5047](https://github.com/seata/seata/pull/5047)] remove useless code -- [[#5051](https://github.com/seata/seata/pull/5051)] undo log dirty throw BranchRollbackFailed_Unretriable -- [[#5075](https://github.com/seata/seata/pull/5075)] intercept the InsertOnDuplicateUpdate statement which has no primary key and unique index value -- [[#5104](https://github.com/seata/seata/pull/5104)] remove the druid dependency in ConnectionProxy -- [[#5124](https://github.com/seata/seata/pull/5124)] support oracle on delete tccfence logs -- [[#4468](https://github.com/seata/seata/pull/4968)] support kryo 5.3.0 -- [[#4807](https://github.com/seata/seata/pull/4807)] optimize docker image and oss publish -- [[#4445](https://github.com/seata/seata/pull/4445)] optimize transaction timeout judgment -- [[#4958](https://github.com/seata/seata/pull/4958)] do not execute triggerAfterCommit() if timeout -- [[#4582](https://github.com/seata/seata/pull/4582)] redis mode support sorted set by timeout -- [[#4963](https://github.com/seata/seata/pull/4963)] add ARM64 CI workflow -- [[#4434](https://github.com/seata/seata/pull/4434)] remove seata-server's CMS parameters - -### test: -- [[#4411](https://github.com/seata/seata/pull/4411)] add UT for oracle in AT mode -- [[#4794](https://github.com/seata/seata/pull/4794)] try to fix the test `DataSourceProxyTest.getResourceIdTest()` -- [[#5101](https://github.com/seata/seata/pull/5101)] fix ClassNotFoundException during the zk unit test - -Thanks to these contributors for their code commits. Please report an unintended omission. - - -- [slievrly](https://github.com/slievrly) -- [renliangyu857](https://github.com/renliangyu857) -- [wangliang181230](https://github.com/wangliang181230) -- [a364176773](https://github.com/a364176773) -- [tuwenlin](https://github.com/tuwenlin) -- [conghuhu](https://github.com/conghuhu) -- [a1104321118](https://github.com/a1104321118) -- [duanqiaoyanyu](https://github.com/duanqiaoyanyu) -- [robynron](https://github.com/robynron) -- [lcmvs](https://github.com/lcmvs) -- [github-ganyu](https://github.com/github-ganyu) -- [1181954449](https://github.com/1181954449) -- [zw201913](https://github.com/zw201913) -- [wingchi-leung](https://github.com/wingchi-leung) -- [AlexStocks](https://github.com/AlexStocks) -- [liujunlin5168](https://github.com/liujunlin5168) -- [pengten](https://github.com/pengten) -- [liuqiufeng](https://github.com/liuqiufeng) -- [yujianfei1986](https://github.com/yujianfei1986) -- [Bughue](https://github.com/Bughue) -- [AlbumenJ](https://github.com/AlbumenJ) -- [doubleDimple](https://github.com/doubleDimple) -- [jsbxyyx](https://github.com/jsbxyyx) -- [tuwenlin](https://github.com/tuwenlin) -- [CrazyLionLi](https://github.com/JavaLionLi) -- [whxxxxx](https://github.com/whxxxxx) -- [neillee95](https://github.com/neillee95) -- [crazy-sheep](https://github.com/crazy-sheep) -- [zhangzq7](https://github.com/zhangzq7) -- [l81893521](https://github.com/l81893521) -- [zhuyoufeng](https://github.com/zhuyoufeng) -- [xingfudeshi](https://github.com/xingfudeshi) -- [odidev](https://github.com/odidev) -- [miaoxueyu](https://github.com/miaoxueyu) - - -Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - -#### Link - -- **Seata:** https://github.com/seata/seata -- **Seata-Samples:** https://github.com/seata/seata-samples -- **Release:** https://github.com/seata/seata/releases -- **WebSite:** https://seata.io - -
- -### 1.5.2 (2022-07-12) - -[source](https://github.com/seata/seata/archive/v1.5.2.zip) | -[binary](https://github.com/seata/seata/releases/download/v1.5.2/seata-server-1.5.2.zip) - -
- Release notes - - -### Seata 1.5.2 - -Seata 1.5.2 Released. - -Seata is an easy-to-use, high-performance, open source distributed transaction solution. - -The version is updated as follows: - -### feature: -- [[#4661](https://github.com/seata/seata/pull/4713)] support xid load balance -- [[#4676](https://github.com/seata/seata/pull/4676)] support server to expose Nacos services by mounting SLB -- [[#4642](https://github.com/seata/seata/pull/4642)] support batch message parallel processing -- [[#4567](https://github.com/seata/seata/pull/4567)] support where method condition(find_in_set) - - -### bugfix: -- [[#4515](https://github.com/seata/seata/pull/4515)] fix the error of SeataTCCFenceAutoConfiguration when database unused -- [[#4661](https://github.com/seata/seata/pull/4661)] fix sql exception with PostgreSQL in module console -- [[#4667](https://github.com/seata/seata/pull/4682)] fix the exception in RedisTransactionStoreManager for update map During iteration -- [[#4678](https://github.com/seata/seata/pull/4678)] fix the error of key transport.enableRmClientBatchSendRequest cache penetration if not configure -- [[#4701](https://github.com/seata/seata/pull/4701)] fix missing command line args -- [[#4607](https://github.com/seata/seata/pull/4607)] fix bug on skipping lock check -- [[#4696](https://github.com/seata/seata/pull/4696)] fix oracle database insert value -- [[#4726](https://github.com/seata/seata/pull/4726)] fix batch message send may return NullPointException -- [[#4729](https://github.com/seata/seata/pull/4729)] fix set AspectTransactional.rollbackForClassName with wrong value -- [[#4653](https://github.com/seata/seata/pull/4653)] fix the sql exception when pk is non-numeric in INSERT_ON_DUPLICATE SQL - -### optimize: -- [[#4650](https://github.com/seata/seata/pull/4650)] fix some security vulnerabilities -- [[#4670](https://github.com/seata/seata/pull/4670)] optimize the thread pool size of branchResultMessageExecutor -- [[#4662](https://github.com/seata/seata/pull/4662)] optimize rollback transaction metrics -- [[#4693](https://github.com/seata/seata/pull/4693)] optimize the console navigation bar -- [[#4700](https://github.com/seata/seata/pull/4700)] fix maven-compiler-plugin and maven-resources-plugin execute failed -- [[#4711](https://github.com/seata/seata/pull/4711)] separate lib dependencies for deployments -- [[#4720](https://github.com/seata/seata/pull/4720)] optimize pom description -- [[#4728](https://github.com/seata/seata/pull/4728)] upgrade logback dependency to 1.2.9 -- [[#4745](https://github.com/seata/seata/pull/4745)] support mysql8 in release package -- [[#4626](https://github.com/seata/seata/pull/4626)] Replace `flatten-maven-plugin` with `easyj-maven-plugin` to fix the conflict between `shade` and `flatten` -- [[#4629](https://github.com/seata/seata/pull/4629)] check relation of before status and after status when updating global session -- [[#4662](https://github.com/seata/seata/pull/4662)] make EnhancedServiceLoader more readable - -### test: - -- [[#4544](https://github.com/seata/seata/pull/4544)] optimize jackson dependencies in TransactionContextFilterTest -- [[#4731](https://github.com/seata/seata/pull/4731)] fix UT failed in AsyncWorkerTest and LockManagerTest - -Thanks to these contributors for their code commits. Please report an unintended omission. - - -- [slievrly](https://github.com/slievrly) -- [pengten](https://github.com/pengten) -- [YSF-A](https://github.com/YSF-A) -- [tuwenlin](https://github.com/tuwenlin) -- [Ifdevil](https://github.com/Ifdevil) -- [wingchi-leung](https://github.com/wingchi-leung) -- [liurong](https://github.com/robynron) -- [opelok-z](https://github.com/opelok-z) -- [a364176773](https://github.com/a364176773) -- [2129zxl](https://github.com/2129zxl) -- [Smery-lxm](https://github.com/Smery-lxm) -- [doubleDimple](https://github.com/doubleDimple) -- [wangliang181230](https://github.com/wangliang181230) -- [Bughue](https://github.com/Bughue) -- [AYue-94](https://github.com/AYue-94) -- [lingxiao-wu](https://github.com/lingxiao-wu) -- [caohdgege](https://github.com/caohdgege) - -Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - -#### Link - -- **Seata:** https://github.com/seata/seata -- **Seata-Samples:** https://github.com/seata/seata-samples -- **Release:** https://github.com/seata/seata/releases -- **WebSite:** https://seata.io - -
- -### 1.5.1 (2021-05-17) - -[source](https://github.com/seata/seata/archive/v1.5.1.zip) | -[binary](https://github.com/seata/seata/releases/download/v1.5.1/seata-server-1.5.1.zip) - -
- Release notes - -### feature: -- [[#4115](https://github.com/seata/seata/pull/4115) ] support console management -- [[#3472](https://github.com/seata/seata/pull/3472) ] add redisLocker's lua mode -- [[#3575](https://github.com/seata/seata/pull/3575) ] support the mixed use of different storages of locks and sessions -- [[#3374](https://github.com/seata/seata/pull/3374) ] add a Executor for INSERT ON DUPLICATE KEY UPDATE -- [[#3642](https://github.com/seata/seata/pull/3642) ] provide an api to share tcc phase-1's params to phase-2 -- [[#3064](https://github.com/seata/seata/pull/3064) ] support configuring the order of the TM and TCC interceptor -- [[#2852](https://github.com/seata/seata/pull/2852) ] support configuring scan target for GlobalTransactionScanner -- [[#3683](https://github.com/seata/seata/pull/3683) ] support redis distributed lock to prevent multi TC competition -- [[#3545](https://github.com/seata/seata/pull/3545) ] TCC mode support idempotent and anti hanging -- [[#3009](https://github.com/seata/seata/pull/3009) ] support server start with springboot and config with application.yaml -- [[#3652](https://github.com/seata/seata/pull/3652) ] support APM with SkyWalking -- [[#3823](https://github.com/seata/seata/pull/3823) ] TCC mode supports customized parameters list of the method in phase two -- [[#3642](https://github.com/seata/seata/pull/3642) ] TCC mode's try method supports passing `BusinessActionContext` implicitly -- [[#3856](https://github.com/seata/seata/pull/3856) ] support edas-hsf RPC framework -- [[#3880](https://github.com/seata/seata/pull/3880) ] contributing md support chinese. -- [[#2568](https://github.com/seata/seata/pull/2568) ] support GlobalTransactionInterceptor expression -- [[#3886](https://github.com/seata/seata/pull/3886) ] support the registry center network preferences -- [[#3869](https://github.com/seata/seata/pull/3869) ] support get configuration from environment -- [[#3906](https://github.com/seata/seata/pull/3906) ] support SPI unload -- [[#3668](https://github.com/seata/seata/pull/3668) ] support kotlin coroutine -- [[#3968](https://github.com/seata/seata/pull/3968) ] support brpc-java RPC framework -- [[#4134](https://github.com/seata/seata/pull/4134) ] init the console basic code -- [[#4268](https://github.com/seata/seata/pull/4268) ] query global session in the file mode -- [[#4281](https://github.com/seata/seata/pull/4281) ] query global session and global lock in the redis mode -- [[#4293](https://github.com/seata/seata/pull/4293) ] get global lock in the file mode -- [[#4335](https://github.com/seata/seata/pull/4335) ] Realize configuration center upload configuration interactive script (nacos,etcd3) -- [[#4360](https://github.com/seata/seata/pull/4360) ] Realize configuration center upload configuration interactive script (apollo,consul,zk) -- [[#4320](https://github.com/seata/seata/pull/4320) ] realize the interface of console: get global session and global lock in the db mode -- [[#4435](https://github.com/seata/seata/pull/4435) ] console front-end page implementation -- [[#4480](https://github.com/seata/seata/pull/4480) ] implementation of DefaultAuthSigner -- [[#3870](https://github.com/seata/seata/pull/3870) ] make seata-bom be the real Bill-Of-Material -- [[#3487](https://github.com/seata/seata/pull/3487) ] add db realization for distribute lock -- [[#3889](https://github.com/seata/seata/pull/3889) ] registry add heartbeat -- [[#3951](https://github.com/seata/seata/pull/3951) ] support zstd compressor -- [[#2838](https://github.com/seata/seata/pull/2838) ] Saga support auto configuration in the spring boot project - -### bugfix: -- [[#3497](https://github.com/seata/seata/pull/3497) ] fix tcc phase two response timeout exception -- [[#3686](https://github.com/seata/seata/pull/3686) ] fix NPE and wrong cluster name of Apollo -- [[#3702](https://github.com/seata/seata/pull/3702) ] fix some comments -- [[#3716](https://github.com/seata/seata/pull/3716) ] fix the problem in the findTargetClass method -- [[#3717](https://github.com/seata/seata/pull/3717) ] fix typo of interval -- [[#3773](https://github.com/seata/seata/pull/3773) ] fix consul not found tc cluster -- [[#3695](https://github.com/seata/seata/pull/3695) ] fix mariadb unable to create XA connection -- [[#3783](https://github.com/seata/seata/pull/3783) ] fix the problem that store mode does not take effect -- [[#3740](https://github.com/seata/seata/pull/3740) ] fix that `LocalThread` is not cleared when the `Saga` transaction ends -- [[#3792](https://github.com/seata/seata/pull/3792) ] fix the Server can't find redis-host property -- [[#3828](https://github.com/seata/seata/pull/3828) ] fix StringUtils StackOverflowError -- [[#3817](https://github.com/seata/seata/pull/3817) ] fix TC SkyWalking topo calling node not gather -- [[#3803](https://github.com/seata/seata/pull/3803) ] fix ReflectionUtil throw unexpected exception -- [[#3879](https://github.com/seata/seata/pull/3879) ] fix postgresql multi schema throw not found channel exception -- [[#3881](https://github.com/seata/seata/pull/3881) ] fix getConfig with different default value return the first -- [[#3897](https://github.com/seata/seata/pull/3897) ] fix LocalDataTime type in FastjsonUndoLogParser can't be rollback -- [[#3901](https://github.com/seata/seata/pull/3901) ] fix seataio/seata-server servlet-api conflict -- [[#3931](https://github.com/seata/seata/pull/3931) ] fix the wrong path and filename when dump the jvm memory for analysis -- [[#3978](https://github.com/seata/seata/pull/3978) ] fix NPE cause by future timeout -- [[#4266](https://github.com/seata/seata/pull/4266) ] fix register branch and release lock failed when the size of rows that modified is greater than 1000 in oracle -- [[#3949](https://github.com/seata/seata/pull/3949) ] fix the problem that `nacos-config.py` will not skip blank options. fix bug that split options may cause content loss -- [[#3988](https://github.com/seata/seata/pull/3988) ] fix the problem that nacos not found user when password has special characters -- [[#3998](https://github.com/seata/seata/pull/3998) ] fix the NPE of jedis multi.exec -- [[#4011](https://github.com/seata/seata/pull/4011) ] fix can not get properties of distributed-lock-table in springboot -- [[#4025](https://github.com/seata/seata/pull/4025) ] fix potential database resource leak -- [[#4023](https://github.com/seata/seata/pull/4023) ] fix the problem that the xid is not cleared in some scenes of dubbo -- [[#4039](https://github.com/seata/seata/pull/4039) ] fix RM did not clear XID after the local transaction threw an exception -- [[#4032](https://github.com/seata/seata/pull/4032) ] fix ApplicationContext already closed problem when Seata server using ShutdownHook to destroy -- [[#4074](https://github.com/seata/seata/pull/4074) ] fix prevents XA mode resource suspension -- [[#4107](https://github.com/seata/seata/pull/4107) ] fix deadlock problems during project construction -- [[#4158](https://github.com/seata/seata/pull/4158) ] fix the logback can't load the `RPC_PORT` -- [[#4162](https://github.com/seata/seata/pull/4162) ] fix correct built-in properties for redis registry -- [[#4165](https://github.com/seata/seata/pull/4165) ] fix `StringUtils.toString(obj)` throw `ClassCastException` when the obj is primitive data array -- [[#4169](https://github.com/seata/seata/pull/4169) ] fix xa mode originalConnection has been closed, cause PhaseTwo fail to execute -- [[#4177](https://github.com/seata/seata/pull/4177) ] fix the problem of accidentally releasing the global lock -- [[#4174](https://github.com/seata/seata/pull/4174) ] fix delete undo log connection already closed -- [[#4189](https://github.com/seata/seata/pull/4189) ] fix the `kafka-appender.xml` and `logstash-appender.xml` -- [[#4213](https://github.com/seata/seata/pull/4213) ] fix code for "sessionMode" not execute problem -- [[#4220](https://github.com/seata/seata/pull/4220) ] fix some problems with `zstd` compressor and add the version of the `kotlin-maven-plugin` -- [[#4222](https://github.com/seata/seata/pull/4222) ] fix could not rollback when insert field list is empty -- [[#4253](https://github.com/seata/seata/pull/4253) ] update executor store the actually modified columns but not only the columns in set condition -- [[#4276](https://github.com/seata/seata/pull/4276) ] fix seata-test module UT not work -- [[#4278](https://github.com/seata/seata/pull/4278) ] fix the problem that mysql's Blob/Clob/NClob data type cannot be deserialized -- [[#4302](https://github.com/seata/seata/pull/4302) ] fix the problem that other ORMs may not be able to obtain the auto-incrementing primary key value -- [[#4233](https://github.com/seata/seata/pull/4233) ] fix data remanence problems in lock and branch under specific circumstances. -- [[#4308](https://github.com/seata/seata/pull/4308) ] fix the TableMetaCache parsing problem with the same table under multiple Postgresql schemas -- [[#4326](https://github.com/seata/seata/pull/4326) ] fix inability to build Executor when using mariadb driver -- [[#4355](https://github.com/seata/seata/pull/4355) ] fix mysql-loadbalance resource id error -- [[#4310](https://github.com/seata/seata/pull/4310) ] fix the problem that failed to obtain the self increment ID of MySQL database through "select last_insert_id" -- [[#4331](https://github.com/seata/seata/pull/4331) ] fix dirty write check exception that may occur when using ONLY_CARE_UPDATE_COLUMNS configuration -- [[#4228](https://github.com/seata/seata/pull/4228) ] fix resource suspension in xa mode caused by choose other ip as channel alternative -- [[#4408](https://github.com/seata/seata/pull/4408) ] fix the invalid environment variable in container env -- [[#4441](https://github.com/seata/seata/pull/4441) ] fix the problem that pipelined resources are not closed in redis mode and add branchSession judge branchSessions is not null -- [[#4438](https://github.com/seata/seata/pull/4438) ] fix the problem that GlobalSession could not be deleted normally in the case of delayed deletion in the file mode of the develop branch -- [[#4432](https://github.com/seata/seata/pull/4432) ] fix the inability to get some remote configurations -- [[#4452](https://github.com/seata/seata/pull/4452) ] fix the change log of 'service.disableGlobalTransaction' config -- [[#4449](https://github.com/seata/seata/pull/4449) ] fix redis mode page npe and optimize get globalSession on average -- [[#4459](https://github.com/seata/seata/pull/4459) ] fix the failure to obtain before image and after image on oracle and pgsql of the develop branch -- [[#4471](https://github.com/seata/seata/pull/4471) ] in branch 'develop', fix the error when service.vgroupMapping change -- [[#4474](https://github.com/seata/seata/pull/4474) ] fix Mysql multi-bit Bit type field rollback error -- [[#4492](https://github.com/seata/seata/pull/4492) ] fix the failure to update cluster list dynamically when use eureka of the develop branch -- [[#4535](https://github.com/seata/seata/pull/4535) ] fix FileSessionManagerTest fail -- [[#4561](https://github.com/seata/seata/pull/4561) ] fix allSessions/findGlobalSessions may return null and cause npe -- [[#4505](https://github.com/seata/seata/pull/4505) ] fix fastjson serialization of time data types -- [[#4579](https://github.com/seata/seata/pull/4579) ] fix prepareUndoLogAll of MySQLInsertOrUpdateExecutor -- [[#4005](https://github.com/seata/seata/pull/4005) ] fix PK constraint name isn't the same as the unique index name which is belong to PK -- [[#4062](https://github.com/seata/seata/pull/4062) ] fix saga complex parameter deserialization problem -- [[#4199](https://github.com/seata/seata/pull/4199) ] fix rpc tm request timeout -- [[#4352](https://github.com/seata/seata/pull/4352) ] fix some problem of the sql parser -- [[#4487](https://github.com/seata/seata/pull/4487) ] fix remove Pagination hideOnlyOnePage attribute -- [[#4449](https://github.com/seata/seata/pull/4449) ] fix optimize redis limit and fix redis page bug -- [[#4608](https://github.com/seata/seata/pull/4608) ] fix test case -- [[#3110](https://github.com/seata/seata/pull/3110) ] fix the problem of unit test - - -### optimize: -- [[#4163](https://github.com/seata/seata/pull/4163) ] improve CONTRIBUTING docs -- [[#3678](https://github.com/seata/seata/pull/3678) ] supplement missing configuration and new version documents -- [[#3654](https://github.com/seata/seata/pull/3654) ] fix typo,applicationContex -> applicationContext -- [[#3615](https://github.com/seata/seata/pull/3615) ] asynchronous deletion after the transaction is committed -- [[#3687](https://github.com/seata/seata/pull/3687) ] fix the case that could not retry acquire global lock -- [[#3689](https://github.com/seata/seata/pull/3689) ] modify the attribute prefix in the file file.properties -- [[#3528](https://github.com/seata/seata/pull/3528) ] optimize the memory footprint of redis mode -- [[#3700](https://github.com/seata/seata/pull/3700) ] optimize the speed of buildLockKey -- [[#3588](https://github.com/seata/seata/pull/3588) ] optimize the logic of datasource auto proxy -- [[#3626](https://github.com/seata/seata/pull/3626) ] remove repeat change status -- [[#3722](https://github.com/seata/seata/pull/3722) ] add the basic code of distributed lock -- [[#3713](https://github.com/seata/seata/pull/3713) ] unified the default value of enableClientBatchSendRequest -- [[#3120](https://github.com/seata/seata/pull/3120) ] optimize `Configuration` and add unit tests -- [[#3735](https://github.com/seata/seata/pull/3735) ] do not load `LoadBalance` if not necessary -- [[#3770](https://github.com/seata/seata/pull/3770) ] close the `Closeable` and optimize some code -- [[#3627](https://github.com/seata/seata/pull/3627) ] use TreeMap instead of the LinkedHashMap in TableMeta to compatible high level MySQL -- [[#3760](https://github.com/seata/seata/pull/3760) ] opt the logback's config of `seata-server` -- [[#3765](https://github.com/seata/seata/pull/3765) ] Transfer the operation of adding configuration class from 'AutoConfiguration' to 'EnvironmentPostProcessor' -- [[#3730](https://github.com/seata/seata/pull/3730) ] Refactoring the code of TCC mode -- [[#3820](https://github.com/seata/seata/pull/3820) ] add column `action_name` to the `tcc_fence_log` -- [[#3738](https://github.com/seata/seata/pull/3738) ] `JacksonUndoLogParser` supports to parsing `LocalDateTime` -- [[#3794](https://github.com/seata/seata/pull/3794) ] optimize the packaging of `seata-server` -- [[#3795](https://github.com/seata/seata/pull/3795) ] optimize zk registry lookup performance -- [[#3840](https://github.com/seata/seata/pull/3840) ] optimiza `apm-skwalking` operation method to generate rules -- [[#3834](https://github.com/seata/seata/pull/3834) ] optimize `seata-distribution` add `apm-seata-skywalking` -- [[#3847](https://github.com/seata/seata/pull/3847) ] optimize ConcurrentHashMap.newKeySet replace ConcurrentSet -- [[#3311](https://github.com/seata/seata/pull/3311) ] supports reading all configurations from a single Consul key -- [[#3849](https://github.com/seata/seata/pull/3849) ] optimize string concat -- [[#3890](https://github.com/seata/seata/pull/3890) ] optimize only the inserted fields are checked -- [[#3895](https://github.com/seata/seata/pull/3895) ] optimize decode exception -- [[#3898](https://github.com/seata/seata/pull/3898) ] add jib-maven-plugin -- [[#3904](https://github.com/seata/seata/pull/3904) ] ehance metrics and fix seata-server UT not work -- [[#3212](https://github.com/seata/seata/pull/3212) ] optimize recognize sql in limit and order by -- [[#3905](https://github.com/seata/seata/pull/3905) ] optimize nacos-config.sh to support ash -- [[#3935](https://github.com/seata/seata/pull/3935) ] optimize Send redis command at one time using pipeline -- [[#3916](https://github.com/seata/seata/pull/3916) ] optimize determine whether the server in the register is alive -- [[#3918](https://github.com/seata/seata/pull/3918) ] cache reflection results of the fields and methods -- [[#3898](https://github.com/seata/seata/pull/3898) ] add jib-maven-plugin -- [[#3907](https://github.com/seata/seata/pull/3907) ] optimize set server port -- [[#3912](https://github.com/seata/seata/pull/3912) ] support config JVM param in env -- [[#3939](https://github.com/seata/seata/pull/3939) ] use map instead of if else judge for more change in the future -- [[#3955](https://github.com/seata/seata/pull/3955) ] add a start banner for seata -- [[#3954](https://github.com/seata/seata/pull/3954) ] replace @Deprecated getOwnernName to getOwnerName in druid -- [[#3981](https://github.com/seata/seata/pull/3981) ] optimize service port priority -- [[#4013](https://github.com/seata/seata/pull/4013) ] optimize channel alive check -- [[#3982](https://github.com/seata/seata/pull/3982) ] optimize readme doc and upgrade some dependencies -- [[#3949](https://github.com/seata/seata/pull/3949) ] `nacos-config.py` support default parameters and optional input parameters -- [[#3991](https://github.com/seata/seata/pull/3991) ] disable listening in the FileConfiguration center in Springboot -- [[#3994](https://github.com/seata/seata/pull/3994) ] Optimize the mechanism of periodically deleting tasks in the `tcc_fence_log` table -- [[#3327](https://github.com/seata/seata/pull/3327) ] supports reading all configurations from a single Etcd3 key -- [[#4001](https://github.com/seata/seata/pull/4001) ] support to read YML configuration from Nacos, Zookeeper, Consul, Etcd3 -- [[#4017](https://github.com/seata/seata/pull/4017) ] optimize file configuration -- [[#4018](https://github.com/seata/seata/pull/4018) ] optimize Apollo configuration -- [[#4021](https://github.com/seata/seata/pull/4021) ] optimize Nacos、Consul、Zookeeper、Etcd3 configuration -- [[#4034](https://github.com/seata/seata/pull/4034) ] optimize Nacos, Consul, Zookeeper and Etcd3 configuration Junit test Class -- [[#4055](https://github.com/seata/seata/pull/4055) ] optimize NetUtil#getLocalAddress0 -- [[#4086](https://github.com/seata/seata/pull/4086) ] optimize lazily load branch transactions and task scheduling -- [[#4056](https://github.com/seata/seata/pull/4056) ] optimize the DurationUtil -- [[#4103](https://github.com/seata/seata/pull/4103) ] optimize AbstractLockManager#collectRowLocks logic -- [[#3733](https://github.com/seata/seata/pull/3733) ] optimize acquire lock logic -- [[#4144](https://github.com/seata/seata/pull/4144) ] support default configuration of tx-service-group -- [[#4157](https://github.com/seata/seata/pull/4157) ] optimize client batch sending. -- [[#4191](https://github.com/seata/seata/pull/4191) ] support rpc timeout can be customized. -- [[#4216](https://github.com/seata/seata/pull/4216) ] no more attempt to clean undolog for none AT mode -- [[#4176](https://github.com/seata/seata/pull/4176) ] use expire key instead hash when using redis as registry center. -- [[#4196](https://github.com/seata/seata/pull/4196) ] tc batch response to client. -- [[#4212](https://github.com/seata/seata/pull/4212) ] optimize the interface of the console -- [[#4237](https://github.com/seata/seata/pull/4237) ] skip check lock when all the before image is empty -- [[#4251](https://github.com/seata/seata/pull/4251) ] optimize partial code handling -- [[#4262](https://github.com/seata/seata/pull/4262) ] optimize tcc module code handling -- [[#4235](https://github.com/seata/seata/pull/4235) ] optimize instance saved in eureka -- [[#4277](https://github.com/seata/seata/pull/4277) ] optimize acquire lock return fail-fast code in redis-pipeline mode. -- [[#4284](https://github.com/seata/seata/pull/4284) ] support authentication of MSE-Nacos with ak/sk -- [[#4299](https://github.com/seata/seata/pull/4299) ] optimize exceptions to make them friendly -- [[#4300](https://github.com/seata/seata/pull/4300) ] optimize let DefaultCoordinator invoke NettyRemotingServer's close method,no longer closed by ServerRunner -- [[#4270](https://github.com/seata/seata/pull/4270) ] improve the performance of global commit and global rollback, asynchronous branch transaction cleanup -- [[#4307](https://github.com/seata/seata/pull/4307) ] when in TCC mode there is no need to delete global locks -- [[#4303](https://github.com/seata/seata/pull/4303) ] `tcc_fence_log` table hanging log records are deleted asynchronously -- [[#4328](https://github.com/seata/seata/pull/4328) ] upload configuration script support comments -- [[#4305](https://github.com/seata/seata/pull/4305) ] optimize acquire global lock fail error log print on tc -- [[#4336](https://github.com/seata/seata/pull/4336) ] add SQL exception prompt not supported by AT mode -- [[#4359](https://github.com/seata/seata/pull/4359) ] support configuration metadata read from environment variables -- [[#4247](https://github.com/seata/seata/pull/4247) ] add tests for `java17` and `springboot` in the `github/actions` -- [[#4353](https://github.com/seata/seata/pull/4353) ] Slimming down for the `seata-all.jar` -- [[#4393](https://github.com/seata/seata/pull/4393) ] skip reload for redis & db mode -- [[#4400](https://github.com/seata/seata/pull/4400) ] asynchronous tasks handle global transactions in parallel -- [[#4391](https://github.com/seata/seata/pull/4391) ] commit/rollback retry timeout event -- [[#4409](https://github.com/seata/seata/pull/4409) ] add copyright header to test classes -- [[#4282](https://github.com/seata/seata/pull/4282) ] optimize build UndoItem logic -- [[#4407](https://github.com/seata/seata/pull/4407) ] file mode does not require lazy processing of sessions -- [[#4436](https://github.com/seata/seata/pull/4436) ] optimize global session query in file mode -- [[#4431](https://github.com/seata/seata/pull/4431) ] limit the number of queries in Redis storage mode -- [[#4465](https://github.com/seata/seata/pull/4465) ] optimize client version transfer in tc batch response to client mode. -- [[#4469](https://github.com/seata/seata/pull/4469) ] optimize the way to get configuration in DB mode of console -- [[#4478](https://github.com/seata/seata/pull/4478) ] optimize Nacos config and naming properties -- [[#4522](https://github.com/seata/seata/pull/4522) ] optimize GC parameters in JVM -- [[#4517](https://github.com/seata/seata/pull/4517) ] enhance fail/timeout status metric and log level -- [[#4451](https://github.com/seata/seata/pull/4451) ] filesessionmanager changed to singleton and optimized task thread pool processing -- [[#4551](https://github.com/seata/seata/pull/4551) ] optimize metrics rt statistics -- [[#4574](https://github.com/seata/seata/pull/4574) ] support accessKey/secretKey auto configuration -- [[#4583](https://github.com/seata/seata/pull/4583) ] use HmacSHA256 instead of HmacSHA1 for ram signature -- [[#4591](https://github.com/seata/seata/pull/4591) ] optimize the default value of the switch -- [[#3780](https://github.com/seata/seata/pull/3780) ] optimize upgrade the Druid version -- [[#3797](https://github.com/seata/seata/pull/3797) ] optimize support instance `BusinessActionContext` outside the TCC try method -- [[#3909](https://github.com/seata/seata/pull/3909) ] optimize `collectRowLocks` method -- [[#3763](https://github.com/seata/seata/pull/3763) ] optimize github actions -- [[#4345](https://github.com/seata/seata/pull/4345) ] optimize fix the path of the package -- [[#4346](https://github.com/seata/seata/pull/4346) ] optimize the log of the server and remove lombok -- [[#4348](https://github.com/seata/seata/pull/4348) ] optimize Unified management the versions of maven-plugin -- [[#4354](https://github.com/seata/seata/pull/4354) ] optimize the tests of `SAGA` -- [[#4227](https://github.com/seata/seata/pull/4227) ] optimize the versions of the dependencies -- [[#4403](https://github.com/seata/seata/pull/4403) ] optimize disable `SAGA` tests -- [[#4453](https://github.com/seata/seata/pull/4453) ] optimize upgrade eureka-clients and xstream dependencies -- [[#4481](https://github.com/seata/seata/pull/4481) ] optimize nacos config and naming properties -- [[#4477](https://github.com/seata/seata/pull/4477) ] optimize debug log and fix typo -- [[#4484](https://github.com/seata/seata/pull/4484) ]optimize the log of TM/RM register -- [[#3874](https://github.com/seata/seata/pull/4484) ] optimize Add logo of registered enterprise,and Change image source to Alicdn -- [[#4458](https://github.com/seata/seata/pull/4458) ] optimize fix the README.md of metrices module -- [[#4482](https://github.com/seata/seata/pull/4482) ] optimize remove duplicated word - -Thanks to these contributors for their code commits. Please report an unintended omission. -- [slievrly](https://github.com/slievrly) -- [wangliang181230](https://github.com/wangliang181230) -- [a364176773](https://github.com/a364176773) -- [lvekee](https://github.com/lvekee) -- [caohdgege](https://github.com/caohdgege) -- [lightClouds917](https://github.com/lightClouds917) -- [objcoding](https://github.com/objcoding) -- [siyu](https://github.com/Pinocchio2018) -- [GoodBoyCoder](https://github.com/GoodBoyCoder) -- [pengten](https://github.com/pengten) -- [Bughue](https://github.com/Bughue) -- [doubleDimple](https://github.com/doubleDimple) -- [zhaoyuguang](https://github.com/zhaoyuguang) -- [liuqiufeng](https://github.com/liuqiufeng) -- [jsbxyyx](https://github.com/jsbxyyx) -- [lcmvs](https://github.com/lcmvs) -- [onlinechild](https://github.com/onlinechild) -- [xjlgod](https://github.com/xjlgod) -- [h-zhi](https://github.com/h-zhi) -- [tanzzj](https://github.com/tanzzj) -- [miaoxueyu](https://github.com/miaoxueyu) -- [selfishlover](https://github.com/selfishlover) -- [tuwenlin](https://github.com/tuwenlin) -- [dmego](https://github.com/dmego) -- [xiaochangbai](https://github.com/xiaochangbai) -- [Rubbernecker](https://github.com/Rubbernecker) -- [ruanun](https://github.com/ruanun) -- [huan415](https://github.com/huan415) -- [drgnchan](https://github.com/drgnchan) -- [cmonkey](https://github.com/cmonkey) -- [13414850431](https://github.com/13414850431) -- [ls9527](https://github.com/ls9527) -- [xingfudeshi](https://github.com/xingfudeshi) -- [spilledyear](https://github.com/spilledyear) -- [kaka2code](https://github.com/kaka2code) -- [iqinning](https://github.com/iqinning) -- [yujianfei1986](https://github.com/yujianfei1986) -- [elrond-g](https://github.com/elrond-g) -- [jameslcj](https://github.com/jameslcj) -- [zhouchuhang](https://github.com/zch0214) -- [xujj](https://github.com/XBNGit) -- [mengxzh](https://github.com/mengxzh) -- [portman](https://github.com/iportman) -- [anselleeyy](https://github.com/anselleeyy) -- [wangyuewen](https://github.com/2858917634) -- [imherewait](https://github.com/imherewait) -- [wfnuser](https://github.com/wfnuser) -- [zhixing](https://github.com/chenlei3641) - -Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. -
- -### 1.4.2 (2021-04-26) - -[source](https://github.com/seata/seata/archive/v1.4.2.zip) | -[binary](https://github.com/seata/seata/releases/download/v1.4.2/seata-server-1.4.2.zip) - -
- Release notes - - -### Seata 1.4.2 - -Seata 1.4.2 Released. - -Seata is an easy-to-use, high-performance, open source distributed transaction solution. - -The version is updated as follows: - -### feature: - -- [[#2933](https://github.com/seata/seata/pull/2933)] add antlr for mysql sqlparser -- [[#3228](https://github.com/seata/seata/pull/3228)] support custom serialization plugin -- [[#3172](https://github.com/seata/seata/pull/3172)] support undo_loge compression mode in AT -- [[#3372](https://github.com/seata/seata/pull/3372)] Saga support customize whether update last retry log -- [[#3411](https://github.com/seata/seata/pull/3411)] support seata-server thread pool parameters configuration -- [[#3348](https://github.com/seata/seata/pull/3348)] support redis sentinel storage mode in TC -- [[#2667](https://github.com/seata/seata/pull/2667)] support password decryption when using db and redis storage mode -- [[#3427](https://github.com/seata/seata/pull/3427)] add distributed lock interface -- [[#3443](https://github.com/seata/seata/pull/3443)] support send the `seata-server` log to `logstash` or `kafka` -- [[#3486](https://github.com/seata/seata/pull/3486)] add transaction service group for metric -- [[#3317](https://github.com/seata/seata/pull/3317)] support to obtain multiple configurations through a single node when using zookeeper as configuration center -- [[#3516](https://github.com/seata/seata/pull/3516)] support acl-token when consul is used registry and configuration center -- [[#3116](https://github.com/seata/seata/pull/3116)] support configuring apollo configService and cluster -- [[#3468](https://github.com/seata/seata/pull/3468)] saga support loop execution on state -- [[#3447](https://github.com/seata/seata/pull/3447)] support Transaction context printing in logging framework - - -### bugfix: - -- [[#3258](https://github.com/seata/seata/pull/3258)] fix AsyncWorker potential OOM problem -- [[#3293](https://github.com/seata/seata/pull/3293)] fix configuration cache get value type mismatch exception -- [[#3241](https://github.com/seata/seata/pull/3241)] forbidden use order by or limit in multi sql -- [[#3406](https://github.com/seata/seata/pull/3406)] fix the value can not be push to nacos when special charset in config.txt -- [[#3418](https://github.com/seata/seata/pull/3418)] fix getGeneratedKeys may get history pk -- [[#3408](https://github.com/seata/seata/pull/3408)] fix the NPE problem of jar running mode when the third-dependency on separate packaging -- [[#3431](https://github.com/seata/seata/pull/3431)] fix property bean may not be initialized when reading configuration -- [[#3413](https://github.com/seata/seata/pull/3413)] fix the logic of rollback to savepoint and release to savepoint -- [[#3367](https://github.com/seata/seata/pull/3367)] when the xa branch is rollback, it cannot be executed due to idle state -- [[#3448](https://github.com/seata/seata/pull/3448)] reduce unnecessary competition and remove missing locks -- [[#3451](https://github.com/seata/seata/pull/3451)] fix set auto-commit to true when local transactions are not being used. Failure to compete for a lock causes the global transaction to exit, invaliding the global row lock and dirty writing of the data. -- [[#3481](https://github.com/seata/seata/pull/3481)] fix seata node refresh failure because of consul client throws exceptions -- [[#3491](https://github.com/seata/seata/pull/3491)] fix typo in README.md -- [[#3531](https://github.com/seata/seata/pull/3531)] fix the NPE of RedisTransactionStoreManager when get branch transactions -- [[#3500](https://github.com/seata/seata/pull/3500)] fix oracle and postgreSQL can't query column info -- [[#3560](https://github.com/seata/seata/pull/3560)] fix the problem that the asynchronous task of the transactions in the committing state has no time threshold and cannot recover the transaction -- [[#3555](https://github.com/seata/seata/pull/3555)] do not call setBlob to invalid the jdbc exception -- [[#3540](https://github.com/seata/seata/pull/3540)] fix server distribution missing files -- [[#3597](https://github.com/seata/seata/pull/3597)] fix the possible NPE -- [[#3568](https://github.com/seata/seata/pull/3568)] fix automatic datasource agent caused by ConcurrentHashMap.computeIfAbsent Deadlock problem -- [[#3402](https://github.com/seata/seata/pull/3402)] fix the problem that the updated column cannot be resolved because the field name in the updated SQL contains the database name -- [[#3464](https://github.com/seata/seata/pull/3464)] fix test case NPE and StackTraceLogger's log. -- [[#3522](https://github.com/seata/seata/pull/3522)] fix register branch and store undolog when AT branch does not need compete lock -- [[#3635](https://github.com/seata/seata/pull/3635)] fix pushing notification failed when the configuration changed in zookeeper -- [[#3133](https://github.com/seata/seata/pull/3133)] fix the case that could not retry acquire global lock -- [[#3156](https://github.com/seata/seata/pull/3156)] optimize the logic of SpringProxyUtils.findTargetClass - - -### optimize: - -- [[#3341](https://github.com/seata/seata/pull/3341)] optimize the format of the path to the specified configuration file -- [[#3385](https://github.com/seata/seata/pull/3385)] optimize github action and fix unit test failure -- [[#3175](https://github.com/seata/seata/pull/3175)] improve UUIDGenerator using "history time" version of snowflake algorithm -- [[#3291](https://github.com/seata/seata/pull/3291)] mysql jdbc connect param -- [[#3336](https://github.com/seata/seata/pull/3336)] support using System.getProperty to get netty config property -- [[#3369](https://github.com/seata/seata/pull/3369)] add github action secrets env for dockerHub -- [[#3343](https://github.com/seata/seata/pull/3343)] Migrate CI provider from Travis CI to Github Actions -- [[#3397](https://github.com/seata/seata/pull/3397)] add the change records folder -- [[#3303](https://github.com/seata/seata/pull/3303)] supports reading all configurations from a single Nacos dataId -- [[#3380](https://github.com/seata/seata/pull/3380)] globalTransactionScanner listener optimize -- [[#3123](https://github.com/seata/seata/pull/3123)] optimize the packing strategy of seata-server -- [[#3415](https://github.com/seata/seata/pull/3415)] optimize maven clean plugin to clear the distribution directory -- [[#3316](https://github.com/seata/seata/pull/3316)] optimize the property bean may not be initialized while reading config value -- [[#3420](https://github.com/seata/seata/pull/3420)] optimize enumerated classes and add unit tests -- [[#3533](https://github.com/seata/seata/pull/3533)] added interface to get current transaction role -- [[#3436](https://github.com/seata/seata/pull/3436)] optimize typo in SQLType class -- [[#3439](https://github.com/seata/seata/pull/3439)] adjust the order of springApplicationContextProvider so that it can be called before the XML bean -- [[#3248](https://github.com/seata/seata/pull/3248)] optimize the config of load-balance migration to belong the client node -- [[#3441](https://github.com/seata/seata/pull/3441)] optimize the auto-configuration processing of starter -- [[#3466](https://github.com/seata/seata/pull/3466)] String comparison uses equalsIgnoreCase() -- [[#3476](https://github.com/seata/seata/pull/3476)] support when the server parameter passed is hostname, it will be automatically converted to IP -- [[#3236](https://github.com/seata/seata/pull/3236)] optimize the conditions for executing unlocking -- [[#3485](https://github.com/seata/seata/pull/3485)] optimize useless codes in ConfigurationFactory -- [[#3505](https://github.com/seata/seata/pull/3505)] optimize useless if judgments in the GlobalTransactionScanner class -- [[#3544](https://github.com/seata/seata/pull/3544)] optimize the get pks by auto when auto generated keys is false -- [[#3549](https://github.com/seata/seata/pull/3549)] unified the length of xid in different tables when using DB storage mode -- [[#3551](https://github.com/seata/seata/pull/3551)] make RETRY_DEAD_THRESHOLD bigger and configurable -- [[#3589](https://github.com/seata/seata/pull/3589)] Changed exception check by JUnit API usage -- [[#3601](https://github.com/seata/seata/pull/3601)] make `LoadBalanceProperties` compatible with `spring-boot:2.x` and above -- [[#3513](https://github.com/seata/seata/pull/3513)] Saga SpringBeanService invoker support switch json parser -- [[#3318](https://github.com/seata/seata/pull/3318)] make CLIENT_TABLE_META_CHECKER_INTERVAL configurable -- [[#3371](https://github.com/seata/seata/pull/3371)] add applicationId for metric -- [[#3459](https://github.com/seata/seata/pull/3459)] remove duplicate validAddress code -- [[#3215](https://github.com/seata/seata/pull/3215)] opt the reload during startup in file mode -- [[#3631](https://github.com/seata/seata/pull/3631)] optimize nacos-config.py parameter -- [[#3638](https://github.com/seata/seata/pull/3638)] optimize the error when use update or delete with join in sql -- [[#3523](https://github.com/seata/seata/pull/3523)] optimize release savepoint when use oracle -- [[#3458](https://github.com/seata/seata/pull/3458)] reversion the deleted md -- [[#3574](https://github.com/seata/seata/pull/3574)] repair Spelling errors in comments in EventBus.java files -- [[#3573](https://github.com/seata/seata/pull/3573)] fix designer directory path in README.md -- [[#3662](https://github.com/seata/seata/pull/3662)] update gpg key -- [[#3664](https://github.com/seata/seata/pull/3664)] optimize some javadocs -- [[#3637](https://github.com/seata/seata/pull/3637)] register the participating companies and pull request information - -### test - -- [[#3381](https://github.com/seata/seata/pull/3381)] test case for tmClient -- [[#3607](https://github.com/seata/seata/pull/3607)] fixed bugs in EventBus unit tests -- [[#3579](https://github.com/seata/seata/pull/3579)] add test case for StringFormatUtils -- [[#3365](https://github.com/seata/seata/pull/3365)] optimize ParameterParserTest test case failed -- [[#3359](https://github.com/seata/seata/pull/3359)] remove unused test case -- [[#3578](https://github.com/seata/seata/pull/3578)] fix UnfinishedStubbing Exception in unit test case -- [[#3383](https://github.com/seata/seata/pull/3383)] optimize StatementProxyTest unit test - - - -Thanks to these contributors for their code commits. Please report an unintended omission. - -- [slievrly](https://github.com/slievrly) -- [caohdgege](https://github.com/caohdgege) -- [a364176773](https://github.com/a364176773) -- [wangliang181230](https://github.com/wangliang181230) -- [xingfudeshi](https://github.com/xingfudeshi) -- [jsbxyyx](https://github.com/jsbxyyx) -- [selfishlover](https://github.com/selfishlover) -- [l8189352](https://github.com/l81893521) -- [Rubbernecker](https://github.com/Rubbernecker) -- [lj2018110133](https://github.com/lj2018110133) -- [github-ganyu](https://github.com/github-ganyu) -- [dmego](https://github.com/dmego) -- [spilledyear](https://github.com/spilledyear) -- [hoverruan](https://github.com/hoverruan ) -- [anselleeyy](https://github.com/anselleeyy) -- [Ifdevil](https://github.com/Ifdevil) -- [lvxianzheng](https://github.com/lvxianzheng) -- [MentosL](https://github.com/MentosL) -- [lian88jian](https://github.com/lian88jian) -- [litianyu1992](https://github.com/litianyu1992) -- [xyz327](https://github.com/xyz327) -- [13414850431](https://github.com/13414850431) -- [xuande](https://github.com/xuande) -- [tanggen](https://github.com/tanggen) -- [eas5](https://github.com/eas5) -- [nature80](https://github.com/nature80) -- [ls9527](https://github.com/ls9527) -- [drgnchan](https://github.com/drgnchan) -- [imyangyong](https://github.com/imyangyong) -- [sunlggggg](https://github.com/sunlggggg) -- [long187](https://github.com/long187) -- [h-zhi](https://github.com/h-zhi) -- [StellaiYang](https://github.com/StellaiYang) -- [slinpq](https://github.com/slinpq) -- [sustly](https://github.com/sustly) -- [cznc](https://github.com/cznc) -- [squallliu](https://github.com/squallliu) -- [81519434](https://github.com/81519434) -- [luoxn28](https://github.com/luoxn28) - -Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. -
- -### 1.4.1 (2021-02-08) - -[source](https://github.com/seata/seata/archive/v1.4.1.zip) | -[binary](https://github.com/seata/seata/releases/download/v1.4.1/seata-server-1.4.1.zip) - -
- Release notes - - -### Seata 1.4.1 - -Seata 1.4.1 Released. - -Seata is an easy-to-use, high-performance, open source distributed transaction solution. - -The version is updated as follows: - -### feature: - -- [[#3238](https://github.com/seata/seata/pull/3238)] add deflater support for seata compressor - - -### bugfix: - -- [[#2879](https://github.com/seata/seata/pull/2879)] fix deadlock during springboot project startup -- [[#3296](https://github.com/seata/seata/pull/3296)] when mixed use of AT and TCC, AT branchs is not deleted -- [[#3254](https://github.com/seata/seata/pull/3254)] clear the listener map of zk registry -- [[#3309](https://github.com/seata/seata/pull/3309)] Saga statemachine definition json cannot enable jackson parser, and when no choice matched in choice state will throw NPE -- [[#3287](https://github.com/seata/seata/pull/3287)] throw exception when update pk -- [[#3323](https://github.com/seata/seata/pull/3323)] clean root context when state machine inst record failed -- [[#3281](https://github.com/seata/seata/pull/3281)] fix wrong status when exception -- [[#2949](https://github.com/seata/seata/pull/2949)] fix throw NPE when get the state list -- [[#3351](https://github.com/seata/seata/pull/3351)] fix throw IllegalArgumentException when use hystrix when using SCA 2.2.3.RELEASE and below -- [[#3349](https://github.com/seata/seata/pull/3349)] the problem test case -- [[#3325](https://github.com/seata/seata/pull/3325)] fix retry commit unsuccess when record subMachineInst failed -- [[#3357](https://github.com/seata/seata/pull/3357)] fix deploy staging rule check failed - - -### optimize: - -- [[#3188](https://github.com/seata/seata/pull/3188)] Local variable 'map' is redundant and check queue offer return value -- [[#3247](https://github.com/seata/seata/pull/3247)] change client.log.exceptionRate to log.exceptionRate -- [[#3260](https://github.com/seata/seata/pull/3260)] use PriorityQueue to simply ShutdownHook -- [[#3319](https://github.com/seata/seata/pull/3319)] delete unnecessary @Sharable -- [[#3313](https://github.com/seata/seata/pull/3313)] replace StringBuffer to StringBuilder -- [[#3335](https://github.com/seata/seata/pull/3335)] modify TransactionPropagationInterceptor name -- [[#3310](https://github.com/seata/seata/pull/3310)] enable NamedThreadFactory to get ThreadGroup from the SecurityManager or Current thread -- [[#3320](https://github.com/seata/seata/pull/3320)] load balance strategy use constants -- [[#3345](https://github.com/seata/seata/pull/3345)] adjust GlobalLockTemplateTest - - -Thanks to these contributors for their code commits. Please report an unintended omission. - -- [slievrly](https://github.com/slievrly) -- [dongzl](https://github.com/dongzl) -- [wangliang181230](https://github.com/wangliang181230) -- [ls9527](https://github.com/ls9527) -- [long187](https://github.com/long187) -- [81519434](https://github.com/81519434) -- [anselleeyy](https://github.com/anselleeyy) -- [a364176773](https://github.com/a364176773) -- [selfishlover](https://github.com/selfishlover) -- [suichen](https://github.com/suichen) -- [h-zhi](https://github.com/h-zhi) -- [jxlgzwh](https://github.com/jxlgzwh) -- [LiWenGu](https://github.com/LiWenGu) - -Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - - -#### Link - -- **Seata:** https://github.com/seata/seata -- **Seata-Samples:** https://github.com/seata/seata-samples -- **Release:** https://github.com/seata/seata/releases -- **WebSite:** https://seata.io - -
- -### 1.4.0 (2020-10-30) - - [source](https://github.com/seata/seata/archive/v1.4.0.zip) | - [binary](https://github.com/seata/seata/releases/download/v1.4.0/seata-server-1.4.0.zip) - -
- Release notes - - - ### Seata 1.4.0 - - Seata 1.4.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - ### feature: - - [[#2380](https://github.com/seata/seata/pull/2380)] support yml configuration - - [[#3191](https://github.com/seata/seata/pull/3191)] support jdbc type nclob - - [[#2676](https://github.com/seata/seata/pull/2676)] support least active load balance - - [[#3198](https://github.com/seata/seata/pull/3198)] spring boot support for custom config and registry type - - [[#2806](https://github.com/seata/seata/pull/2806)] support configuring default global transaction timeoutMillis - - [[#2941](https://github.com/seata/seata/pull/2941)] add apollo secret key configuration - - [[#2080](https://github.com/seata/seata/pull/2080)] support ConsistentHashLoadBalance - - [[#2950](https://github.com/seata/seata/pull/2950)] support the reentrant lock in redis module - - [[#2913](https://github.com/seata/seata/pull/2913)] The data source proxy mode can be selected as AT or XA - - [[#2856](https://github.com/seata/seata/pull/2856)] support for undoLog using Fst serialization - - [[#3076](https://github.com/seata/seata/pull/3076)] check lock in TC when use @GlobalLock - - [[#2825](https://github.com/seata/seata/pull/2825)] support send authentication msg - - [[#2962](https://github.com/seata/seata/pull/2962)] @GlobalTransactional and @GlobalLock can support customize lock retry config - - ### bugfix: - - [[#3214](https://github.com/seata/seata/pull/3214)] fix the 'RootContext.DEFAULT_BRANCH_TYPE' is wrong in some cases - - [[#3129](https://github.com/seata/seata/pull/3129)] forbidding execute SQL which update pk value - - [[#3205](https://github.com/seata/seata/pull/3205)] fix can not get boolean value in configuration - - [[#3170](https://github.com/seata/seata/pull/3170)] the disposables tree set won't accept another Disposable with the same priority - - [[#3180](https://github.com/seata/seata/pull/3180)] serializer fst package name error - - [[#3178](https://github.com/seata/seata/pull/3178)] remove next line to space - - [[#2929](https://github.com/seata/seata/pull/2929)] fix the application was configured to degrade at startup and can't be dynamically switch to upgraded - - [[#3050](https://github.com/seata/seata/pull/3050)] fix fetch before images when delete and update statements - - [[#2935](https://github.com/seata/seata/pull/2935)] fix saga designer bug that the property box does not switch when switching nodes - - [[#3140](https://github.com/seata/seata/pull/3140)] fix Propagation.REQUIRES_NEW and add some comments - - [[#3130](https://github.com/seata/seata/pull/3130)] fix some problems in the automatic data source proxy - - [[#3148](https://github.com/seata/seata/pull/3148)] the redis lock key and the session key has conflict - - [[#3136](https://github.com/seata/seata/pull/3136)] fix the redis pipeline - - [[#2551](https://github.com/seata/seata/pull/2551)] Saga can't be used when the dataSource is AT's dataSourceProxy - - [[#3073](https://github.com/seata/seata/pull/3073)] do not proxy connections without an xid - - [[#3074](https://github.com/seata/seata/pull/3074)] There is no need to retry if the XA schema cannot find the XID - - [[#3097](https://github.com/seata/seata/pull/3097)] fix HttpAutoConfiguration always instantiation in springboot env - - [[#3071](https://github.com/seata/seata/pull/3071)] part of the connection is not unpacked - - [[#3056](https://github.com/seata/seata/pull/3056)] fixed a bug that after branch deletion, there are still remaining branch lock - - [[#3025](https://github.com/seata/seata/pull/3025)] fix the wrong package path - - [[#3031](https://github.com/seata/seata/pull/3031)] redis locker delete lock incomplete - - [[#2973](https://github.com/seata/seata/pull/2973)] fix oracle database in field size over 1000 - - [[#2986](https://github.com/seata/seata/pull/2986)] fix checkstyle plugin can't exclude single file - - [[#2910](https://github.com/seata/seata/pull/2910)] fix error registry type comment - - [[#2914](https://github.com/seata/seata/pull/2914)] fix branchType not cleaned when consumer is in TCC mode - - [[#2926](https://github.com/seata/seata/pull/2926)] fastjson write undo log not parser - - [[#2897](https://github.com/seata/seata/pull/2897)] fix jedis unlock fail - - [[#2918](https://github.com/seata/seata/pull/2918)] fix the isolation problem when rollback in AT mode - - [[#2972](https://github.com/seata/seata/pull/2972)] UUIDGenerator generates duplicated id - - [[#2932](https://github.com/seata/seata/pull/2932)] nacos-config.py script could not run with namespace - - [[#2900](https://github.com/seata/seata/pull/2900)] ColumnUtils add escape with scheme - - [[#2904](https://github.com/seata/seata/pull/2904)] fix getConfig cache value is 'null' - - [[#2890](https://github.com/seata/seata/pull/2890)] fix misspelling in statelang examples - - [[#3040](https://github.com/seata/seata/pull/3040)] fix repeated commit when autocommit is false - - [[#3230](https://github.com/seata/seata/pull/3230)] fix use @EnableAutoDataSourceProxy startup failed - - [[#2979](https://github.com/seata/seata/pull/2979)] columns of resultset integrated with sharingjdbc need to be lowercase - - [[#3233](https://github.com/seata/seata/pull/3233)] fix Collections NPE - - [[#3242](https://github.com/seata/seata/pull/3242)] fix batch sql getTableMeta error - - [[#3246](https://github.com/seata/seata/pull/3246)] fix the exception when limit condition contains VariantRefExpr - - - ### optimize: - - [[#3062](https://github.com/seata/seata/pull/3062)] refactor the redis session store - - [[#3201](https://github.com/seata/seata/pull/3201)] optimize the wrong stack not fully display - - [[#3117](https://github.com/seata/seata/pull/3117)] make log more clearly and remove the useless code - - [[#3134](https://github.com/seata/seata/pull/3134)] optimize codes related to Map and List - - [[#3195](https://github.com/seata/seata/pull/3195)] optimize XID related codes - - [[#3200](https://github.com/seata/seata/pull/3200)] optimize rpc message when message was substring - - [[#3186](https://github.com/seata/seata/pull/3186)] remove duplicated in string utils - - [[#3162](https://github.com/seata/seata/pull/3162)] remove repeated conditional tests - - [[#2969](https://github.com/seata/seata/pull/2969)] upgrade to druid 1.1.23 - - [[#3141](https://github.com/seata/seata/pull/3141)] upgrade nacos and FastJSON dependencies - - [[#3118](https://github.com/seata/seata/pull/3118)] add more configuration tips in additional-spring-configuration-metadata.json - - [[#2597](https://github.com/seata/seata/pull/2597)] judging xid status to avoid repeated processing - - [[#3102](https://github.com/seata/seata/pull/3102)] optimize ContextCore, can be set 'Object' value - - [[#3016](https://github.com/seata/seata/pull/3016)] refactor the redis lock string to hash - - [[#3046](https://github.com/seata/seata/pull/3046)] remove unused code in serializer factory - - [[#3053](https://github.com/seata/seata/pull/3053)] jedis pool adds maxtotal configuration - - [[#3012](https://github.com/seata/seata/pull/3012)] remove set port repeatedly - - [[#2978](https://github.com/seata/seata/pull/2978)] optimize globalCommit for mixed use of AT and TCC - - [[#2967](https://github.com/seata/seata/pull/2967)] replace with lambda - - [[#2968](https://github.com/seata/seata/pull/2968)] ensure that the register message is sent after RM client initialization - - [[#2945](https://github.com/seata/seata/pull/2945)] optimize async commit and reduce one update - - [[#2952](https://github.com/seata/seata/pull/2952)] optimize additional-spring-configuration-metadata.json - - [[#2920](https://github.com/seata/seata/pull/2920)] optimize some grammatical errors - - [[#2906](https://github.com/seata/seata/pull/2906)] added some configuration items to keep consistent with official documents - - [[#3222](https://github.com/seata/seata/pull/3222)] optimize fileListener to decrease cpu time usage - - [[#2843](https://github.com/seata/seata/pull/2843)] removed Reloadable from the redis/db SessionManager - - [[#3209](https://github.com/seata/seata/pull/3209)] add using company logos - - - Thanks to these contributors for their code commits. Please report an unintended omission. - - - [slievrly](https://github.com/slievrly) - - [wangliang181230](https://github.com/wangliang181230) - - [a364176773](https://github.com/a364176773) - - [jsbxyyx](https://github.com/jsbxyyx) - - [l81893521](https://github.com/l81893521) - - [lightClouds917](https://github.com/lightClouds917) - - [caohdgege](https://github.com/caohdgege) - - [yujianfei1986](https://github.com/yujianfei1986) - - [ph3636](https://github.com/ph3636) - - [PeineLiang](https://github.com/PeineLiang) - - [heyaping388](https://github.com/heyaping388) - - [guang384](https://github.com/guang384) - - [zdrjson](https://github.com/zdrjson) - - [ITAlexSun](https://github.com/ITAlexSun) - - [dongzl](https://github.com/dongzl) - - [81519434](https://github.com/81519434) - - [wangwei-yin](https://github.com/wangwei-yin) - - [jujinghao](https://github.com/jujinghao) - - [JRial95](https://github.com/JRial95) - - [mxszs1](https://github.com/mxszs1) - - [RayneHwang](https://github.com/RayneHwang) - - [everyhook1](https://github.com/everyhook1) - - [li469791221](https://github.com/li469791221) - - [luorenjin](https://github.com/luorenjin) - - [yangxb2010000](https://github.com/yangxb2010000) - - [selfishlover](https://github.com/selfishlover) - - [yyjgit66](https://github.com/yyjgit66) - - Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - - #### Link - - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - - **WebSite:** https://seata.io - -
- - -### 1.3.0 (2020-07-14) - - [source](https://github.com/seata/seata/archive/v1.3.0.zip) | - [binary](https://github.com/seata/seata/releases/download/v1.3.0/seata-server-1.3.0.zip) - -
- Release notes - - ### Seata 1.3.0 - - Seata 1.3.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - ### feature: - - [[#2398](https://github.com/seata/seata/pull/2398)] support multi pk for MySQL - - [[#2484](https://github.com/seata/seata/pull/2484)] store mode support redis - - [[#2817](https://github.com/seata/seata/pull/2817)] Saga StateMachine engine and Designer support Groovy Script Task - - [[#2646](https://github.com/seata/seata/pull/2646)] server support for HikariCP - - [[#2253](https://github.com/seata/seata/pull/2253)] support for dynamic upgrade and downgrade - - [[#2565](https://github.com/seata/seata/pull/2565)] support for transaction annotations on classes - - [[#2510](https://github.com/seata/seata/pull/2510)] support LZ4 compressor - - [[#2622](https://github.com/seata/seata/pull/2622)] support version valid check - - [[#2658](https://github.com/seata/seata/pull/2658)] dataSources support different permissions of Oracle users - - [[#2620](https://github.com/seata/seata/pull/2620)] support group configuration in Nacos registry - - [[#2699](https://github.com/seata/seata/pull/2699)] compatible with ACM - - [[#2509](https://github.com/seata/seata/pull/2509)] support for undo full data columns on update operate - - [[#2584](https://github.com/seata/seata/pull/2584)] StateHandlerInterceptor and StateRouterInterceptor support SPI - - [[#2808](https://github.com/seata/seata/pull/2808)] server check auth support SPI - - [[#2616](https://github.com/seata/seata/pull/2616)] TCC adapter for Dubbo And Sofa reference annotation - - [[#2831](https://github.com/seata/seata/pull/2831)] Saga support jackson json parser - - [[#2554](https://github.com/seata/seata/pull/2554)] support zk serializer - - [[#2708](https://github.com/seata/seata/pull/2708)] support jdbc type array, datalink etc - - [[#2412](https://github.com/seata/seata/pull/2412)] xid generation strategy support snowflake - - [[#2611](https://github.com/seata/seata/pull/2611)] support the cache of configuration values - - ### bugfix: - - [[#2893](https://github.com/seata/seata/pull/2893)] fix get table meta failed in postgresql - - [[#2887](https://github.com/seata/seata/pull/2887)] fix rm client receive response logic - - [[#2610](https://github.com/seata/seata/pull/2610)] nacos-script adapt to Nacos 1.2 on permission control - - [[#2588](https://github.com/seata/seata/pull/2588)] fix when the check_style does not pass, no detail information output - - [[#2543](https://github.com/seata/seata/pull/2543)] fix ApplicationKeeper ShutdownHook signal invalid. - - [[#2598](https://github.com/seata/seata/pull/2598)] fix unable to register Nacos - - [[#2618](https://github.com/seata/seata/pull/2618)] fix could not create folder in zookeeper - - [[#2628](https://github.com/seata/seata/pull/2628)] fix get tableName and alias error in mysql delete - - [[#2639](https://github.com/seata/seata/pull/2639)] fix Apollo configuration load fail due to camel style - - [[#2629](https://github.com/seata/seata/pull/2629)] fix duplicated resource id with different currentSchema in PostgreSQL - - [[#2659](https://github.com/seata/seata/pull/2659)] fix mysql insert use select last_insert_id is undo_log id value - - [[#2670](https://github.com/seata/seata/pull/2670)] fix dataSource initialize more times - - [[#2617](https://github.com/seata/seata/pull/2617)] fix incorrect getAnnotation about class and method - - [[#2603](https://github.com/seata/seata/pull/2603)] fix can't get generated keys value. - - [[#2725](https://github.com/seata/seata/pull/2725)] fix other expression before insert row primary key. - - [[#2698](https://github.com/seata/seata/pull/2698)] fix nested GlobalLock unbind prematurely - - [[#2755](https://github.com/seata/seata/pull/2755)] fix not return value when branchCommit and branchRollback throw exception - - [[#2777](https://github.com/seata/seata/pull/2777)] fix can't rollback when set rollback retry count was zero. - - [[#2812](https://github.com/seata/seata/pull/2812)] fix get PostgreSQL tableMeta error when using shardingSphere - - [[#2760](https://github.com/seata/seata/pull/2760)] fix TM rollback fail throw the seata exception, rollback retrying throw NPE - - [[#2837](https://github.com/seata/seata/pull/2837)] fix wrong constant used in the saga SubStateMachineHandler - - [[#2839](https://github.com/seata/seata/pull/2839)] fix business exception is lost when compensation succeed in saga mode - - [[#2650](https://github.com/seata/seata/pull/2650)] fix TCC and Saga branches will also parse SQL in AbstractConnectionProxy - - [[#2850](https://github.com/seata/seata/pull/2850)] Fix Saga designer rounded polylines cause page crashes - - [[#2868](https://github.com/seata/seata/pull/2868)] fix can't find AsyncEventBus dependency - - [[#2871](https://github.com/seata/seata/pull/2871)] fix get tableMeta failed when table name like 'schame'.'table' - - [[#2685](https://github.com/seata/seata/pull/2685)] fix oracle insert sql use sysdate error. - - [[#2872](https://github.com/seata/seata/pull/2872)] fix missing escape char in the primary key for the undo sql - - [[#2875](https://github.com/seata/seata/pull/2875)] fix ColumnUtils delEscape with scheme error - - - ### optimize: - - [[#2573](https://github.com/seata/seata/pull/2573)] replace Random with ThreadLocalRandom in RandomLoadBalance - - [[#2540](https://github.com/seata/seata/pull/2540)] refactor rpc request method and rpc interface - - [[#2642](https://github.com/seata/seata/pull/2642)] optimize unsafe double-checked locking in SofaRegistryServiceImpl - - [[#2561](https://github.com/seata/seata/pull/2561)] keep the same logic of get tableMeta - - [[#2591](https://github.com/seata/seata/pull/2591)] support the default timeout for zookeeper register - - [[#2601](https://github.com/seata/seata/pull/2601)] repackage spring-boot-starter - - [[#2415](https://github.com/seata/seata/pull/2415)] distinguish database behavior according to the branch type - - [[#2647](https://github.com/seata/seata/pull/2647)] remove the unused variable - - [[#2649](https://github.com/seata/seata/pull/2649)] optimize get tableMeta - - [[#2652](https://github.com/seata/seata/pull/2652)] consul supports custom port - - [[#2660](https://github.com/seata/seata/pull/2660)] modify IdWorker position to make it reasonable - - [[#2625](https://github.com/seata/seata/pull/2625)] polish testing code, replace with `Mockito.verify` - - [[#2666](https://github.com/seata/seata/pull/2666)] add using users organization logos - - [[#2680](https://github.com/seata/seata/pull/2680)] Change GlobalTransactionalInterceptor to singleton - - [[#2683](https://github.com/seata/seata/pull/2683)] optimize TccActionInterceptor log print - - [[#2477](https://github.com/seata/seata/pull/2477)] refactoring client request processing logic. - - [[#2280](https://github.com/seata/seata/pull/2280)] refactor InsertExecutor - - [[#2044](https://github.com/seata/seata/pull/2044)] optimize ColumnUtils.addEscape method performance - - [[#2730](https://github.com/seata/seata/pull/2730)] optimize get config type from configuration - - [[#2723](https://github.com/seata/seata/pull/2723)] optimize get tableMeta in postgreSql - - [[#2734](https://github.com/seata/seata/pull/2734)] change postgreSql driver scope to provide - - [[#2749](https://github.com/seata/seata/pull/2749)] optimize logger class misWrite - - [[#2751](https://github.com/seata/seata/pull/2751)] copy jdbc driver to image - - [[#2759](https://github.com/seata/seata/pull/2759)] optimized the generation rules of thread name factory - - [[#2607](https://github.com/seata/seata/pull/2607)] support insert pkValue support check - - [[#2765](https://github.com/seata/seata/pull/2765)] optimize the processing logic of XA's RM for unsupported transaction resources. - - [[#2771](https://github.com/seata/seata/pull/2771)] disable unstable unit tests - - [[#2779](https://github.com/seata/seata/pull/2779)] CollectionUtils.decodeMap method variables ConcurrentHashMap refact to HashMap - - [[#2486](https://github.com/seata/seata/pull/2486)] refactor server handle request process logic from client - - [[#2770](https://github.com/seata/seata/pull/2770)] TCC two phase method return type supports void - - [[#2788](https://github.com/seata/seata/pull/2788)] optimize server log pattern and support for colored log - - [[#2816](https://github.com/seata/seata/pull/2816)] optimize create clazz instance - - [[#2787](https://github.com/seata/seata/pull/2787)] modify workerId generation method - - [[#2776](https://github.com/seata/seata/pull/2776)] optimize paramsPlaceHolder generate by StringUtils.repeat() - - [[#2799](https://github.com/seata/seata/pull/2799)] code opt format - - [[#2829](https://github.com/seata/seata/pull/2829)] downgrade check unlock and asynchronous - - [[#2842](https://github.com/seata/seata/pull/2842)] code opt format about the sqls and typos - - [[#2242](https://github.com/seata/seata/pull/2242)] optimize PreparedStatementProxy initialization logic - - [[#2613](https://github.com/seata/seata/pull/2613)] fix typo and some coding guidelines - - - Thanks to these contributors for their code commits. Please report an unintended omission. - - [slievrly](https://github.com/slievrly) - - [a364176773](https://github.com/a364176773) - - [wangliang181230](https://github.com/wangliang181230) - - [jsbxyyx](https://github.com/jsbxyyx) - - [l81893521](https://github.com/l81893521) - - [objcoding](https://github.com/objcoding) - - [long187](https://github.com/long187) - - [CharmingRabbit](https://github.com/CharmingRabbit) - - [diguage](https://github.com/diguage) - - [helloworlde](https://github.com/helloworlde) - - [chenxi-null](https://github.com/chenxi-null) - - [ph3636](https://github.com/ph3636) - - [xianlaioy](https://github.com/xianlaioy) - - [qq925716471](https://github.com/qq925716471) - - [horoc](https://github.com/horoc) - - [XavierChengZW](https://github.com/XavierChengZW) - - [anic](https://github.com/anic) - - [fxtahe](https://github.com/fxtahe) - - [wangwengeek](https://github.com/wangwengeek) - - [yangfuhai](https://github.com/yangfuhai) - - [PeineLiang](https://github.com/PeineLiang) - - [f654c32](https://github.com/f654c32) - - [dagmom](https://github.com/dagmom) - - [caohdgege](https://github.com/caohdgege) - - [zjinlei](https://github.com/zjinlei) - - [yyjgit66](https://github.com/yyjgit66) - - [lj2018110133](https://github.com/lj2018110133) - - [wxbty](https://github.com/wxbty) - - [hsoftxl](https://github.com/hsoftxl) - - [q294881866](https://github.com/q294881866) - - [81519434](https://github.com/81519434) - - Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - - **WebSite:** https://seata.io - -
- -### 1.2.0 (2020-04-20) - - [source](https://github.com/seata/seata/archive/v1.2.0.zip) | - [binary](https://github.com/seata/seata/releases/download/v1.2.0/seata-server-1.2.0.zip) -
- Release notes - - ### Seata 1.2.0 - - Seata 1.2.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - ### feature: - - [[#2381](https://github.com/seata/seata/pull/2381)] support XA transaction mode - - [[#2206](https://github.com/seata/seata/pull/2206)] support REQUIRED、REQUIRES_NEW、SUPPORTS and NOT_SUPPORTED transaction propagation - - [[#2112](https://github.com/seata/seata/pull/2112)] support batch update and delete with multiple sql - - [[#2275](https://github.com/seata/seata/pull/2275)] support hsf on TCC transaction mode - - [[#2108](https://github.com/seata/seata/pull/2108)] support zip bzip2 7z compressor - - [[#2328](https://github.com/seata/seata/pull/2328)] support for isolated loading of mysql 5.x and 8.x jdbc drivers classes - - [[#2367](https://github.com/seata/seata/pull/2367)] add permission configuration support for Nacos 1.2 - - [[#2359](https://github.com/seata/seata/pull/2359)] support propagation.never, propagation.mandatory and transaction suspend and resume api - - [[#2418](https://github.com/seata/seata/pull/2418)] support fst serialization - - [[#2135](https://github.com/seata/seata/pull/2135)] support SPI scope - - [[#2370](https://github.com/seata/seata/pull/2370)] support failureHandler implement can be read from the container - - [[#2481](https://github.com/seata/seata/pull/2481)] support the max wait configuration for db - - [[#2379](https://github.com/seata/seata/pull/2379)] support custom service name when registering with Nacos - - [[#2308](https://github.com/seata/seata/pull/2308)] add switch to control whether to register branch on Saga transaction mode - - [[#2301](https://github.com/seata/seata/pull/2301)] support default expr and nextval for postgresql - - - ### bugfix: - - [[#2575](https://github.com/seata/seata/pull/2575)] fix executeBatch can not get targetSql in Statement mode - - [[#2283](https://github.com/seata/seata/pull/2283)] fix oracle get tableMeta fail - - [[#2312](https://github.com/seata/seata/pull/2312)] fix the judgement of configuration condition - - [[#2309](https://github.com/seata/seata/pull/2309)] fix timestamp deserialize lost nano - - [[#2292](https://github.com/seata/seata/pull/2292)] fix some configuration not converted to camel style - - [[#2306](https://github.com/seata/seata/pull/2306)] fix deprecated maven prerequisites - - [[#2287](https://github.com/seata/seata/pull/2287)] fix connection context can't be remove when global lock retry - - [[#2361](https://github.com/seata/seata/pull/2361)] fix the error configuration name - - [[#2333](https://github.com/seata/seata/pull/2333)] fix wrong exception information when rollback fails due to dirty data - - [[#2390](https://github.com/seata/seata/pull/2390)] fix configuration item containing spaces - - [[#2408](https://github.com/seata/seata/pull/2408)] fix missing sequence in undo_log table - - [[#2391](https://github.com/seata/seata/pull/2391)] fix configuration exceptions lead to increased CPU usage - - [[#2427](https://github.com/seata/seata/pull/2427)] fix StringUtils.toString(o) StackOverflowError - - [[#2384](https://github.com/seata/seata/pull/2384)] fix StateMachineRepository#getStateMachineById will replace the last version in cache - - [[#2323](https://github.com/seata/seata/pull/2323)] fix wrong proxy of datasource bean - - [[#2466](https://github.com/seata/seata/pull/2466)] fix memory visibility of active attribute in file mode - - [[#2349](https://github.com/seata/seata/pull/2349)] fix insert sql primary key value support check - - [[#2479](https://github.com/seata/seata/pull/2479)] fix postgresql schema when not use lowerCase - - [[#2449](https://github.com/seata/seata/pull/2449)] fix can't get table structure when startup - - [[#2505](https://github.com/seata/seata/pull/2505)] fix bug of session store path value judgment - - [[#2456](https://github.com/seata/seata/pull/2456)] fix server encode request error - - [[#2495](https://github.com/seata/seata/pull/2495)] fix the NPE and reduce the request when lockkey is null - - [[#2490](https://github.com/seata/seata/pull/2490)] fix RpcContext.addResource when resource is null - - [[#2419](https://github.com/seata/seata/pull/2419)] fix http testcase run failed - - [[#2535](https://github.com/seata/seata/pull/2535)] fix wrong configuration name in config.txt - - [[#2524](https://github.com/seata/seata/pull/2524)] registration service configuration missing and inconsistent - - [[#2473](https://github.com/seata/seata/pull/2473)] fix flush condition of disk in file mode - - [[#2455](https://github.com/seata/seata/pull/2455)] fix child module can't execute copyright and checkstyle inspection - - - ### optimize: - - [[#2409](https://github.com/seata/seata/pull/2409)] reduce the db and network request when undoLog or lockKey is empty - - [[#2329](https://github.com/seata/seata/pull/2329)] separate the different storage pattern processing logic - - [[#2354](https://github.com/seata/seata/pull/2354)] optimize the unsupported listener logic for spring cloud config - - [[#2320](https://github.com/seata/seata/pull/2320)] optimize protostuff and kryo serialize timestamp - - [[#2307](https://github.com/seata/seata/pull/2307)] optimize transaction context switch logic when switch transaction mode - - [[#2364](https://github.com/seata/seata/pull/2364)] optimize generated instances that were not actually used when the class was loaded - - [[#2368](https://github.com/seata/seata/pull/2368)] add zk missing configuration - - [[#2351](https://github.com/seata/seata/pull/2351)] add get local global status - - [[#2529](https://github.com/seata/seata/pull/2529)] optimize druid parameter - - [[#2288](https://github.com/seata/seata/pull/2288)] codecov.yml ignore mock test - - [[#2297](https://github.com/seata/seata/pull/2297)] remove duplicated dependency - - [[#2336](https://github.com/seata/seata/pull/2336)] add using organization logos - - [[#2348](https://github.com/seata/seata/pull/2348)] remove redundant configuration - - [[#2362](https://github.com/seata/seata/pull/2362)] optimize stackTraceLogger param - - [[#2382](https://github.com/seata/seata/pull/2382)] optimize RegistryFactory singleton pattern and RegistryType judgement - - [[#2400](https://github.com/seata/seata/pull/2400)] optimize the magic num of date at UUIDGenerator - - [[#2397](https://github.com/seata/seata/pull/2397)] fix typo - - [[#2407](https://github.com/seata/seata/pull/2407)] inaccurate judgment may be lead to NPE - - [[#2402](https://github.com/seata/seata/pull/2402)] optimize the rm and tm register log - - [[#2422](https://github.com/seata/seata/pull/2422)] add link of script in document - - [[#2440](https://github.com/seata/seata/pull/2440)] optimize contact us and startup log - - [[#2445](https://github.com/seata/seata/pull/2445)] optimize the class registration method for kryo and fst - - [[#2372](https://github.com/seata/seata/pull/2372)] refactor lock store sql with SPI - - [[#2453](https://github.com/seata/seata/pull/2453)] optimize unnecessary server configuration item - - [[#2369](https://github.com/seata/seata/pull/2369)] refactor log store sql with SPI - - [[#2526](https://github.com/seata/seata/pull/2526)] optimize spring-boot startup log - - [[#2530](https://github.com/seata/seata/pull/2530)] remove use connPool - - [[#2489](https://github.com/seata/seata/pull/2489)] optimize exceptionHandler's method signature - - [[#2494](https://github.com/seata/seata/pull/2494)] reduce the redundant code - - [[#2523](https://github.com/seata/seata/pull/2523)] optimize abnormal global transaction's output logs by frequency - - [[#2549](https://github.com/seata/seata/pull/2549)] optimize the exception log for ZookeeperConfiguration - - [[#2558](https://github.com/seata/seata/pull/2558)] optimize config and server module log - - [[#2464](https://github.com/seata/seata/pull/2464)] enhance Saga transaction editor - - [[#2553](https://github.com/seata/seata/pull/2553)] add some notes about using scripts - - Thanks to these contributors for their code commits. Please report an unintended omission. - - [slievrly](https://github.com/slievrly) - - [a364176773](https://github.com/a364176773) - - [ph3636](https://github.com/ph3636) - - [lightClouds917](https://github.com/lightClouds917) - - [l81893521](https://github.com/l81893521) - - [jsbxyyx](https://github.com/jsbxyyx) - - [objcoding](https://github.com/objcoding) - - [CharmingRabbit](https://github.com/CharmingRabbit) - - [xingfudeshi](https://github.com/xingfudeshi) - - [lovepoem](https://github.com/lovepoem) - - [SevenSecondsOfMemory](https://github.com/SevenSecondsOfMemory ) - - [zjinlei](https://github.com/zjinlei) - - [ggndnn](https://github.com/ggndnn) - - [tauntongo](https://github.com/tauntongo) - - [threefish](https://github.com/threefish) - - [helloworlde](https://github.com/helloworlde) - - [long187](https://github.com/long187) - - [jaspercloud](https://github.com/jaspercloud) - - [dk-lockdown](https://github.com/dk-lockdown) - - [wxbty](https://github.com/wxbty) - - [sharajava](https://github.com/sharajava) - - [ppj19891020](https://github.com/ppj19891020) - - [YuKongEr](https://github.com/YuKongEr) - - [Zh1Cheung](https://github.com/Zh1Cheung) - - [wangwei-ying](https://github.com/wangwei-ying) - - [mxszs](https://github.com/mxszs) - - [q294881866](https://github.com/q294881866) - - [HankDevelop](https://github.com/HankDevelop) - - Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - - **WebSite:** https://seata.io - -
- -### 1.1.0 (2020-02-19) - - [source](https://github.com/seata/seata/archive/v1.1.0.zip) | - [binary](https://github.com/seata/seata/releases/download/v1.1.0/seata-server-1.1.0.zip) -
- Release notes - - ### Seata 1.1.0 - - Seata 1.1.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - ### feature: - - [[#2200](https://github.com/seata/seata/pull/2200)] support postgresql(client and server) - - [[#1746](https://github.com/seata/seata/pull/1746)] integrate with httpClient - - [[#2240](https://github.com/seata/seata/pull/2240)] support custom saga transaction recovery strategy on transaction timeout - - [[#1693](https://github.com/seata/seata/pull/1693)] support for druid class isolation loading - - [[#2245](https://github.com/seata/seata/pull/2245)] zookeeper digest support - - [[#2239](https://github.com/seata/seata/pull/2239)] compatibility dubbo 2.7.4+ - - [[#2203](https://github.com/seata/seata/pull/2203)] support nacos configuration group - - [[#2086](https://github.com/seata/seata/pull/2086)] support apollo configuration namespace - - [[#2106](https://github.com/seata/seata/pull/2106)] support FastThreadLocalContextCore - - [[#1703](https://github.com/seata/seata/pull/1703)] create sql parser SPI and a druid type sql parser - - [[#2151](https://github.com/seata/seata/pull/2151)] Saga provide a switch to skip branch report on branch success - - - ### bugfix: - - [[#2270](https://github.com/seata/seata/pull/2270)] fix worker size not support enum type and some minor problem - - [[#2258](https://github.com/seata/seata/pull/2258)] fix channelHandler not sharable - - [[#2261](https://github.com/seata/seata/pull/2261)] fix ApplicationContext has not been refreshed - - [[#2262](https://github.com/seata/seata/pull/2262)] fix nacos script set group error - - [[#2249](https://github.com/seata/seata/pull/2249)] fix saga statemachine status incorrect on register branch failed - - [[#2262](https://github.com/seata/seata/pull/2262)] fix nacos script set group error - - [[#2126](https://github.com/seata/seata/pull/2126)] fix escape characters for column and table names - - [[#2234](https://github.com/seata/seata/pull/2234)] fix type error when fastjson deserialize long type - - [[#2237](https://github.com/seata/seata/pull/2237)] fix DefaultCoordinatorTest failed in Windows OS - - [[#2233](https://github.com/seata/seata/pull/2233)] fix fastjson undo filter tableMeta - - [[#2172](https://github.com/seata/seata/pull/2172)] fix configuration center can't read configuration using SpringCloudConfig - - [[#2217](https://github.com/seata/seata/pull/2217)] correct wrong property names in seata-spring-boot-starter - - [[#2219](https://github.com/seata/seata/pull/2219)] fix the value of disableGlobalTransaction not being read correctly - - [[#2187](https://github.com/seata/seata/pull/2187)] fix the wrong rollback sequence caused by the same record request from different transaction branches on different servers - - [[#2175](https://github.com/seata/seata/pull/2175)] fix direct buffer OOM - - [[#2210](https://github.com/seata/seata/pull/2210)] fix retry expired commit and rollback globalSession can't be removed - - [[#2179](https://github.com/seata/seata/pull/2179)] fix type casting problem when using redis as registry - - [[#2192](https://github.com/seata/seata/pull/2192)] fix override eureka getHostName() return ipAddress - - [[#2198](https://github.com/seata/seata/pull/2198)] fix global lock not released when rollback retry timeout - - [[#2167](https://github.com/seata/seata/pull/2167)] fix saga concurrent asynchronous execution with duplicate primary key xid - - [[#2185](https://github.com/seata/seata/pull/2185)] fix issue of judgement container in kubernetes - - [[#2145](https://github.com/seata/seata/pull/2145)] fix Saga report branch status incorrect when service retried succeed - - [[#2113](https://github.com/seata/seata/pull/2113)] fix when branchRollback failed, it will trigger retry of multi-tc - - - ### optimize: - - [[#2255](https://github.com/seata/seata/pull/2255)] optimize some default configuration value - - [[#2230](https://github.com/seata/seata/pull/2230)] unify the config style and keep defaults consistent - - [[#1935](https://github.com/seata/seata/pull/1935)] some about rpc optimize - - [[#2215](https://github.com/seata/seata/pull/2215)] optimize handing saga transaction timeout - - [[#2227](https://github.com/seata/seata/pull/2227)] separate tc In/Outbound interface - - [[#2033](https://github.com/seata/seata/pull/2033)] an optimization about DefaultRemotingParser - - [[#1688](https://github.com/seata/seata/pull/1688)] reduce unnecessary dependences in client side - - [[#2134](https://github.com/seata/seata/pull/2134)] separate the different transaction pattern processing logic - - [[#2224](https://github.com/seata/seata/pull/2224)] optimize ContextCoreLoader code style - - [[#2171](https://github.com/seata/seata/pull/2171)] optimize script and add script usage demo - - [[#2208](https://github.com/seata/seata/pull/2208)] replace getDbType with LoadLevel name - - [[#2182](https://github.com/seata/seata/pull/2182)] optimize configuration item prefix judgment - - [[#2211](https://github.com/seata/seata/pull/2211)] optimize RootContext code style - - [[#2140](https://github.com/seata/seata/pull/2140)] optimize GzipUtil code style - - [[#2209](https://github.com/seata/seata/pull/2209)] refactor seata-discovery more readable - - [[#2055](https://github.com/seata/seata/pull/2055)] refactor tableMetaCache and undoLogManager with SPI - - [[#2184](https://github.com/seata/seata/pull/2184)] refactor seata-config more readable - - [[#2095](https://github.com/seata/seata/pull/2095)] refactor of auto proxying of datasource - - [[#2178](https://github.com/seata/seata/pull/2178)] saga statemachine designer add default properties for catch node - - [[#2103](https://github.com/seata/seata/pull/2103)] optimize tcc module code style - - [[#2125](https://github.com/seata/seata/pull/2125)] change the package path of MySQL recognizer - - [[#2176](https://github.com/seata/seata/pull/2176)] fix typos - - [[#2156](https://github.com/seata/seata/pull/2156)] refactor sql parser type druid as constant - - [[#2170](https://github.com/seata/seata/pull/2170)] enhance test coverage of seata common - - [[#2139](https://github.com/seata/seata/pull/2139)] gracefully close resources - - [[#2097](https://github.com/seata/seata/pull/2097)] use serializer package name instead of codec - - [[#2159](https://github.com/seata/seata/pull/2159)] optimize spring module code style - - [[#2036](https://github.com/seata/seata/pull/2036)] optimize Dubbo parser - - [[#2062](https://github.com/seata/seata/pull/2062)] optimize seata-rm-datasource module code style - - [[#2146](https://github.com/seata/seata/pull/2146)] optimize log specifications - - [[#2038](https://github.com/seata/seata/pull/2038)] simplify to make seata-common more readable - - [[#2120](https://github.com/seata/seata/pull/2120)] fix typos - - [[#2078](https://github.com/seata/seata/pull/2078)] enhance oracle table meta cache code coverage - - [[#2115](https://github.com/seata/seata/pull/2115)] fix typos - - [[#2099](https://github.com/seata/seata/pull/2099)] optimize tm module code style - - Thanks to these contributors for their code commits. Please report an unintended omission. - - [slievrly](https://github.com/slievrly) - - [xingfudeshi](https://github.com/xingfudeshi) - - [objcoding](https://github.com/objcoding) - - [long187](https://github.com/long187) - - [zjinlei](https://github.com/zjinlei) - - [ggndnn](https://github.com/ggndnn) - - [lzf971107](https://github.com/lzf971107) - - [CvShrimp](https://github.com/CvShrimp) - - [l81893521](https://github.com/l81893521) - - [ph3636](https://github.com/ph3636) - - [koonchen](https://github.com/koonchen) - - [leizhiyuan](https://github.com/leizhiyuan) - - [a364176773](https://github.com/a364176773) - - [caioguedes](https://github.com/caioguedes) - - [helloworlde](https://github.com/helloworlde) - - [wxbty](https://github.com/wxbty) - - [bao-hp](https://github.com/bao-hp) - - [guojingyinan219](https://github.com/guojingyinan219) - - [CharmingRabbit](https://github.com/CharmingRabbit) - - [jaspercloud](https://github.com/jaspercloud) - - [jsbxyyx](https://github.com/jsbxyyx) - - Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - - **WebSite:** https://seata.io - -
- -### 1.0.0 (2019-12-21) - - [source](https://github.com/seata/seata/archive/v1.0.0.zip) | - [binary](https://github.com/seata/seata/releases/download/v1.0.0/seata-server-1.0.0.zip) -
- Release notes - - ### Seata 1.0.0 - - Seata 1.0.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### feature: - - - [[#1966](https://github.com/seata/seata/pull/1966)] add single send request for client - - [[#2004](https://github.com/seata/seata/pull/2004)] add config center synchronization script - - [[#1997](https://github.com/seata/seata/pull/1997)] provides a tool for generating graphics that show the state machine execution path - - [[#1992](https://github.com/seata/seata/pull/1992)] support dynamic disable - - [[#1898](https://github.com/seata/seata/pull/1898)] support dynamic config - - [[#1983](https://github.com/seata/seata/pull/1983)] add hessian codec for rpc serialization - - [[#1960](https://github.com/seata/seata/pull/1960)] Provide a visual graph designer for Seata Saga StateMachine based on GGEditor - - [[#1900](https://github.com/seata/seata/pull/1900)] Saga state language support "Retry" service when error occurred - - [[#1885](https://github.com/seata/seata/pull/1885)] add configuration for build docker image in server module - - [[#1914](https://github.com/seata/seata/pull/1914)] support where condition exists for Oracle - - [[#1878](https://github.com/seata/seata/pull/1878)] support exists in where condition - - [[#1871](https://github.com/seata/seata/pull/1871)] adapt springcloud-alibaba-seata autoconfig - - [[#1844](https://github.com/seata/seata/pull/1844)] StateMachine ServiceTask supports asynchronous execution - - [[#1742](https://github.com/seata/seata/pull/1742)] add seata-spring-boot-starter - - [[#1460](https://github.com/seata/seata/pull/1460)] support gzip compressor - - [[#1492](https://github.com/seata/seata/pull/1492)] support gRpc - - #### bugfix: - - - [[#2066](https://github.com/seata/seata/pull/2066)] fix thread unsafe which missing double check when initial eureka client - - [[#2059](https://github.com/seata/seata/pull/2059)] fix repeated rollback caused by asynchronous rollback thread - - [[#2050](https://github.com/seata/seata/pull/2050)] fix if add configListener but dataId not exist, it will throw NPE - - [[#2053](https://github.com/seata/seata/pull/2053)] fix when tableName is keyword, the insert operation will get afterImage fail - - [[#2054](https://github.com/seata/seata/pull/2054)] fix RetryRollbackingSessionManager lost Rollbacking - - [[#2043](https://github.com/seata/seata/pull/2043)] fix startup failure when dynamic proxy is turned on and use druid-spring-boot-starter - - [[#1668](https://github.com/seata/seata/pull/1668)] fix sql statement escape symbol - - [[#2029](https://github.com/seata/seata/pull/2029)] fix seata-spring-boot-starter does not work - - [[#2037](https://github.com/seata/seata/pull/2037)] fix mysql connection unable to release - - [[#2032](https://github.com/seata/seata/pull/2032)] fix Etcd3Configuration FILE_CONFIG reference incorrect - - [[#1929](https://github.com/seata/seata/pull/1929)] fix duplicated table meta cache key - - [[#1996](https://github.com/seata/seata/pull/1996)] fix auto proxying of datasource which has final modifier - - [[#2001](https://github.com/seata/seata/pull/2001)] replace deprecated jvm args - - [[#1984](https://github.com/seata/seata/pull/1984)] fix presuppose environment variable and replace base image for tool - - [[#1978](https://github.com/seata/seata/pull/1978)] fix FileTransactionStoreManagerTest failed on wins OS - - [[#1953](https://github.com/seata/seata/pull/1953)] fix get table meta failed with catalog - - [[#1973](https://github.com/seata/seata/pull/1973)] fix error of get server port in container - - [[#1905](https://github.com/seata/seata/pull/1905)] solve the lock_key length problem - - [[#1927](https://github.com/seata/seata/pull/1927)] fix class with private access constructors should not be loaded by SPI. - - [[#1961](https://github.com/seata/seata/pull/1961)] fix travis-ci exceeded the maximum log length - - [[#1893](https://github.com/seata/seata/pull/1893)] fix saga dose not delete branches when transaction ended - - [[#1932](https://github.com/seata/seata/pull/1932)] fix issue of doesn't match environment when build docker image - - [[#1912](https://github.com/seata/seata/pull/1912)] fix string.format() method formatting error - - [[#1917](https://github.com/seata/seata/pull/1917)] fix NullPointerException in DB mock during CI - - [[#1909](https://github.com/seata/seata/pull/1909)] fix xidInterceptorType is null - - [[#1902](https://github.com/seata/seata/pull/1902)] fix NPE in UndoExecutorFactory - - [[#1789](https://github.com/seata/seata/pull/1789)] fix xid header lowercase - - [[#1889](https://github.com/seata/seata/pull/1889)] fix register branch thread hang on tcc mode - - [[#1813](https://github.com/seata/seata/pull/1813)] fix TCC does not support cross-service - - [[#1825](https://github.com/seata/seata/pull/1825)] fix global status inconsistent when rollback and branch register are concurrent - - [[#1850](https://github.com/seata/seata/pull/1850)] fix server restart not recover max sessionId on db mode - - [[#1879](https://github.com/seata/seata/pull/1879)] fix jdbc parameter set null - - [[#1874](https://github.com/seata/seata/pull/1874)] fix when write the new file throw ClosedChannelException - - [[#1863](https://github.com/seata/seata/pull/1863)] fix the other of column type cause rollback fail - - [[#1837](https://github.com/seata/seata/pull/1837)] fix saga ExpressionEvaluator not support null value - - [[#1810](https://github.com/seata/seata/pull/1810)] fix statemachine def can't store to db and provide query the state logs - - [[#1834](https://github.com/seata/seata/pull/1834)] fix StateInstance log can't record output parameters - - [[#1856](https://github.com/seata/seata/pull/1856)] fix protostuff undo log get default content - - [[#1845](https://github.com/seata/seata/pull/1845)] fix when branchCommit failed,it will trigger retry of multi-tc and throw npe - - [[#1858](https://github.com/seata/seata/pull/1858)] fix Global transaction does not work - - [[#1846](https://github.com/seata/seata/pull/1846)] fix multi-thread concurrent add listener problem - - [[#1839](https://github.com/seata/seata/pull/1839)] fix filter repeated lock - - [[#1768](https://github.com/seata/seata/pull/1768)] fix problem when set useInformationSchema true and table name was keyword - - [[#1796](https://github.com/seata/seata/pull/1796)] fix unexcepted exception can roll back - - [[#1805](https://github.com/seata/seata/pull/1805)] fix connectionproxy prepareStatement not in global transaction - - [[#1780](https://github.com/seata/seata/pull/1780)] fix can't use select for update in oracle - - [[#1802](https://github.com/seata/seata/pull/1802)] changing HashMap to LinkedHashMap for deterministic iterations - - [[#1793](https://github.com/seata/seata/pull/1793)] fix auto proxy for multiple-datasource does not work - - [[#1788](https://github.com/seata/seata/pull/1788)] fix mysql can not get primary key value - - [[#1764](https://github.com/seata/seata/pull/1764)] fix jdk 11 remoteAddress is null - - [[#1778](https://github.com/seata/seata/pull/1778)] fix clean up resources in time to avoid mutual influence between unit tests - - [[#1777](https://github.com/seata/seata/pull/1777)] fix DeleteExecutor buildBeforeImageSQL keyword checker by db type - - #### optimize: - - - [[#2068](https://github.com/seata/seata/pull/2068)] optimize get database connection - - [[#2056](https://github.com/seata/seata/pull/2056)] remove non-javadoc element - - [[#1775](https://github.com/seata/seata/pull/1775)] optimize datasource manager branch rollback exception log - - [[#2000](https://github.com/seata/seata/pull/2000)] classify script to correspond directory - - [[#2007](https://github.com/seata/seata/pull/2007)] enhance test coverage of seata common - - [[#1969](https://github.com/seata/seata/pull/1969)] add ops script for Docker-Compose, Kubernetes and Helm - - [[#1967](https://github.com/seata/seata/pull/1967)] Add Dockerfile - - [[#2018](https://github.com/seata/seata/pull/2018)] optimize about ConfigFuture - - [[#2020](https://github.com/seata/seata/pull/2020)] optimize saga log output - - [[#1975](https://github.com/seata/seata/pull/1975)] Flatten Saga nested transactions - - [[#1980](https://github.com/seata/seata/pull/1980)] show the applicationId when register TM - - [[#1994](https://github.com/seata/seata/pull/1994)] rename zk configuration root path. - - [[#1990](https://github.com/seata/seata/pull/1990)] add netty config constant keys. - - [[#1979](https://github.com/seata/seata/pull/1979)] optimize get select for update recognizer - - [[#1957](https://github.com/seata/seata/pull/1957)] load keywordChecker through SPI - - [[#1956](https://github.com/seata/seata/pull/1956)] modify no available server error more clearly, and fixed NP - - [[#1958](https://github.com/seata/seata/pull/1958)] transform desinger json to statemachine standard json - - [[#1951](https://github.com/seata/seata/pull/1951)] add using organization logo - - [[#1950](https://github.com/seata/seata/pull/1950)] leak of error trace while handleAsyncCommitting - - [[#1931](https://github.com/seata/seata/pull/1931)] nacos-config.py support namespace - - [[#1938](https://github.com/seata/seata/pull/1938)] optimize the speed when batch insert or batch update - - [[#1930](https://github.com/seata/seata/pull/1930)] reduce HashMap initial size - - [[#1919](https://github.com/seata/seata/pull/1919)] force check code style - - [[#1918](https://github.com/seata/seata/pull/1918)] optimize assert throw exception - - [[#1911](https://github.com/seata/seata/pull/1911)] javadoc should be used for classes, class variables and methods. - - [[#1920](https://github.com/seata/seata/pull/1920)] use iterator to remove timeout future. - - [[#1907](https://github.com/seata/seata/pull/1907)] encapsulation determines the supported database type - - [[#1903](https://github.com/seata/seata/pull/1903)] batch query branchSession by xid list - - [[#1910](https://github.com/seata/seata/pull/1910)] all Override methods must be annotated with [@override](https://github.com/override) - - [[#1906](https://github.com/seata/seata/pull/1906)] add exception system exit code when rpcServer init. - - [[#1897](https://github.com/seata/seata/pull/1897)] remove clientTest it's not use - - [[#1883](https://github.com/seata/seata/pull/1883)] restructure SQLRecognizer and UndoExecutor - - [[#1890](https://github.com/seata/seata/pull/1890)] reformat saga module - - [[#1798](https://github.com/seata/seata/pull/1798)] improving method format performance - - [[#1884](https://github.com/seata/seata/pull/1884)] optimize auto closeable - - [[#1869](https://github.com/seata/seata/pull/1869)] add phase one successful reporting switch - - [[#1842](https://github.com/seata/seata/pull/1842)] add some init script - - [[#1838](https://github.com/seata/seata/pull/1838)] simplify and groom configuration items - - [[#1866](https://github.com/seata/seata/pull/1866)] server lack of error trace - - [[#1867](https://github.com/seata/seata/pull/1867)] optimization of seata-spring-boot-starter - - [[#1817](https://github.com/seata/seata/pull/1817)] add unit test for seata-tm module - - [[#1823](https://github.com/seata/seata/pull/1823)] reduce server rpc with db - - [[#1835](https://github.com/seata/seata/pull/1835)] SagaTransactionalTemplate provide reloadTransaction method - - [[#1861](https://github.com/seata/seata/pull/1861)] optimize no primary key output log - - [[#1836](https://github.com/seata/seata/pull/1836)] change "IsPersist" property value type from String to Boolean - - [[#1824](https://github.com/seata/seata/pull/1824)] remove deprecated JVM arguments in Java 11 - - [[#1820](https://github.com/seata/seata/pull/1820)] adjust check style - - [[#1806](https://github.com/seata/seata/pull/1806)] format error log - - [[#1815](https://github.com/seata/seata/pull/1815)] update codecov.yml - - [[#1811](https://github.com/seata/seata/pull/1811)] adjust codecov configuration - - [[#1799](https://github.com/seata/seata/pull/1799)] reduce unnecessary synchronized - - [[#1674](https://github.com/seata/seata/pull/1674)] increase rm code coverage by db mock - - [[#1710](https://github.com/seata/seata/pull/1710)] add prefix counter for NamedThreadFactory - - [[#1790](https://github.com/seata/seata/pull/1790)] format seata server register eureka instance id - - [[#1760](https://github.com/seata/seata/pull/1760)] put message to logQueue - - [[#1787](https://github.com/seata/seata/pull/1787)] make rpc remoting log easier to read - - [[#1786](https://github.com/seata/seata/pull/1786)] simplify code - - [[#1766](https://github.com/seata/seata/pull/1766)] remove unused method - - [[#1770](https://github.com/seata/seata/pull/1770)] string splice and release lock - - Thanks to these contributors for their code commits. Please report an unintended omission. - - - [slievrly](https://github.com/slievrly) - - [long187](https://github.com/long187) - - [jsbxyyx](https://github.com/jsbxyyx) - - [l81893521](https://github.com/l81893521) - - [helloworlde](https://github.com/helloworlde) - - [xingfudeshi](https://github.com/xingfudeshi) - - [zjinlei](https://github.com/zjinlei) - - [CharmingRabbit](https://github.com/CharmingRabbit) - - [objcoding](https://github.com/objcoding) - - [cmonkey](https://github.com/cmonkey) - - [lzf971107](https://github.com/lzf971107) - - [ggndnn](https://github.com/ggndnn) - - [lightClouds917](https://github.com/lightClouds917) - - [ruqinhu](https://github.com/ruqinhu) - - [yuhuangbin](https://github.com/yuhuangbin) - - [anrror](https://github.com/anrror) - - [a364176773](https://github.com/a364176773) - - [caohdgege](https://github.com/caohdgege) - - [contextshuffling](https://github.com/contextshuffling) - - [echooymxq](https://github.com/echooymxq) - - [github-ygy](https://github.com/github-ygy) - - [iapplejohn](https://github.com/iapplejohn) - - [jKill](https://github.com/jKill) - - [Justice-love](https://github.com/Justice-love) - - [lovepoem](https://github.com/lovepoem) - - [niaoshuai](https://github.com/niaoshuai) - - [ph3636](https://github.com/ph3636) - - [wangwei-ying](https://github.com/wangwei-ying) - - [whjjay](https://github.com/whjjay) - - [yangfuhai](https://github.com/yangfuhai) - - [zhongfuhua](https://github.com/zhongfuhua) - - [lizwmaster](https://github.com/lizwmaster) - - Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.9.0 (2019-10-16) - - [source](https://github.com/seata/seata/archive/v0.9.0.zip) | - [binary](https://github.com/seata/seata/releases/download/v0.9.0/seata-server-0.9.0.zip) -
- Release notes - - ### Seata 0.9.0 - - Seata 0.9.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### feature: - - [[#1608](https://github.com/seata/seata/pull/1608)] Saga implementation base on state machine - - [[#1625](https://github.com/seata/seata/pull/1625)] support custom config and registry type - - [[#1656](https://github.com/seata/seata/pull/1656)] support spring cloud config - - [[#1689](https://github.com/seata/seata/pull/1689)] support -e startup parameter for specifying the environment name - - [[#1739](https://github.com/seata/seata/pull/1739)] support retry when tm commit or rollback failed - - - #### bugfix: - - [[#1605](https://github.com/seata/seata/pull/1605)] fix deadlocks that can be caused by object locks and global locks and optimize the granularity of locks - - [[#1685](https://github.com/seata/seata/pull/1685)] fix pk too long in lock table on db mode and optimize error log - - [[#1691](https://github.com/seata/seata/pull/1691)] fix can't access private member of DruidDataSourceWrapper - - [[#1699](https://github.com/seata/seata/pull/1699)] fix use 'in' and 'between' in where condition for Oracle and Mysql - - [[#1713](https://github.com/seata/seata/pull/1713)] fix LockManagerTest.concurrentUseAbilityTest assertion condition - - [[#1720](https://github.com/seata/seata/pull/1720)] fix can't refresh table meta data for oracle - - [[#1729](https://github.com/seata/seata/pull/1729)] fix oracle batch insert error - - [[#1735](https://github.com/seata/seata/pull/1735)] clean xid when tm commit or rollback failed - - [[#1749](https://github.com/seata/seata/pull/1749)] fix undo support oracle table meta cache - - [[#1751](https://github.com/seata/seata/pull/1751)] fix memory lock is not released due to hash conflict - - [[#1761](https://github.com/seata/seata/pull/1761)] fix oracle rollback failed when the table has null Blob Clob value - - [[#1759](https://github.com/seata/seata/pull/1759)] fix saga service method not support interface type parameter - - [[#1401](https://github.com/seata/seata/pull/1401)] fix the first registration resource is null when RM starts - -​ - #### optimize: - - [[#1701](https://github.com/seata/seata/pull/1701)] remove unused imports - - [[#1705](https://github.com/seata/seata/pull/1705)] Based on Java5 optimization - - [[#1706](https://github.com/seata/seata/pull/1706)] optimize inner class to static class - - [[#1707](https://github.com/seata/seata/pull/1707)] default charset use StandardCharsets.UTF_8 instead - - [[#1712](https://github.com/seata/seata/pull/1712)] abstract undolog manager class - - [[#1722](https://github.com/seata/seata/pull/1722)] simplify to make codes more readable - - [[#1726](https://github.com/seata/seata/pull/1726)] format log messages - - [[#1738](https://github.com/seata/seata/pull/1738)] add server's jvm parameters - - [[#1743](https://github.com/seata/seata/pull/1743)] improve the efficiency of the batch log - - [[#1747](https://github.com/seata/seata/pull/1747)] use raw types instead of boxing types - - [[#1750](https://github.com/seata/seata/pull/1750)] abstract tableMeta cache class - - [[#1755](https://github.com/seata/seata/pull/1755)] enhance test coverage of seata-common module - - [[#1756](https://github.com/seata/seata/pull/1756)] security: upgrade jackson to avoid security vulnerabilities - - [[#1657](https://github.com/seata/seata/pull/1657)] optimize the problem of large direct buffer when file rolling in file storage mode - - -​ - Thanks to these contributors for their code commits. Please report an unintended omission. -​ - - [slievrly](https://github.com/slievrly) - - [long187](https://github.com/long187) - - [ggndnn](https://github.com/ggndnn) - - [xingfudeshi](https://github.com/xingfudeshi) - - [BeiKeJieDeLiuLangMao](https://github.com/BeiKeJieDeLiuLangMao) - - [zjinlei](https://github.com/zjinlei) - - [cmonkey](https://github.com/cmonkey) - - [jsbxyyx](https://github.com/jsbxyyx) - - [zaqweb](https://github.com/zaqweb) - - [tjnettech](https://github.com/tjnettech) - - [l81893521](https://github.com/l81893521) - - [abel533](https://github.com/abel533) - - [suhli](https://github.com/suhli) - - [github-ygy](https://github.com/github-ygy) - - [worstenemy](https://github.com/worstenemy) - - [caioguedes](https://github.com/caioguedes) - - Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- - -### 0.8.1 (2019-09-18) - - [source](https://github.com/seata/seata/archive/v0.8.1.zip) | - [binary](https://github.com/seata/seata/releases/download/v0.8.1/seata-server-0.8.1.zip) -
- Release notes - - ### Seata 0.8.1 - - Seata 0.8.1 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### feature: - - [[#1598](https://github.com/seata/seata/pull/1598)] support profile to use absolute path - - [[#1617](https://github.com/seata/seata/pull/1617)] support profile’s(registry.conf) name configurable - - [[#1418](https://github.com/seata/seata/pull/1418)] support undo_log kryo serializer - - [[#1489](https://github.com/seata/seata/pull/1489)] support protobuf maven plugin - - [[#1437](https://github.com/seata/seata/pull/1437)] support kryo codec - - [[#1478](https://github.com/seata/seata/pull/1478)] support db mock - - [[#1512](https://github.com/seata/seata/pull/1512)] extended support for mysql and oracle multiple insert batch syntax - - [[#1496](https://github.com/seata/seata/pull/1496)] support auto proxy of DataSource - - - #### bugfix: - - [[#1646](https://github.com/seata/seata/pull/1646)] fix selectForUpdate lockQuery exception in file mode - - [[#1572](https://github.com/seata/seata/pull/1572)] fix get tablemeta fail in oracle when table name was lower case - - [[#1663](https://github.com/seata/seata/pull/1663)] fix get tablemeta fail when table name was keyword - - [[#1666](https://github.com/seata/seata/pull/1666)] fix restore connection's autocommit - - [[#1643](https://github.com/seata/seata/pull/1643)] fix serialize and deserialize in java.sql.Blob, java.sql.Clob - - [[#1628](https://github.com/seata/seata/pull/1628)] fix oracle support ROWNUM query - - [[#1552](https://github.com/seata/seata/pull/1552)] fix BufferOverflow when BranchSession size too large - - [[#1609](https://github.com/seata/seata/pull/1609)] fix thread unsafe of oracle keyword checker - - [[#1599](https://github.com/seata/seata/pull/1599)] fix thread unsafe of mysql keyword checker - - [[#1607](https://github.com/seata/seata/pull/1607)] fix NoSuchMethodError when the version of druid used < 1.1.3 - - [[#1581](https://github.com/seata/seata/pull/1581)] fix missing some length in GlobalSession and FileTransactionStoreManager - - [[#1594](https://github.com/seata/seata/pull/1594)] fix nacos's default namespace - - [[#1550](https://github.com/seata/seata/pull/1550)] fix calculate BranchSession size missing xidBytes.length - - [[#1558](https://github.com/seata/seata/pull/1558)] fix NPE when the rpcMessage's body is null - - [[#1505](https://github.com/seata/seata/pull/1505)] fix bind public network address listen failed - - [[#1539](https://github.com/seata/seata/pull/1539)] fix nacos namespace setting does not take effect - - [[#1537](https://github.com/seata/seata/pull/1537)] fix nacos-config.txt missing store.db.driver-class-name property - - [[#1522](https://github.com/seata/seata/pull/1522)] fix ProtocolV1CodecTest testAll may be appears test not pass - - [[#1525](https://github.com/seata/seata/pull/1525)] fix when getAfterImage error, trx autocommit - - [[#1518](https://github.com/seata/seata/pull/1518)] fix EnhancedServiceLoader may be appears load class error - - [[#1514](https://github.com/seata/seata/pull/1514)] fix when lack serialization dependence can't generate undolog and report true - - [[#1445](https://github.com/seata/seata/pull/1445)] fix DefaultCoordinatorMetricsTest UT failed - - [[#1481](https://github.com/seata/seata/pull/1481)] fix TableMetaCache refresh problem in multiple datasource - - - #### optimize: - - [[#1629](https://github.com/seata/seata/pull/1629)] optimize the watcher efficiency of etcd3 - - [[#1661](https://github.com/seata/seata/pull/1661)] optimize global_table insert transaction_name size - - [[#1633](https://github.com/seata/seata/pull/1633)] optimize branch transaction repeated reporting false - - [[#1654](https://github.com/seata/seata/pull/1654)] optimize wrong usage of slf4j - - [[#1593](https://github.com/seata/seata/pull/1593)] optimize and standardize server log - - [[#1648](https://github.com/seata/seata/pull/1648)] optimize transaction_name length when building the table - - [[#1576](https://github.com/seata/seata/pull/1576)] eliminate the impact of instructions reordering on session async committing task - - [[#1618](https://github.com/seata/seata/pull/1618)] optimize undolog manager and fix delete undolog support oracle - - [[#1469](https://github.com/seata/seata/pull/1469)] reduce the number of lock conflict exception - - [[#1619](https://github.com/seata/seata/pull/1416)] replace StringBuffer with StringBuilder - - [[#1580](https://github.com/seata/seata/pull/1580)] optimize LockKeyConflictException and change register method - - [[#1574](https://github.com/seata/seata/pull/1574)] optimize once delete GlobalSession locks for db mode when commit success - - [[#1601](https://github.com/seata/seata/pull/1601)] optimize typo - - [[#1602](https://github.com/seata/seata/pull/1602)] upgrade fastjson version to 1.2.60 for security issue - - [[#1583](https://github.com/seata/seata/pull/1583)] optimize get oracle primary index - - [[#1575](https://github.com/seata/seata/pull/1575)] add UT for RegisterTMRequest - - [[#1559](https://github.com/seata/seata/pull/1559)] optimize delay to delete the expired undo log - - [[#1547](https://github.com/seata/seata/pull/1547)] TableRecords delete jackson annotation - - [[#1542](https://github.com/seata/seata/pull/1542)] optimize AbstractSessionManager debug log - - [[#1535](https://github.com/seata/seata/pull/1535)] remove H2 and pgsql get primary index code and close resultSet - - [[#1541](https://github.com/seata/seata/pull/1541)] code clean - - [[#1544](https://github.com/seata/seata/pull/1544)] remove Chinese comment - - [[#1533](https://github.com/seata/seata/pull/1533)] refactor of the logics of Multi-configuration Isolation - - [[#1493](https://github.com/seata/seata/pull/1493)] add table meta checker switch - - [[#1530](https://github.com/seata/seata/pull/1530)] throw Exception when no index in the table - - [[#1444](https://github.com/seata/seata/pull/1444)] simplify operation of map - - [[#1497](https://github.com/seata/seata/pull/1497)] add seata-all dependencies - - [[#1490](https://github.com/seata/seata/pull/1490)] remove unnecessary code - - - - Thanks to these contributors for their code commits. Please report an unintended omission. - - - [slievrly](https://github.com/slievrly) - - [BeiKeJieDeLiuLangMao](https://github.com/BeiKeJieDeLiuLangMao) - - [jsbxyyx](https://github.com/jsbxyyx) - - [ldcsaa](https://github.com/ldcsaa) - - [zjinlei](https://github.com/zjinlei) - - [l81893521](https://github.com/l81893521) - - [ggndnn](https://github.com/ggndnn) - - [github-ygy](https://github.com/github-ygy) - - [chenxi-null](https://github.com/chenxi-null) - - [tq02ksu](https://github.com/tq02ksu) - - [AjaxXu](https://github.com/AjaxXu) - - [finalcola](https://github.com/finalcola) - - [lovepoem](https://github.com/lovepoem) - - [cmonkey](https://github.com/cmonkey) - - [xingfudeshi](https://github.com/xingfudeshi) - - [andyqian](https://github.com/andyqian) - - [tswstarplanet](https://github.com/tswstarplanet) - - [zhengyangyong](https://github.com/zhengyangyong) - - Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.8.0 (2019-08-16) - -* [source](https://github.com/seata/seata/archive/v0.8.0.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.8.0/seata-server-0.8.0.zip) - -
- Release notes - - ### Seata 0.8.0 - - Seata 0.8.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### feature: - - [[#902](https://github.com/seata/seata/pull/902)] support oracle database in AT mode - - [[#1447](https://github.com/seata/seata/pull/1447)] support oracle batch operation - - [[#1392](https://github.com/seata/seata/pull/1392)] support undo log table name configurable - - [[#1353](https://github.com/seata/seata/pull/1353)] support mysql batch update and batch delete - - [[#1379](https://github.com/seata/seata/pull/1379)] support -Dkey=value SysConfig - - [[#1365](https://github.com/seata/seata/pull/1365)] support schedule check table mata - - [[#1371](https://github.com/seata/seata/pull/1371)] support mysql preparedStatement batch self-increment primary keys - - [[#1337](https://github.com/seata/seata/pull/1337)] support mysql batch insert for non-self-inc primary keys - - [[#1235](https://github.com/seata/seata/pull/1453)] support delete expired undolog use protobuf codec - - [[#1235](https://github.com/seata/seata/pull/1235)] support to delete undolog in back task use seata codec - - [[#1323](https://github.com/seata/seata/pull/1323)] support database driver class configuration item - - - #### bugfix: - - [[#1456](https://github.com/seata/seata/pull/1456)] fix xid would be duplicate in cluster mode - - [[#1454](https://github.com/seata/seata/pull/1454)] fix DateCompareUtils can not compare byte array - - [[#1452](https://github.com/seata/seata/pull/1452)] fix select for update retry get dirty value - - [[#1443](https://github.com/seata/seata/pull/1443)] fix serialize the type of timestamp lost nano value - - [[#1374](https://github.com/seata/seata/pull/1374)] fix store.mode get configuration inconsistent - - [[#1409](https://github.com/seata/seata/pull/1409)] fix map.toString() error - - [[#1344](https://github.com/seata/seata/pull/1344)] fix ByteBuffer allocates a fixed length, which cause BufferOverflowException - - [[#1419](https://github.com/seata/seata/pull/1419)] fix if the connection is autocommit=false will cause fail to delete - - [[#1370](https://github.com/seata/seata/pull/1370)] fix begin failed not release channel and throw exception - - [[#1396](https://github.com/seata/seata/pull/1396)] fix ClassNotFound problem for Nacos config implementation - - [[#1395](https://github.com/seata/seata/pull/1395)] fix check null channel - - [[#1385](https://github.com/seata/seata/pull/1385)] fix get SessionManager error when rollback retry timeout - - [[#1378](https://github.com/seata/seata/pull/1378)] fix clusterAddressMap did not remove the instance after the instance was offline - - [[#1332](https://github.com/seata/seata/pull/1332)] fix nacos script initialization the configuration value contains ’=‘ failed - - [[#1341](https://github.com/seata/seata/pull/1341)] fix multiple operations on the same record in the same local transaction, rollback failed - - [[#1339](https://github.com/seata/seata/pull/1339)] fix when image is EmptyTableRecords, rollback failed - - [[#1314](https://github.com/seata/seata/pull/1314)] fix if don't specify the startup parameters, db mode don't take effect - - [[#1342](https://github.com/seata/seata/pull/1342)] fix ByteBuffer allocate len error - - [[#1333](https://github.com/seata/seata/pull/1333)] fix netty memory leak - - [[#1338](https://github.com/seata/seata/pull/1338)] fix lock is not acquired when multiple branches have cross locks - - [[#1334](https://github.com/seata/seata/pull/1334)] fix lock key npe bug, when tcc use protobuf - - [[#1313](https://github.com/seata/seata/pull/1313)] fix DefaultFailureHandler check status NPE - - - #### optimize: - - [[#1474](https://github.com/seata/seata/pull/1474)] optimize data image compare log - - [[#1446](https://github.com/seata/seata/pull/1446)] optimize the server's schedule tasks - - [[#1448](https://github.com/seata/seata/pull/1448)] refactor executor class remove the duplicate code - - [[#1408](https://github.com/seata/seata/pull/1408)] change ChannelFactory package in TmRpcClientTest - - [[#1432](https://github.com/seata/seata/pull/1432)] implement equals and hashcode of the object that is used as the hash key - - [[#1429](https://github.com/seata/seata/pull/1429)] remove unused imports - - [[#1426](https://github.com/seata/seata/pull/1426)] fix syntax error - - [[#1425](https://github.com/seata/seata/pull/1425)] fix typo - - [[#1356](https://github.com/seata/seata/pull/1356)] optimize sql join - - [[#1416](https://github.com/seata/seata/pull/1416)] optimize some javadoc comments - - [[#1417](https://github.com/seata/seata/pull/1417)] optimize oracle keyword - - [[#1404](https://github.com/seata/seata/pull/1404)] optimize BranchStatus comments - - [[#1414](https://github.com/seata/seata/pull/1414)] optimize mysql keywords - - [[#1407](https://github.com/seata/seata/pull/1407)] disable unstable unit tests - - [[#1398](https://github.com/seata/seata/pull/1398)] optimize eureka registry serviceUrl with default port - - [[#1364](https://github.com/seata/seata/pull/1364)] optimize table columns name defined as constants - - [[#1389](https://github.com/seata/seata/pull/1389)] add the oracle support prompt information - - [[#1375](https://github.com/seata/seata/pull/1375)] add compareRows failed log - - [[#1358](https://github.com/seata/seata/pull/1358)] clean temporary file file runs when UT is finished - - [[#1355](https://github.com/seata/seata/pull/1355)] add test case for rpc protocol - - [[#1357](https://github.com/seata/seata/pull/1357)] code clean of Consul&Etcd config center implementations - - [[#1345](https://github.com/seata/seata/pull/1345)] code clean and modify log level - - [[#1329](https://github.com/seata/seata/pull/1329)] add `STORE_FILE_DIR` default value - - - Thanks to these contributors for their code commits. Please report an unintended omission. - - - [slievrly](https://github.com/slievrly) - - [Justice-love](https://github.com/Justice-love) - - [l81893521](https://github.com/l81893521) - - [ggndnn](https://github.com/ggndnn) - - [zjinlei](https://github.com/zjinlei) - - [andyqian](https://github.com/andyqian) - - [cmonkey](https://github.com/cmonkey) - - [wangjin](https://github.com/wangjin) - - [Arlmls](https://github.com/Arlmls) - - [lukairui](https://github.com/lukairui) - - [kongwang](https://github.com/kongwang) - - [lightClouds917](https://github.com/lightClouds917) - - [xingfudeshi](https://github.com/xingfudeshi) - - [alicexiaoshi](https://github.com/alicexiaoshi) - - [itxingqing](https://github.com/itxingqing) - - [wanghuizuo](https://github.com/wanghuizuo) - - [15168326318](https://github.com/15168326318) - - [github-ygy](https://github.com/github-ygy) - - [ujjboy](https://github.com/ujjboy) - - [leizhiyuan](https://github.com/leizhiyuan) - - [vikenlove](https://github.com/vikenlove) - - Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.7.1 (2019-07-15) - -* [source](https://github.com/seata/seata/archive/v0.7.1.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.7.1/seata-server-0.7.1.zip) - -
- Release notes - - ### Seata 0.7.1 - - Seata 0.7.1 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Bugfix & Optimize - - [[#1297](https://github.com/seata/seata/pull/1297)] seata-spring add dependency seata-codec-all - - [[#1305](https://github.com/seata/seata/pull/1305)] fix unable to instantiate org.springframework.cloud.alibaba.seata.GlobalTransactionAutoConfiguration - - fix in the 0.7.0 version, unable to get the seata dependency problem from the central repository - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.7.0 (2019-07-12) - -* [source](https://github.com/seata/seata/archive/v0.7.0.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.7.0/seata-server-0.7.0.zip) - -
- Release notes - - ### Seata 0.7.0 - - Seata 0.7.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Feature - - [[#1276](https://github.com/seata/seata/pull/1276)] New RPC protocol - - [[#1266](https://github.com/seata/seata/pull/1266)] add enabled configuration for metrics ([97](https://github.com/seata/seata/issues/97)) - - [[#1236](https://github.com/seata/seata/pull/1236)] support metrics for tc server - - [[#1214](https://github.com/seata/seata/pull/1214)] add config `shutdown.wait` and update version to 0.7.0-SNAPSHOT ([1212](https://github.com/seata/seata/issues/1212)) - - [[#1206](https://github.com/seata/seata/pull/1206)] setting default values using trinomial operators - - [[#1174](https://github.com/seata/seata/pull/1174)] add nacos config initialization python script ([1172](https://github.com/seata/seata/issues/1172)) - - [[#1145](https://github.com/seata/seata/pull/1145)] Change LockMode from MEMORY to DB when the StoreMode is DB - - [[#1125](https://github.com/seata/seata/pull/1125)] Add protostuff as serializer of UndoLogParser. - - [[#1007](https://github.com/seata/seata/pull/1007)] support protobuf feature ([97](https://github.com/seata/seata/issues/97)) - - - #### Bugfix & Optimize - - - [[#1286](https://github.com/seata/seata/pull/1286)] bugfix: add some configuration and exclude log dependency ([97](https://github.com/seata/seata/issues/97)) - - [[#1278](https://github.com/seata/seata/pull/1278)] bugfix: pass txId into TCC interceptor - - [[#1274](https://github.com/seata/seata/pull/1274)] 1. optimization SQL join - - [[#1271](https://github.com/seata/seata/pull/1271)] bugfix: @GlobalLock get error with Response ([97](https://github.com/seata/seata/issues/97), [1224](https://github.com/seata/seata/issues/1224)) - - [[#1270](https://github.com/seata/seata/pull/1270)] bugfix: print error exception - - [[#1269](https://github.com/seata/seata/pull/1269)] bugfix: fix TMClinet reconnect exception - - [[#1265](https://github.com/seata/seata/pull/1265)] Invoke addBatch of targetStatement if not in global transaction - - [[#1264](https://github.com/seata/seata/pull/1264)] configuration:update ignore and coverage ([97](https://github.com/seata/seata/issues/97)) - - [[#1263](https://github.com/seata/seata/pull/1263)] docs: add doc about contribution ([97](https://github.com/seata/seata/issues/97)) - - [[#1262](https://github.com/seata/seata/pull/1262)] bugfix: fix find target class issue if scan the web scope bean such a… ([97](https://github.com/seata/seata/issues/97)) - - [[#1261](https://github.com/seata/seata/pull/1261)] add warn log when fail to get auto-generated keys. (#1259) ([97](https://github.com/seata/seata/issues/97), [1259](https://github.com/seata/seata/issues/1259)) - - [[#1258](https://github.com/seata/seata/pull/1258)] move metrics config keys and simplify metrics modules dependency - - [[#1250](https://github.com/seata/seata/pull/1250)] fix codecov for protobuf ([97](https://github.com/seata/seata/issues/97)) - - [[#1245](https://github.com/seata/seata/pull/1245)] refactor metrics let it initialize by configuration - - [[#1242](https://github.com/seata/seata/pull/1242)] perfect sql - - [[#1239](https://github.com/seata/seata/pull/1239)] bugfix:fix CME in ZK discovery implementation. ([97](https://github.com/seata/seata/issues/97)) - - [[#1237](https://github.com/seata/seata/pull/1237)] bugfix:server start and handle remain branch session may cause NPE ([97](https://github.com/seata/seata/issues/97)) - - [[#1232](https://github.com/seata/seata/pull/1232)] Add unit tests for io.seata.common.util CompressUtil, DurationUtil, ReflectionUtil - - [[#1230](https://github.com/seata/seata/pull/1230)] prioritize global transaction scanner #1227 ([97](https://github.com/seata/seata/issues/97), [1227](https://github.com/seata/seata/issues/1227)) - - [[#1229](https://github.com/seata/seata/pull/1229)] fix a typo ([97](https://github.com/seata/seata/issues/97)) - - [[#1225](https://github.com/seata/seata/pull/1225)] optimize the name of seata config environment. ([97](https://github.com/seata/seata/issues/97), [1209](https://github.com/seata/seata/issues/1209)) - - [[#1222](https://github.com/seata/seata/pull/1222)] fix bug of refresh cluster ([1160](https://github.com/seata/seata/issues/1160)) - - [[#1221](https://github.com/seata/seata/pull/1221)] bugfix: fix in which SQL and database field names are inconsistent#1217 ([1217](https://github.com/seata/seata/issues/1217)) - - [[#1218](https://github.com/seata/seata/pull/1218)] bugfix:containsPK ignoreCase ([1217](https://github.com/seata/seata/issues/1217)) - - [[#1210](https://github.com/seata/seata/pull/1210)] 1. optimize arrayList single value - - [[#1207](https://github.com/seata/seata/pull/1207)] All overriding methods must be preceded by @Override annotations. - - [[#1205](https://github.com/seata/seata/pull/1205)] remove useless code - - [[#1202](https://github.com/seata/seata/pull/1202)] output branchRollback failed log ([97](https://github.com/seata/seata/issues/97)) - - [[#1200](https://github.com/seata/seata/pull/1200)] bugfix:DefaultCoreTest.branchRegisterTest ([1199](https://github.com/seata/seata/issues/1199)) - - [[#1198](https://github.com/seata/seata/pull/1198)] check the third-party dependencies license ([1197](https://github.com/seata/seata/issues/1197)) - - [[#1195](https://github.com/seata/seata/pull/1195)] Clear the transaction context in TCC prepare methed - - [[#1193](https://github.com/seata/seata/pull/1193)] Get lockmode by the storemode - - [[#1190](https://github.com/seata/seata/pull/1190)] remove unused semicolons ([97](https://github.com/seata/seata/issues/97), [540](https://github.com/seata/seata/issues/540)) - - [[#1179](https://github.com/seata/seata/pull/1179)] fix jackson default content - - [[#1177](https://github.com/seata/seata/pull/1177)] write session may be failed,throw TransactionException but hold lock. ([97](https://github.com/seata/seata/issues/97), [1154](https://github.com/seata/seata/issues/1154)) - - [[#1169](https://github.com/seata/seata/pull/1169)] bugfix: use Set to avoid duplicate listeners. fixes #1126 ([1126](https://github.com/seata/seata/issues/1126)) - - [[#1165](https://github.com/seata/seata/pull/1165)] add a missing placeholder in INSERT_UNDO_LOG_SQL ([1164](https://github.com/seata/seata/issues/1164)) - - [[#1162](https://github.com/seata/seata/pull/1162)] Reset initialized flag & instance while destroy(). split [##1105 ([983](https://github.com/seata/seata/issues/983), [97](https://github.com/seata/seata/issues/97)) - - [[#1159](https://github.com/seata/seata/pull/1159)] bugfix: AT mode resourceId(row_key) too long ([97](https://github.com/seata/seata/issues/97), [1158](https://github.com/seata/seata/issues/1158)) - - [[#1150](https://github.com/seata/seata/pull/1150)] updates seata's version in README.md ([97](https://github.com/seata/seata/issues/97)) - - [[#1148](https://github.com/seata/seata/pull/1148)] bugfix:the buffer may cause overflows when sql statement is long - - [[#1146](https://github.com/seata/seata/pull/1146)] revise the package name of the module ([97](https://github.com/seata/seata/issues/97)) - - [[#1105](https://github.com/seata/seata/pull/1105)] refactor TmRpcClient & RmClient for common use. ([97](https://github.com/seata/seata/issues/97)) - - [[#1075](https://github.com/seata/seata/pull/1075)] Multiple environmental isolation - - [[#768](https://github.com/seata/seata/pull/768)] #751 add event bus mechanism and apply it in tc - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.6.1 (2019-05-31) - -* [source](https://github.com/seata/seata/archive/v0.6.1.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.6.1/seata-server-0.6.1.zip) - -
- Release notes - - ### Seata 0.6.1 - - Seata 0.6.1 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Feature - - [[#1119](https://github.com/seata/seata/pull/1119)] support weibo/motan - - [[#1075](https://github.com/seata/seata/pull/1075)] Multiple environmental isolation - - #### Bugfix & Optimize - - [[#1099](https://github.com/seata/seata/pull/1099)] UndoLogParser change to SPI - - [[#1113](https://github.com/seata/seata/pull/1113)] optimize check style - - [[#1087](https://github.com/seata/seata/pull/1087)] Remove unnecessary copy of bytes - - [[#1090](https://github.com/seata/seata/pull/1090)] Change the method definition of UndoLogParser for better extensibility - - [[#1120](https://github.com/seata/seata/pull/1120)] bugfix : use xid wrong when do branch commit and rollback - - [[#1135](https://github.com/seata/seata/pull/1135)] bugfix:zk vulnerability and optimize dependencies - - [[#1138](https://github.com/seata/seata/pull/1138)] bugfix: seata-server.bat classpath too long - - [[#1117](https://github.com/seata/seata/pull/1117)] bugfix: fix field type is datetime equals fail - - [[#1115](https://github.com/seata/seata/pull/1115)] modify seata-all & seata-bom deploy config - - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.6.0 (2019-05-24) - -* [source](https://github.com/seata/seata/archive/v0.6.0.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.6.0/seata-server-0.6.0.zip) - -
- Release notes - - ### Seata 0.6.0 - - Seata 0.6.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Feature - - - [[#942](https://github.com/seata/seata/pull/942)] Store the transaction log into database - - [[#1014](https://github.com/seata/seata/pull/1014)] Support etcd3 as configuration center - - [[#1060](https://github.com/seata/seata/pull/1060)] Do data validation when undo - - #### Bugfix & Optimize - - - [[#1064](https://github.com/seata/seata/pull/1064)] bugfix:size wrong between xid and branchId - - [[#1074](https://github.com/seata/seata/pull/1074)] bugfix:typos and replace AIT's with lambdas - - [[#824](https://github.com/seata/seata/pull/824)] Add time limit when transaction retry on the server - - [[#1082](https://github.com/seata/seata/pull/1082)] add Cache configuration instance - - [[#1084](https://github.com/seata/seata/pull/1084)] Refactor Charset using and blob utils - - [[#1080](https://github.com/seata/seata/pull/1080)] upgrade fastjson and nacos-client - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.5.2 (2019-05-17) - -* [source](https://github.com/seata/seata/archive/v0.5.2.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.5.2/seata-server-0.5.2.zip) - -
- Release notes - - ### Seata 0.5.2 - - Seata 0.5.2 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Feature - - - [[#988](https://github.com/seata/seata/pull/988)] support Consul configuration center - - [[#1043](https://github.com/seata/seata/pull/1043)] support sofa-rpc - - - #### Bugfix & Optimize - - - [[#987](https://github.com/seata/seata/pull/987)] optimize the use of reentrantLock instead of spinlock in concurrent scenarios within the same transaction - - [[#943](https://github.com/seata/seata/pull/943)] fix configuration wait timeout when there is no corresponding file configuration item - - [[#965](https://github.com/seata/seata/pull/965)] fix PreparedStatement where in,between problem - - [[#929](https://github.com/seata/seata/pull/929)] optimize GlobalSession for the first time to wait for locks - - [[#967](https://github.com/seata/seata/pull/967)] optimize partial log description - - [[#970](https://github.com/seata/seata/pull/970)] fix unable to read flush-disk-mode configuration item problem - - [[#916](https://github.com/seata/seata/pull/916)] optimize the readable index problem of decoding - - [[#979](https://github.com/seata/seata/pull/979)] optimize copyright - - [[#981](https://github.com/seata/seata/pull/981)] optimize pom dependencies, use caffine instead of guava cache, junit upgrade to junit5, use junit5 to transform original testng unit tests - - [[#991](https://github.com/seata/seata/pull/991)] optimize the header of the core module import - - [[#996](https://github.com/seata/seata/pull/996)] fix maven-surefire-plugin compilation error in mac environment - - [[#994](https://github.com/seata/seata/pull/994)] Fix ByteBuffer multiple flip problem - - [[#999](https://github.com/seata/seata/pull/999)] change the community's email subscription address - - [[#861](https://github.com/seata/seata/pull/861)] optimize the FailureHandler to periodically get the retrieved transaction result and print the successful result - - [[#802](https://github.com/seata/seata/pull/802)] optimize the lambda code style in GlobalTransactionalInterceptor - - [[#1026](https://github.com/seata/seata/pull/1026)] fix troubleshooting for data* code files, add local transaction file exclusion path - - [[#1024](https://github.com/seata/seata/pull/1024)] fix Consul module SPI configuration file path problem - - [[#1023](https://github.com/seata/seata/pull/1023)] add the seata-all.jar for client full dependency - - [[#1029](https://github.com/seata/seata/pull/1029)] fix the delay rollback caused by no channel when the client is restarting - - [[#1027](https://github.com/seata/seata/pull/1027)] fix release-seata can not generate zip file problem - - [[#1033](https://github.com/seata/seata/pull/1033)] fix createDependencyReducedPom to generate redundant xml problem - - [[#1035](https://github.com/seata/seata/pull/1035)] fix branchCommit/branchRollback in TCC mode, but branchId is null - - [[#1040](https://github.com/seata/seata/pull/1040)] refactor exceptionHandleTemplate and fix the problem that cannot be returned when the GlobalRollback branch throw exception - - [[#1036](https://github.com/seata/seata/pull/1036)] replace Chinese comment with English comment - - [[#1051](https://github.com/seata/seata/pull/1051)] optimize to check data changes when rollback, stop rollback if there is no data change - - [[#1017](https://github.com/seata/seata/pull/1017)] optimize the processing logic of mysql undo executor construct undo sql - - [[#1063](https://github.com/seata/seata/pull/1063)] fix the problem that the new transaction id conflict fails after the server is restarted after the server is restarted - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.5.1 (2019-04-30) - -* [source](https://github.com/seata/seata/archive/v0.5.1.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.5.1/seata-server-0.5.1.zip) - -
- Release notes - - ### Seata 0.5.1 - - Seata 0.5.1 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Feature - - - [[#774](https://github.com/seata/seata/pull/869)] support Etcd3 registration center - - [[#793](https://github.com/seata/seata/pull/793)] support sofa-registry registration center - - [[#856](https://github.com/seata/seata/pull/856)] add batch delete undolog processing - - [[#786](https://github.com/seata/seata/pull/786)] support for branch transaction concurrency in global transactions - - - - #### Bugfix & Optimize - - - [[#879](https://github.com/seata/seata/pull/879)] fix when batch delete undolog,the preparedStatement does not close - - [[#945](https://github.com/seata/seata/pull/945)] add the releaseLock method in the LockManager interface to optimize the calling logic - - [[#938](https://github.com/seata/seata/pull/938)] optimize the TransactionManager service loading logic - - [[#913](https://github.com/seata/seata/pull/913)] optimize the module structure of the RPC integration framework - - [[#795](https://github.com/seata/seata/pull/795)] optimize the performance of server node write files - - [[#921](https://github.com/seata/seata/pull/921)] fix NPE exception when select for update - - [[#925](https://github.com/seata/seata/pull/925)] optimize the same DefaultCoordinator instance when the server starts - - [[#930](https://github.com/seata/seata/pull/930)] optimize field access modifiers - - [[#907](https://github.com/seata/seata/pull/907)] fix hostname can't be null exception - - [[#923](https://github.com/seata/seata/pull/923)] fix the problem that the key is not formatted when the nettyClientKeyPool connection is destroyed - - [[#891](https://github.com/seata/seata/pull/891)] fix the NPE exception when using select union all - - [[#888](https://github.com/seata/seata/pull/888)] fix copyright checkstyle verification - - [[#901](https://github.com/seata/seata/pull/901)] fix parent node path does not exist when Zookeeper is registered - - [[#904](https://github.com/seata/seata/pull/904)] optimize updated data query logic in UpdateExecutort - - [[#802](https://github.com/seata/seata/pull/802)] optimize checkstyle and add plugins - - [[#882](https://github.com/seata/seata/pull/882)] modify copyright, add copyright automatic plugin - - [[#874](https://github.com/seata/seata/pull/874)] add the communication default configuration value - - [[#866](https://github.com/seata/seata/pull/866)] fix unable to generate dubbo:reference proxy class - - [[#877](https://github.com/seata/seata/pull/877)] fix concurrentModifyException when batch deleting undolog - - [[#855](https://github.com/seata/seata/pull/855)] optimize the globalCommit always returns committed to the user in AT mode - - [[#875](https://github.com/seata/seata/pull/875)] fix select for update, Boolean cast ResultSet failed - - [[#830](https://github.com/seata/seata/pull/830)] fix RM late registration problem - - [[#872](https://github.com/seata/seata/pull/872)] fix RegisterRMRequest decoding message length check is not accurate - - [[#831](https://github.com/seata/seata/pull/831)] optimize CountDownLatch in MessageFuture and replace it with CompletableFuture - - [[#834](https://github.com/seata/seata/pull/834)] fix non-SQLException in ExecuteTemplate does not throw a exception - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.5.0 (2019-04-19) - -* [source](https://github.com/seata/seata/archive/0.5.0.zip) -* [binary](https://github.com/seata/seata/releases/download/0.5.0/seata-server-0.5.0.zip) - -
- Release notes - - ### Seata 0.5.0 - - Seata 0.5.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Compatibility - - - [[#809](https://github.com/seata/seata/pull/809)] Change groupId,artifactId, and package - - [[#815](https://github.com/seata/seata/pull/815)] Add maven plugin to release seata with groupId io.seata - - [[#790](https://github.com/seata/seata/pull/790)] Change the startup parameters of seata-server to support database-storage - - [[#769](https://github.com/seata/seata/pull/769)] Modify the RPC protocol, remove the client's resolution of xid to be stateless - - #### Feature - - - [[#774](https://github.com/seata/seata/pull/774)] optimizes the structure of config module and discovery module - - [[#783](https://github.com/seata/seata/pull/783)] Allow users config the count for client report retry dynamicly - - [[#791](https://github.com/seata/seata/pull/791)] replace magic judgement of timeout status with status enum - - [[#836](https://github.com/seata/seata/pull/836)] Use maven-compiler-plugin to revision the version and add mvnw script to unify the maven version - - [[#820](https://github.com/seata/seata/pull/820)] Add rollback on for GlobalTransaction - - - #### Bugfix - - - [[#772](https://github.com/seata/seata/pull/772)] Fix FileConfiguration config listener logic - - [[#807](https://github.com/seata/seata/pull/807)] Optimize the setting of full name of FileBasedSessionManager - - [[#804](https://github.com/seata/seata/pull/804)] bugfix:branchCommit retry always failed - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.4.2 (2019-04-12) - -* [source](https://github.com/seata/seata/archive/v0.4.2.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.4.2/fescar-server-0.4.2.zip) - -
- Release notes - - ### Seata 0.4.2 - - Seata 0.4.2 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Feature - - - [[#704](https://github.com/seata/seata/pull/704)] add local file write ByteBuffer pool - - [[#679](https://github.com/seata/seata/issues/679)] The existing registry adds the implementation of the close interface, which optimizes the server's elegant offline - - [[#713](https://github.com/seata/seata/pull/713)] add local file writes enable compression for messages that exceed the configured size - - [[#587](https://github.com/seata/seata/issues/587)] Added MySQL DDL statement support - - [[#717](https://github.com/seata/seata/pull/717)] add Nacos Initialization Configuration Script Configuration and Completion Program Configuration File - - [[#726](https://github.com/seata/seata/pull/726)] support for DBCP, C3P0, BoneCP, HikariCP and Tomcat-JDBC connection pools - - [[#744](https://github.com/seata/seata/pull/744)] add ZooKeeper disconnection re-registration and subscription when reconnected - - [[#728](https://github.com/seata/seata/pull/728)] Supports service discovery with Consul - - #### Bugfix - - - [[#569](https://github.com/seata/seata/pull/695)] fix already jdk proxy and no target only traverses the first implementation interface problem - - [[#721](https://github.com/seata/seata/pull/721)] fix ConfigFuture constructor timeout parameter does not work - - [[#725](https://github.com/seata/seata/pull/725)] fix MergedSendRunnable channel is unexpectedly closed, and add fail-fast - - [[#723](https://github.com/seata/seata/pull/723)] fix defaultServerMessageListener is not initialized - - [[#746](https://github.com/seata/seata/pull/746)] fix the failure of the test module caused by the DataSourceManager SPI - - [[#754](https://github.com/seata/seata/pull/754)] optimize Eureka registry - - [[#750](https://github.com/seata/seata/pull/750)] fix undolog caused by DataSourceManager SPI cannot delete problem - - [[#747](https://github.com/seata/seata/pull/747)] Delete MT mode, then will be replaced by TCC mode - - [[#757](https://github.com/seata/seata/pull/757)] fix rollback caused by RPC exception when performing BranchRollback retry - - [[#776](https://github.com/seata/seata/pull/776)] fix connection creation failure caused by toString exception when connection pool creates channel - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.4.1 (2019-03-29) - -* [source](https://github.com/seata/seata/archive/v0.4.1.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.4.1/fescar-server-0.4.1.zip) - -
- Release notes - - ### Seata 0.4.1 - - Seata 0.4.1 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - - TBD - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.4.0 (2019-03-19) - -* [source](https://github.com/seata/seata/archive/v0.4.0.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.4.0/fescar-server-0.4.0.zip) - -
- Release notes - - ### Seata 0.4.0 - - Seata 0.4.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Feature - - - [[#583](https://github.com/alibaba/fescar/pull/583)] Add TCC model of Ant Financial to fescar, to suppot Local TCC bean,Dubbo TCC bean and SOFARPC TCC bean - - [[#611](https://github.com/alibaba/fescar/pull/611)] Apply p3c pmd plugin/rules - - [[#627](https://github.com/alibaba/fescar/pull/627)] Optimization dependency - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.3.1 (2019-03-15) - -* [source](https://github.com/seata/seata/archive/v0.3.1.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.3.1/fescar-server-0.3.1.zip) - -
- Release notes - - ### Seata 0.3.1 - - Seata 0.3.1 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Feature - - - [[#557](https://github.com/alibaba/fescar/issues/557)] add custom hook access point at each stage of the transaction process - - [[#594](https://github.com/alibaba/fescar/pull/594)] support Zookeeper Registration Center - - #### Bugfix - - - [[#569](https://github.com/alibaba/fescar/issues/569)] fix eureka renew url encode - - [[#551](https://github.com/alibaba/fescar/pull/551)] fix ConfigType NPE - - [[#489](https://github.com/alibaba/fescar/issues/489)] fix GlobalRollback request but not receive report - - [[#598](https://github.com/alibaba/fescar/pull/598)] fix blocker and critical level issues scanned by p3c - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.3.0 (2019-03-08) - -* [source](https://github.com/seata/seata/archive/v0.3.0.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.3.0/fescar-server-0.3.0.zip) - -
- Release notes - - ### Seata 0.3.0 - - Seata 0.3.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Feature - - - [[#510](https://github.com/alibaba/fescar/pull/510)] Support eureka registry center - - [[#498](https://github.com/alibaba/fescar/pull/498)] Implement local transaction mode with global locks and resolve local transaction isolation issues - - #### Bugfix - - - [[#459](https://github.com/alibaba/fescar/issues/459)] Fix mysql keyword generating sql problem as table name and column name - - [[#312](https://github.com/alibaba/fescar/issues/312)] Fix the original business sql no where condition generation sql error problem - - [[#522](https://github.com/alibaba/fescar/issues/522)] Fix file path security vulnerability - - Remove useless, format, optimize import, javadoc, copyright for all module code - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.2.3 (2019-03-02) - -* [source](https://github.com/seata/seata/archive/v0.2.3.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.2.3/fescar-server-0.2.3.zip) - -
- Release notes - - ### Seata 0.2.3 - - Seata 0.2.3 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Feature - - - [[#478](https://github.com/alibaba/fescar/pull/478)] Support redis registry and apollo configuration - - [[#478](https://github.com/alibaba/fescar/pull/478)] Support redis registry and apollo configuration - - #### Bugfix - - - [[#462](https://github.com/alibaba/fescar/issues/462)] Separation configuration and registration judgment - - - [[#466](https://github.com/alibaba/fescar/issues/466)] Add run oldest task reject policy - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.2.2 (2019-02-22) - -* [source](https://github.com/seata/seata/archive/v0.2.2.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.2.2/fescar-server-0.2.2.zip) - -
- Release notes - - ### Seata 0.2.2 - - Seata 0.2.2 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - - Fixed several bugs - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.2.1 (2019-02-18) - -* [source](https://github.com/seata/seata/archive/v0.2.1.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.2.1/fescar-server-0.2.1.zip) - -
- Release notes - - ### Seata 0.2.1 - - Seata 0.2.1 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Feature - - - Support BETWEEN in update statement - - Add Random and RoundRobin LoadBalance - - Add dubbo-alibaba module support Alibaba Dubbo - - #### Bugfix - - - Fix NettyClientConfig variable name isPoolFifo-> isPoolLifo - - Fix fescar-dubbo filter SPI reference error - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.2.0 (2019-02-14) - -* [source](https://github.com/seata/seata/archive/v0.2.0.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.2.0/fescar-server-0.2.0.zip) - -
- Release notes - - ### Seata 0.2.0 - - Seata 0.2.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - #### Feature - - - Support MySQL distributed transaction automatic mode (AT) - - Supports Dubbo seamless integration - - Support for distributed transaction API - - Support Spring transaction annotation - - Support Mybatis、JPA - - Support Nacos Service Registration and Configuration Center - - Add Fescar-Server automatic recovery from file unfinished transaction operations to memory when restarting - - Support multiple IP environment, start server to specify IP parameters - - #### Bugfix - - - Fix Fescar-Server restart may cause XID duplication - - Fix Windows startup script $EXTRA_JVM_ARGUMENTS parameter error - - Fix distributed transaction local nested inner transaction commit/rollback causes outer transaction exception problem - - Fix local transaction commit exception, local transaction does not rollback problem - - Fix MySQL table alias resolution - - #### other - - Upgrade depends on JDK version to 1.8 - - Upgrade dependency Alibaba Dubbo to Apache Dubbo 2.7.0 - - Optimize related dependency references - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.1.4 (2019-02-11) - -* [source](https://github.com/seata/seata/archive/v0.1.4.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.1.4/fescar-server-0.1.4.zip) - -
- Release notes - - ### Seata 0.1.4 - - Seata 0.1.4 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - - Fixed several bugs - - Upgrade jdk version to 1.8 - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.1.3 (2019-01-29) - -* [source](https://github.com/seata/seata/archive/v0.1.3.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.1.3/fescar-server-0.1.3.zip) - -
- Release notes - - ### Seata 0.1.3 - - Seata 0.1.3 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - - Fixed several bugs - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.1.2 (2019-01-25) - -* [source](https://github.com/seata/seata/archive/V0.1.2.zip) -* [binary](https://github.com/seata/seata/releases/download/V0.1.2/fescar-server-0.1.2.zip) - -
- Release notes - - ### Seata 0.1.2 - - Seata 0.1.2 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - - Fixed several bugs - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.1.1 (2019-01-18) - -* [source](https://github.com/seata/seata/archive/v0.1.1.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.1.1/fescar-server-0.1.1.zip) - -
- Release notes - - ### Seata 0.1.1 - - Seata 0.1.1 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - - Fixed several bugs - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
- -### 0.1.0 (2019-01-09) - -* [source](https://github.com/seata/seata/archive/v0.1.0.zip) -* [binary](https://github.com/seata/seata/releases/download/v0.1.0/fescar-server-0.1.0.zip) - -
- Release notes - - ### Seata 0.1.0 - - Seata 0.1.0 Released. - - Seata is an easy-to-use, high-performance, open source distributed transaction solution. - - The version is updated as follows: - - - Support MySQL in AT Mode - - Support Dubbo - - API Provided - - Spring based annotation Provided - - Standalone server - - #### Link - - **Seata:** https://github.com/seata/seata - - **Seata-Samples:** https://github.com/seata/seata-samples - - **Release:** https://github.com/seata/seata/releases - -
diff --git a/src/pages/community/index.tsx b/src/pages/community/index.tsx index ae4c465663..6152e0321d 100644 --- a/src/pages/community/index.tsx +++ b/src/pages/community/index.tsx @@ -22,7 +22,7 @@ const data = { link: '/zh-cn/blog/seata-meetup-hangzhou.html', }, { - img: '/img/blog/20191218210552.png', + img: 'img/blog/20191218210552.png', title: 'Seata Community Meetup·杭州站', content: 'Seata Community Meetup·杭州站,将于12月21号在杭州市梦想小镇浙江青年众创空间正式召开', dateStr: 'Dec 18nd,2019', @@ -35,8 +35,8 @@ const data = { desc: translate({ id: 'community.contactsDesc', message: '有问题需要反馈?请通过一下方式联系我们。' }), list: [ { - img: '/img/community/mailinglist.png', - imgHover: '/img/community/mailinglist_hover.png', + img: 'img/community/mailinglist.png', + imgHover: 'img/community/mailinglist_hover.png', title: translate({ id: 'community.contactsListTitle1', message: '邮件列表' }), link: 'mailto:dev-seata+subscribe@googlegroups.com', }, @@ -67,7 +67,7 @@ const data = { desc: translate({ id: 'community.contributeDesc', message: '欢迎为 Seata 做贡献!' }), list: [ { - img: '/img/community/mailinglist.png', + img: 'img/community/mailinglist.png', title: translate({ id: 'community.contributeListTitle1', message: '邮件列表' }), content: ( @@ -78,7 +78,7 @@ const data = { ), }, { - img: '/img/community/issue.png', + img: 'img/community/issue.png', title: translate({ id: 'community.contributeListTitle2', message: '报告问题' }), content: ( @@ -90,7 +90,7 @@ const data = { ), }, { - img: '/img/community/documents.png', + img: 'img/community/documents.png', title: translate({ id: 'community.contributeListTitle3', message: '改进文档' }), content: ( @@ -102,7 +102,7 @@ const data = { ), }, { - img: '/img/community/pullrequest.png', + img: 'img/community/pullrequest.png', title: translate({ id: 'community.contributeListContent4_1', message: '提交 PR' }), content: (