[Report][AWS Summit Online ASEAN RE:CAP 2020]Các mẫu tích hợp ứng dụng cho microservice

Thảo luận về một số mẫu tích hợp ứng dụng cơ bản chủ yếu dựa trên nhắn tin và sẽ kết nối chúng với các trường hợp sử dụng trong thế giới thực trong kịch bản microservice.
2020.10.01

Ngày 29/09/2020 vừa rồi đã diễn ra sự kiện AWS Summit Online ASEAN RE:CAP. Link sự kiện: https://live.awsevents.com/ASEANSummitreCap/

Chủ để của session lần này là "Các mẫu kiến trúc: Xử lý luồng không cần máy chủ ở quy mô" Diễn giả: Anshul Sharma, Solutions Architect, Amazon Web Services

Khi bạn có một kiểu kiến trúc microservice, phần lớn giao tiếp giữa các thành phần được thực hiện qua mạng. Để đạt được hết khả năng của microservice, giao tiếp này phải diễn ra theo cách thức rời. Trong phần này, chúng ta sẽ thảo luận về một số mẫu tích hợp ứng dụng cơ bản chủ yếu dựa trên nhắn tin và sẽ kết nối chúng với các trường hợp sử dụng trong thế giới thực trong kịch bản microservice. Chúng tôi cũng nêu bật những lợi ích mà nhắn tin không đồng bộ có thể có trên API REST để liên lạc giữa các dịch vụ microservice.

1. Giới thiệu

Khi nói về kiến trúc phần mềm, có 2 khía cạnh rất quan trọng: thứ nhất là phần chia và chế ngự, thứ 2 là liên kết ít ràng buộc. Khi bạn làm việc với microservice, bạn đã áp dụng phân chia và chế ngự. Một cách tốt để áp dụng liên kế ít ràng buộc là sử dụng nhắn tin cận đồng bộ.

Là một kiến trúc sư giải pháp, khi chúng tôi nói chuyện với khách hàng và thảo luận về các kiểu giao tiếp giữa các microservices của họ, chúng tôi thường nghe thấy họ sử dụng REST API chứ không nhất thiết phải là nhắn tin cận đồng bộ. Chúng tôi khuyên khách hàng thực sự nên chú ý tới nhắn tin cận đồng bộ khi nói tới microservices vì nó có một số lợi thế về kiến trúc để giúp bạn mở rộng quy mô, phát triển và vận hành microservices của mình một cách độc lập.

Hãy bắt đầu với lý do tại sao chúng ta cần nhắn tin. Nếu ứng dụng của bạn là ứng dụng chuyên đám mây gốc, hoặc quy mô lớn hoặc phân tán và không có 1 thành phần nhắn tin thì đó khả năng là một lỗi thiết kế của ứng dụng. Có nhiều nhược điểm tiềm tàng của các hệ thống ứng dụng đồng bộ vì chúng vốn có quan hệ và phụ thuộc chặt chẽ lẫn nhau. Khi một trong những ứng dụng bị lỗi, tất cả ứng dụng khác sẽ bị ảnh hưởng.

2. Một số mẫu tích hợp ứng dụng dựa trên nhắn tin cơ bản

Về cơ bản có 2 cách để trao đổi tin nhắn với hệ thống: "trao đổi 1 chiều" và "trao đổi yêu cầu - đáp ứng"

  • Trao đổi 1 chiều: bạn có 1 người gửi, 1 người nhận và 1 tin nhắn ở giữa. Chúng ta có thể thấy ngay là các nhin nhắn sẽ phân cách người gửi và người nhận, chúng không cần biết về nhau. Người gửi không mong đợi bất kì phản hồi nào từ người nhận. Chúng ta có thể coi trao đổi 1 chiều là trao đổi đồng bộ. Chúng ta có thể coi trao đổi 1 chiều là "truyền đồng bộ" và "truyền và quên"
  • Trao đổi yêu cầu - đáp ứng: Chúng ta có kênh phản hồi bổ sung cho Responder. Điều này có thể đặt ra 2 câu hỏi cho bạn. Đầu tiên là làm thế nào Responder biết nơi gửi phản hồi. Chúng ta có thể áp dụng mô hình mẫu địa chỉ trả về là Requester sẽ đưa một phần thông tin từ điển vào tin nhắn. Và Responder có thể trích xuất và cung cấp thông tin đâu là kênh trả về. Câu hỏi thứ 2 bạn có thể đặt ra là làm thế nào để Requester biết trong trường hợp có nhiều tin nhắn phản hồi cho cùng một yêu cầu. Để trả lời cho câu hỏi này, chúng ta có mô hình mẫu định danh tương quan. Một lần nữa, nó lại là một phần trong thông tin từ điển mà Requester có thể đưa vào tin nhắn và Responder đưa thông tin định danh tương quan này vào tin nhắn phản hồi.

Các kênh tin nhắn:

  • Point-to-point (quece): các kênh point-to-point được triển khai dưới dạng hàng đợi. Ý nghĩa của hàng đợi là với mỗi tin nhắn gửi đến, nó chỉ được nhận bởi 1 người nhận. Bạn có thể có nhiều người nhận và hàng đợi sẽ tự động phân phối các tin nhắn cho các người nhận. Chúng có 2 ưu điểm rất lớn như sau: Thứ nhất là khả năng mở rộng quy mô người sử dụng, điều đó có nghĩa là nếu có nhiều lưu lượng tin nhắn trên hàng đợi và các tiến trình hiện tại không có khả năng xử lý, thì bạn có thể thêm nhiều tiến trình khác, và hàng đợi sẽ phân phối tải cho các tiến trình bổ sung. Theo cách đó, hàng đợi hoạt động như 1 bộ cân bằng tải. Ví các tin nhắn có thể đợi ở hàng đợi, bạn cũng có thể gọi nó là bộ đệm cân bằng tải. Thứ hai là hàng đợi có thể giúp bạn đáp ứng được khi hệ thống có tải cao. Vì nó lưu trữ các tin nhắn tạm thời, nên ngay cả khi bạn không có đủ bên nhận để xử lý ngay tất cả các tin nhắn, thì bạn vẫn có thể xử lý các tin nhắn theo tốc độ của riêng bạn. Thông thường bạn sẽ không thay đổi quy mô và tiết kiệm chi phí bằng cách không chạy quá nhiều tài nguyên tính toán ở phía người nhận.
  • Publish-subscribe (topic): các kênh được triển khai dưới dạng chủ đề. Mỗi tin nhắn được gửi tới người đăng kí. Người nhận được gọi là người đăng kí (Subscriber). Nếu có nhiều lưu lượng truy cập về chủ đề thì bạn không thể mở rộng thêm số lượng người đăng kí bởi vì bất kì người đăng kí nào cũng sẽ nhận được tất cả các tin nhắn. Cách làm đó không giúp bạn mở rộng được. Chúng ta có thể giải quyết vấn đề này bằng mô hình tổng hợp (giải thích phía dưới). Một điều cần lưu ý là các tin nhắn chủ đề không được duy trì theo mặc định, nhưng bạn có thể áp dụng mô hình "Durable subscriber" với chuỗi kết nối SQS, SNS để duy trì nó.
  • Chúng ta có dịch vụ Amazon SQS và Amazon SNS, cả 2 đều là dịch vụ đám mây gốc và điện toán không máy chủ. SQS dành cho point-to-point và SNS dành cho pub-sub. Bạn chỉ cần sử dụng các dịch vụ này thông qua các API đơn giản.

Một số mô hình tổng hợp được xây dựng với sự kết hợp của những mô hình cơ bản

  • Xâu chuỗi theo chủ đề (Topic-quece-chaining) là giải pháp cho vấn đề ở trên khi chúng ta muốn mở rộng quy mô người đăng kí. Chúng ta có một người xuất bản tin nhắn về chủ đề. Bây giờ, chúng ta không gắn người đăng kí trực tiếp vào chủ đề, mà chúng ta cho vào hàng đợi. Với điều đó, chúng ta có thể có được ưu điểm tốt nhất của cả 2 bên. Chúng ta có chức năng kiểu fan-out, và đồng thời chúng ta cũng có khả năng mở rộng người sử dụng.
  • Ngoài ra, vì hàng đợi vẫn lưu giữ đệm tin nhắn, nên nếu sự cố gây ngừng hoạt động trên một trong những người nhận, tin nhắn sẽ không bị mất.

  • Xử lý sự cố: Các hàng đợi thư chết (Dead letter quece) có thể giúp bạn kiểm tra và xử lý các lỗi khi nhắn tin. Hãy giả sử một kịch bàn: Tin nhắn được gửi đến hàng đợi và người gửi trả về một trường hợp ngoại lệ trong khi xử lý. Rồi tin nhắn được gửi trở lại hàng đợi. Sau đó tin nhắn được gửi tới người nhận một lần nữa, và trường hợp ngoại lệ lại xảy ra. Tin nhắn được gửi trở lại hàng đợi và cứ thế. Bây giờ, quá trình này bị kẹt trong 1 vòng lặp vô tận. Và chúng ta có hàng đợi thư chết, chúng ta có thể cấu hình trên kệnh nhắn tin của mình: Sau một số lần không gửi được mà bạn muốn, sẽ gửi tin nhắn đến hàng đợi thư chết. Bây giờ bạn có thể điều tra tin nhắn bị lỗi và đưa tin nhắn trở lại hàng đợi nếu cần. Các dịch vụ SQS, Lambda và SNS đều hỗ trợ hàng đợi thư chết.

Một điều cần lưu ý về hàng đợi tiêu chuẩn SQS là nó không đảm bảo thứ tự tin nhắn, mà nó cố gắng nhắn tin theo thứ tự tốt nhất có thể. Nhưng đôi khi, có những trường hợp bạn cần đảm bảo rằng thứ tự tin nhắn phải được giữ nguyên. Vì vậy chúng ta có loại hàng đợi FIFO (First In First Out) SQS

  • Trong đó thứ tự các tin nhắn sẽ được đảm bảo. Hàng đợi FIFO có lưu lượng ít hơn đáng kể so với hàng đợi tiêu chuẩn. Hàng đợi FIFO cũng có một khía cạnh khác là bạn không thể có nhiều hơn một người nhận khi cần đảm bảo thứ tự tin nhắn
  • Vậy chúng ta có thể làm gì với hàng đợi FIFO để thứ tự tin nhắn có khả năng mở rộng ở phía bên nhận? Chúng ta có thể sử dụng mô hình nhóm tin nhắn. Các tin nhắn thuộc cùng một nhóm luôn được xử lý từng cái một theo thứ tự nghiêm ngặt liên quan tới nhóm tin nhắn. Các tin nhắn thuộc các nhóm tin nhắn khác nhau có thể được xử lý không theo thứ tự

Bạn cũng cần hiểu chất lượng dịch vụ QoS trước khi xây dựng kiến trúc ứng dụng của bạn:

  • QoS là một thoả thuận giữa người gửi và người nhận bảo đảm sự chuyển giao của một tin nhắn cụ thể.
  • Có 3 loại QoS: Chuyển giao tối thiểu 1 lần, Nhiều nhất 1 lần và Chính xác 1 lần
  • Có một vài thách thức khi nói tới chuyển giao Chính xác 1 lần. Thông thường, nếu bạn muốn xử lý tin nhắn ở quy mô lớn, thì chỉ có thể đạt được bằng cách chuyển giao Tối thiểu 1 lần. Do đó, bạn nên thiết kế hệ thống của mình để xử lý các tin nhắn trùng lặp và xử lý theo cách không làm thay đổi giá trị

3. Một số mô hình mẫu khác

Hãy cùng xem các mô hình mẫu hữu ích khác: Bộ lọc tin nhắn (Message filter)

  • Chúng ta đã nói về mẫu tin nhắn chủ đề, mỗi người đăng kí sẽ nhận được tất cả tin nhắn, nhưng có thể có trường hợp người đăng kí chỉ quan tâm tới một phần nhất định. Mỗi người đăng kí có thể chủ động nói về chủ đề như tôi chỉ quan tâm tới tin nhắn màu xanh hoặc vàng. Ở đây, người xuất bản vẫn hoàn toàn không biết về người đăng kí.
  • Có một cách khác để thực hiện điều này mà không cần nhắn tin đó là mô hình mẫu danh sách người nhận. Danh sách người nhận là một mã bổ sung để biết người đăng kí nào quan tâm đến tin nhắn nào

Mô hình điều phối Saga (Saga Orchestration)

  • Điều phối Saga là chuỗi các giao dịch cục bộ. Mỗi một dịch vụ trong 1 Saga thực hiện một giao dịch riêng của mình và xuất bản một sự kiện. Các dịch vụ khác sẽ lắng nghe sự kiện đó và thực hiện giao dịch cục bộ tiếp theo mà không cần kết nối chặt chẽ. Tất cả sẽ được điều phối theo sự kiện.
  • Chúng ta có Event source và Result target. Chúng ta cần thực hiện nhiều biến đổi ở giữa. Tại trung tâm của mô hình này là 1 thành phần được gọi là bộ điều phối (Orchestrator). Chúng ta đưa thông tin về các bước xử lý vào bộ điều phối. Bộ điều phối sẽ giúp bạn làm tất cả công việc khó khăn để tạo ra 1 quy trình công việc.  thành phần tham gia sẽ duy trì kết nối ít ràng buộc nhất có thể
  • Bộ điều phối sẽ cho bạn thực hiện các quy trình chuyển đổi truyền thống như: phân nhánh, thử lại các tình huống và xử lý song song. Nếu bạn muốn sử dụng mô hình mẫu này, bạn nên xem dịch vụ AWS Step Functions (1 dịch vụ không máy chủ và đám mây gốc)

4. Use case

Chúng ta sẽ thiết kế hệ thống cho một công ty start up giả tưởng Wild Rydes, Inc. Wild Rydes đang thay đổi việc vận tải bằng cách thay thế taxi truyền thống bằng các con kỳ lân. Nó là 1 ứng dụng di chuyển bằng kỳ lân. Wild Rydes có kiến trúc Microservices và một số chức năng thực tế thú vị

Chức năng gửi thông tin hoàn thành chuyến đi

Hãy tưởng tượng bạn đã đặt 1 chuyến đi kỳ lân và trở về khách sạn của bạn. Sau khi đến đích, kỳ lân muốn gửi thông tin hoàn thành chuyến đi này đến các hệ thống của Wild Rydes và có thể thu tiền bạn bằng một số bitcoin. Nó sử dụng ứng dụng Wild Rydes unicorn. Ứng dụng giao tiếp với backend thông qua các REST API. Các dịch vụ quản lý kỳ lân nhận được yêu cầu này. Điều đầu tiên nó làm là lưu trữ file load này trong cơ sở dữ liệu Rides store. Nó có thể trả lời ứng dụng cho biết yêu cầu đã được nhận

Bây giờ có một số dịch vụ Microservices khác có thể quan tâm đến sự kiện hoàn thành 1 chuyến đi ví dụ như dịch vụ kế toán để thanh toán. Dịch vụ khách hàng thân thiết để tặng khách hàng điểm thưởng. Hoặc dịch vụ cung cấp dữ liệu để phân tích.  Chúng ta cũng có 1 dịch vụ gọi là dịch vụ cưỡi kỳ lân đặc biệt cho những người chỉ quan tâm đến chuyến đi vượt quá một ngưỡng giá vé hoặc khoảng cách nhất định.

Bây giờ hãy nghĩ cách chúng ta tích hợp tất cả dịch vụ này. Bạn có thể thắc mắc rằng chúng ta đã lưu trữ dữ liệu trong cơ sở dữ liệu, thì tại sao chúng ta lại không mang dữ liệu Rides dùng cho tất cả các dịch vụ khác. Đó là vì nó có thể hoạt động nhưng có thể không phải là thiết kế ứng dụng tốt nhất.

Chúng ta sẽ kết hợp chặt chẽ tất cả các dịch vụ Microservices của chúng ta ở phía bên phải lược đồ. Hãy tưởng tượng bất kì thay đổi nào trong cơ sở dữ liệu sẽ ảnh hưởng trực tiếp đến các dịch vụ khác thì chúng ta chắc chắn sẽ không muốn làm điều này. Một cách khác để làm điều đó là có thể tích hợp tất cả thông qua các REST API. Tuy nhiên sẽ đi kèm với chi phí. Bây giờ chúng ta có các REST API trên tất cả các dịch vụ này. Vì vậy dịch vụ quản lý kỳ lân bằng cách nào đó cần gửi yêu cầu HTTP đến chúng. Nhưng để làm điều này, nó phải biết gửi chúng đi đâu để chúng ta có được mô hình mẫu danh sách người nhận. Chúng ta có thể định nghĩa nó trong 1 dịch vụ phân phối yêu cầu. Điều đó sẽ thêm mã chương trình và thêm cơ sở hạ tầng. Chúng ta cũng sẽ triển khai bộ lọc cho dịch vụ đi xe đặc biệt. Điều này có thể dễ dàng đưa vào 1 loạt các mệnh đề nếu-thì trong dịch vụ phân phối của bạn. Và đó là cái chúng ta có thể tránh.

Cách tốt nhất để thực hiện yêu cầu này là Nhắn tin. Chúng ta chỉ đơn giản đọc tin nhắn trong chủ đề SNS hoàn thành chuyến đi, và tất cả các microservices quan tâm có thể đăng kí nhận nó. Đây là kịch bản xuất bản - đăng kí cổ điển. Chúng ta cũng có thể sử dụng chức năng lọc tin nhắn SNS, dịch vụ đi xe đặc biệt có thể đăng kí với chính sách bộ lọc mong muốn. Và nó sẽ chỉ nhận được các tin nhắn muốn nhận.

Nếu chúng ta cũng muốn khả năng phục hồi khi các dịch vụ microservices ngừng hoạt động, chúng ta có thể đặt hàng đợi ở giữa và thực hiện chuỗi hàng đợi theo chủ đề. Điều này sẽ đảm bảo không mất tin nhắn, và cũng đem lại khả năng mở rộng cho microservices. Đây là một kiến trúc tốt cho hệ thống này của chúng ta

Chức năng chiến dịch đặt chỗ trước (Prebooking Campaigns)

Bây giờ Wild Rydes đang chạy một chiến dịch tiếp thị giảm giá cho các chuyến đi đặt trước cho tuần tới. Khách hàng có thể gửi yêu cầu đặt trước thông qua Wild Rydes app. Yêu cầu được nhận bởi dịch vụ đặt chuyến tại hệ thống back-end. Dịch vụ vẫn duy trì dữ liệu trong cơ sở dữ liệu đặt trước chuyến đi và trả lời lại khách hàng.

Chiến dịch này đặc biệt thành công. Chúng ta đang chứng kiến sự gia tăng của các yêu cầu đặt trước lên đến hàng triệu yêu cầu đẫn tới thêm tải cho dịch vụ đặt chỗ. Chúng ta có thể làm gì để xử lý sự đột biến này mà không mất bất kỳ một đơn đặt hàng nào?

Một phương án là đặt hàng đợi SQS trước dịch vụ đặt trước và để ứng dụng di động của bạn nói chuyện với nó. Chúng ta có thể gửi phản hồi ứng dụng di động ngay khi tin nhắn đến hàng đợi. Hãy nhớ rằng điều này rất quan trọng đối với trải nghiệm khách hàng khi chúng ta chấp nhận yêu cầu đặt trước của họ. Nhưng nó chỉ thực sự được chấp nhận khi chúng ta xử lý nó vài giây sau đó. Hàng đợi SQS sẽ dàn trải mức tải tối đa của dịch vụ đặt trước chuyến đi và bạn có thể xử lý nó sau đó.

Bây giờ chúng ta cần thực sử xử lý yêu cầu tài và chuyển tiếp tới chủ đề SNS. Chủ đề sau đó có thể được kết nối tới nhiều hàng đợi, thông qua đó, tin nhắn sẽ được sử dụng bởi dịch vụ cụ thể để xử lý thêm. Sau khi đặt chuyến được xử lý, chúng ta có thể gửi thông báo cho khách hàng rằng chuyến đi đã được xác nhận. Kiến trúc tách rời này sẽ cung cấp trải nghiệm tốt cho khách hàng bằng cách mở rộng khi tải cao điểm.

Chức năng thu tiền

Chúng ta sẽ xem xét sâu hơn về dịch vụ kế toán. Dịch vụ này đảm bảo chúng ta sẽ tính phí cho khách hàng sau chuyến đi. Thanh toán thường là một quy trình gồm nhiều bước. Và nó cũng liên quan đến việc duy trì trạng thái qua các bước này. Dó đó chúng ta sẽ sử dụng điều phối Saga ở đây, và sử dụng AWS Step Functions.

Bước đầu tiên là tiền-xác-thực thẻ tín dụng. Bên tay phải bạn sẽ thấy 1 phần của quy trình với AWS Step Functions. Nhánh này thông báo thất bại trong trường hợp tiền-xác-thực thất bại. Nếu tiền-xác-thực thành công, chúng ta sẽ tính phí thẻ bằng mã tiền xác thực. Nếu thanh toán thành công, chúng ta sẽ gọi dịch vụ tài khoản khách hàng để cập nhật thông tin thanh toán và thông báo thành công cho khách hàng.

Bằng cách sử dụng 1 máy trạng thái và cũng có các nhánh cho các trường hợp nắng và mưa, chúng ta có thể coi chuỗi 3 bước riêng biệt này như một giao dịch phân tán, và đảm bảo rằng hệ thống vẫn ở trạng thái nhất quán. Mô hình này rất mạnh khi trạng thái nhất quán được đảm bảo

Session kết thúc tại đây. Nếu bạn thấy thú vị và muốn tìm hiểu thêm.. Hay truy cập đường link của sự kiện và tìm video của session "Các mẫu tích hợp ứng dụng cho microservice"

Cảm ơn các bạn đã theo dõi blog. Xin chào và hẹn gặp lại!