This article is the nuggets community first contract article, not authorized to reprint

The distributed CAP theory, which describes A series of trade-offs between consistency (C), availability (A), and fault tolerance (P) of partitions, should be well known. Most of the time, we have to trade off between consistency and availability, and distributed transactions are about achieving consistency as much as possible under this large premise.

The goals are small, the problems large, and the approaches varied.

“How do you implement distributed transactions in microservices?” When asked this question, I usually say “avoid distributed transactions as much as possible,” which Martin Fowler recommends. But the reality is that with microservices broken down, distributed transactions are a very hard core requirement, and we still have to figure out how to do it. However, with the complexity of distributed environment and the time-outs caused by network conditions, it is difficult to achieve a consistent state of transactions.

Distributed transactions, consisting of a series of small sub-transactions. These subtransactions, like large distributed transactions, are subject to ACID principles. Consistency can be divided into strong consistency and final consistency (BASE) according to the time before consistency is achieved.

Note that there is a slight misunderstanding regarding sub-transactions. Transactions are not the only operations that deal with databases. In a microservice environment, if you call another remote interface through an RPC and cause a change in the state of the associated data, this RPC interface is also called a transaction.

So, in distributed transactions, we call the operations involved in these sub-transactions resources. When the operation completes normally, no extra processing is required. Transactions mainly deal with the flow after an exception occurs.

Let’s take a look at common distributed transaction solutions.

1. One-stage submission (1PC)

Let’s start with the simplest transaction commit case.

If your business has only one resource to coordinate, then it can be submitted directly. For example, if you are using a database, you can use begin, COMMIT, etc.

In Spring, you can do this with annotations. If a nested transaction occurs, its implementation is, in essence, passed down through ThreadLocal. So if your application has subthread-related transactions to manage, it can’t.

Let’s look at distributed transactions. Distributed transaction refers to the coordination of two or more resources to achieve the effect of joint commit or joint failure. Distributed transaction refers to distributed ACID.

2. Two-stage Submission (2PC)

Extending the concept of one-phase commit, the simplest solution for distributed transactions is two-phase commit. A two-phase commit does not mean that there are two participating resources, but rather that there are two distributed coordination phases, which may have multiple resources to coordinate.

2.1 Key Participants

  1. A coordinator, which we need to build our own transaction manager, usually has only one in the system
  2. Participants refer to the resources we refer to. Usually, there are more than one resources, otherwise they would not be considered as distributed transactions

2.2 process

What are the two phases of 2PC (two Phase commit)?

  1. clientDistributed transaction initiator
  2. commit-request/votingPreparation stage
  3. commit/rollbackCommit or roll back

The preparation stage, also known as the voting stage. Voting is when participants tell the coordinator whether their resource can be committed (indicating that it is ready) or if the transaction is cancelled (for example, if an exception occurs).

If one of the participants returns false, the transaction needs to terminate and rollback is performed. Commit normally occurs only when all votes pass. This result is known by the coordinator to all participants in the process as phase two.

Two-phase commit is actually pretty easy to understand. You can think of each participant’s execution as a normal SQL update. They hang there and wait until the coordinator gives an exact COMMIT or ROLLBACK message before proceeding normally.

2.3 the problem

  1. Blocking problems. The biggest problem with two-phase commit is that it is a blocking protocol and inefficient. If the coordinator fails permanently, some participants will never be able to complete their transactions
  2. Single point of failure. Since the coordinator has a very important role in the whole process, once it happensSPOFThe whole system will become unusable which is intolerable
  3. Transaction integrity issues. In some cases, such as after the coordinator sends a COMMIT command, an exception occurs and a portion of the execution succeeds, causing the entire transaction to be inconsistent. Because the first stage determines whether or not you can submit it, and the second stage is just a notice that you will submit it to me even if you die
  4. Not all resources support 2PC (or XA)

For the third point, let’s take an example. Let’s say your commit-request phase all returns yes, and then the coordinator sends a COMMIT directive. However, at this time, one of the servers (SERVER A) is down and cannot perform the commit. At this point, our client will also receive a success message. After machine A is restarted, the ability to recover and continue to execute commit instructions is A project must deal with.

2.4 framework

2PC is also called XA transactions, and most databases, such as MySQL, support XA. In Java, JTA (not JPA) is the XA protocol implementation. Spring also has a JTA transaction manager.

  • Atomikos and Bitronix implement JTA and all they need to do is provide jar packages. Databases or message queues that implement the XA protocol already have various capabilities for preparing, committing, and rolling back
  • With frameworks such as SEATA, you need to start a separate SEATA service coordinator node. The AT used by SEATA, with the help of an external transaction manager, is similar in concept to XA

3. Three-phase submission (3PC)

Compared with the two-phase commit, the most typical feature of the three-phase commit is the addition of a timeout mechanism. And, of course, three stages proves that it has three stages, which is even more significant. It’s essentially just an improvement on 2PC, so it’s completely like 2PC.

3.1 Key Participants

3PC is the same thing as 2PC.

3.2 process

3PC has one more step than 2PC, which is the inquiry stage.

  1. CanCommit Query phase
  2. PreCommit Preparation phase
  3. DoCommit Commit phase

In the commit phase, which is nothing more than sending a COMMIT or rollback command, the important processing is still in the preparation phase, which is broken down into 2 by 3PC.

Notice the following correspondence: 2PC and 3PC both have a preparation phase, but they do different things.

3PC					2PC
CanCommit			commit-request/voting
PreCommit 
DoCommit 			commit
Copy the code

The question stage of 3PC corresponds to the preparation stage of 2PC, which is to ask participants whether they are ready, but there will be some differences in the execution process.

Why would you do that? Because of the 2PC efficiency problem. The execution of 2PC is blocked. After a resource enters the preparation phase, it must wait for all resources to be ready before it can proceed to the next step. During this process, they have no knowledge of the global situation.

For example, with five participants such as ABCDE, E is actually a problematic participant resource. However, 2PC will perform the pre-submission of ABCD every time. When E is asked, it finds that there is a problem, and then performs the rollback of ABCD and other participants in turn. In this case, ABCD performs useless transaction preprocessing and rollback, which is very wasteful.

3PC can be more efficient and fault-tolerant by splitting the query phase and initiating the actual transaction while ensuring that all participants are in good health. In terms of probability, since the granularity before commit becomes smaller, the probability of problems during commit becomes smaller, which can save a lot of things.

In addition, 3PC introduced a timeout mechanism. During the PreCommit phase, a timeout is considered a failure; During the DoCommit phase, execution continues if a timeout occurs. But in any case, the whole affair will not wait forever.

3.3 the problem

The 3PC is good in theory and avoids the blocking problem, but it has one more network communication. If the number of participants is large and the network quality is poor, this cost can be considerable. Its implementation is also more complex, in practical application, is not too much.

3PC is not perfect either, because the PreCommit phase and DoCommit phase are not atomic, and like 2PC, there are still consistency issues.

4. TCC

TCC is a flexible transaction, whereas all the above are rigid transactions. Sometimes, a technical problem can be solved through business modeling.

2PC and 3PC May seem simple in concept, but in a distributed environment, with all the timeouts and outages involved, it can be a death trap.

2PC’s framework is still more, but 3PC’s entire network to find a search, found that there is almost no famous implementation.

Don’t be sad, we have distributed transactions that are easier to understand and more intuitive. That’s TCC, the 2007 old fuddy-duddy.

TCC is the well-known compensation transaction, which is the most commonly used distributed transaction in Internet environment. Its core idea is: for each operation, prepare a confirmation action and corresponding compensation action, a total of three methods.

Rather than rely on the database, it is better to rely on their own code! 2PC, 3PC, all tied to the database, TCC is the coder’s favorite (that is, you write more code).

As shown, TCC is also divided into three stages, but very rough!

  • Try Indicates that the resource is locked
  • The Confirm confirmation phase attempts to commit the locked resource
  • Cancel Cancel phase If a link fails to execute, the transaction is cancelled

So it looks like these three phases are a kind of phase 2 commit, right? Not at all. But their processes can be compared.

TCC 2PC Try Confirm commit-request/voting + commit Cancel rollbackCopy the code

As you can see from the above, 2PC is a partition of the transaction process, while TCC is a compensation for normal commits and exceptions. Together, try and confirm are the real business logic, as opposed to traditional code.

TCC is very easy to understand, but it has a big premise that all three actions must be idempotent and have certain requirements on the business. In the case of a money transfer, try is to freeze the amount. Confirm means to complete a deduction. Cancel is unfreeze, and as long as the corresponding order number is always the same, there will be no problem executing it multiple times.

Since the TCC transaction is initiated by the originator, it can be completed directly at the business node, in the same place as the TCC code. Therefore, TCC does not require an additional coordinator and transaction handler, it can be stored in local tables or resources.

Yes, it also has to record some information, even in a HashMap, otherwise it rolls back to what?

4.1 the problem

TCC transactions, which require more coding and proper try and confirm partitioning. Because there is no central coordinator and no need to block, TCC has high concurrency and is widely used by Internet services.

The team should be able to design the TCC interface, break it down into the correct Try and Confirm phases, and achieve hierarchical business logic.

4.2 framework

ByteTCC, TCC-Transaction, seATA, etc.

5. SAGA

SAGA is also a flexible transaction.

Saga is even older, dating back to a 1987 paper, and is a bottle of old wine. It primarily deals with live transactions, but it does not guarantee ACID, only final consistency.

So-called live transactions can be broken down into interleaving sub-transactions that coordinate a series of local sub-transactions through messages to achieve final consistency.

We can think of the SAGA choreographer as a state machine. Each time a message is processed, it can proceed to the next message (subtransaction) to be executed.

For example, we split transaction T into T1, T2, T3, and T4. Then we have to provide execution logic and compensation logic for these subtransactions. Yes, it’s the same as TCC, but with a Try less than TCC, it also requires these operations to be idempotent.

Look, the SAGA concept is easy to understand and you just follow normal business logic. However, if an exception occurs at any step, all previously committed data is rolled back (compensated). The only exception is that it is usually message-driven to complete transaction execution.

If you want to pursue its essence, it is that SAGA, like TCC, tracks execution and then retries to reach the final state.

The figure above is a typical SAGA transaction breakdown diagram drawn by Rob Vettor. In the figure, the black line is the normal business process and the red line is the compensation business process. This is a simple e-commerce checkout process, the whole transaction across 5 micro services, can be said to be a very large long transaction.

As you can see, such transaction flows are not easily understood by text description, so SAGA usually has a process editor that visualizes the process of transaction orchestration.

5.1 the problem

That’s a much more interesting question.

  1. Nesting problem. SAGA only allows two levels of nesting, because the flow of messages is inherently complex and deep nesting is not allowed in terms of performance or timing
  2. If your transaction contains many sub-transactions, it is likely to fail at some stage. But what if compensation also goes wrong? In extreme cases, human involvement is required. In many cases, a saga log is needed to complete the task
  3. Because these small transactions are not committed at the same time, dirty data is generated during execution, similar to the concept of a database read uncommited

5.2 framework

In Chapter 4 of Microservices Architecture Design Patterns, a detailed example of how to use SAGA is illustrated, which is now the source of most articles on the web. However, as far as I know, not many Internet companies use SAGA, but many use TCC (probably because the distributed transactions encountered are not long transactions).

Seata also offers the SAGA approach, using the state machine-driven choreography mode. To support orchestration of transactions, SEATA provides a dedicated process editor (online).

http://seata.io/saga_designer/index.html
Copy the code

Once the design is complete, it can be exported as a JSON file that can be parsed and written to the database.

Although called TCC, BytetCC also supports SAGA.

5.3 SAGA vs TCC

As mentioned above, I use TCC more than SAGA in my daily work, which is also determined by business scenarios. Here’s a quick comparison.

  1. Development difficulty. TCC is more difficult to develop than SAGA because it requires the Try phase to freeze resources, whereas SAGA executes local transactions directly
  2. Dirty read problem. TCC does not have dirty reads because the try phase does not affect the data; SAGA has dirty reads between small transactions, or between cancels
  3. Efficiency. TCC needs to interact with participants twice, whether it succeeds or fails; SAGA interacts once in normal cases and twice in abnormal cases, so it is more efficient
  4. Business processes. TCC is good for a small number of distributed transaction flows that would otherwise be a nightmare to write; SAGA is suitable for businesses with long business processes and multiple parties, or businesses with legacy systems that cannot be transformed into TCC
  5. Methods. TCC solves technical problems through business modeling; SAGA is a technical solution to transaction choreography

6. Local message table

The local message table scenario is more limited, it relies on MQ to implement, it solves the database transaction and MQ transaction problem.

As shown in the figure, there is a distributed transaction that needs to be coordinated through MQ after a normal drop-off. However, writing to DB and MQ cannot be consistent, and a local message table needs to be added to cache the state sent to MQ. Let me describe the process.

  • 1.1 Writing data to the Database normally
  • 1.2 Write a local message table while writing to the database. This table, which records the status of MQ message processing, can haveSend in theandHas been completedTwo states. This can be achieved because the message table and the normal business table are in the same DBlocalTransactions to ensure simultaneous completion
  • 2 After a successful write to the message table, MQ messages can be sent asynchronously, regardless of whether the delivery was successful
  • 3 Subsequent services Subscribe to MQ messages. After the consumption is successful, the status of the successful execution will be sent through MQ. The local business subscribes to the execution state and changes the corresponding record state in the message table to completed. If the consumption fails, do not do much processing
  • 4 A scheduled task continuously scans the local message table. The status isSend in the(note the delay), and send these messages to MQ again, repeating the process of 2

Through this loop, local DB and MQ consumer state consistency can be achieved, resulting in a distributed transaction that is ultimately consistent.

As you can see, we have the process of resending MQ, so this pattern requires the consumer to implement idempotent functionality as well to avoid duplicating the business.

6.1 the problem

There are many systems that use the local message table scheme, but its disadvantages are obvious.

  1. You need to develop specialized code that is coupled to the business, not an abstract framework
  2. Local message tables need to be written to the database, which can add strain to the database if the DATABASE’s OWN I/O is already high

7. Do your best to make amends

Maximum effort compensation is a kind of attenuation compensation mechanism.

Take the simplest example. If you are the access party of wechat Pay, after wechat Pay succeeds, it will push the payment result to the interface you specify.

Wechat Pay + your payment results processing can be regarded as a large distributed transaction. It involves the system of wechat and your own system.

If your system has not been successfully processed, wechat Pay will continue to retry. This is called maximum effort compensation, and it works both within and between systems.

However, it is not possible to retry indefinitely, as the interval between retries usually diminishes over time. Common attenuation strategies are.

messageDelayLevel = 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
Copy the code

The above formula means that if Chen Gong cannot be processed, it will take 1s… , and retry in 2 hours at most. If you don’t, you can only enter the manual processing channel.

Maximum effort compensation is just an idea that can be applied in many ways. For example, it is possible for me to first drop the transaction to the message queue, and then rely on the retry mechanism of the message queue for maximum compensation.

8. To summarize

In this article, we started from local affairs, talking about 2PC, 3PC, TCC, SAGA, local message table, maximum effort compensation, etc., and also learned some application scenarios and solutions of various solutions.

Distributed transaction framework, based on these theories, has been more or less revised, there are many innovations. For example, THE LCN framework (Lock, Confirm, notify) abstracts the concepts of the control side and the initiator side, and those who are interested can learn about it by themselves.

In Internet companies, due to the demands of high concurrency, soft transactions are generally selected for business processing in practice, compared with strong transactions. The most used solutions are TCC, SAGA, local message tables, etc. SAGA is particularly good at handling long transactions, but less isolated; TCC has high concurrency but requires more coding. Local message table application scenarios are limited, and coupled services cannot be reused. Each solution has its pros and cons, so make sure you choose it in a context.

On the framework side, Alibaba’s SeATA (fescar in the early years) has been widely used and supports XA, TCC, SAGA and other modes. If you need this feature, you can try it out.

After reading this article, I hope to come across the question “How to implement distributed transactions in microservices?” again. In addition to answering “avoid distributed transactions as much as possible,” you can find a practical solution to this problem.

This article is the nuggets community first contract article, not authorized to reprint