Distributed transaction
0. Prepare before class
0.1. Learning Objectives
- Understand why distributed transactions occur
- Know several distributed transaction solutions: XA, TCC, message transaction, TA, SAGA
- Know the pros and cons and usage scenarios of various solutions for distributed transactions
- Learn to use Seata to solve distributed transactions
0.2. Knowledge preparation
- Will use SpringBoot
- Use SpringCloud’s Eureka and Feign components
- Know about mysql’s traditional transaction features
1. What are distributed transactions
To understand distributed transactions, you must first understand local transactions.
1.1. Local transactions
Local transactions, which refer to traditional stand-alone database transactions, must have the ACID principle:
- Atomicity (A)
Atomicity means that all operations in the entire transaction either complete or do not complete, with no intermediate states. If a transaction fails during execution, all operations are rolled back, as if the entire transaction had never been executed.
- Consistency (C)
Transaction execution must ensure the consistency of the system, after the transaction prior to the start and end of the transaction, the integrity of the database is not damaged, take the transfer, for example, A 500 yuan, B 500 yuan, if in A transaction A success to B50 yuan, so no matter what happens, then the final accounts and B account data must be the sum of 1000 yuan.
- Isolation (I)
The so-called isolation means that transactions do not affect each other, and the intermediate state of a transaction is not perceived by other transactions. Database guaranteed isolation includes four different isolation levels:
Read Uncommitted
Read Committed
Repeatable Read (Repeatable Read)
Serializable
- Persistence (D)
Persistence means that once a transaction is committed, the changes made by the transaction to the data are completely stored in the database, even if there is a power outage or system outage.
Because in traditional projects, project deployment is basically a single point: a single server and a single database. In this case, the database’s own transaction mechanism guarantees ACID’s principle, and such a transaction is a local transaction.
In general, the transactions generated in a single service and a single database schema are local transactions.
Atomicity and persistence are achieved through undo and redo logs.
1.2. The undo and redo
Reference for this section: mysqlops
In a database system, there are both data files and log files. There are also Log buffers and disk files in memory.
There are two types of log files in MySQL related to transactions: undo logs and redo logs.
1.2.1. Undo log
Database transactions have Atomicity and need to be rolled back if the transaction fails.
Transactions also offer Durability ** * so that changes they make to data are completely stored in the database and cannot be lost due to failures.
Atomicity can be achieved using undo logging.
The principle of Undo Log is very simple. To satisfy the atomicity of transactions, data is backed up to Undo Log before any data is manipulated. Then the data is modified. If an error occurs or the user performs a ROLLBACK statement, the system can use the backup in the Undo Log to restore the data to the state before the transaction began.
Before the database writes data to disk, the data is cached in memory and written to disk only when the transaction is committed.
Using Undo Log to simplify atomic and persistent transactions:
Suppose we have data A and B, and the values are 1,2. A. Transaction starts. B. Record A=1 to undo log. C. Modify A= 3.d. Record B=2 to undo log. E. Modify B= 4.f. Write undo log to disk. G. Write data to disks. H. Transaction commit
-
How to ensure persistence?
Before a transaction is committed, the modified data is put to disk, which means that as long as the transaction is committed, the data must be persisted.
-
How do you guarantee atomicity?
-
Every time the database is modified, the data before the modification is recorded in the Undo log. When the rollback is required, the undo log can be read to restore the data.
-
If the system crashes between G and H
The transaction is not committed and needs to be rolled back. The undo log has been persisted and can be used to restore data
-
If the system crashes before G
At this point, the data is not persisted to the hard disk and remains in the state before the transaction
-
** Disadvantages: ** Data and Undo Log are written to disk before each transaction commits, which causes a lot of disk I/O and therefore poor performance.
If you can cache data for a period of time, you can reduce IO and improve performance. But then the persistence of the transaction is lost. Therefore, another mechanism for persistence was introduced, called Redo logs.
1.2.2. Redo log
In contrast to the Undo Log, the Redo Log records a backup of new data. The Redo Log is persisted before a transaction is committed. Data is not persisted, which reduces the number of I/OS.
Let’s start with the basics:
Simplification of Undo + Redo transactions
Suppose we have data A and B, and the values are 1,2
A. Transaction starts. B. Record A=1 to undo log buffer. C. Modify A= 3.d. Log A=3 to redo log buffer. e. log B=2 to undo log buffer. F. Redo log buffer.h. Redo log buffer.h. Write undo log to disk I. write redo log to disk J. Transaction commit
Safety and performance issues
-
How do you guarantee atomicity?
If a failure occurs before the transaction commits, data is recovered using undo log logs. If the undo log has not been written, the data has not been persisted and does not need to be rolled back
-
How do YOU guarantee persistence?
As you can see, there is no persistence of data. Because the data is already written to the redo log, and the redo log is persisted to the hard disk, transactions can be committed after step I.
-
When does in-memory database data persist to disk?
Because the redo log is persistent, it does not matter whether the database data is written to disk or not. However, to avoid dirty data, memory data is flushed to disk after a transaction is committed (you can also flush memory data to disk at a fixed frequency).
-
When the redo log is written to the disk
Redo logs are written to disk before a transaction commits or when the redo log buffer is full
There are two problems:
Problem 1: The previous undo and database data was written to disk, now undo and redo data is written to disk, it does not seem to reduce I/O times
- Database data write is random IO, performance is poor
- Redo logs initialize a contiguous space and write sequential I/OS
- In fact, the undo log is not written directly to the disk. Instead, it is written to the redo log buffer first. When the redo log is persisted, the undo log is persisted to the disk.
Therefore, you only need to persist the redo log before the transaction commits.
In addition, redo logs are not persisted once they are written. Redo logs have their own buffer pool in memory: redo log buffer. Each redo log is written to the buffer and persisted to disk at commit time to reduce I/O times.
Problem 2: Redo log data is written to the buffer. When the buffer is full or a transaction is committed, the buffer data is written to disk.
How do I recover data recorded in redo logs that may contain uncommitted transactions if the database crashes?
There are two data recovery strategies:
- When recovering, only committed transactions are redone
- On recovery, all transactions are redone including uncommitted and rolled back transactions. The uncommitted transactions are then rolled back using Undo Log
The Inodb engine uses the second solution, so undo logs are persisted before redo logs
1.2.3. Summary
To sum up:
- Undo log records data before update to ensure atomicity of transactions
- The redo log records updated data and is used to ensure transaction persistence
- The redo log has its own buffer that is written to first and to disk when a transaction is committed
- When the redo log is persisted, it means that the transaction is committed
1.3. Distributed transactions
Distributed transactions refer to transactions that occur outside of a single service or database schema:
- Distributed transactions across data sources
- Distributed transactions across services
- Comprehensive situation
1) Across data sources
With the rapid development of business data scale, the amount of data is increasing, and single database and single table gradually become the bottleneck. Therefore, we split the database horizontally, splitting the original single database and single table into database shards, and then the problem of cross-database transactions occurs.
2) Cross-service
In the early stage of business development, the single business system architecture of “one big pie” can meet the basic business needs. However, with the rapid development of the business, the volume of traffic and the complexity of the business are increasing rapidly, and the single-system architecture has gradually become the bottleneck of the business development, and the demand to solve the problems of high coupling and scalability of the business system is becoming stronger and stronger.
As shown in the following figure, according to the design principles of service-oriented architecture (SOA), a single service system is divided into multiple service systems to reduce the coupling degree between the systems and enable different service systems to focus on their own services, which is more conducive to service development and system capacity expansion.
3) Data consistency of distributed system
After a horizontal database split and a vertical service split, a business operation typically spans multiple databases and services. In the distributed network environment, we cannot guarantee that all services and databases are 100% available. Some services and databases will be executed successfully while others will fail.
If some service operations succeed and some services fail, service data may be inconsistent.
For example, the common order and payment cases in the e-commerce industry include the following behaviors:
- Create a new order
- Deduct goods inventory
- Deduct the amount from the user’s account balance
To do this, you need to access three different microservices and three different databases.
In a distributed environment, it is certain that some operations will succeed and some operations will fail. For example, the order is generated and the inventory is deducted, but the balance of the user account is insufficient, resulting in inconsistent data.
Order creation, inventory deduction, and account deduction are local transactions within each service and database that guarantee the ACID principle.
However, when we regard three things as one thing, to ensure the atomicity of “business”, either all operations succeed or all fail, and the phenomenon of partial success and partial failure is not allowed, this is the transaction under distributed system.
ACID is hard to satisfy at this point, which is the problem for distributed transactions
2. The idea of solving distributed transactions
Why is the ACID principle of transactions difficult to satisfy in distributed systems?
It starts with CAP theorem and BASE theory.
2.1 CAP theorem
This section is excerpted from: Meaning of CAP theorem
What is the CAP theorem?
In 1998, Eric Brewer, a computer scientist at the University of California, Proposed that distributed systems have three metrics.
- Consistency
- Availability
- Partition tolerance
They start with C, A and P.
Eric Brewer says it is impossible to do all three at once. This conclusion is called the CAP theorem.
2.1.1. How
Let’s start with Partition tolerance.
Most distributed systems are distributed over multiple subnetworks. Each sub-network is called a partition. Partition fault tolerance means that interval communication may fail. For example, if one server is located in Shanghai and the other server is located in Beijing, these are two zones, and they may not be able to communicate with each other due to network problems.
As shown in figure:
In the figure above, G1 and G2 are two servers across regions. G1 sends a message to G2, which may not receive it. Systems must be designed with this in mind.
In general, partition fault tolerance is unavoidable in distributed systems, so it can be assumed that P of CAP is always true. According to the CAP theorem, the rest of C and A cannot be done at the same time.
2.1.2. Consistency
Consistency is called “Consistency” in Chinese. This means that any read operation that follows a write operation must return this value. For example, if a record is v0, the user initiates a write operation to G1 to change it to v1.
Next, the user’s read will get v1. This is called consistency.
The problem is that it is possible for the user to initiate a read operation to G2, and since the value of G2 has not changed, v0 is returned. The results of G1 and G2 read operations are inconsistent, which is inconsistent.
In order for G2 to change to V1, G1 should send a message to G2 asking G2 to change to V1 when G1 writes.
In this case, the user can read to G2 and get v1.
2.1.3. The Availability
Availability means that servers must respond to user requests whenever they are received (whether true or false).
Users can choose to initiate read operations to G1 or G2. No matter which server receives the request, it must tell the user whether it is V0 or V1, otherwise availability is not satisfied.
2.1.4. Inconsistency between Consistency and Availability
Why can’t consistency and usability be the same?
The simple answer is that communication can fail (i.e., partition tolerance).
To ensure G2 consistency, G1 must lock G2’s read and write operations during write operations. Data can be read and written only after data synchronization. During the lock, G2 cannot read or write, and is not available.
If the availability of G2 is guaranteed, then G2 cannot be locked, so consistency is not valid.
In summary, G2 cannot be consistent and usable at the same time. Only one target can be selected during system design. If consistency is pursued, then availability of all nodes cannot be guaranteed; If you pursue availability across all nodes, you can’t achieve consistency.
2.1.5. A few questions
-
How can I satisfy CA at the same time?
Unless it’s a single point architecture
-
When should CP be satisfied?
Scenarios that require high consistency. For example, the Zookeeper service is unavailable during data synchronization between service nodes.
-
When is AP satisfied?
Scenarios with high availability requirements. For Eureka, for example, you must ensure that the registry is always available, or you may have problems pulling services.
2.2. The Base theory
BASE is a contraction of three words:
-
Basically Available
-
Soft state
-
Eventually consistent
And we solve distributed transactions, is based on the above theory to achieve.
Take the above orders for inventory reduction and deduction as an example:
Order service, inventory service, user service and their corresponding database are the three parts of distributed application.
-
CP mode: in order to achieve strong transaction consistency, we must lock the inventory service and user service data resources at the same time as the order service database. Resources can be released only after all three services are processed. If there are other requests that want to operate on the locked resource, it will be blocked and CP will be satisfied.
That’s strong consistency, weak availability
-
AP mode: The databases of the three services run their own services independently and perform local transactions. Resources are not locked for each other. However, in this intermediate state, when we visit the database, we may encounter inconsistent data. However, we need to take some remedial measures to ensure that the data finally meets the consistency after a period of time.
This is high availability, but weak consistency (and eventually consistency).
From the above two ideas, many distributed transaction solutions are extended:
- XA
- TCC
- Reliable information agrees in the end
- AT
2.4. Commit in phases
Against 2.4.1 DTP and XA
One of the solutions to distributed transactions is the two-phase Commit protocol (2PC: two-phase Commit).
So what exactly is a two-phase commit protocol?
In 1994, the X/Open Organization (now known as the Open Group) defined a DTP model for distributed transaction processing. The model includes several roles:
- Applications (AP) : Our microservices
- Transaction Manager (TM) : Global transaction manager
- Resource Manager (RM) : Typically a database
- Communication Resource Manager (CRM) : is the communication middleware between TM and RM
In this model, a distributed transaction (global transaction) can be split into many local transactions running on different APS and RMS. ACID per local transaction is well implemented, but a global transaction must ensure that every local transaction contained within it succeeds at the same time, and if one fails, all other transactions must be rolled back. The problem is that during a local transaction, you do not know the running status of other transactions. Therefore, CRM is required to notify individual local transactions and synchronize the status of transaction execution.
Therefore, there must be a unified standard for the communication of local transactions, otherwise different databases cannot communicate with each other. XA is the interface specification for communication middleware and TM in X/Open DTP. It defines interfaces for notifying transaction start, commit, terminate, rollback, etc. Each database vendor must implement these interfaces.
2.4.2. Two-stage commit
Reference: Diffuse Distributed System Consensus protocol: 2PC/3PC
The second-order commit protocol is derived from this idea, splitting the global transaction into two phases for execution:
- Phase 1: Preparation phase, each local transaction completes the preparation of the local transaction.
- Phase 2: Execution phase. Each local transaction is committed or rolled back based on the execution result of the previous phase.
This process requires a coordinator and a transactional voter.
1) Normal conditions
Voting phase: The coordination group asks each transaction participant if the transaction can be executed. Each transaction participant executes the transaction, writes redo and undo logs, and reports success (Agree)
Commit phase: The coordination group finds that each participant can execute a transaction (Agree) and issues a COMMIT directive to each participant, who commits the transaction.
2) Abnormal conditions
Of course, there are exceptions:
Voting phase: The coordination group asks each transaction participant if the transaction can be executed. Each participant executes the transaction, writes redo and undo logs, and reports back the result of the transaction execution, but as long as one participant returns Disagree, the transaction fails.
Commit phase: The coordination group finds that one or more participants have returned Disagree, arguing that the execution failed. Abort instructions are issued to the individual transaction participants, who roll back the transaction.
3) defects
Questions submitted in Phase ii:
-
Single point of failure
The disadvantage of 2PC is that it cannot handle node failures in the form of fail-stop. Take the picture below.
Assume that coordinator and Voter3 both crash during the Commit phase, but Voter1 and Voter2 do not receive Commit messages. This is where Voter1 and Voter2 find themselves in a bind. Because they can’t tell which of the two scenarios it is:
Voter3 received the commit message first and crashed after the commit operation
(2) The previous round of voter3 opposed it, so it simply did not pass.
-
congestion
In the preparation phase and commit phase, each transaction participant locks the local resource and waits for the execution result of other transactions. The blocking time is long and the resource lock time is too long, so the execution efficiency is relatively low.
Faced with the above shortcomings of two-phase commit, a three-phase commit was developed later, but the problems of blocking and resource locking were still not completely solved, and some new problems were introduced, so the actual use of scenarios was few.
2.4.3. Application Scenarios
They have strong consistency requirements for transactions, are insensitive to transaction execution efficiency, and do not want too much code intrusion.
2.5. TCC
The TCC mode can solve the problem of resource locking and blocking on 2 PCS and shorten the resource locking time.
2.5.1. Basic Principles
It is essentially an idea of compensation. The transaction process consists of three methods,
- Try: detects and reserves resources.
- Confirm: indicates that the service operation is submitted. Try Confirm it must be successful.
- Cancel: Releases the reserved resource.
There are two stages of implementation:
- Preparation phase (TRY) : Resource detection and reservation;
- Execution phase (Confirm/Cancel) : Determine the following execution method based on the result of the previous step. If all transaction participants in the previous step succeeded, confirm is executed here. Otherwise, cancel is performed
At first glance, this might seem like a two-phase commit, but it’s quite different:
- Try, Confirm, and Cancel are independent transactions that are not affected by other participants and do not block those waiting for them
- Try, confirm, and Cancel are written by programmers at the business layer, and the lock granularity is controlled by code
2.5.2. Instance
Let’s take the deduction balance of the previous order business as an example to see how to write the next three different methods. Assume that the original balance of account A is 100 and 30 yuan is deducted from the balance. As shown in figure:
-
Phase 1 (Try) : Check the balance and freeze part of the user’s amount. This phase is completed and the transaction has been committed
- Check whether the user balance is sufficient, if so, freeze part of the balance
- Add a frozen amount field to the account table with a value of 30 and the balance unchanged
-
Two stage
- Confirm: a genuine deduction. The frozen amount is deducted from the balance and empty
- Change the frozen amount to 0, and change the balance to 100-30 = 70 yuan
- Cancel: Release the amount frozen previously, not roll back
- The balance remains unchanged and the frozen amount of the account is changed to 0
- Confirm: a genuine deduction. The frozen amount is deducted from the balance and empty
2.5.3. Advantages and Disadvantages
-
advantage
Each phase of TCC execution commits the local transaction and releases the lock without waiting for the results of other transactions. If other transactions fail, instead of being rolled back, compensation is performed. In this way, the long term resource lock and blocking wait are avoided, and the execution efficiency is relatively high, which is a distributed transaction mode with better performance.
-
disadvantages
- Code intrusion: It is necessary to manually write codes to implement try, confirm, and cancel
- The development cost is high: a business needs to be divided into three steps, and the business implementation is written separately, which is complicated
- Security considerations: If the Cancel action fails, the resource cannot be released, a retry mechanism needs to be introduced, which can lead to repeated execution, and idempotence when retry is considered
2.5.4. Application Scenarios
- Certain consistency requirements for transactions (final consistency)
- High performance requirements
- Developers have high coding ability and idempotent processing experience
2.6. Reliable messaging services
The idea of this implementation is actually derived from ebay. The basic design idea is to split remote distributed transactions into a series of local transactions.
2.6.1. Basic Principles
Generally divided into A, the initiator of the transaction, and B, other participants of the transaction:
- Transaction initiator A performs A local transaction
- Transaction initiator A sends the information about the transaction to be executed to transaction participant B via MQ
- Transaction participant B executes the local transaction after receiving the message
As shown in figure:
The process is a bit like when you go to the school cafeteria for dinner:
- Take the money to the register, order a braised beef noodles and pay
- The cashier will issue you a small ticket and a number plate. Don’t lose the ticket!
- You are sure to get a braised beef noodle dish with your receipt and number plate, no matter how long it takes
A few notes:
- The transaction initiator A must ensure that the message is successfully sent after the local transaction succeeds
- MQ must ensure that messages are delivered correctly and persisted
- Transaction participant B must ensure that the message will eventually be consumed and retry multiple times if it fails
- Transaction B fails and will be retried, but transaction A will not be rolled back
So the question is, how do we ensure that the message is sent successfully? How do you ensure that consumers get the message?
2.6.2. Local message tables
To avoid message failure or loss, we can persist the message to the database. There are two ways to implement simplified version and coupled version.
1) Simplified version
Schematic diagram:
-
Transaction initiator:
- Enabling local Transactions
- Perform transaction-related business
- Send a message to MQ
- Persist the message to the database and mark it as sent
- Commit local Transaction
-
Transaction receiver:
- Receives the message
- Enabling local Transactions
- Handle transaction related business
- Change the database message status to consumed
- Commit local Transaction
-
Additional scheduled tasks
- Unconsumed messages timed out in the periodic scan table are resend
Advantages:
- Compared with TCC, the implementation method is simpler and the development cost is lower.
Disadvantages:
-
Data consistency is entirely dependent on messaging services, so messaging services must be reliable.
-
The idempotent problem of the passive business side needs to be addressed
-
A passive service failure does not cause the rollback of the active service, but retries the passive service
-
Transaction business and message sending business are coupled, business data and message table together
2) Independent messaging services
To solve the above problems, we will introduce an independent message service to complete a series of behaviors such as message persistence, sending, acknowledgement, failure retry, and so on. The general model is as follows:
Sequence diagram of a message sent:
The basic execution steps of transaction initiator A:
- Enabling local Transactions
- Notifies the message service that it is ready to send a message (the message service persists the message and marks it as ready to send)
- Perform local business,
- Abort if execution fails, notify message service, cancel sending (message service modifies order status)
- If the execution is successful, continue, notify the message service, confirm to send (the message service sends the message, modify the order status)
- Commit local Transaction
The message service itself provides the following interfaces:
- Ready to send: Persist the message to the database and mark the state as ready to send
- Unsend: Changes the database message status to cancel
- Confirm send: Changes the database message status to Confirm send. Attempts to send a message and changes the status to sent after success
- Confirm consumption: The consumer has received and processed the message, changing the database message status to consumed
- Scheduled task: periodically scans the database for messages whose status is confirm and asks the transaction initiator whether the transaction service is successfully executed. The result is as follows:
- The service is successfully executed: Attempts to send a message and changes its status to sent
- Business execution failed: Database message status changed to cancel
Basic steps for transaction participant B:
- Receives the message
- Enabling local Transactions
- The execution of business
- Notifies the message service that a message has been received and processed
- Commit the transaction
Advantages:
- Decoupled transaction business from message-related business
Disadvantages:
- It’s complicated to implement
2.6.3.RocketMQ transaction messages
RocketMQ comes with its own transaction message, which ensures the reliability of the message. The idea is that RocketMQ comes with its own local message table, similar to the idea we discussed above.
2.6.4.RabbitMQ message confirmation
RabbitMQ has a strange way of ensuring that messages are not lost. Rather than using a traditional local table, RabbitMQ uses a message acknowledgement mechanism:
- Producer validation mechanism: Ensures that messages from producers can reach MQ without problems
- When message producers send messages to RabbitMQ, they can set up an asynchronous listener to listen for ACKS from MQ
- When MQ receives the message, it returns a receipt to the producer:
- If the message fails to route to the switch, an ACK failure is returned
- Message routing succeeds, persistence fails, and an ACK failure is returned
- A successful ACK is returned if the message is successfully routed and persisted
- The producer writes in advance how the different receipts will be handled
- Failure receipt: resend after waiting a certain amount of time
- Success acknowledgement: Logs and other activities
- Consumer confirmation: Ensures that messages are consumed correctly by consumers
- The consumer needs to specify manual ACK mode when listening to the queue
- RabbitMQ sends a message to a consumer and waits for the consumer to receive an ACK before deleting it. If it does not receive an ACK, the message remains on the server. If the consumer is disconnected or abnormal, the message is sent to another consumer.
- After the consumer has processed the message and committed the transaction, manual ACK is performed. If an exception is thrown during execution, no ACK is performed, and the service fails to process and waits for the next message
Through the above two validation mechanisms, message security can be ensured from the producer to the consumer, and combined with local transactions at both ends, the final consistency of a distributed transaction can be guaranteed.
2.6.5. Advantages and disadvantages of message transactions
Summarizing the above models, the advantages and disadvantages of message transactions are as follows:
- Advantages:
- The business is relatively simple and there is no need to write a three-phase business
- Is a combination of multiple local transactions, so the resource lock period is short and the performance is good
- Disadvantages:
- Code into
- Depends on the reliability of MQ
- The originator of a message can roll back, but the message participant cannot cause a transaction rollback
- Transaction timeliness is poor, depending on whether MQ messages are delivered in a timely manner and the execution of message participants
In response to the problem of a transaction not being able to roll back, it has been suggested that the transaction participant could be notified of the failure using MQ messaging services, which would then inform other participants to roll back. So, congratulations, you’ve implemented the 2PC model again with MQ and custom messaging services and built another big wheel
2.7. The AT mode
In January 2019, Seata opened source the AT model. AT pattern is a non-intrusive distributed transaction solution. It can be regarded as an optimization of TCC or two-phase submission model, which solves the problems of code intrusion and complex coding in TCC mode.
In AT mode, users only need to focus on their own “business SQL” as a phase, and Seata framework will automatically generate two-phase commit and rollback operations for transactions.
You can refer to the official Seata documentation.
2.7.1. Basic Principles
Let’s start with a flow chart:
Does it feel very similar to TCC implementation, which is divided into two phases:
- Phase one: Executes the local transaction and returns the execution result
- Phase 2: Based on the results of phase 1, determine the phase 2 actions: commit or rollback
But the bottom layer of the AT pattern does something completely different, and the second phase doesn’t need to be written AT all, it’s all Seata’s own implementation. In other words: we write the same code as we do when dealing with local transactions, without manually handling distributed transactions.
So, how does the AT pattern achieve no code intrusion and help us automatically implement two-phase code?
A phase
In the first phase, Seata intercepts “business SQL”, parses SQL semantics, finds the business data to be updated by “Business SQL”, saves it as “Before image” before the business data is updated, and then executes “Business SQL” to update the business data. After the business data is updated, Save it as “After Image”, and finally acquire the global row lock and commit the transaction. All of the above operations are done within a single database transaction, which ensures atomicity of the one-phase operations.
The before image and After Image here are similar to the database’s undo and redo logs, but are actually simulated using the database.
Two-stage submission
Seata framework only needs to delete the snapshot data and row locks saved in the first phase to complete data cleaning because the “business SQL” has been committed to the database in the first phase.
Two-phase rollback:
In phase 2 rollback mode, Seata needs to roll back the “business SQL” executed in phase 1 to restore the business data. The rollback method is to use “before Image” to restore service data. However, check dirty write data before restoring the database. Compare current service data in the database with After Image. If the two data files are identical, there is no dirty write data and the service data can be restored.
However, because of the global locking mechanism, the probability of dirty writes can be reduced.
Phase ONE, phase two commit and rollback of AT mode are automatically generated by Seata framework. Users can easily access distributed transactions by writing “business SQL”. AT mode is a distributed transaction solution without any intrusion on business.
2.7.2. Detailed architecture and process
Some basic concepts in Seata:
-
TC (Transaction Coordinator) – Transaction Coordinator
Maintains the state of global and branch transactions and drives global transaction commit or rollback (coordinator between TM).
-
TM (Transaction Manager) – Transaction Manager
Define the scope of a global transaction: start, commit, or roll back the global transaction.
-
RM Resource Manager (RM) – Resource Manager
Manage resources for branch transaction processing, talk to TCS to register branch transactions and report status of branch transactions, and drive commit or rollback of branch transactions.
Let’s look at an architecture diagram below
- TM: the initiator of a global transaction in a business module
- Start a global transaction with the TC
- Invoke other microservices
- RM: The service module executive includes the RM part, which is responsible for reporting the transaction execution status to the TC
- Performing local transactions
- Register branch transactions with TCS and submit local transaction execution results
- TM: End the invocation of the microservice and inform TC that the global transaction is completed and the transaction phase one is over
- TC: Summarize the execution results of each branch transaction to determine whether the distributed transaction should be committed or rolled back.
- The TC notifies all RM to commit or roll back resources, and the transaction phase 2 ends.
A phase:
- TM starts global transaction and declares global transaction to TC, including global transaction XID information
- The TM service invokes other microservices
- Microservices are mainly implemented by RM
- The query
before_image
- Performing local transactions
- The query
after_image
- generate
undo_log
And write to the database - Register branch transactions with THE TC and inform the result of transaction execution
- Acquire a global lock (to prevent other global transactions from concurrently modifying the current data)
- Release a local lock (without affecting other services’ operations on data)
- The query
- After all services are completed, the transaction initiator (TM) attempts to submit a global transaction to the TC
Stage 2:
- TC collects statistics on the execution of branch transactions and determines the next action according to the results
- The branches are all successful: notify the branch transaction, commit the transaction
- Failed branch: Notifies successful branch transactions and rolls back data
- RM of the branch transaction
- Commit transactions: Empty them directly
before_image
andafter_image
Information to release global locks - Rollback transaction:
- Verify after_image to determine whether there are dirty writes
- If there are no dirty writes, roll back the data to
before_image
To removebefore_image
andafter_image
- If there is dirty writing, request manual intervention
- Commit transactions: Empty them directly
2.7.3. Working mechanism
IO/zh-CN /docs/…
scenario
Take an example to illustrate how the entire AT branch works.
Business table: Product
Field | Type | Key |
---|---|---|
id | bigint(20) | PRI |
name | varchar(100) | |
since | varchar(100) |
The business logic of an AT branch transaction:
update product set name = 'GTS' where name = 'TXC';
Copy the code
A phase
Process:
- SQL: SQL type (UPDATE), table (product), condition (where name = ‘TXC’), etc.
- Image before query: Generates a query statement to locate data based on the condition information obtained through parsing.
select id, name, since from product where name = 'TXC';
Copy the code
Get the front image:
id | name | since |
---|---|---|
1 | TXC | 2014 |
- Execute business SQL: update this record with name ‘GTS’.
- Mirror after query: Locate data by primary key based on the result of the former mirror.
select id, name, since from product where id = 1`;
Copy the code
After obtaining the mirror image:
id | name | since |
---|---|---|
1 | GTS | 2014 |
- Insert rollback log: The system inserts the mirror data and service SQL information into a rollback log record
UNDO_LOG
In the table.
{
"branchId": 641789253."undoItems": [{
"afterImage": {
"rows": [{
"fields": [{
"name": "id"."type": 4."value": 1
}, {
"name": "name"."type": 12."value": "GTS"
}, {
"name": "since"."type": 12."value": "2014"}}]]."tableName": "product"
},
"beforeImage": {
"rows": [{
"fields": [{
"name": "id"."type": 4."value": 1
}, {
"name": "name"."type": 12."value": "TXC"
}, {
"name": "since"."type": 12."value": "2014"}}]]."tableName": "product"
},
"sqlType": "UPDATE"}]."xid": "xid:xxx"
}
Copy the code
- Before submitting, apply to TC Registration Branch:
product
Table where the primary key is equal to 1Global lock 。 - Local transaction commit: The update of the business data is committed along with the UNDO LOG generated in the previous step.
- The local transaction submission result is reported to the TC.
Phase 2 – Rollback
- After receiving the branch rollback request from the TC, start a local transaction and perform the following operations:
- UNDO LOG records are found based on the XID and Branch ID.
- Data verification: Compare the mirror in UNDO LOG with the current data. If there is a difference, the data has been modified by an action other than the current global transaction. In this case, you need to handle it according to the configuration policy, which is detailed in another document.
- Generate and execute a rollback statement based on the former image and business SQL information in UNDO LOG:
update product set name = 'TXC' where id = 1;
Copy the code
- Commit a local transaction. In addition, the execution result of the local transaction (that is, the rollback result of the branch transaction) is reported to the TC.
Phase two – Commit
- After receiving the branch commit request from TC, it puts the request into an asynchronous task queue and immediately returns the result of successful commit to TC.
- A branch submit request in the asynchronous task phase deletes the corresponding UNDO LOG records asynchronously and in batches.
2.7.4. The advantages and disadvantages of
Advantages:
- Compared to 2PC: Each branch transaction is committed independently and does not wait for each other, reducing resource locking and blocking time
- Compared with TCC: all the two-stage execution operations are automatically generated, no code intrusion, and the development cost is low
Disadvantages:
- Compared to TCC, a two-stage reverse compensation operation needs to be dynamically generated, with slightly lower performance
2.8. Saga mode
Saga mode is Seata’s upcoming open source long transaction solution, which will be mainly contributed by Ant Financial.
The theoretical basis is Hector & Kenneth’s 1987 paper Sagas.
Seata. IO /zh-cn/docs/…
The basic model
In Saga mode, there are multiple participants in distributed transactions, and each participant is a positive compensation service, requiring users to implement forward and reverse rollback operations according to business scenarios.
In the process of distributed transaction execution, the forward operations of each participant are successively executed. If all forward operations are successfully executed, the distributed transaction is committed. If any of the forward actions fail, the distributed transaction goes back and performs the reverse rollback of the previous participants, rolling back the committed participants and returning the distributed transaction to its initial state.
Distributed transactions in Saga mode are usually event-driven and executed asynchronously among participants. Saga mode is a long transaction solution.
Applicable scenarios:
- Long business process, many business process
- Participants include other corporate or legacy system services that do not provide the three interfaces required by the TCC pattern
Advantage:
- One-stage commit local transaction, no lock, high performance
- Event-driven architecture, asynchronous execution of participants, high throughput
- Compensation services are easy to implement
Disadvantages:
- Isolation is not guaranteed (see user documentation for solutions)
3.Seata
3.1. Introduction
Seata (Simple Extensible Autonomous Transaction Architecture) is a distributed Transaction solution jointly opened by Ant Financial and Alibaba in January 2019. Seata has been open source for about half a year and now has nearly 10,000 stars, with a very active community. We warmly welcome you to join the Seata community and help make Seata the benchmark for open source distributed transactions.
Seata:https://github.com/seata/seata
3.1.1. Seata product module
As shown in the figure below, there are three modules in Seata, which are TM, RM and TC. TM and RM are integrated with the business system as clients of Seata, and TC is deployed independently as server of Seata.
3.1.2. Transaction model supported by Seata
Seata will have four distributed transaction solutions, namely AT mode, TCC mode, Saga mode and XA mode.
3.2. Actual combat of AT mode
One of the most common patterns in Seata is the AT pattern, which is used here as a demonstration to see how Seata can be integrated into SpringCloud microservices.
Let’s assume a business logic for a user to purchase an item. The entire business logic is supported by three microservices:
- Storage service: deduct the storage quantity for a given item.
- Order service: Creates orders based on purchasing requirements.
- Account service: deducts balances from user accounts.
Flow chart:
The issue of distributed transactions across services and across data sources occurs when the order service invokes both the inventory service and the user service when placing an order.
3.2.1. Prepare data
Run the seata_demo. SQL file provided in the documentation to import data.
It contains four tables.
The Order table:
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL COMMENT 'user id'.`commodity_code` varchar(255) DEFAULT NULL COMMENT 'Trade Code'.`count` int(11) unsigned DEFAULT '0' COMMENT 'Purchase Quantity'.`money` int(11) unsigned DEFAULT '0' COMMENT 'Total amount',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
Copy the code
Merchandise Inventory table:
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL COMMENT 'Trade Code'.`count` int(11) unsigned DEFAULT '0' COMMENT 'Merchandise inventory',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `commodity_code` (`commodity_code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
Copy the code
User account table:
CREATE TABLE `account_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL COMMENT 'user id'.`money` int(11) unsigned DEFAULT '0' COMMENT 'User balance',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
Copy the code
There is also a transaction log table undo_log in Seata that contains after_image and before_image data for data rollback:
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`) USING BTREE,
UNIQUE KEY `ux_undo_log` (`xid`.`branch_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
Copy the code
3.2.2. Introduce Demo project
We first prepare the basic project environment and implement the business code for placing orders
Import the project
Use Idea to open the Seata-Demo project provided in the materials:
Locate the project directory, select and open:
The project structure is as follows:
Structure Description:
- Account-service: a user service that operates account balances. Port 8083
- Eureka-server: registry, port 8761
- Order-service: order service, which provides the function of creating orders based on data. Port 8082
- Storage-service: storage service, which provides the function of reducing inventory, port 8081
Test the transaction
Next, let’s test the phenomenon of distributed transactions.
The interface for placing orders is:
- Request mode: POST
- Request path: /order
- Request parameters: Form form, including:
- UserId: indicates the userId
- CommodityCode: commodityCode
- Count: purchase quantity
- Money: Indicates the amount of the call fee
- Return value type: long, id of the order
Original database data:
Balance:
Inventory:
The other two tables are empty.
The normal order
At this time, start the project and try to place an order. At present, the inventory of goods is 10 and the balance of users is 1000, so the order can be placed normally as long as the data does not exceed these two values.
View database data:
Balance:
Inventory:
Order:
Abnormal order
This time, we set the money parameter to 1200, which will exceed the maximum balance and theoretically all data should be rolled back:
Take a look at the user balance:
There is no deduction here because the deduction failed
Take a look at the inventory numbers:
This shows that deduction inventory is still successful, did not roll back!
Next, let’s introduce Seata to see if we can solve this problem.
3.2.3. Preparing TC services
As we talked about earlier in the Seata principle, there are three important roles:
- TC: transaction coordinator
- TM: Transaction manager
- RM: Resource manager
Among them, TC is an independent service, responsible for coordinating each branch transaction, while TM and RM are integrated in each transaction participant through jar package.
Therefore, we need to set up an independent TC service first.
1) installation
Download the TC server installation package from the official website at GitHub: github.com/seata/seata…
Here we provide you with the 1.1.0 installation package in the materials:
The directory structure is as follows:
Include:
- Bin: startup script
- Conf: indicates the configuration file
- Lib: dependencies
2) configuration
Seata’s core configuration consists of two main parts:
- Registry configuration: in
${seata_home}/conf/
In the catalog, usuallyregistry.conf
file - The current service can be configured in two ways:
- Unified configuration center for distributed services, such as Zookeeper
- Through local files
Let’s start with Registry. Conf, which is JSON style
Registry {# specifies the registry type, using the Eureka type type ="eureka"# configuration of various registries. Only eureka and Zookeeper eureka {serviceUrl = are retained"http://localhost:8761/eureka"
application = "seata_tc_server"
weight = "1"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"Timeout = 6000 connect.timeout = 2000}} config {# Config file mode, which can support file, nacOS, Apollo, Zk, Consul, etCD3 type ="file"
nacos {
serverAddr = "localhost"
namespace = ""
group = "SEATA_GROUP"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
file {
name = "file.conf"}}Copy the code
There are two main configurations in this file:
- The type and address of the registry. In this example, we choose Eureka as the registry
- Eureka. ServiceUrl: is the address of eureka, such as http://localhost:8761/eureka
- Application: is the service name when the TC registers with Eureka, for example
seata_tc_server
- Configure the type and address of the center. In this example, we choose a local file for configuration, that is, the current directory
file.conf
file
Take a look at file.conf again:
## transaction log store, only used in seata-server
store {
## store mode: file、db
mode = "file"
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "dbcp"
## mysql/oracle/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "JDBC: mysql: / / 127.0.0.1:3306 / seata_demo"
user = "root"
password = "123"
minConn = 1
maxConn = 10
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
}
}
Copy the code
Key configuration:
- Store: data store configuration on the TC server
- Mode: indicates the data storage mode. File and DB are supported
- File: Stores data in local files, which provides better performance but does not support horizontal scaling
- Db: Saves data in a specified database. You need to specify database connection information
- Mode: indicates the data storage mode. File and DB are supported
If you use files as storage media, no additional configuration is required and you can run them directly.
However, if db is used as the storage medium, three tables need to be created in the database:
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,
`gmt_modified` DATETIME,
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;
Copy the code
3) start
${seatA_HOME}/bin/
For Linux (requiring JRE), run seata-server.sh
In Windows, run seata-server.bat
3.2.4. Transform Order service
Next comes the transformation of microservices. No matter which microservice is a participant in a transaction, the steps are basically the same.
1) Introduce dependencies
We already managed dependencies in the parent project, Seata-Demo:
<alibaba.seata.version>2.1.0. RELEASE</alibaba.seata.version>
<seata.version>1.1.0</seata.version>
Copy the code
Therefore, we can introduce dependent coordinates in the POM file of the order-service project:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>${alibaba.seata.version}</version>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
<version>${seata.version}</version>
</dependency>
Copy the code
2) Add a configuration file
First add a line of configuration to application.yml:
spring:
cloud:
alibaba:
seata:
tx-service-group: test_tx_group Define the transaction group name
Copy the code
Here is the name that defines the transaction group, which you will use next.
You then delegate two configuration files to the Resources directory: file.conf and Registry.conf
Registry. Conf is the same as TC server and is not explained here.
Let’s take a look at file.conf
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
# the client batch send request enable
enableClientBatchSendRequest = true
#thread factory for netty
threadFactory {
bossThreadPrefix = "NettyBoss"
workerThreadPrefix = "NettyServerNIOWorker"
serverExecutorThread-prefix = "NettyServerBizHandler"
shareBossWorker = false
clientSelectorThreadPrefix = "NettyClientSelector"
clientSelectorThreadSize = 1
clientWorkerThreadPrefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
bossThreadSize = 1
#auto default pin or 8
workerThreadSize = "default"
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
service {
vgroup_mapping.test_tx_group = "seata_tc_server"
#only support when registry.type=file, please don't set multiple addresses
seata_tc_server.grouplist = "127.0.0.1:8091"
#degrade, current not support
enableDegrade = false
#disable seata
disableGlobalTransaction = false
}
client {
rm {
asyncCommitBufferLimit = 10000
lock {
retryInterval = 10
retryTimes = 30
retryPolicyBranchRollbackOnConflict = true
}
reportRetryCount = 5
tableMetaCheckEnable = false
reportSuccessEnable = false
}
tm {
commitRetryCount = 5
rollbackRetryCount = 5
}
undo {
dataValidation = true
logSerialization = "jackson"
logTable = "undo_log"
}
log {
exceptionRate = 100
}
}
Copy the code
Configuration interpretation:
transport
: Configures the interaction with TCSheartbeat
: Indicates whether to detect the heartbeat communication between the client and serverenableClientBatchSendRequest
: Indicates whether the client transaction message requests are sent in batches
service
: TC address, used to obtain the TC addressvgroup_mapping.test_tx_group = "seata_tc_server"
:test_tx_group
Yml: is the transaction group name, which must be the same as that configured in application.yml.seata_tc_server
: indicates the ID of the TC server in the registry. The TC address can be obtained from the registryenableDegrade
: Service degradation switch, disabled by default. If this function is enabled, global transactions will be abandoned after repeated service retry failuresdisableGlobalTransaction
: Global transaction switch. Default is false. False indicates that the function is enabled and true indicates that the function is disabled
default.grouplist
This is only used when the registry is file
client
: Client configurationrm
: Resource manager configurationasynCommitBufferLimit
: The two-phase commit is executed asynchronously by default, where the size of the asynchronous queue is specifiedlock
: Global lock configurationretryInterval
: Retry interval for checking or occupying the global lock. Default value: 10 millisecondsretryTimes
: Indicates the number of retries for verifying or occupying a global lock. The default value is 30retryPolicyBranchRollbackOnConflict
: lock policy when a branch transaction conflicts with another global rollback transaction. The default value is true and the local lock is released before rollback succeeds
reportRetryCount
: Number of retries after a TC fails to be reported. The default value is 5
tm
: Transaction manager configurationcommitRetryCount
: Number of TC retries for reporting one-phase global submission results. The default value is 1rollbackRetryCount
: Indicates the number of TC retries reported after one-phase rollback. The default value is 1
undo
: undo_log configurationdataValidation
: Specifies whether to enable two-phase rollback mirror verification. The default value is truelogSerialization
: undo serialization mode, default JacksonlogTable
: User-defined undo table name. The default value isundo_log
log
: Log configurationexceptionRate
: Indicates the log frequency when a rollback exception occurs. The default value is 100 with a probability of 1%. A rollback failure is basically dirty data and does not require an output stack to take up disk space
3) The DataSource
The two-stage execution of Seata intercepts THE SQL statement and analyzes the semantics to specify a rollback policy, so it needs to proxy the DataSource. We added a configuration class to the project’s cn.itcast.order.config package:
package cn.itcast.order.config;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DataSourceProxyConfig {
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource) throws Exception {
// The order service introduces mybatis- Plus, so use the special SqlSessionFactoryBean
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
// Proxy data source
sqlSessionFactoryBean.setDataSource(new DataSourceProxy(dataSource));
/ / generated SqlSessionFactory
returnsqlSessionFactoryBean.getObject(); }}Copy the code
Note that the order service uses the Mybatis -Plus framework (this is a MyBatis integration framework that automatically generates single-table Sql), So we need to use mybatis – plus MybatisSqlSessionFactoryBean SqlSessionFactoryBean instead
If you are using native Mybatis, use SqlSessionFactoryBean.
4) Add transaction annotations
Turn ona global transaction by adding the @GlobalTransactional annotation to the createOrder() method of the transaction initiator order_Service’s OrderServiceImpl:
Restart.
3.2.5. Modify Storage and Account Services
Similar to OrderService, the following steps are required:
-
Importing dependencies: the same as order-service
-
Adding a configuration file: The configuration file must be the same as that of the order-service
-
DataSource = mybatis = SqlSessionFactory = SqlSessionFactory
package cn.itcast.order.config; import io.seata.rm.datasource.DataSourceProxy; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class DataSourceProxyConfig { @Bean public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource) throws Exception { // Since we are using mybatis, we define SqlSessionFactoryBean SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); // Configure the data source agent sqlSessionFactoryBean.setDataSource(new DataSourceProxy(dataSource)); returnsqlSessionFactoryBean.getObject(); }}Copy the code
In addition, transaction annotations can use @transactionnal instead of @GlobalTransactional, so the transaction initiator needs to add @GlobalTransactional.
3.2.6. Test
After restarting all microservices, we tested again.
Current data: user balance 900, inventory 6.
Let’s try to deduct $1200, and if the deduction fails, theoretically all data will be rolled back.
Take a look at the user balance:
There is no deduction here because the deduction failed
Take a look at the inventory numbers:
The inventory reduction is still 6, successfully rolled back, indicating that the distributed transaction is effective!