Thứ tự message trong mô hình Event Sourcing

 Mô hình Event Sourcing là một mô hình thiết kế hệ thống mà theo đó sẽ lưu giữ trạng thái thay đổi của một đối tượng là một chuỗi các thay đổi đã xảy ra. Ví dụ gần nhất là việc lưu trữ các transaction giao dịch của một tài khoản ngân hàng. Có thể tham khảo thêm tại hai link sau:

https://msdn.microsoft.com/en-us/library/dn589792.aspx

http://martinfowler.com/eaaDev/EventSourcing.html

Theo mô hình event sourcing thì các event sau khi được lưu trữ vào event store thì sẽ được gửi thông qua message queue, để đồng bộ các external data source khác ví dụ như database, cache, index, data warehouse... Các data source còn được gọi là read side, con event store là write side.

Trong mô hình này các event thay đổi của model dữ liệu sẽ đánh thứ version và append vào một event log. Loại database này cũng rất tối ưu cho việc ghi dữ liệu vì chỉ insert mà không có update và delete nên rất nhanh. Các event là immutable, sẽ lưu trữ các thay đổi xảy ra với một model dữ liệu.

Nhưng khi xây dựng hệ thống dựa trên mô hình này thì vấn đề thứ tự message là vô cùng đau đầu. Việc không đảm bảo thứ tự message sẽ gây ra rất nhiều vấn đề với sự ổn định của hệ thống.

Xét một ví dụ đơn giản: xây dựng mô hình event sourcing cho việc quản lý đơn hàng đi. Rất chuẩn, vì sẽ tracking được các thay đổi với một quá trình xử lý đơn hàng đó. Giả sử đơn hàng có hai trạng thái là xác nhận khách hàng xong, nhưng vì lý do lỗi dịch vụ mà phải hủy. Các event nếu gửi lộn sẽ gây ra sai lệch trong vận hành.

Do đó khi xây dựng cần phải rất chú ý vấn đề thứ tự event. Đặc biệt là trong môi trường phân tán. Kinh nghiệm của mình là có các điểm sau cần chú ý:

1. Thứ tứ phần ghi và vấn đề consistency write side.

Đây là một vấn đề mà khi mình đọc hầu hết các bài viết về mô hình này không nhấn mạnh, xem code mẫu nhiều bộ thì cũng không làm chặt. Đa phần vẫn implement một cách rất giản đơn khi insert thêm các row event mới.

Khi mỗi event mới sinh ra nó sẽ được đánh version tăng dần. Giả sử tại thời điểm update version của order object v=1, khi phát sinh event e1 thì sẽ có version v=2, nhưng đồng thời tại thời điểm đó, cũng có một request khác tác động tới object order và cũng phát sinh một event có version v=2. Như vậy khi lưu xuống, thì nếu không chú ý mà chỉ dùng các mode transaction thông thường thì bản chất các lệnh insert không lock nhau, khi đó trong db chúng ta sẽ ghi nhận: e1, e2, e2.

Như vậy là bị sai rồi. Đối với trường hợp này bạn phải buộc lock theo key range lock ví dụ lock theo key là id order, sử dụng mode serializable. Một cách khác là bạn có một bảng snapshot của order và lock trên table đó và insert vào table events. Như vậy thì thằng nào vào trước sẽ lock được bảng snapshot của order. Đây là cách mình đang làm.

2. Vấn đề đảm bảo thứ tự gửi event.

Sau khi ghi thành công thì vấn đề là phải gửi đi đúng thứ tự. Cũng đau đầu chẳng kém và cũng thấy rất ít người chú ý. Giả sử object order thay đổi hai lần và có hai e1, e2. Theo logic là e1 và e2 lần lượt gửi đi. Giả sử e1 gửi thì bị đứt mạng, nhưng e2 thì gửi bình thường. Vậy là sai thứ tự.

Trường hợp này phải đảm bảo là e1 không gửi được thì e2 cũng không gửi được. Có một giải pháp là: lưu trữ các event e1 và e2 theo trong một partition là id của object order. Khi thread gửi đi, sẽ đọc dữ liệu ở partition đó và gửi tuần tự từng event, không gửi được cái nào thì dừng ngay cho tới khi khắc phục được lỗi để gửi bình thường.

3. Vấn đề đảm bảo thứ tự nhận.

Ghi đúng thứ tự, gửi đúng thứ tự nhưng nhận vẫn có thể sai thứ tự. Các event sẽ được gửi vào một message queue. Thứ tự dequeue là chuẩn, vào trước ra trước, vào sau ra sau. Nhưng nếu trong môi trường phân tán, thì các message có thể được dequeue trên nhiều tiến trình khác nhau. Lúc này event e1 được dequeue ở millisecond 1, thì e2 được dequeue ở millisecond thứ 2, vì lý do nào đó là e1 xử lý lâu hơn e2, thế là e2 được cập nhật trước, e1 được cập nhật sau. Điều này dẫn tới là dữ liệu cập nhật ở read side bị sai.

Trong trường hợp này thì giải pháp phải áp dụng là phải group được các event này theo nhóm và với mỗi nhóm bắt buộc chỉ dequeue trên một thread. Các tính năng partition của kafka và session của windows service bus là giải pháp tốt nhất. Khi đó trên mỗi event thì phải có thêm thuộc tính để phân cụm. Ví dụ theo order id, để các event của một order chỉ đổ về một nơi tại một thời điểm thôi.

Trên đây có ba vấn đề chính, hy vọng có ích cho anh em nào định build hệ thống theo mô hình event sourcing. Một mô hình rất hay để scale hệ thống.

#ntechdevelopers



Ntech Developers

Programs must be written for people to read, and only incidentally for machines to execute.

Post a Comment

Previous Post Next Post