This is an automated email from the ASF dual-hosted git repository.
jimin pushed a commit to branch docusaurus
in repository https://gitbox.apache.org/repos/asf/incubator-seata-website.git
The following commit(s) were added to refs/heads/docusaurus by this push:
new b3c0d689f4 feat: translate seata-tcc.md to En (#822)
b3c0d689f4 is described below
commit b3c0d689f44506b5c833cd30791ac83296c0e574
Author: Xinyi Chai <[email protected]>
AuthorDate: Thu Jan 25 14:52:33 2024 +0800
feat: translate seata-tcc.md to En (#822)
---
.../en/docusaurus-plugin-content-blog/seata-tcc.md | 305 +++++++++++++++++++++
.../docusaurus-plugin-content-blog/seata-tcc.md | 19 +-
static/img/blog/20220116160157.png | Bin 0 -> 102009 bytes
static/img/blog/20220116161933.png | Bin 0 -> 162745 bytes
static/img/blog/20220116175059.png | Bin 0 -> 69704 bytes
static/img/blog/20220116192544.png | Bin 0 -> 141376 bytes
static/img/blog/20220116201900.png | Bin 0 -> 165046 bytes
static/img/blog/20220116203816.png | Bin 0 -> 164433 bytes
static/img/blog/20220116205241.png | Bin 0 -> 172279 bytes
9 files changed, 312 insertions(+), 12 deletions(-)
diff --git a/i18n/en/docusaurus-plugin-content-blog/seata-tcc.md
b/i18n/en/docusaurus-plugin-content-blog/seata-tcc.md
new file mode 100644
index 0000000000..13379af672
--- /dev/null
+++ b/i18n/en/docusaurus-plugin-content-blog/seata-tcc.md
@@ -0,0 +1,305 @@
+---
+title: In-Depth Analysis of Seata TCC Mode (1)
+author: Zhang Chenghui
+keywords: [Seata、distributed transaction、TCC]
+description: Seata currently supports AT mode, XA mode, TCC mode, and SAGA
mode. Previous articles have talked more about non-intrusive AT mode. Today, we
will introduce TCC mode, which is also a two-phase commit.
+date: 2022/01/18
+---
+
+# Preface
+
+Seata currently supports AT mode, XA mode, TCC mode, and SAGA mode. Previous
articles have talked more about non-intrusive AT mode. Today, we will introduce
TCC mode, which is also a two-phase commit.
+
+# What is TCC
+
+TCC is a two-phase commit protocol in distributed transactions. Its full name
is Try-Confirm-Cancel. Their specific meanings are as follows:
+
+1. Try: Check and reserve business resources;
+2. Confirm: Commit the business transaction, i.e., the commit operation. If
Try is successful, this step will definitely be successful;
+3. Cancel: Cancel the business transaction, i.e., the rollback operation. This
step will release the resources reserved in Try.
+
+TCC is an intrusive distributed transaction solution. All three operations
need to be implemented by the business system itself, which has a significant
impact on the business system. The design is relatively complex, but the
advantage is that TCC does not rely on the database. It can manage resources
across databases and applications, and can implement an atomic operation for
different data access through intrusive coding, better solving the distributed
transaction problems in various c [...]
+
+<img src="/img/blog/20220116160157.png" alt="img" style={{ zoom:'50%' }} />
+
+# Seata TCC mode
+
+Seata TCC mode follows the same principle as the general TCC mode. Let's first
use Seata TCC mode to implement a distributed transaction:
+
+Suppose there is a business that needs to use service A and service B to
complete a transaction operation. We define a TCC interface for this service in
service A:
+
+```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);
+}
+```
+
+Similarly, we define a TCC interface for this service in service B:
+
+```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);
+}
+```
+
+In the business system, we start a global transaction and execute the TCC
reserve resource methods for service A and service B:
+
+```java
+@GlobalTransactional
+public String doTransactionCommit(){
+ // Service A transaction participant
+ tccActionOne.prepare(null,"one");
+ // Service B transaction participant
+ tccActionTwo.prepare(null,"two");
+}
+```
+
+The example above demonstrates the implementation of a global transaction
using Seata TCC mode. It can be seen that the TCC mode also uses the
`@GlobalTransactional` annotation to initiate a global transaction, while the
TCC interfaces of Service A and Service B are transaction participants. Seata
treats a TCC interface as a Resource, also known as a TCC Resource.
+
+TCC interfaces can be RPC or internal JVM calls, meaning that a TCC interface
has both a sender and a caller identity. In the example above, the TCC
interface is the sender in Service A and Service B, and the caller in the
business system. If the TCC interface is a Dubbo RPC, the caller is a
dubbo:reference and the sender is a dubbo:service.
+
+<img src="/img/blog/20220116161933.png" alt="img" style={{ zoom:'50%' }} />
+
+When Seata starts, it scans and parses the TCC interfaces. If a TCC interface
is a sender, Seata registers the TCC Resource with the TC during startup, and
each TCC Resource has a resource ID. If a TCC interface is a caller, Seata
proxies the caller and intercepts the TCC interface calls. Similar to the AT
mode, the proxy intercepts the call to the Try method, registers a branch
transaction with the TC, and then executes the original RPC call.
+
+When the global transaction decides to commit/rollback, the TC will callback
to the corresponding participant service to execute the Confirm/Cancel method
of the TCC Resource using the resource ID registered by the branch.
+
+# How Seata Implements TCC Mode
+
+From the above Seata TCC model, it can be seen that the TCC mode in Seata also
follows the TC, TM, RM three-role model. How to implement TCC mode in these
three-role models? I mainly summarize the implementation as resource parsing,
resource management, and transaction processing.
+
+## Resource Parsing
+
+Resource parsing is the process of parsing and registering TCC interfaces. As
mentioned earlier, TCC interfaces can be RPC or internal JVM calls. In the
Seata TCC module, there is a remoting module that is specifically used to parse
TCC interfaces with the `TwoPhaseBusinessAction` annotation:
+
+<img src="/img/blog/20220116175059.png" alt="img" style={{ zoom:'50%' }} />
+
+The `RemotingParser` interface mainly has methods such as `isRemoting`,
`isReference`, `isService`, `getServiceDesc`, etc. The default implementation
is `DefaultRemotingParser`, and the parsing of various RPC protocols is
executed in `DefaultRemotingParser`. Seata has already implemented parsing of
Dubbo, HSF, SofaRpc, and LocalTCC RPC protocols while also providing SPI
extensibility for additional RPC protocol parsing classes.
+
+During the Seata startup process, the `GlobalTransactionScanner` annotation is
used for scanning and executes the following method:
+
+`io.seata.spring.util.TCCBeanParserUtils#isTccAutoProxy`
+
+The purpose of this method is to determine if the bean has been TCC proxied.
In the process, it first checks if the bean is a Remoting bean. If it is, it
calls the `getServiceDesc` method to parse the remoting bean, and if it is a
sender, it registers the resource:
+
+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;
+ }
+```
+
+The above method first calls the parsing class `getServiceDesc` method to
parse the remoting bean and puts the parsed `remotingBeanDesc` into the local
cache `remotingServiceMap`. At the same time, it calls the parsing class
`isService` method to determine if it is the initiator. If it is the initiator,
it parses the content of the `TwoPhaseBusinessAction` annotation to generate a
`TCCResource` and registers it as a resource.
+
+## Resource Management
+
+**1. Resource Registration**
+
+The resource for Seata TCC mode is called `TCCResource`, and its resource
manager is called `TCCResourceManager`. As mentioned earlier, after parsing the
TCC interface RPC resource, if it is the initiator, it will be registered as a
resource:
+
+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` contains the relevant information of the TCC interface and is
cached locally. It continues to call the parent class `registerResource` method
(which encapsulates communication methods) to register with the TC. The TCC
resource's resourceId is the actionName, and the actionName is the name in the
`@TwoParseBusinessAction` annotation.
+
+**2. Resource Commit/Rollback**
+
+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;
+ }
+ }
+```
+
+When the TM resolves the phase two commit, the TC will callback to the
corresponding participant (i.e., TCC interface initiator) service to execute
the Confirm/Cancel method of the TCC Resource registered by the branch.
+
+In the resource manager, the corresponding `TCCResource` will be found in the
local cache based on the resourceId, and the corresponding
`BusinessActionContext` will be found based on xid, branchId, resourceId, and
applicationData, and the parameters to be executed are in the context. Finally,
the commit method of the `TCCResource` is executed to perform the phase two
commit.
+
+The phase two rollback is similar.
+
+## Transaction Processing
+
+As mentioned earlier, if the TCC interface is a caller, the Seata TCC proxy
will be used to intercept the caller and register the branch before processing
the actual RPC method call.
+
+The method `io.seata.spring.util.TCCBeanParserUtils#isTccAutoProxy` not only
parses the TCC interface resources, but also determines whether the TCC
interface is a caller. If it is a caller, it returns true:
+
+io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary
+
+<img src="/img/blog/20220116192544.png" alt="img" style={{ zoom:'50%' }} />
+
+As shown in the figure, when `GlobalTransactionalScanner` scans the TCC
interface caller (Reference), it will proxy and intercept it with
`TccActionInterceptor`, which implements `MethodInterceptor`.
+
+In `TccActionInterceptor`, it will also call `ActionInterceptorHandler` to
execute the interception logic, and the transaction-related processing is in
the `ActionInterceptorHandler#proceed` method:
+
+```java
+public Object proceed(Method method, Object[] arguments, String xid,
TwoPhaseBusinessAction businessAction,
+ Callback<Object> 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 {
+ // ... ...
+ }
+ }
+}
+```
+
+In the process of executing the first phase of the TCC interface, the
`doTccActionLogStore` method is called for branch registration, and the
TCC-related information such as parameters is placed in the context. This
context will be used for resource submission/rollback as mentioned above.
+
+# How to control exceptions
+
+In the process of executing the TCC model, various exceptions may occur, the
most common of which are empty rollback, idempotence, and suspense. Here I will
explain how Seata handles these three types of exceptions.
+
+## How to handle empty rollback
+
+What is an empty rollback?
+
+An empty rollback refers to a situation in a distributed transaction where the
TM drives the second-phase rollback of the participant's Cancel method without
calling the participant's Try method.
+
+How does an empty rollback occur?
+
+<img src="/img/blog/20220116201900.png" alt="img" style={{ zoom:'50%' }} />
+
+As shown in the above figure, after the global transaction is opened,
participant A will execute the first-phase RPC method after completing branch
registration. If the machine where participant A is located crashes or there is
a network anomaly at this time, the RPC call will fail, meaning that
participant A's first-phase method did not execute successfully. However, the
global transaction has already been opened, so Seata must progress to the final
state. When the global transaction is [...]
+
+To prevent empty rollback, it is necessary to identify it in the Cancel
method. How does Seata do this?
+
+Seata's approach is to add a TCC transaction control table, which contains the
XID and BranchID information of the transaction. A record is inserted when the
Try method is executed, indicating that phase one has been executed. When the
Cancel method is executed, this record is read. If the record does not exist,
it means that the Try method was not executed.
+
+## How to Handle Idempotent Operations
+
+Idempotent operation refers to TC repeating the two-phase commit, so the
Confirm/Cancel interface needs to support idempotent processing, which means
that it will not cause duplicate resource submission or release.
+
+So how does idempotent operation arise?
+
+<img src="/img/blog/20220116203816.png" alt="img" style={{ zoom:'50%' }} />
+
+As shown in the above figure, after participant A completes the two phases,
network jitter or machine failure may cause TC not to receive the return result
of participant A's execution of the two phases. TC will continue to make
repeated calls until the two-phase execution result is successful.
+
+How does Seata handle idempotent operations?
+
+Similarly, a status field is added to the TCC transaction control table. This
field has 3 values:
+
+1. tried: 1
+2. committed: 2
+3. rollbacked: 3
+
+After the execution of the two-phase Confirm/Cancel method, the status is
changed to committed or rollbacked. When the two-phase Confirm/Cancel method is
called repeatedly, checking the transaction status can solve the idempotent
problem.
+
+## How to Handle Suspend
+
+Suspension refers to the two-phase Cancel method being executed before the
phase Try method, because empty rollback is allowed. After the execution of the
two-phase Cancel method, directly returning success, the global transaction has
ended. However, because the Try method is executed later, this will cause the
resources reserved by the phase Try method to never be committed or released.
+
+So how does suspension arise?
+
+<img src="/img/blog/20220116205241.png" alt="img" style={{ zoom:'50%' }} />
+
+As shown in the above figure, when participant A's phase Try method is
executed, network congestion occurs, and due to Seata's global transaction
timeout limit, after the Try method times out, TM resolves to roll back the
global transaction. After the rollback is completed, if the RPC request arrives
at participant A at this time and the Try method is executed to reserve
resources, it will cause suspension.
+
+How does Seata handle suspension?
+
+Add a status to the TCC transaction control table:
+
+1. suspended: 4
+
+When the two-phase Cancel method is executed, if it is found that there is no
related record in the TCC transaction control table, it means that the
two-phase Cancel method is executed before the phase Try method. Therefore, a
record with status=4 is inserted. Then, when the phase Try method is executed,
if status=4 is encountered, it means that the two-phase Cancel has been
executed, and false is returned to prevent the phase Try method from succeeding.
+
+# Author Introduction
+
+Zhang Chenghui, currently working at Ant Group, loves to share technology. He
is the author of the WeChat public account "Advanced Backend," the author of
the technical blog (https://objcoding.com/), a Seata Committer, and his GitHub
ID is: objcoding.
+
diff --git a/i18n/zh-cn/docusaurus-plugin-content-blog/seata-tcc.md
b/i18n/zh-cn/docusaurus-plugin-content-blog/seata-tcc.md
index 4ec4d3cef6..c9024fbcfd 100644
--- a/i18n/zh-cn/docusaurus-plugin-content-blog/seata-tcc.md
+++ b/i18n/zh-cn/docusaurus-plugin-content-blog/seata-tcc.md
@@ -1,14 +1,9 @@
---
title: 深度剖析 Seata TCC 模式(一)
-
author: 张乘辉
-
keywords: [Seata、分布式事务、TCC]
-
description: Seata 目前支持 AT 模式、XA 模式、TCC 模式和 SAGA 模式,之前文章更多谈及的是非侵入式的 AT
模式,今天带大家认识一下同样是二阶段提交的 TCC 模式。
-
date: 2022/01/18
-
---
# 前言
@@ -26,7 +21,7 @@ TCC 是分布式事务中的二阶段提交协议,它的全称为 Try-Confirm-
TCC 是一种侵入式的分布式事务解决方案,以上三个操作都需要业务系统自行实现,对业务系统有着非常大的入侵性,设计相对复杂,但优点是 TCC
完全不依赖数据库,能够实现跨数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决了在各种复杂业务场景下的分布式事务问题。
-
+<img src="/img/blog/20220116160157.png" alt="img" style={{ zoom:'50%' }} />
# Seata TCC 模式
@@ -76,7 +71,7 @@ public String doTransactionCommit(){
TCC 接口可以是 RPC,也可以是 JVM 内部调用,意味着一个 TCC 接口,会有发起方和调用方两个身份,以上例子,TCC 接口在服务 A 和服务 B
中是发起方,在业务所在系统中是调用方。如果该 TCC 接口为 Dubbo
RPC,那么调用方就是一个 dubbo:reference,发起方则是一个 dubbo:service。
-
+<img src="/img/blog/20220116161933.png" alt="img" style={{ zoom:'50%' }} />
Seata 启动时会对 TCC 接口进行扫描并解析,如果 TCC 接口是一个发布方,则在 Seata 启动时会向 TC 注册 TCC Resource,每个
TCC Resource 都有一个资源 ID;如果 TCC
接口时一个调用方,Seata 代理调用方,与 AT 模式一样,代理会拦截 TCC 接口的调用,即每次调用 Try 方法,会向 TC
注册一个分支事务,接着才执行原来的 RPC 调用。
@@ -92,7 +87,7 @@ Seata 启动时会对 TCC 接口进行扫描并解析,如果 TCC 接口是一
资源解析即是把 TCC 接口进行解析并注册,前面说过,TCC 接口可以是 RPC,也可以是 JVM 内部调用,在 Seata TCC 模块有中一个
remoting
模块,该模块专门用于解析具有 `TwoPhaseBusinessAction` 注解的 TCC 接口资源:
-
+<img src="/img/blog/20220116175059.png" alt="img" style={{ zoom:'50%' }} />
`RemotingParser` 接口主要有 `isRemoting`、`isReference`、`isService`、`getServiceDesc`
等方法,默认的实现为 `DefaultRemotingParser`,其余各自的
RPC 协议解析类都在 `DefaultRemotingParser` 中执行,Seata 目前已经实现了对
Dubbo、HSF、SofaRpc、LocalTCC 的 RPC 协议的解析,同时具备 SPI 可扩展性,未来欢迎大家为
@@ -227,7 +222,7 @@ public BranchStatus branchCommit(BranchType
branchType,String xid,long branchId,
io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary
-
+<img src="/img/blog/20220116192544.png" alt="img" style={{ zoom:'50%' }} />
如图,当 `GlobalTransactionalScanner` 扫描到 TCC 接口调用方(Reference)时,会使
`TccActionInterceptor` 对其进行代理拦截处理,`TccActionInterceptor`
实现 `MethodInterceptor`。
@@ -270,7 +265,7 @@ public Object proceed(Method
method,Object[]arguments,String xid,TwoPhaseBusines
那么空回滚是如何产生的呢?
-
+<img src="/img/blog/20220116201900.png" alt="img" style={{ zoom:'50%' }} />
如上图所示,全局事务开启后,参与者 A 分支注册完成之后会执行参与者一阶段 RPC 方法,如果此时参与者 A 所在的机器发生宕机,网络异常,都会造成 RPC
调用失败,即参与者 A 一阶段方法未成功执行,但是此时全局事务已经开启,Seata
必须要推进到终态,在全局事务回滚时会调用参与者 A 的 Cancel 方法,从而造成空回滚。
@@ -285,7 +280,7 @@ Seata 的做法是新增一个 TCC 事务控制表,包含事务的 XID 和 Bra
那么幂等问题是如何产生的呢?
-
+<img src="/img/blog/20220116203816.png" alt="img" style={{ zoom:'50%' }} />
如上图所示,参与者 A 执行完二阶段之后,由于网络抖动或者宕机问题,会造成 TC 收不到参与者 A 执行二阶段的返回结果,TC
会重复发起调用,直到二阶段执行结果成功。
@@ -306,7 +301,7 @@ Seata 是如何处理幂等问题的呢?
那么悬挂是如何产生的呢?
-
+<img src="/img/blog/20220116205241.png" alt="img" style={{ zoom:'50%' }} />
如上图所示,在执行参与者 A 的一阶段 Try 方法时,出现网路拥堵,由于 Seata 全局事务有超时限制,执行 Try 方法超时后,TM
决议全局回滚,回滚完成后如果此时 RPC 请求才到达参与者 A,执行 Try
方法进行资源预留,从而造成悬挂。
diff --git a/static/img/blog/20220116160157.png
b/static/img/blog/20220116160157.png
new file mode 100644
index 0000000000..8d0de99d8a
Binary files /dev/null and b/static/img/blog/20220116160157.png differ
diff --git a/static/img/blog/20220116161933.png
b/static/img/blog/20220116161933.png
new file mode 100644
index 0000000000..6f311996cb
Binary files /dev/null and b/static/img/blog/20220116161933.png differ
diff --git a/static/img/blog/20220116175059.png
b/static/img/blog/20220116175059.png
new file mode 100644
index 0000000000..ae06a6a1f7
Binary files /dev/null and b/static/img/blog/20220116175059.png differ
diff --git a/static/img/blog/20220116192544.png
b/static/img/blog/20220116192544.png
new file mode 100644
index 0000000000..6912914a2d
Binary files /dev/null and b/static/img/blog/20220116192544.png differ
diff --git a/static/img/blog/20220116201900.png
b/static/img/blog/20220116201900.png
new file mode 100644
index 0000000000..d392eedaf7
Binary files /dev/null and b/static/img/blog/20220116201900.png differ
diff --git a/static/img/blog/20220116203816.png
b/static/img/blog/20220116203816.png
new file mode 100644
index 0000000000..6ec091a939
Binary files /dev/null and b/static/img/blog/20220116203816.png differ
diff --git a/static/img/blog/20220116205241.png
b/static/img/blog/20220116205241.png
new file mode 100644
index 0000000000..f1dc544527
Binary files /dev/null and b/static/img/blog/20220116205241.png differ
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]