Get in touch
or send us a question?
CONTACT

Kiến trúc xử lý dữ liệu hướng sự kiện. Ứng dụng Kafka xây dựng kiến trúc hướng sự kiện.

Table of Contents

Dẫn nhập

Sự dịch chuyển sang kiến trúc hướng sự kiện: Rất nhiều các công ty và tập đoàn lớn trên thế giới đã sử dụng các hệ thống Event-Driven, nó như là 1 xu hướng không thể đứng ngoài.

Bản thân sự chuyển đổi này đang ở chính chúng ta, khi mà facebook, instagram cập nhật thông tin của bạn bè từng giây, từng phút hay những nền tảng tin tức như Twitter vẫn miệt mài push notification cho chúng ta mỗi khi có tin tức mới đâu đó trên thế giới. Đằng sau đó là những hệ thống hướng sự kiện (Event – Driven System).

Event-Driven Architect là gì?

EDA là một dạng kiến trúc phần mềm được xây dựng trên luồng các event, sử dụng event như là phương tiện giao tiếp giữa các thành phần hệ thống. Về cơ bản, hệ thống được xây dựng xung quanh các thao tác như tạo, khám phá, tiêu thụ và đáp trả lại các sự kiện (event).

Một event trong EDA được hiểu là một “thay đổi trạng thái đáng chú ý” của một thành phần nào đó. Event có thể được phát sinh do người dùng, do các thiết bị phần cứng hoặc do chính phần mềm phát sinh trong một điều kiện nào đó. EDA được xem như một trong những kĩ thuật thiết kế hiệu quả nhất trong việc hạn chế đến mức nhỏ nhất quan hệ phụ thuộc giữa các thành phần hệ thống hay các module.

EDA là mẫu kiến trúc được ứng dụng rất rộng rãi trong các hệ thống hiện đại và đặc biệt được dùng như phương tiện giao tiếp giữa các dịch vụ trong các hệ thống Service-oriented architecture (SOA) kiểu mới. EDA còn được sử dụng rộng rãi trong các framework phổ biến như Cairngorm, PureMVC…

Ví dụ về Event Driven Architect

Một ví dụ đơn giản của EDA: một module quản lý việc đăng nhập của user cần chứng thực thông tin của user vừa nhập xong nên tự phát sinh và gửi đi một event gọi là LoginEvent chứa thông tin user. Event này sau đó được một module có khả năng thao tác với dữ liệu như WebServer, Database… bắt lấy, thực hiện việc kiểm chứng và sau đó trả lời kết quả thông qua LoginResultEvent để module đăng nhập bắt lấy.

Theo cách xử lý này thì module đăng nhập không cần biết module nào và sẽ làm thế nào để thực hiện việc kiểm tra, nó chỉ cần biết gửi yêu cầu và nhận kết quả sau khi kiểm tra và tất cả những gì nó quan tâm chỉ là các event được định nghĩa ở mức hệ thống. Hơn nữa, event kết quả trong trường hợp trên có thể được quan tâm bởi nhiều module khác như module đảm nhận ghi log và do đó làm cho hệ thống càng mềm dẻo hơn.

Khi nào nên sử dụng kiến trúc EDA?

Cũng giống như class, component cũng được thiết kế với tính low coupling và high cohesion. Khi các component cần phải cộng tác, giả sử một Component “A” cần kích hoạt một số logic trong Component “B”, cách tự nhiên để làm điều đó chỉ đơn giản là Componentn A sẽ gọi một method trong một object của Component B. Tuy nhiên, nếu A biết về sự tồn tại của B, chúng đang tigh coupling với nhau, A phụ thuộc vào B, làm cho hệ thống khó khăn hơn để thay đổi và duy trì. Do đó, event có thể được sử dụng ngăn chặn tình trạng “dính chặt” vào nhau giữa các components.

Event còn có tác dụng trong một số tác vụ asynchronous.

Event nghe thì có vẻ ưu việt, một số dự án, tôi toàn thấy event, listener, fireXxxChange. Tuy nhiên, có những mối nguy hiểm từ nó. Nếu chúng ta sử dụng nó một cách bừa bãi, chúng ta có nguy cơ kết thúc với các luồng logic có ý nghĩa rất cao, nhưng kết hợp với nhau bởi các event là một cơ decoupling. Nói cách khác, mã nên được với nhau sẽ được tách ra và sẽ rất khó để theo dõi luồng đi của nó. Nó sẽ là mã spaghetti! Nói chung là rất là đọc hiểu được các hàm callback này sẽ được gọi khi nào, rồi callback lại lồng callback, …

Để ngăn chuyển codebase của chúng ta thành một đống lớn mã spaghetti, chúng ta nên giữ cho việc sử dụng event giới hạn trong các tình huống được xác định rõ ràng. Theo kinh nghiệm của tôi, có ba trường hợp để sử dụng các event:

  • Để tách các thành phần
  • Để thực hiện các tác vụ async
  • Để lưu vết các thay đổi (audit log)

Các Pattern của EDA

Martin Fowler định nghĩa ra 3 loại event patterns:

  • Event Notification
  • Event-Carried State Transfer
  • Event-Sourcing

Tất cả pattern này đề có chung key concept:

  • Event đại diện cho một cái gì đó đã xảy ra.
  • Event sẽ được phát tán (broadcasted) đến tất cả những nơi đang lắng nghe (chờ đợi) event đó.

Event Notification

Giả sử chúng ta có một application core với các component được xác định rõ ràng. Lý tưởng nhất là các component này hoàn toàn tách rời nhau nhưng một số chức năng của chúng yêu cầu một số logic trong các component khác sẽ được thực hiện.

Đây là trường hợp điển hình nhất, được mô tả trước đó: Khi Component A thực hiện logic cần kích hoạt logic của Component B, thay vì gọi nó trực tiếp, chúng ta có thể kích hoạt một event gửi đến event dispatcher. Component B sẽ lắng nghe event cụ thể đó trong dispatcher và sẽ hành động bất cứ khi nào event xảy ra.

Điều quan trọng cần lưu ý là, một đặc điểm của mô hình này là event chỉ nên chứa các dữ liệu tối thiểu. Nó chỉ mang đủ dữ liệu cho listener biết điều gì đã xảy ra và thực hiện logic của chúng, thường là các Entity ID và có thể ngày và giờ mà event được tạo ra.

Event Carried State Transfer

Hãy xem xét lại ví dụ trước của một core ứng dụng với các components được xác định rõ ràng. Lần này, đối với một số chức năng của chúng, chúng cần dữ liệu từ các components khác. Cách tự nhiên nhất để lấy dữ liệu là invoke các component khác, nhưng điều đó có nghĩa là các component sẽ phải coupled với nhau!

Một cách khác để chia sẻ dữ liệu là sử dụng event được kích hoạt khi data trong component có sự thay đổi. Event sẽ chứa những thông tin mới nhất của data và các component quan tâm đến dữ liệu đó sẽ được notify bằng những event này và sẽ có cách thức để handle nó. Bằng cách này, các component sẽ không cần phải ask các component khác một cách trực tiếp nữa.

Event Sourcing:

Theo cách lưu trữ trạng thái truyền thống, các entity chỉ được lưu trong DB dưới dạng các row trong 1 table, và nó chỉ phản ảnh trạng thái sau cùng của entity đó.

Sử dụng Event Sourcing, thay vì lưu trữ trạng thái Thực thể, chúng ta tập trung vào việc lưu trữ thay đổi trạng thái Thực thể và tính toán trạng thái Thực thể từ những thay đổi đó. Mỗi thay đổi trạng thái là một event, được lưu trữ trong một event stream (ví dụ một bảng trong một RDBMS). Khi cần trạng thái hiện tại của một Thực thể, chúng ta tính toán nó từ tất cả sự kiện của nó trong event stream.

Lấy ví dụ, chúng ta có một tài khoản ngân hàng, nếu theo cách lưu trữ truyền thống, chúng ta có thể xem số dư tại thời điểm hiện tại của tài khoản chúng ta. Và nếu ngân hàng không support sao kê, để xem tiền vào, tiền ra trong một khoảng thời gian nào đó, thậm chí là từ lúc mở toàn khoản đến giờ thì sẽ không có cách nào để kiểm tra liệu hệ thống tính toán, lưu trữ ở phía ngân hàng làm việc có chính xác hay không. “Sao kê” ở đây chính là một ví dụ cho Event Sourcing.

Ứng dụng Kafka trong kiến trúc EDA

Kafka là 1 hệ thống messaging queue phân tán, cho phép gửi và nhận (publish and subcribe) các bản ghi theo luồng. Thực tế Apache Kafka đã trở thành 1 tiêu chuẩn cho các hệ thống Event Streaming thời gian thực.

VD với 1 hệ thống xử lý dữ liệu nhiều nguồn, mô hình truyền thống thì ứng dụng cần kết nối đến từng nguồn để lấy dữ liệu về xử lý, Kafka lúc này đóng vai trò trung gian ở giữa, lúc này dữ liệu tức các nguồn khác nhau sẽ đẩy vào kafka, ứng dụng chỉ cần kết nối đến Kafka để lấy dữ liệu.