Ứng dụng Event Driven Design vào thiết kế Microservice

Microservice và vấn đề quản lý dữ liệu phân tán

Một ứng dụng nguyên khối thường có một cơ sở dữ liệu quan hệ duy nhất. Lợi ích chính của việc sử dụng cơ sở dữ liệu quan hệ là tính ACID (tính nhất quán và toàn vẹn của dữ liệu)

Một lợi ích lớn khác của việc sử dụng một cơ sở dữ liệu quan hệ là có SQL, một ngôn ngữ truy vấn phong phú, khai báo và được chuẩn hóa. Bạn có thể dễ dàng viết truy vấn kết hợp dữ liệu từ nhiều bảng và có thể tối ưu. Và bởi vì tất cả dữ liệu của ứng dụng nằm trong một cơ sở dữ liệu nên rất dễ truy vấn.

Tuy nhiên, truy cập dữ liệu trở nên phức tạp hơn nhiều khi chúng ta chuyển sang microservices. Đó là bởi vì dữ liệu thuộc sở hữu của mỗi microservice là riêng tư đối với service đó và chỉ có thể được truy cập thông qua API của nó. Việc đóng gói dữ liệu đảm bảo rằng các microservice được kết hợp lỏng lẻo (loosely coupled) và có thể phát triển độc lập với nhau. Nếu nhiều service truy cập cùng một dữ liệu thì sẽ cần sự phối hợp của tất cả các service để đảm bảo tính nhất quán và toàn vẹn dữ liệu.

Thậm chí các microservice khác nhau thường sử dụng các loại cơ sở dữ liệu khác nhau. Các ứng dụng hiện đại lưu trữ và xử lý các loại dữ liệu đa dạng chứ không nhất thiết phải sử dụng cơ sở dữ liệu quan hệ. Trong một số trường hợp, sử dụng NoSQL sẽ thuận tiện và giúp tăng hiệu suất và khả năng mở rộng tốt hơn nhiều. Hoặc có thể sử dụng Elasticsearch cho lưu trữ và truy vấn văn bản. Do đó, các ứng dụng dựa trên microservices thường sử dụng hỗn hợp các cơ sở dữ liệu SQL và NoSQL được gọi là phương pháp “polyglot persistence“.

Một số thách thức trong việc quản lý dữ liệu phân tán

Thách thức đầu tiên là làm thế nào để thực hiện các business transactions duy trì tính thống nhất trên nhiều service. Để xem tại sao đây là một vấn đề, chúng ta hãy xem một ví dụ về một cửa hàng B2B trực tuyến.

  • Customer Service lưu trữ thông tin về khách hàng, bao gồm cả hạn mức tín dụng của họ.
  • Order Service quản lý đơn đặt hàng và phải xác minh rằng đơn hàng mới không vượt quá giới hạn tín dụng của khách hàng.

Trong phiên bản nguyên khối của ứng dụng này, Order Service chỉ cần sử dụng ACID transaction để kiểm tra tín dụng có sẵn và tạo đơn đặt hàng.

Ngược lại, trong kiến ​​trúc microservices, các bảng ORDER và CUSTOMER nằm ở các service khác nhau như diagram dưới đây.

Order Service không thể truy cập trực tiếp vào bảng CUSTOMER. Nó chỉ có thể sử dụng API được cung cấp bởi Customer Service. Order Service có thể sử dụng distributed transaction hay còn được gọi là two-phase commit (2PC). Tuy nhiên, 2PC thường không phải là một lựa chọn khả thi trong các ứng dụng hiện đại. 

Định lý CAP đòi hỏi bạn phải lựa chọn giữa tính sẵn sàng và tính nhất quán ACID, và tính sẵn sàng thường là sự lựa chọn tốt hơn. Hơn nữa, nhiều công nghệ hiện đại, chẳng hạn như hầu hết các cơ sở dữ liệu NoSQL, không hỗ trợ 2PC. Dù vậy, việc duy trì tính nhất quán dữ liệu giữa các service và cơ sở dữ liệu là rất cần thiết, cho nên chúng ta phải có giải pháp cho vấn đề này.

Thách thức thứ hai là cách triển khai các truy vấn lấy dữ liệu từ nhiều service.

Ví dụ, hãy tưởng tượng rằng ứng dụng cần hiển thị một khách hàng và các đơn đặt hàng gần đây của anh ta. Nếu Order Service cung cấp một API để truy xuất các đơn đặt hàng của khách hàng thì bạn có thể truy xuất dữ liệu này bằng cách sử dụng các join dữ liệu từ nhiều service khác nhau bao gồm thông tin khách hàng từ Customer Service và các đơn đặt hàng của khách hàng từ Order Service. Tuy nhiên, giả sử rằng Order Service chỉ hỗ trợ tra cứu các đơn hàng bằng khóa chính của chúng (có lẽ NoSQL chỉ hỗ trợ các truy vấn dựa trên khóa chính). Trong tình huống này, không có cách rõ ràng để lấy dữ liệu cần thiết.

Event Driven Design

Đối với nhiều ứng dụng, giải pháp là sử dụng Event-Driven Architecture. Trong kiến ​​trúc này, một service publish một event khi có gì đó đáng chú ý xảy ra, chẳng hạn như khi cập nhật một business entity. Các microservice khác đăng ký các event đó. Khi microservice nhận được một event, nó có thể cập nhật các business entity của riêng nó.

Bạn có thể sử dụng các event để update business transactions trên nhiều service. Một transaction bao gồm một loạt các bước. Mỗi bước bao gồm một microservice cập nhật một business entity và publish một event kích hoạt bước tiếp theo. Trình tự sơ đồ sau đây cho thấy cách sử dụng phương pháp tiếp cận theo event để kiểm tra tín dụng khi tạo đơn đặt hàng. Các event trao đổi microservices thông qua một Message Broker.

1. Order Service tạo Đơn đặt hàng có trạng thái NEW và publish event Order Created.

2. Customer Service lắng nghe và xử lý (consume) event Tạo đơn đặt hàng bằng cách dự trữ tín dụng cho đơn đặt hàng này và publish event Credit Reserved.

3. Order Service lắng nghe và xử lý event Credit Reserved và thay đổi trạng thái của đơn đặt hàng thành OPEN.

Với điều kiện (a) mỗi service cập nhật cơ sở dữ liệu một cách nguyên tố (atomically) và publish một event và (b) Message Broker đảm bảo rằng các event được gửi ít nhất một lần, bằng cách này bạn có thể thực hiện các business transactions trên nhiều service. Điều quan trọng cần lưu ý rằng nó không đảm bảo tính ACID mà chỉ có thể đạt được tính nhất quán cuối cùng (eventual consistency). Mô hình transaction này được gọi là BASE model.

Bạn cũng có thể sử dụng các event để duy trì chế độ xem được thực hiện trước (materialized views) khi kết hợp dữ liệu từ nhiều microservice. Service duy trì View sẽ xem đăng ký các event có liên quan và cập nhật lại View khi có thay đổi. Ví dụ: Customer Order View Updater Service duy trì chế độ xem Đơn hàng của khách hàng sẽ đăng ký các event được publish từ Customer Service và Order Service.

Event-driven architecture có một số lợi ích cũng như hạn chế. Nó cho phép thực hiện các transaction trên nhiều service và cung cấp tính nhất quán cuối cùng (eventual consistency). Một lợi ích khác là nó cũng cho phép một ứng dụng duy trì materialized views.

Một nhược điểm là mô hình lập trình phức tạp hơn so với việc sử dụng các transaction ACID. Thông thường, bạn phải thực hiện các transaction bù trừ để phục hồi từ các lỗi cấp ứng dụng; ví dụ: bạn phải hủy đơn hàng nếu kiểm tra tín dụng không thành công. Ngoài ra, các ứng dụng phải đối phó với dữ liệu không phù hợp khi bị thay đổi giữa chừng. Một hạn chế khác là subscribers phải phát hiện và bỏ qua các event trùng lặp.

Bảo đảm tính nguyên tố

Trong event-driven architecture, chúng ta thường gặp phải vấn đề về cập nhật cơ sở dữ liệu và xuất bản một event. Ví dụ, Order Service phải insert một row vào table ORDER và xuất bản (publish) một event (event) Order Created. Điều quan trọng là hai hoạt động này được thực hiện một cách nguyên tố. Nếu dịch vụ bị crash sau khi cập nhật cơ sở dữ liệu nhưng trước khi xuất bản event, hệ thống sẽ trở nên không nhất quán. Cách tiêu chuẩn để đảm bảo nguyên tố là sử dụng một distributed transaction liên quan đến cơ sở dữ liệu và Message Broker. Tuy nhiên, vì những lý do được mô tả ở trên, chẳng hạn như định lý CAP, đây chính xác là những gì chúng ta không muốn làm.

Publishing Event sử dụng Local Transaction

Một cách để đạt được nguyên tố là bằng cách sử dụng quy trình nhiều bước chỉ liên quan đến các Local Transactions. Bí quyết là có một bảng EVENT, có chức năng như một hàng đợi thông báo, trong cơ sở dữ liệu lưu trữ trạng thái của các thực thể nghiệp vụ. Ứng dụng bắt đầu một local transaction, cập nhật trạng thái của các thực thể nghiệp vụ, insert một event vào bảng EVENT và commits transaction đó. Một process khác biệt truy vấn bảng EVENT, và pulish các event cho Message Broker, và sau đó sử dụng một local transaction để đánh dấu các event được xuất bản như diagram dưới đây:

Order Service insert một hàng vào bảng ORDER và insert một Order Created event vào bảng EVENT. Event Publisher thread truy vấn bảng EVENT cho các event chưa được xuất bản để xuất bản các event và sau đó cập nhật bảng EVENT để đánh dấu các event được xuất bản.

Cách tiếp cận này có một số lợi ích và hạn chế. Một lợi ích là nó đảm bảo một event được xuất bản cho mỗi lần cập nhật mà không dựa vào 2PC. Ngoài ra, ứng dụng xuất bản các event cấp doanh nghiệp, giúp loại bỏ sự cần thiết phải suy ra chúng. Một nhược điểm của phương pháp này là nó có khả năng bị lỗi vì developer phải nhớ xuất bản các event. Một hạn chế của phương pháp này là nó rất khó thực hiện khi sử dụng một số cơ sở dữ liệu NoSQL vì khả năng truy vấn và transaction hạn chế của chúng.

Bây giờ chúng ta hãy xem xét một phương pháp đạt được tính nguyên tố khác.

Mining a Database Transaction Log

Một cách khác để đạt được nguyên tố mà không có 2PC là cho các event được xuất bản bởi một thread hoặc một process khai thác database’s transaction hoặc commit log. Ứng dụng cập nhật cơ sở dữ liệu, dẫn đến những thay đổi được ghi lại trong nhật ký transaction của cơ sở dữ liệu. Transaction Log Miner thread đọc nhật ký transaction và xuất bản event cho Message Broker như diagram dưới đây:

Một ví dụ về cách tiếp cận này là dự án mã nguồn mở LinkedIn Databus. Databus khai thác nhật ký transaction Oracle và xuất bản các event tương ứng với các thay đổi. LinkedIn sử dụng Databus để giữ các kho dữ liệu có nguồn gốc khác nhau phù hợp với hệ thống bản ghi.

Transaction log mining có nhiều lợi ích và hạn chế khác nhau. Một lợi ích là nó đảm bảo rằng một event được xuất bản cho mỗi lần cập nhật mà không cần sử dụng 2PC. Transaction log mining cũng có thể đơn giản hóa ứng dụng bằng cách tách xuất bản event khỏi logic nghiệp vụ của ứng dụng. Một nhược điểm lớn là định dạng của transaction log là khác nhau đối với mỗi cơ sở dữ liệu và thậm chí có thể thay đổi giữa các phiên bản cơ sở dữ liệu.

Sừ dụng Event Sourcing

Event Sourcing đạt được tính nguyên tố mà không cần 2PC bằng cách sử dụng phương pháp tiếp cận event khác. Thay vì lưu trữ trạng thái hiện tại của một thực thể, ứng dụng lưu trữ một chuỗi các event thay đổi trạng thái. Ứng dụng xây dựng lại trạng thái hiện tại của một thực thể bằng cách phát lại các event. Bất cứ khi nào trạng thái của một thực thể nghiệp vụ thay đổi, một event mới được nối vào danh sách các event. Vì việc lưu một event là một hoạt động đơn lẻ, nó vốn dĩ là nguyên tố.

Để xem cách hoạt động của nguồn event, hãy xem xét thực thể Đơn hàng làm ví dụ. Trong cách tiếp cận truyền thống, mỗi đơn hàng ánh xạ tới một hàng trong bảng ORDER và tới các hàng trong, ví dụ như bảng ORDER_LINE_ITEM. Nhưng khi sử dụng Event Sourcing, Order Service lưu trữ một Đơn đặt hàng dưới dạng các event thay đổi trạng thái của nó: Created, Approved, Shipped, Cancelled. Mỗi event chứa đủ dữ liệu để tái tạo lại trạng thái của Đơn đặt hàng.

Events được lưu trữ trong Event Store. Event Store cũng hoạt động giống như Message Broker trong các kiến ​​trúc mà chúng tôi đã mô tả trước đó. Nó cung cấp một API cho phép các dịch vụ đăng ký các event. Event Store cung cấp tất cả event cho tất cả người đăng ký quan tâm. Event Store là xương sống của event-driven microservices architecture.

Event sourcing có nhiều lợi ích. Nó giải quyết một trong những vấn đề chính trong việc thực hiện event-driven architecture và làm cho nó có thể xuất bản event đáng tin cậy bất cứ khi nào thay đổi trạng thái. Kết quả là, nó giải quyết các vấn đề nhất quán về dữ liệu trong kiến ​​trúc microservices. Event sourcing cũng cung cấp nhật ký kiểm tra đáng tin cậy 100% về các thay đổi được thực hiện cho một thực thể nghiệp vụ và có thể thực hiện các truy vấn thời gian xác định trạng thái của một thực thể tại bất kỳ thời điểm nào. Điều này làm cho nó dễ dàng hơn nhiều để di chuyển từ một ứng dụng nguyên khối sang microservices.

Event sourcing cũng có một số hạn chế. Đó là một phong cách lập trình khác và không quen thuộc và do đó cần thời gian để học hỏi, nghiên cứu thêm. Bạn phải sử dụng Command Query Responsibility Segregation (CQRS) để thực hiện truy vấn và phải xử lý để đạt được trạng thái dữ liệu nhất quán cuối cùng (eventually consistent data).

Để hiểu rõ hơn về Event Souring và Event-driven delopment, các bạn có thể đọc tại đây: Event-Driven Architecture

Tóm lược

Trong kiến ​​trúc microservices, mỗi service có kho dữ liệu riêng của nó. Các microservices khác nhau có thể sử dụng các cơ sở dữ liệu SQL và NoSQL khác nhau. Trong khi kiến ​​trúc cơ sở dữ liệu này có những lợi ích đáng kể, nó tạo ra một số thách thức quản lý dữ liệu phân tán. Thách thức đầu tiên là làm thế nào để thực hiện các business transactions duy trì tính thống nhất trên nhiều dịch vụ. Thách thức thứ hai là cách triển khai các truy vấn lấy dữ liệu từ nhiều dịch vụ.

Đối với nhiều ứng dụng, giải pháp là sử dụng event-driven architecture. Một thách thức với việc triển khai kiến ​​trúc hướng event là cách cập nhật trạng thái nguyên tố và cách xuất bản các event. Có một vài cách để thực hiện việc này, bao gồm việc sử dụng cơ sở dữ liệu như message queue, transaction log mining, và event sourcing.

Tham khảo:

Blog Edward Thien Hoang

Leave a Reply

Your email address will not be published. Required fields are marked *