Trong phần này chúng ta sẽ xem xét Kafka, và đối chiếu nó với RabbitMQ để tìm ra sự khác biệt giữa hai loại. Lưu ý rằng, sự so sánh ở đây được giới hạn trong bối cảnh của một kiến trúc ứng dụng hướng sự kiện hơn là data processing pipeline, mặc dù hai loại kiến trúc này khá giống nhau.
Sự khác biệt đầu tiên ta có thể nghĩ đến đó là các pattern retry và delay không có ý nghĩa với Kafka. Các thông điệp trong RabbitMQ chỉ là các thông điệp là tạm thời, chúng được di chuyển và sau đó biến mất. Vì vậy, việc ta thêm lại các thông điệp giống với những lần tiêu thụ thông điệp trước đó hoàn toàn là usecase thực tế. Nhưng trong Kafka thì log
mới là trung tâm của toàn bộ hệ thống. Việc ta thêm lại một thông điệp trùng lặp với lần trước đó cho lần xử lý thông điệp thứ hai sẽ không mang ý nghĩa gì cả và điều này sẽ chỉ làm cho log
bị lỗi. Một trong những điểm mạnh của Kafka đó là tính đảm bảo trật tự của nó bên trong một phân vùng log
, việc thêm các bản sao sẽ làm rối tung tất cả. Trong RabbitMQ, bạn có thể thêm lại một thông điệp vào hàng đợi mà một consumer đơn lẻ có thể tiêu thụ, nhưng Kafka là một hệ thống log
thống nhất mà tất cả consumer đều tiêu thụ. Việc trì hoãn không đi ngược lại với khái niệm của log
nhưng Kafka không cung cấp cho ta cơ chế này.
Một điểm khác biệt lớn thứ hai ảnh hưởng đến các mô hình mà RabbitMQ và Kafka có thể thực hiện đó là trong RabbitMQ các thông điệp được gửi không bền vững, còn trong Kafka thì bền vững hơn. Trong RabbitMQ một khi một thông điệp được sử dụng, nó sẽ biến mất, không còn dấu vết của thông điệp đó. Nhưng trong Kafka, mỗi thông điệp được lưu vào log
và vẫn ở đó cho đến khi được dọn sạch. Việc làm sạch dữ liệu thực sự phụ thuộc vào lượng dữ liệu bạn có, dung lượng bạn có thể cung cấp cho nó và các pattern mà bạn muốn thực hiện được. Ta có thể thực hiện theo cách tiếp cận thời gian, sử dụng ngày/tuần/tháng cuối của thông điệp hoặc ta có thể sử dụng Log Compaction
để cung cấp cho ta trạng thái gần nhất theo cách tiếp cận khóa thông điệp.
Dù bằng cách nào, chúng ta đều cho phép consumer quay lại và tái tiêu thụ các thông điệp cũ. Nghe có vẻ rất giống với hành vi thử lại mặc dù không giống như cách thực hiện của RabbitMQ.
Trong khi RabbitMQ di chuyển các thông điệp và cung cấp cho ta những cách thức rất mạnh mẽ để có thể tạo ra các mẫu định tuyến sáng tạo, thì Kafka lại cho ta cách lưu trữ trạng thái hiện tại và lưu trữ lịch sử của một hệ thống, nó có thể được sử dụng như một nguồn dữ liệu đáng tin cậy mà RabbitMQ không thể đáp ứng được.
Trường hợp sử dụng đơn giản nhất cho cả RabbitMQ và Kafka là kiến trúc publish-subscribe
. Một publisher hoặc nhiều publisher chỉ cần ghi thông điệp vào phân vùng log và một hoặc nhiều nhóm consumer tiêu thụ những thông điệp đó.
Hình 1
Ngoài các chi tiết về việc quản lý các publisher gửi thông điệp đến đúng phân vùng và các nhóm consumer tự phối hợp với nhau, thì còn lại không khác gì một cấu trúc liên kết Fanout exchange
trong RabbitMQ.
Tính năng này (sử dụng log thay vì queue) rất hữu ích để phục hồi khi có lỗi. Trong nhiều trường hợp, các thông điệp đã biến mất, nhưng thông thường chúng ta có thể lấy dữ liệu gốc từ hệ thống của bên thứ ba và publish
lại các thông điệp, chỉ cho một thuê bao có vấn đề. Điều này đòi hỏi chúng ta phải xây dựng cơ sở hạ tầng re-publish
tùy biến. Nếu chúng ta đã có Kafka, nó sẽ đơn giản như thay đổi khoảng trống cho ứng dụng đó.
Mẫu này về cơ bản là event sourcing
mặc dù không thuộc phạm vi của một ứng dụng. Có hai loại event sourcing
, cấp ứng dụng và cấp hệ thống và pattern này liên quan đến loại thứ hai.
Ứng dụng quản lý trạng thái của chính nó như là một chuỗi các sự kiện thay đổi bất biến. Những sự kiện này được lưu trữ trong một event store
. Để có được trạng thái hiện tại của một thực thể có nghĩa là thực hiện lại hoặc kết hợp các sự kiện của thực thể đó theo đúng thứ tự. Nó được kết hợp tự nhiên với CQRS. Đối với một số ứng dụng, đây có thể là kiến trúc phù hợp nhưng nó thường bị hiểu sai và áp dụng sai, thêm độ phức tạp không cần thiết, trong khi một ứng dụng CRUD truyền thống sẽ ổn.
Chúng ta sẽ bỏ qua loại này.
Các ứng dụng có thể quản lý trạng thái của riêng chúng theo bất kỳ cách nào chúng muốn. Nếu chúng có thể lưu trữ trạng thái của chúng trong cơ sở dữ liệu quan hệ và có được sự thống nhất giao dịch ngay lập tức đi kèm với điều đó thì thật tuyệt - chúng ta nên thực hiện điều đó. Không nhất thiết phải là mối quan hệ nhưng tính nhất quán ngay lập tức với đảm bảo ACID
thực sự là "chén thánh" cho các hệ thống OLTP. Cuối cùng, chúng ta chỉ chuyển đến kiến trúc nhất quán khi tính nhất quán ngay tức thì không mở rộng.
Nhưng các ứng dụng thường cần dữ liệu của nhau và nhu cầu đó có thể dẫn đến các kiến trúc không tối ưu như cơ sở dữ liệu dùng chung, ranh giới domain không tồn tại hoặc phụ thuộc vào API REST tồi.
Có một podcast, trong đó họ mô tả một kịch bản event sourcing
với một dịch vụ hồ sơ trong mạng xã hội. Có một loạt các dịch vụ liên quan khác như tìm kiếm, biểu đồ xã hội, recommendation service,... Tất cả cần phải biết về các thay đổi của hồ sơ. Theo kinh nghiệm của tôi, ví dụ về một hệ thống đặt vé của một hãng hàng không, chúng ta có một vài hệ thống phần mềm lớn với vô số dịch vụ nhỏ hơn quay quanh các hệ thống lớn hơn đó. Các dịch vụ phụ trợ cần dữ liệu đặt chỗ và dữ liệu hoạt động bay. Mỗi lần đặt chỗ được thực hiện, sửa đổi, một chuyến bay bị trì hoãn hoặc hủy các dịch vụ này cần phải đi vào hoạt động.
Đây là nơi event sourcing
có ích. Nhưng trước tiên, hãy xem xét một số vấn đề phổ biến phát sinh trong các hệ thống phần mềm lớn và sau đó xem cách event sourcing
có thể giải quyết điều đó.
Hệ thống doanh nghiệp phức tạp lớn thường phát triển có hệ thống; có sự chuyển đổi sang công nghệ mới và kiến trúc mới có thể không đạt tới 100% hệ thống. Dữ liệu được truyền tải khắp doanh nghiệp ở nhiều nơi khác nhau, các ứng dụng dùng chung cơ sở dữ liệu để tích hợp nhanh chóng và không ai chắc chắn làm thế nào tất cả phù hợp với nhau.
Dữ liệu được phân phối và quản lý ở nhiều nơi khiến bạn khó hiểu:
-
Dữ liệu phát sinh qua nghiệp vụ của bạn như thế nào
-
Thay đổi trong một phần của hệ thống có thể ảnh hưởng đến các phần khác như thế nào
-
Nhiều bản sao của dữ liệu tồn tại, phân mảnh theo thời gian và xuất hiện các xung đột dữ liệu
Không có các ranh giới domain rõ ràng, các thay đổi sẽ tốn kém và rủi ro vì điều đó có nghĩa là phải đụng chạm vào nhiều hệ thống.
Dùng chung cơ sở dữ liệu có thể gây ra một vài vấn đề đau đầu:
-
Không thực sự tối ưu hóa cho bất kỳ ứng dụng nào. Nó có thể là tập dữ liệu đầy đủ và được chuẩn hóa hoàn toàn. Một số ứng dụng phải viết các truy vấn lớn để có được dữ liệu chúng cần.
-
Các ứng dụng có thể ảnh hưởng đến hiệu suất của nhau.
-
Các thay đổi đối với
schema
có nghĩa là các dự án migrate quy mô lớn, trong đó việc phát triển bị tạm dừng trong vài tháng để đưa tất cả các ứng dụng cùng được cập nhật, tất cả cùng một lúc. -
Không có thay đổi nào cho
schema
được thực hiện. Sự thay đổi mà mọi người muốn sẽ không bao giờ được thực hiện vì nó quá tốn kém.
Mỗi API REST có thể có một kiểu dáng và quy ước khác nhau. Để có được dữ liệu mà bạn muốn có thể bạn sẽ cần phải sử dụng rất nhiều HTTP request
và điều này có thể khá rắc rối. GraphQL có thể giúp bạn việc này nhưng nó vẫn không thể hỗ trợ bạn lấy được tất cả dữ liệu mà bạn muốn để lưu trữ trong cơ sở dữ liệu cục bộ, nơi bạn có thể xử lý dữ liệu bằng SQL.
Chúng ta đang chuyển nhiều hơn đến các kiến trúc trung tâm API và có nhiều lợi ích cho kiến trúc đó, đặc biệt là khi các API này nằm ngoài tầm kiểm soát của chúng ta. Có rất nhiều API hiện nay mà chúng ta không phải xây dựng quá nhiều phần mềm như trước đây. Tuy nhiên, nó không phải là công cụ duy nhất trong bộ công cụ và đối với kiến trúc phụ trợ nội bộ, có những lựa chọn thay thế khác.
Hãy lấy một ví dụ đơn giản. Chúng ta có một hệ thống quản lý đặt vé được quản lý trong một cơ sở dữ liệu quan hệ. Hệ thống sử dụng tất cả các thuộc tính ACID
được cung cấp bởi cơ sở dữ liệu để quản lý trạng thái của nó một cách hiệu quả và nói chung mọi người đều hài lòng với nó. Không sử dụng CQRS, không event sourcing
, không microservice, chỉ là một ứng dụng truyền thống được xây dựng khá hợp lý. Tuy nhiên, có vô số dịch vụ phụ trợ (có lẽ là microservice) liên quan đến đặt vé như: push notifications, email, chống gian lận, tích hợp với bên thứ ba, chương trình khách hàng thân thiết, lập hóa đơn, hủy vé,... Tất cả chúng đều cần dữ liệu đặt vé và có nhiều cách khác nhau để chúng có được nó. Chúng cũng có thể tự sản xuất dữ liệu của riêng mình, điều này rất hữu ích cho các ứng dụng khác.
Hình 2:
Một kiến trúc thay thế khác đặt Kafka ở trung tâm. Trên mỗi lần đặt vé mới hoặc sửa đổi đặt vé, hệ thống đặt vé sẽ publish
toàn bộ trạng thái hiện tại của đặt vé đó cho Kafka. Chúng tôi sử dụng nén log
để giảm các thông báo xuống chỉ trạng thái mới nhất của các đặt vé, giữ cho kích thước của log
được kiểm soát.
Hình 3:
Theo như tất cả các ứng dụng khác có liên quan thì đây là nguồn dữ liệu đáng tin cậy và chúng tiêu thụ nguồn dữ liệu duy nhất đó. Đột nhiên chúng ta đi từ một mạng lưới phụ thuộc và công nghệ phức tạp để sản xuất và tiêu thụ đến/từ các Topic Kafka.
Kafka như là một event store:
-
Nếu dung lượng lớn không phải là một vấn đề, thì Kafka có thể lưu trữ toàn bộ lịch sử của các sự kiện, điều đó có nghĩa là một ứng dụng mới có thể được triển khai và tự khởi động từ
log
của Kafka. Các sự kiện thể hiện đầy đủ trạng thái của thực thể có thể được nén bằngLog Compaction
làm cho phương pháp này khả thi hơn trong nhiều tình huống. -
Các sự kiện cần phải được phát lại theo đúng thứ tự. Miễn là các thông điệp được phân vùng chính xác, chúng ta có thể phát lại các thông điệp theo thứ tự và áp dụng các bộ lọc, biến đổi, v.v để chúng ta luôn kết thúc với đúng dữ liệu ở cuối. Tùy thuộc vào "khả năng phân vùng" của dữ liệu, chúng ta có thể xử lý dữ liệu song song cao, theo đúng thứ tự.
-
Mô hình dữ liệu có thể cần phải thay đổi. Có thể chúng ta phát triển một chức năng lọc/biến đổi mới và muốn phát lại trên tất cả các sự kiện hoặc các sự kiện từ tuần trước.
Kafka có thể được cung cấp không chỉ bởi các ứng dụng trong tổ chức của bạn xuất bản tất cả các thay đổi trạng thái của họ (hoặc kết quả của thay đổi trạng thái) mà còn từ tích hợp với các hệ thống của bên thứ ba:
-
Các ETL định kỳ trích xuất dữ liệu từ các hệ thống của bên thứ ba và đổ dữ liệu vào Kafka
-
Một số dịch vụ của bên thứ ba cung cấp các móc nối web và các dịch vụ web REST được kích hoạt của bạn có thể ghi vào Kafka.
-
Một số dịch vụ của bên thứ ba cung cấp các
messaging system interface
có thể được đăng ký và ghi vào Kafka. -
Một số hệ thống bên thứ ba phân phối dữ liệu qua CSV, có thể được ghi vào Kafka.
Quay trở lại các vấn đề tôi đã giải thích trước đó. Với kiến trúc trung tâm Kafka, chúng ta đơn giản hóa việc phân phối dữ liệu. Chúng ta biết nguồn gốc tin cậy là gì và tất cả các ứng dụng tiếp theo đều hoạt động với các bản sao xuất phát của dữ liệu. Luồng dữ liệu từ publisher đến consumer. Dữ liệu chủ được sở hữu bởi publisher đơn lẻ nhưng những người khác có thể làm việc trên các phép chiếu của dữ liệu đó một cách tự do. Chúng có thể lọc nó, biến đổi nó, gia tăng nó với các nguồn dữ liệu khác và lưu trữ nó trong cơ sở dữ liệu của riêng chúng.
Hình 4:
Mỗi ứng dụng cần đặt chỗ và dữ liệu hoạt động chuyến bay hiện có ứng dụng cục bộ vì nó đăng ký các chủ đề Kafka có chứa dữ liệu đó. Các ứng dụng có thể sử dụng sức mạnh của SQL, Cypher, JSON hoặc bất kỳ ngôn ngữ truy vấn nào. Dữ liệu có thể được cắt và cắt nhỏ hiệu quả vì nó được lưu trữ trong một cấu trúc và một kho lưu trữ dữ liệu được tối ưu hóa cho nhu cầu của nó. Chúng ta có thể thay đổi schema
mà không lo ảnh hưởng đến các ứng dụng khác.
Không phải tất cả các ứng dụng cần lưu trữ dữ liệu đó và chỉ có thể phản ứng với dữ liệu có trong mỗi sự kiện. Điều đó là tốt, event sourcing
hỗ trợ cả hai mô hình.
Tại thời điểm này, bạn có thể hỏi: Tại sao tôi không thể làm điều này với RabbitMQ? Câu trả lời là bạn có thể sử dụng RabbitMQ để xử lý sự kiện theo thời gian thực, nhưng không phải là một nền tảng event sourcing
. RabbitMQ chỉ là một giải pháp hoàn chỉnh để phản ứng với các sự kiện đang xảy ra hiện nay. Khi bạn thêm một ứng dụng mới cần một lát cắt riêng cho tất cả dữ liệu đặt chỗ, ở định dạng được tối ưu hóa cho vấn đề mà nó giải quyết, thì RabbitMQ sẽ không giúp bạn. Với RabbitMQ, chúng tôi quay lại cơ sở dữ liệu dùng chung hoặc sử dụng dịch vụ web REST được cung cấp bởi nguồn dữ liệu.
Thứ hai, thứ tự xử lý sự kiện là rất quan trọng. Với RabbitMQ ngay khi bạn thêm consumer thứ hai vào hàng đợi, bạn sẽ mất mọi đảm bảo về trật tự. Vì vậy, bạn có thể giới hạn RabbitMQ cho một consumer và nhận được trật tự thông điệp chính xác, nhưng cách này lại không có tính mở rộng.
Mặt khác, Kafka có thể cung cấp tất cả dữ liệu mà ứng dụng mới này cần để xây dựng bản sao dữ liệu của riêng mình và luôn cập nhật, với các đảm bảo xử lý thông điệp.
Bây giờ hãy nghĩ lại về kiến trúc trung tâm API. API cho mọi thứ vẫn có vẻ là ý tưởng tốt nhất? Tôi nghĩ rằng khi cần chia sẻ dữ liệu readonly thì tôi thích kiến trúc event sourcing
. Chúng tôi tránh các thất bại xếp tầng và mức độ thời gian hoạt động thấp hơn đi kèm với số lượng phụ thuộc lớn hơn vào các dịch vụ khác. Chúng tôi tăng khả năng cắt và xúc xắc dữ liệu một cách sáng tạo và hiệu quả. Nhưng đôi khi bạn cần thay đổi dữ liệu trong một hệ thống khác một cách đồng bộ và sau đó API có ý nghĩa. Một số API thích các phương thức không đồng bộ hơn và tôi nghĩ rằng điều đó có ích.
Cách tiếp cận event sourcing
này để tích hợp dữ liệu hoàn toàn phù hợp với microservice và Self-Contained Systems (SCS). Cũng có một podcast nói về SCS bạn có thể tham khảo qua.
Có một vấn đề có thể xảy ra khi sử dụng RabbitMQ, một ví dụ cụ thể đó là khi các consumer tiêu thụ một queue
RabbitMQ mà nguồn cung cấp dữ liệu là từ một bên thứ ba. Số lượng dữ liệu lớn và theo tự nhiên ứng dụng được scale-out
để xử lý số lượng thông điệp. Vấn đề ở đây là do dữ liệu trong cơ sở dữ liệu không nhất quán và nó gây ra vấn đề trong nghiệp vụ.
Vấn đề là đôi khi hai dữ liệu liên quan đến cùng một thực thể và chúng đến cách nhau trong vòng vài giây. Cả hai đều được xử lý và do tải trên một máy chủ, thông điệp thứ hai cuối cùng được ghi vào cơ sở dữ liệu và thông điệp đầu tiên sau đó sẽ ghi đè lên thông điệp thứ hai. Vì vậy, thành ra chúng ta sẽ có được dữ liệu không như mong muốn. RabbitMQ đã thực hiện đúng công việc của mình, nó đã gửi các thông điệp theo đúng thứ tự, nhưng cuối cùng ta vẫn chèn dữ liệu theo một thứ tự sai.
Ta có thể giải quyết nó bằng cách kiểm tra nhãn thời gian trên bản ghi hiện có và không chèn nếu thông điệp cũ hơn. Ta cũng có thể giải quyết điều này bằng cách sử dụng Consistent Hashing exchange
và phân vùng hàng đợi giống như cách Kafka sử dụng các phân vùng.
Kafka lưu trữ các thông điệp trong một phân vùng theo thứ tự mà chúng được gửi đến nó. Thứ tự thông điệp chỉ tồn tại ở cấp độ của phân vùng. Trong ví dụ ở trên, với Kafka, chúng ta sẽ sử dụng hàm băm trên id
thực thể để chọn phân vùng. Ta đã có một loạt các phân vùng, quá đủ để mở rộng quy mô. Lệnh xử lý sẽ đạt được vì mỗi phân vùng chỉ được tiêu thụ bởi một consumer. Điều này khá đơn giản và hiệu quả.
Kafka có một số lợi thế so với RabbitMQ về phân vùng thông qua chức năng băm. Với RabbitMQ, không có gì ngăn cản bạn có những consumer cạnh tranh trong một hàng đợi được cung cấp bởi một Consistent Hashing exchange
. RabbitMQ không giúp điều phối consumer để đảm bảo rằng chỉ có một consumer tiêu thụ từ mỗi hàng đợi. Kafka làm điều đó cho bạn với các nhóm consumer và một node
điều phối. Vì vậy, chúng ta có thể yên tâm rằng với Kafka, ta có được đảm bảo là một consumer trên mỗi phân vùng và có sự đảm bảo về thứ tự xử lý.
Bằng cách sử dụng chức năng băm để định tuyến thông điệp đến các phân vùng, Kafka cung cấp cho chúng ta dữ liệu cục bộ. Ví dụ: các thông điệp liên quan đến id người dùng 1001 luôn đến tay consumer 3. Vì các sự kiện của người dùng 1001 luôn đến consumer 3 có nghĩa là consumer 3 có thể thực hiện một số hoạt động không khả thi nếu network round-trip là cần thiết. Chúng ta có thể thực hiện các bộ đếm, tập hợp thời gian thực và tương tự như trong bộ nhớ. Đây là nơi ranh giới giữa các ứng dụng hướng sự kiện và stream processing pipeline
bắt đầu hợp nhất.
Điều này nhắc nhở ta về sticky session
trong quá khứ khi mọi người thường lưu trữ dữ liệu phiên làm việc web trong bộ nhớ và sử dụng stateful load balancing
để định tuyến các yêu cầu của một người dùng cụ thể đến cùng một máy chủ mỗi lần. Điều đó gây ra tất cả các loại vấn đề như vấn đề mở rộng và tải không cân bằng.
Sticky session
có nghĩa là nếu 10 máy chủ của chúng ta bị quá tải và chúng ta đã thêm 10 máy chủ khác, thì chúng ta không thể chuyển qua người dùng hiện tại sang các máy chủ khác vì phiên của họ tồn tại trên 10 máy chủ ban đầu. Chúng ta chỉ có thể di chuyển người dùng mới vào các phiên bản mới được triển khai. Điều này có nghĩa là 10 máy chủ ban đầu của chúng ta vẫn bị quá tải và 10 máy chủ mới không được sử dụng đúng mức. Ngày nay, chúng ta thường có xu hướng đưa session vào Redis.
Vậy làm thế nào để Kafka tránh những cạm bẫy tương tự như sticky session
? Với Kafka, chúng ta không mở rộng quy mô và trong các phân vùng của mình. Trước hết bạn không thể "chia tỷ lệ" số lượng phân vùng; một khi bạn có 10 phân vùng, bạn không thể xuống 9. Nhưng thứ hai là không cần thiết. Mỗi consumer có thể tiêu thụ 1 đến nhiều phân vùng, vì vậy có rất ít lý do để muốn giảm số lượng phân vùng. Thêm vào đó các phân vùng trong Kafka giới thiệu các đột biến độ trễ trong khi tái cân bằng tải xảy ra, vì vậy chúng ta có xu hướng mở rộng các phân vùng theo tải trọng cao nhất và nhân rộng nhu cầu của consumer.
Nhưng nếu chúng ta cần tăng số lượng phân vùng và consumer cho mục đích mở rộng thì chúng ta chỉ cần trả một chi phí trễ tạm thời trong khi tái cân bằng xảy ra. Lưu ý rằng dữ liệu trong các phân vùng sẽ được đặt nếu chúng ta thêm dữ liệu mới. Không có dữ liệu được cân bằng lại, chỉ có consumer. Nhưng các thông điệp mới đến bây giờ sẽ được định tuyến khác và các phân vùng mới sẽ bắt đầu nhận thông điệp. Điều này cũng có nghĩa là các thông điệp của người dùng 1001 hiện có thể chuyển đến consumer 4 (có nghĩa là người dùng 1001 hiện có hai phân vùng).
Vì vậy, chúng ta cần có khả năng duy trì trạng thái đó ở đâu đó ngay khi tái cân bằng xảy ra vì tất cả các cược đều liên quan đến việc ai sẽ nhận được phân vùng nào sau khi tái cân bằng. Sau khi reloadbalancing
kết thúc, chúng ta có thể truy xuất trạng thái của các thông điệp mới đến và tiếp tục hoạt động trong bộ nhớ.
Trong phần 2, tôi đã kết thúc bài viết với một vòng đời thông điệp hoàn chỉnh xử lý các lỗi thông qua sự kết hợp của:
-
Thử lại ngay lập tức
-
Tạm dừng tiêu thụ trong một khoảng thời gian cấu hình
-
Hoãn thử lại
-
Loại bỏ thông điệp
-
Đóng gói các thông điệp dưới dạng các
Failed Event
và chuyển đến các kỹ sư hỗ trợ để phân loại và phản hồi.
Với Kafka chúng ta có thể có một cách tiếp cận tương tự. Sự khác biệt là bản thân các thông điệp không di chuyển bất cứ nơi nào, chúng được đặt trong phân vùng log
của chúng. Mặc dù vậy, chúng có thể được dọn sạch, vì vậy chúng ta sẽ phải tính đến cả hai tình huống đó. Pattern delay-retry không có ý nghĩa nhiều với Kafka. Thay vào đó, chúng ta có thể dựa vào thử lại ngay lập tức, tạm dừng tiêu thụ và gửi thông điệp đến Failed Event
topic.
Thử lại ngay lập tức sẽ giải quyết các vấn đề như các vấn đề sẵn có trong thời gian rất ngắn với các dịch vụ khác và những thứ như bế tắc cơ sở dữ liệu.
Nếu vấn đề còn tồn tại lâu hơn thế thì consumer có thể tạm dừng một phút. Nó có thể thử lại thông điệp cứ sau 1 phút, hoặc một số loại backoff có thể được thực hiện. Nếu vấn đề là API không khả dụng trong vài phút thì chiến lược này sẽ giải quyết vấn đề.
Nếu sự cố vẫn còn trong một khoảng thời gian nhất định thì các thông báo sẽ được đưa đến các kỹ sư hỗ trợ để họ có thể bắt đầu điều tra vấn đề.
Hình 5:
Đôi khi vấn đề không nhất thời và chỉ ảnh hưởng đến một số ít thông điệp. Có lẽ thông điệp có dữ liệu xấu gây ra bởi một lỗi trong publisher gửi thông điệp. Có lẽ có một lỗi trong consumer chỉ xảy ra khi xử lý một tập hợp nhỏ các thông điệp. Trong trường hợp này, chúng ta có thể thích chuyển sang các thông điệp tiếp theo và tiếp tục tiêu thụ các thông điệp. Chúng ta có thể đóng gói thông điệp thất bại trong một Failed Event
và gửi nó đến Failed Event
topic.
Những sự kiện thất bại này chứa:
-
Ứng dụng tiêu thụ
-
Thông báo ngoại lệ hoặc lỗi
-
Máy chủ
-
Thời gian
-
Ai công bố thông điệp
-
Thông điệp này đã được thử bao nhiêu lần rồi
Điều này làm cho chẩn đoán đơn giản hơn nhiều so với log
lỗi tương quan với dấu thời gian thông báo. Từ topic đó, các kỹ sư hỗ trợ có thể sử dụng ứng dụng xử lý thông điệp, phản hồi và chọn:
-
Không làm gì cả
-
Nếu sự cố được giải quyết và ứng dụng khách hàng sẽ hiển thị dịch vụ web REST, ứng dụng xử lý có thể gọi dịch vụ chuyển qua phần bù của thông báo sẽ được thử lại.
-
Nếu sự cố được giải quyết và thông điệp gốc đã được xóa, ứng dụng xử lý có thể giải nén thông điệp gốc từ Sự kiện thất bại và gửi lại cho chủ đề ban đầu.
Khi có sự cố xảy ra, cách chúng tôi phản ứng phụ thuộc hoàn toàn vào domain cụ thể đó là gì. Nếu đó là một thông báo rằng chuyến bay của bạn sẽ khởi hành sau 30 phút thì có nghĩa là thử lại thông điệp 4 giờ sau không? Hoặc có thể đó là một loại thông điệp mà trong mọi trường hợp không thể bị mất. Cho phép các đội hỗ trợ xem những thông điệp nào không thể được xử lý và để họ đưa ra quyết định dựa trên kiến thức về domain của họ đôi khi là lựa chọn duy nhất. Các kỹ sư hỗ trợ có thể tự động hóa các phản ứng đối với các sự cố cụ thể theo thời gian có thể giảm bớt gánh nặng. Nhưng các nhà phát triển ứng dụng cũng có thể tự động hóa quá trình ra quyết định này. Sử dụng cùng một ví dụ "30 phút cho đến khi thông báo chuyến bay của bạn", nhà phát triển có thể tạo logic khiến consumer loại bỏ thông điệp, ghi lại lỗi và tiếp tục.