- What is a Two-Phase Commit Transactions (2PC) in Microservices
- How to Manage Distributed Transaction in Microservices? SAGA and 2 Phase Commit Example Tutorial
- How to manage distributed transaction in Microservices?
- What are Distributed Transactions in Microservices?
- Challenges of Managing Distributed Transactions in Microservices
- 6 Ways to Manage Distributed Transactions in Microservices?
- 1. Two-Phase Commit (2PC)
- 2. Saga Pattern
- 3. Event-Driven Architecture
- Code Example of SAGA and 2-Phase Commit in Java Microservices
- 4. Use Idempotent Operations
- 5. Implement Retry and Timeout Mechanisms
- 6. Use a Distributed Transaction Coordinator
What is a Two-Phase Commit Transactions (2PC) in Microservices
In this tutorial, we are going to learn about Two-Phase Commit Transactions (2PC) in detail.
Two-Phase Commit Transactions (2PC)
- Widely used in database systems because it is very strong transaction.
- Sometimes useful in microservices (only when there are only few microservices involved in building the system).
- It has two phases due to which it is called a Two-Phase Commit pattern, i.e., a Prepare Phase and a Commit Phase.
- Prepare Phase: To start a transaction, microservices are asked to prepare for a change, is called prepare phase. Example: Create a record in the database and set its status field or column to 0(0 means not stable)
- Commit Phase: Microservices are asked to make the final and actual change, which is the commit phase. Example: Set status field or column to 1 or finalized.
Successful Two-Phase Commit
Here is an example of Successful Two-Phase Commit, which means all the microservices are successfully committed their transactions.
- Coordinator Microservice starts or creates a transaction, which generates or returns the transaction ID.
- Now, CustomerMicroservice is hit by the prepareUpdateCustomerFund(id) followed by the orderMicroservice where PrepareCreateOder(id) is hit and both the microservices returns prepared.
- After preparing the order, we need to update customer funds by hitting the CommitUpdateCustomerFund(id) to the CustomerMicroservice and CommitCreateOrder(id) to the OrderMicroservice and returns done.
Here is another example of Failed Two-Phase Commit, which means all the microservices are not successfully committed their transactions.
- Similarly, as the previous example, Coordinator microservice starts the transaction and creates a transaction ID.
- It hits CustomerMicroservice and OrderMicroservice to prepareUpdateCustomerFund(id) and PrepareCreateOder(id) in which one of them fails and returns failed.
- In this case, coordinator microservice sends a message to orderMicroservice to abort(id) the current transaction.
- Entire transaction is cancelled.
How to Manage Distributed Transaction in Microservices? SAGA and 2 Phase Commit Example Tutorial
Hello guys, managing transaction in a distributed system like Microservices is very challenging and if you don’t have a proper approach then you will end of with data lose, data corruption and app failure.That’s why distributed transaction is also an important topic on Microservice interviews. There is a good chance that you may have already heard about SAGA and 2 Phase Commit etc. If not, don’t worry, in this article, I will tell you how to manage distributed transaction. In a Microservices architecture, services are designed to be small, autonomous, and loosely coupled. This design philosophy provides many benefits, including scalability, flexibility, and faster development cycles. However, managing transactions across multiple microservices can be a challenging task. In this article, we will explore how to manage distributed transactions in microservices and provide relevant code examples.
How to manage distributed transaction in Microservices?
There are multiple ways to manage transactions in Microservices like applying SAGA Pattern, using 2 Phase commit or using Event Sourcing Design Pattern. In this article, we will understand all of those but before that, let’s understand what is distributed transactions and what are challenges related to transaction management in Microservices.
What are Distributed Transactions in Microservices?
A distributed transaction involves multiple services that need to work together to complete a transaction. In a microservices architecture, each service may have its own database, and transactions that require data from multiple services may need to coordinate the work of all these services to ensure that the transaction is completed successfully.
For example, when a user makes a purchase, the transaction may involve several services, such as the order service, payment service, and inventory service.
Challenges of Managing Distributed Transactions in Microservices
Managing distributed transactions in a microservices architecture can be challenging due to the following reasons:
1. Complexity
Managing transactions across multiple services can be complex, and coordinating the work of all these services can be challenging.2. Failure Handling
In a distributed system, failures are inevitable, and handling these failures in a distributed transaction can be challenging.3. Scalability
Microservices architecture is designed to be scalable, and managing transactions across multiple services can be challenging in a highly scalable environment.4. Latency
Coordinating the work of multiple services can introduce latency, which can impact the performance of the system.6 Ways to Manage Distributed Transactions in Microservices?
There are several approaches to manage distributed transactions in a microservices architecture. Here are some of the most popular approaches:
1. Two-Phase Commit (2PC)
Two-phase commit is a protocol used to ensure that all participants in a distributed transaction agree to commit or abort the transaction. In this approach, a coordinator service is responsible for coordinating the work of all the services involved in the transaction.
The coordinator asks all the services to prepare for the transaction, and if all the services are ready, the coordinator sends a commit message to all the services. If any of the services fail to prepare or respond, the coordinator sends an abort message to all the services.
2. Saga Pattern
The Saga pattern is a pattern used to manage long-running transactions in a distributed system. In this pattern, each service involved in the transaction performs a local transaction and sends a message to the next service to perform its transaction. If any of the services fail, the Saga can be rolled back by sending a compensating transaction to undo the work that has already been done.
3. Event-Driven Architecture
Event-driven architecture is an architectural pattern that involves the use of events to trigger actions in a system. In a distributed transaction, each service can publish events when it has completed its part of the transaction. Other services can then subscribe to these events and perform their part of the transaction.
Code Example of SAGA and 2-Phase Commit in Java Microservices
Let’s take a look at some code examples of how to manage distributed transactions in microservices using the two-phase commit and Saga pattern approaches.
@Service public class OrderService < @Autowired private PaymentService paymentService; @Autowired private InventoryService inventoryService; @Transactional public void placeOrder(Order order) throws Exception < // Reserve inventory inventoryService.reserveInventory(order); // Charge payment paymentService.chargePayment(order); // Commit transaction // This is handled by the transaction manager > >
Now that we have seen code example of using 2 phase commit for managing transactions in Microservices, its time to look at SAGA Pattern, another popular ways to manage distributed transactions.
public class OrderSaga < @Autowired private OrderService orderService; @Autowired private PaymentService paymentService; @Autowired private InventoryService inventoryService; @SagaStart public public void placeOrder(Order order) throws Exception < try < // Step 1: Reserve inventory inventoryService.reserveInventory(order); order.setStatus("Inventory Reserved"); // Step 2: Charge payment paymentService.chargePayment(order); order.setStatus("Payment Charged"); // Step 3: Confirm order orderService.confirmOrder(order); order.setStatus("Order Confirmed"); > catch (Exception ex) < // Step 4: Handle failure inventoryService.cancelInventoryReservation(order); paymentService.refundPayment(order); orderService.cancelOrder(order); order.setStatus("Transaction Failed"); > > >
In addition to the above approaches, there are a few more techniques and best practices that can help manage distributed transactions in microservices.
4. Use Idempotent Operations
Idempotent operations are operations that can be repeated without changing the outcome. In a distributed system, using idempotent operations can help ensure that the same operation is not performed twice, even if it is retried due to a failure.
For example, if a service is trying to update a record in a database, it can check if the record already exists before performing the update operation. If the record already exists, the service can skip the update operation and return a success response.
5. Implement Retry and Timeout Mechanisms
In a distributed system, network failures and timeouts are common. Implementing retry and timeout mechanisms can help handle these failures gracefully. For example, if a service fails to respond to a request, the client can retry the request a few times before giving up.
Similarly, if a service takes too long to respond, the client can timeout the request and handle the failure gracefully.
6. Use a Distributed Transaction Coordinator
A distributed transaction coordinator is a service that manages distributed transactions in a microservices architecture. It provides a centralized mechanism for coordinating the work of all the services involved in a transaction.
The coordinator can ensure that all the services commit or abort the transaction in a coordinated manner, even in the event of failures.
Let’s take a look at a code example that implements idempotent operations and retry mechanisms to handle failures in a distributed transaction.
public class PaymentService < @Autowired private RestTemplate restTemplate; @Value("$ ") private String inventoryServiceUrl; @Value("$ ") private int maxAttempts; @Value("$ ") private int backoff; @Retryable(value = < HttpClientErrorException.class >, maxAttempts = "$ ", backoff = @Backoff(delay = "$ ")) public void chargePayment(Order order) throws Exception < try < // Check if payment already exists Payment payment = restTemplate.getForObject( "http://payment-service/payments/", Payment.class, order.getId()); if (payment != null) < // Payment already exists, skip the operation return; > // Perform payment restTemplate.postForObject("http://payment-service/payments", order, Payment.class); > catch (HttpClientErrorException ex) < // Handle 4xx errors if (ex.getStatusCode() == HttpStatus.CONFLICT) < // Payment already exists, skip the operation return; > throw ex; > catch (HttpServerErrorException ex) < // Handle 5xx errors throw ex; > > @Recover public void recoverChargePayment(HttpClientErrorException ex, Order order) < // Handle retry failure // Log the error and throw an exception throw new RuntimeException("Failed to charge payment after " + maxAttempts + " retries"); > >
That’s all about how to manage transactions in Microservices. Managing distributed transactions in a microservices architecture is a complex task that requires careful planning and implementation. In this article, we explored some of the popular approaches and best practices to manage distributed transactions, including idempotent operations, retry and timeout mechanisms, and the use of a distributed transaction coordinator.
You have also see the code example that implements idempotent operations and retry mechanisms to handle failures in a distributed transaction. By following these approaches and best practices, you can effectively manage distributed transactions in your microservices architecture and ensure that your system is reliable and scalable.