마이크로 서비스 간 트랜잭션 가이드

1. 소개

이 기사에서는 마이크로 서비스에서 트랜잭션을 구현하는 옵션에 대해 설명합니다.

또한 분산 마이크로 서비스 시나리오에서 트랜잭션에 대한 몇 가지 대안을 확인합니다.

2. 마이크로 서비스 간 트랜잭션 방지

분산 트랜잭션은 실패 할 수있는 움직이는 부분이 많은 매우 복잡한 프로세스입니다. 또한 이러한 부품이 다른 시스템 또는 다른 데이터 센터에서 실행되는 경우 트랜잭션 커밋 프로세스가 매우 길어지고 불안정해질 수 있습니다.

이는 사용자 경험과 전체 시스템 대역폭에 심각한 영향을 미칠 수 있습니다. 따라서 분산 트랜잭션 문제를 해결하는 가장 좋은 방법 중 하나는이를 완전히 피하는 것입니다.

2.1. 트랜잭션이 필요한 아키텍처의 예

일반적으로 마이크로 서비스는 독립적이고 그 자체로 유용하도록 설계되었습니다. 원자 적 비즈니스 과제를 해결할 수 있어야합니다.

이러한 마이크로 서비스로 시스템을 분할 할 수 있다면 이들 간의 트랜잭션을 전혀 구현할 필요가 없을 가능성이 높습니다.

예를 들어 사용자 간의 브로드 캐스트 메시징 시스템을 고려해 보겠습니다.

사용자 microservice 다음 기본 도메인 클래스 (프로파일 데이터 등 편집, 새로운 사용자를 생성) 사용자 프로파일과 관련되는 것이다 :

@Entity public class User implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; @Basic private String name; @Basic private String surname; @Basic private Instant lastMessageTime; }

메시지 microservice은 방송과 관련 될 것이다. 엔티티 Message 와 그 주변의 모든 것을 캡슐화합니다 .

@Entity public class Message implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; @Basic private long userId; @Basic private String contents; @Basic private Instant messageTimestamp; }

각 마이크로 서비스에는 자체 데이터베이스가 있습니다. 우리는 개체를 참조하지 않도록주의 사용자 엔티티에서 메시지 사용자 클래스가에서 액세스 할 수없는 한, 메시지 microservice. 아이디로만 사용자를 참조합니다.

이제 프로필에 마지막 사용자 활동 시간에 대한 정보를 표시하려고하므로 User 엔터티에는 lastMessageTime 필드 가 포함 됩니다.

그러나 사용자에게 새 메시지를 추가하고 lastMessageTime을 업데이트 하려면 이제 마이크로 서비스에서 트랜잭션을 구현해야합니다.

2.2. 거래없는 대안 적 접근

마이크로 서비스 아키텍처를 변경하고 User 엔터티 에서 lastMessageTime 필드를 제거 할 수 있습니다 .

그런 다음 메시지 마이크로 서비스에 별도의 요청을 발행 하고이 사용자의 모든 메시지에 대한 최대 messageTimestamp 값을 찾아 사용자 프로필에이 시간을 표시 할 수 있습니다 .

아마도 메시지 마이크로 서비스의 부하가 높거나 다운 된 경우 프로필에서 사용자의 마지막 메시지 시간을 표시 할 수 없습니다.

그러나 이는 사용자 마이크로 서비스가 제때 응답하지 않았기 때문에 메시지를 저장하기 위해 분산 트랜잭션을 커밋하지 않는 것보다 더 수용 가능할 수 있습니다.

물론 여러 마이크로 서비스에서 비즈니스 프로세스를 구현해야하는 경우 더 복잡한 시나리오가 있으며 이러한 마이크로 서비스간에 불일치를 허용하지 않으려 고합니다.

3. 2 상 커밋 프로토콜

2 단계 커밋 프로토콜 (또는 2PC)은 서로 다른 소프트웨어 구성 요소 (다중 데이터베이스, 메시지 대기열 등)에서 트랜잭션을 구현하기위한 메커니즘입니다.

3.1. 2PC의 아키텍처

분산 트랜잭션의 중요한 참여자 중 하나는 트랜잭션 조정자입니다. 분산 트랜잭션은 두 단계로 구성됩니다.

  • 준비 단계 —이 단계에서 트랜잭션의 모든 참가자는 커밋을 준비하고 코디네이터에게 트랜잭션을 완료 할 준비가되었음을 알립니다.
  • 커밋 또는 롤백 단계 —이 단계에서 트랜잭션 코디네이터가 모든 참여자에게 커밋 또는 롤백 명령을 실행합니다.

The problem with 2PC is that it is quite slow compared to the time for operation of a single microservice.

Coordinating the transaction between microservices, even if they are on the same network, can really slow the system down, so this approach isn't usually used in a high load scenario.

3.2. XA Standard

The XA standard is a specification for conducting the 2PC distributed transactions across the supporting resources. Any JTA-compliant application server (JBoss, GlassFish etc.) supports it out-of-the-box.

The resources participating in a distributed transactions could be, for example, two databases of two different microservices.

However, to take advantage of this mechanism, the resources have to be deployed to a single JTA platform. This isn't always feasible for a microservice architecture.

3.3. REST-AT Standard Draft

Another proposed standard is REST-AT which had undergone some development by RedHat but still didn't get out of the draft stage. It's however supported by the WildFly application server out-of-the-box.

This standard allows using the application server as a transaction coordinator with a specific REST API for creating and joining the distributed transactions.

The RESTful web services that wish to participate in the two-phase transaction also have to support a specific REST API.

Unfortunately, to bridge a distributed transaction to local resources of the microservice, we'd still have to either deploy these resources to a single JTA platform or solve a non-trivial task of writing this bridge ourselves.

4. Eventual Consistency and Compensation

By far, one of the most feasible models of handling consistency across microservices is eventual consistency.

This model doesn't enforce distributed ACID transactions across microservices. Instead, it proposes to use some mechanisms of ensuring that the system would be eventually consistent at some point in the future.

4.1. A Case for Eventual Consistency

For example, suppose we need to solve the following task:

  • register a user profile
  • do some automated background check that the user can actually access the system

The second task is to ensure, for example, that this user wasn't banned from our servers for some reason.

But it could take time, and we'd like to extract it to a separate microservice. It wouldn't be reasonable to keep the user waiting for so long just to know that she was registered successfully.

One way to solve it would be with a message-driven approach including compensation. Let's consider the following architecture:

  • the user microservice tasked with registering a user profile
  • the validation microservice tasked with doing a background check
  • the messaging platform that supports persistent queues

The messaging platform could ensure that the messages sent by the microservices are persisted. Then they would be delivered at a later time if the receiver weren't currently available

4.2. Happy Scenario

In this architecture, a happy scenario would be:

  • the user microservice registers a user, saving information about her in its local database
  • the user microservice marks this user with a flag. It could signify that this user hasn't yet been validated and doesn't have access to full system functionality
  • a confirmation of registration is sent to the user with a warning that not all functionality of the system is accessible right away
  • the user microservice sends a message to the validation microservice to do the background check of a user
  • the validation microservice runs the background check and sends a message to the user microservice with the results of the check
    • if the results are positive, the user microservice unblocks the user
    • if the results are negative, the user microservice deletes the user account

After we've gone through all these steps, the system should be in a consistent state. However, for some period of time, the user entity appeared to be in an incomplete state.

The last step, when the user microservice removes the invalid account, is a compensation phase.

4.3. Failure Scenarios

Now let's consider some failure scenarios:

  • if the validation microservice is not accessible, then the messaging platform with its persistent queue functionality ensures that the validation microservice would receive this message at some later time
  • suppose the messaging platform fails, then the user microservice tries to send the message again at some later time, for example, by scheduled batch-processing of all users that were not yet validated
  • if the validation microservice receives the message, validates the user but can't send the answer back due to the messaging platform failure, the validation microservice also retries sending the message at some later time
  • if one of the messages got lost, or some other failure happened, the user microservice finds all non-validated users by scheduled batch-processing and sends requests for validation again

일부 메시지가 여러 번 발행 된 경우에도 마이크로 서비스 데이터베이스의 데이터 일관성에 영향을 미치지 않습니다.

가능한 모든 오류 시나리오를 신중하게 고려하여 시스템이 최종 일관성 조건을 충족하는지 확인할 수 있습니다. 동시에 비용이 많이 드는 분산 트랜잭션을 처리 할 필요가 없습니다.

그러나 최종 일관성을 보장하는 것은 복잡한 작업이라는 것을 알아야합니다. 모든 경우에 단일 솔루션이있는 것은 아닙니다.

5. 결론

이 기사에서는 마이크로 서비스에서 트랜잭션을 구현하기위한 몇 가지 메커니즘에 대해 논의했습니다.

또한 처음에 이러한 스타일의 트랜잭션을 수행하는 것에 대한 몇 가지 대안을 탐색했습니다.