Microservices: Từ Thiết Kế Đến Triển Khai – Phần 2: API Gateway

Trong bài đầu tiên trong loạt bài bảy phần này về Microservices: Từ Thiết Kế Đến Triển Khai đã giới thiệu về Microservices architecture. Chúng ta đã thảo luận về những lợi ích và hạn chế của việc sử dụng microservices. Microservices tuy phức tạp, nhưng thường là lựa chọn lý tưởng cho các ứng dụng lớn, phức tạp và đòi hỏi sự đáp ứng nhanh. Đây là bài viết thứ hai trong loạt bài này và sẽ thảo luận về việc xây dựng microservices bằng cách sử dụng API Gateway.

(Các bạn có thể đọc bài viết gốc tiếng Anh tại đây: https://www.nginx.com/blog/building-microservices-using-an-api-gateway/)

Khi xây dựng ứng dụng của mình bằng một tập hợp các microservices, bạn cần quyết định cách các application clients sẽ tương tác với microservices như thế nào. Với một ứng dụng nguyên khối, chỉ có một tập hợp các endpoints (có thể được nhân rộng (replicated) và cân bằng tải). Tuy nhiên, trong kiến ​​trúc microservices, mỗi microservice sẽ có tập các endpoints nhỏ của riêng nó. Trong bài này, chúng ta sẽ cùng xem xét cách sử dụng API Gateway để giúp các application client giao tiếp với microservices như thế nào.

Giới thiệu

Hãy tưởng tượng rằng bạn đang phát triển một ứng dụng native mobile cho một ứng dụng shopping. Bạn cần làm trang chi tiết sản phẩm hiển thị thông tin về bất kỳ sản phẩm cụ thể nào.

Ví dụ, diagram sau đây cho thấy những gì bạn sẽ thấy trên trang chi tiết sản phẩm trong ứng dụng di động của Amazon.

1

Mặc dù đây là một ứng dụng mobile, trang chi tiết sản phẩm hiển thị rất nhiều thông tin. Ví dụ: không chỉ có thông tin sản phẩm cơ bản (chẳng hạn như tên, mô tả và giá) nhưng trang này cũng hiển thị:

  • Số lượng mặt hàng trong giỏ hàng
  • Lịch sử đơn hàng
  • Phản hồi khách hàng
  • Cảnh báo hàng tồn kho thấp
  • Tùy chọn giao hàng
  • Các đề xuất khác nhau, bao gồm các sản phẩm khác mà sản phẩm này thường xuyên mua, các sản phẩm khác được khách hàng mua sản phẩm này mua và các sản phẩm khác được khách hàng mua sản phẩm này đã xem
  • Tùy chọn mua thay thế

Khi sử dụng kiến ​​trúc nguyên khối, một ứng dụng mobile sẽ lấy ra dữ liệu bằng cách thực hiện một REST request (GET api.company.com/productdetails/productId) tới ứng dụng. Load balancer sẽ định tuyến request đến một trong N instances giống hệt nhau. Sau đó, instance này sẽ truy vấn các bảng cơ sở dữ liệu khác nhau và response kết quả về cho client.

Ngược lại, khi sử dụng kiến ​​trúc microservices, dữ liệu được hiển thị trên trang chi tiết sản phẩm được thực hiện bởi nhiều microservices. Ví dụ dưới đây là một số dịch vụ microservices có dữ liệu được hiển thị trên trang chi tiết sản phẩm:

  • Dịch vụ giỏ hàng (card) – Số lượng mặt hàng trong giỏ hàng
  • Dịch vụ đặt hàng (order) – Lịch sử đặt hàng
  • Dịch vụ danh mục (category) – Thông tin sản phẩm cơ bản, chẳng hạn như tên, hình ảnh và giá của nó
  • Dịch vụ đánh giá (rating) – Nhận xét của khách hàng
  • Dịch vụ kho hàng (inventory) – Cảnh báo hàng tồn kho thấp
  • Dịch vụ giao hàng (shipping) – Tùy chọn giao hàng, thời hạn và chi phí đổi trả riêng biệt với API của nhà cung cấp giao hàng
  • Dịch vụ đề xuất (recommendation) – Các mục được đề xuất

2

Chúng ta cần quyết định cách ứng dụng mobile client truy cập đến các service này. Hãy xem xét các tùy chọn sau đây.

Client giao tiếp trực tiếp với Microservice

Về lý thuyết, một client có thể request trực tiếp đến từng microservice. Mỗi microservice sẽ có một public endpoint (https://serviceName.api.company.name). URL này sẽ ánh xạ tới load balancer của microservice, phân phối các request trên các instances có sẵn. Để truy xuất chi tiết sản phẩm, ứng dụng mobile client sẽ gửi request đến từng dịch vụ được liệt kê ở trên.

Tuy nhiên điều này mang đến những thách thức và hạn chế. Một là sự không phù hợp giữa nhu cầu của client và các API chi tiết được cung cấp bởi từng microservice. Client trong ví dụ này phải thực hiện 7 request riêng biệt. Trong các ứng dụng phức tạp hơn, nó có thể phải làm nhiều hơn nữa. Ví dụ: Amazon mô tả có hàng trăm dịch vụ liên quan đến việc hiển thị trang sản phẩm của họ. Một client có thể thực hiện nhiều request qua mạng LAN, tuy nhiên sẽ không hiệu quả trên Internet và chắc chắn sẽ không khả thi trên mạng di động. Cách tiếp cận này cũng làm cho mã nguồn client phức tạp hơn nhiều.

Một vấn đề khác với việc client trực tiếp gọi microservices là một số microservice có thể sử dụng các giao thức không thân thiện với web. Một service có thể sử dụng RPC trong khi một dịch vụ khác có thể sử dụng giao thức AMQP, các protocol đó chỉ phù hợp với các giao tiếp nội bộ. Một ứng dụng nên sử dụng các giao thức như HTTP và WebSocket bên ngoài tường lửa.

Một hạn chế khác với phương pháp này là sẽ gây khó khăn trong việc tái cấu trúc microservices. Theo thời gian, chúng ta có thể muốn thay đổi cách hệ thống để phân vùng thành các service. Ví dụ: có thể hợp nhất hai dịch vụ hoặc chia dịch vụ thành hai hoặc nhiều dịch vụ. Tuy nhiên, nếu client giao tiếp trực tiếp với các dịch vụ, thì việc thực hiện tái cấu trúc này có thể cực kỳ khó khăn.

Vì vấn đề trên nên hiếm khi chúng ta để cho client giao tiếp trực tiếp với các microservices.

Sử dụng API Gateway

Cách tiếp cận tốt hơn là sử dụng API Gateway. API Gateway là một máy chủ và là điểm đầu vào duy nhất của hệ thống. Tương tự như Facade pattern trong object oriented design. API Gateway đóng gói hệ thống nội bộ và cung cấp các API được thiết kế riêng cho từng client. Nó có thể có trách nhiệm khác như xác thực (authentication), giám sát (monitoring), cân bằng tải (load balancing), bộ nhớ đệm (caching), chuyển đổi request (request shaping and management) và xử lý phản hồi tĩnh (static response handling).

Diagram sau đây minh họa về cách áp dụng API Gateway trong hệ thống:

3

API Gateway chịu trách nhiệm định tuyến các request, tổng hợp và chuyển đổi giao thức. Tất cả các request từ client sẽ đi qua API Gateway trước tiên. Sau đó nó định tuyến các request tới các microservice thích hợp. API Gateway thường xử lý request bằng cách gọi nhiều microservices và tổng hợp kết quả. Nó có thể chuyển đổi giữa các giao thức web như HTTP và WebSocket và cả các giao thức không thân thiện với web.

API Gateway cũng có thể cung cấp cho mỗi client các API tùy chỉnh. Nó thường cho thấy một API tổng hợp (coarse grained) cho các ứng dụng mobile. Hãy xem xét, ví dụ chi tiết sản phẩm. API Gateway có thể cung cấp endpoint (/productdetails?productid=xxx) cho phép ứng dụng mobile client truy xuất tất cả chi tiết sản phẩm bằng một request duy nhất. API Gateway xử lý request bằng cách gọi các dịch vụ khác nhau – thông tin sản phẩm, đề xuất, đánh giá, v.v. – và kết hợp các kết quả.

Một ví dụ tuyệt vời về API Gateway là API Gateway Netflix. Các dịch vụ trực tuyến Netflix có trên hàng trăm loại thiết bị khác nhau bao gồm TV, set-top box, điện thoại, hệ thống chơi game, máy tính bảng v.v. Ban đầu, Netflix đã cố gắng để cung cấp một one-size-fits-all API cho các dịch vụ trực tuyến của họ. Tuy nhiên, họ phát hiện ra rằng nó không hoạt động tốt vì sự đa dạng của các thiết bị và nhu cầu riêng của họ. Ngày nay, họ sử dụng API Gateway cung cấp API được điều chỉnh cho từng thiết bị bằng cách chạy các adapter code cụ thể cho thiết bị. Adapter thường xử lý từng request bằng cách gọi trung bình từ 6 đến 7 dịch vụ khác.

Lợi ích và Hạn chế của API Gateway

Việc sử dụng API Gateway có cả lợi ích và nhược điểm. Lợi ích chính của việc sử dụng API Gateway là đóng gói cấu trúc bên trong của ứng dụng. Thay vì phải gọi các dịch vụ cụ thể, client chỉ cần nói chuyện với Gateway. API Gateway cung cấp cho từng loại client với API cụ thể. Điều này làm giảm số lượng request/response giữa client và hệ thống. Nó cũng giúp đơn giản hóa client code.

API Gateway cũng có một số nhược điểm. Đó là cần phải develope, deploye, and manage một component khác có độ sẵn sàng cao (highly available) đảm nhận vai trò API Gateway. Cũng có nguy cơ là API Gateway trở thành nút thắt cổ chai (bottleneck). Developer phải cập nhật API Gateway để làm việc với các endpoints của microservice khi có thay đổi. Điều quan trọng là phải làm cho quá trình cập nhật API Gateway càng nhẹ càng tốt. Nếu không, các developer sẽ bị buộc phải xếp hàng để cập nhật API Gateway. Tuy nhiên, mặc dù có những hạn chế này, đối với hầu hết các ứng dụng thực tế, việc sử dụng API Gateway là việc rất hợp lý và cần thiết.

Implement API Gateway

Bây giờ chúng ta đã xem xét một số khía cạnh trong việc triển khai và sử dụng API Gateway, trước tiên hãy xem xét các design issues dưới đây:

Hiệu suất và khả năng mở rộng

Chỉ một số ít các công ty hoạt động ở quy mô của Netflix và cần xử lý hàng tỷ request mỗi ngày. Tuy nhiên, đối với hầu hết các ứng dụng, hiệu năng và khả năng mở rộng của API Gateway cũng thường rất quan trọng. Do đó, để xây dựng API Gateway trên một nền tảng hỗ trợ I/O bất đồng bộ, nonblocking có nhiều công nghệ khác nhau. Trên JVM, bạn có thể sử dụng một trong các frameworks NIO-based như Netty, Vertx, Spring Reactor hoặc JBoss Undertow. Một tùy chọn non-JVM phổ biến là Node.js, một nền tảng được xây dựng trên JavaScript engine của Chrome. Một lựa chọn khác là sử dụng NGINX Plus.

Sử dụng Reactive Programming Model

API Gateway xử lý một số request bằng cách định tuyến chúng tới các dịch vụ thích hợp. Nó xử lý các request bằng cách gọi nhiều dịch vụ và tổng hợp các kết quả. Để giảm thiểu thời gian phản hồi, API Gateway nên thực hiện đồng thời các request độc lập. Tuy nhiên, đôi khi có sự phụ thuộc giữa các request. API Gateway trước tiên có thể cần phải xác nhận request bằng cách gọi một authentication service trước khi định tuyến request đến một dịch vụ cụ thể. Tương tự, để fetch thông tin về các sản phẩm trong wish list của khách hàng, trước tiên API Gateway phải truy xuất hồ sơ của khách hàng chứa thông tin đó và sau đó lấy thông tin cho mỗi sản phẩm.

Viết API Gateway bằng cách sử dụng phương pháp asynchronous callback sẽ gây ra callback hell. Code sẽ bị rối, khó hiểu và dễ bị lỗi. Cách tiếp cận tốt hơn là viết API Gateway theo kiểu khai báo bằng cách sử dụng phương thức phản ứng (reactive). Ví dụ về reactive bao gồm Future trong Scala, CompletableFuture trong Java 8 và Promise trong JavaScript. Ngoài ra còn có Reactive Extensions (còn được gọi là Rx hoặc ReactiveX) ban đầu được phát triển bởi Microsoft cho nền tảng .NET. Netflix đã tạo RxJava cho JVM để sử dụng trong API Gateway của họ. Ngoài ra còn có RxJS cho JavaScript, chạy trong cả trình duyệt và Node.js. Cách tiếp cận Reactive sẽ cho phép viết mã API Gateway đơn giản nhưng hiệu quả hơn.

Service Invocation

Một ứng dụng dựa trên microservices là một hệ thống phân tán và phải sử dụng cơ chế inter-process communication. Có hai kiểu giao tiếp giữa các process. Một là sử dụng cơ chế dựa trên asynchronous messaging-based sử dụng các message broker như JMS hoặc AMQP. Một số khác, chẳng hạn như Zeromq, không dùng broker mà các services giao tiếp trực tiếp với nhau. Một cách giao tiếp giữa các process khác là cơ chế synchronous như HTTP hoặc Thrift. Một hệ thống thường sẽ sử dụng cả kiểu asynchronous và synchronous, do đó API Gateway sẽ cần hỗ trợ nhiều cơ chế giao tiếp khác nhau.

Service Discovery

API Gateway cần biết vị trí (IP address và port) của từng service mà nó giao tiếp. Trong một ứng dụng truyền thống, các service được cố định vị trí, nhưng trong một ứng dụng microservices hiện đại, dựa trên cloud, đây là một vấn đề nan giải. Các dịch vụ cơ sở hạ tầng, chẳng hạn như message broker, thường sẽ có một vị trí tĩnh, có thể được xác định thông qua OS environment variables. Tuy nhiên, việc xác định vị trí của một Application services không phải là dễ dàng như vậy. Application services có vị trí được gán động. Ngoài ra, tập hợp các instance của service sẽ thay đổi tự động dựa trên sự tự mở rộng (autoscaling) và nâng cấp (upgrade). Do đó, API Gateway, giống như bất kỳ ứng dụng client nào khác trong hệ thống, cần phải sử dụng cơ chế Service Discovery: Server-Side Discovery hoặc Client-Side Discovery. Bài viết sau sẽ nói về Service Discovery chi tiết hơn. Bây giờ, cần lưu ý rằng nếu hệ thống sử dụng Client-Side Discovery thì API Gateway phải có khả năng truy vấn đến Service Registry, là cơ sở dữ liệu của tất cả các microservice instances và vị trí của chúng.

Xử lý lỗi giữa chừng (Partial Failures)

Một vấn đề khác mà chúng ta phải giải quyết khi triển khai API Gateway là vấn đề Partial Failures. Vấn đề này phát sinh trong tất cả các hệ thống phân tán bất cứ khi nào một dịch vụ gọi một dịch vụ khác, phản hồi chậm hoặc không có sẵn. API Gateway sẽ không bao giờ đứng chờ một dịch vụ vô thời hạn. Tuy nhiên, cách xử lý sự cố phụ thuộc vào kịch bản cụ thể và dịch vụ nào bị lỗi. Ví dụ: nếu recommendation service không phản hồi trong trường hợp lấy về product details, API Gateway sẽ trả lại phần còn lại của product details cho client vì chúng vẫn hữu ích cho client. Phần recommendation có thể trống hoặc được thay thế bằng ví dụ như: danh sách 10 mặt hàng đầu được ưu tiên. Tuy nhiên, nếu product information service không phản hồi thì API Gateway nên trả về lỗi cho client.

API Gateway cũng có thể trả về dữ liệu đã lưu trong bộ nhớ cache nếu có sẵn. Ví dụ, vì giá sản phẩm thay đổi không thường xuyên, API Gateway có thể trả về dữ liệu định giá được lưu trong bộ nhớ cache nếu dịch vụ định giá không khả dụng. Dữ liệu có thể được lưu trữ bởi chính API Gateway hoặc được lưu trữ trong bộ nhớ đệm bên ngoài như Redis hoặc Memcached. Bằng cách trả về dữ liệu mặc định hoặc dữ liệu được lưu trong bộ nhớ cache, API Gateway đảm bảo rằng các lỗi hệ thống không ảnh hưởng đến trải nghiệm người dùng.

Netflix Hystrix là một thư viện cực kỳ hữu ích để viết mã gọi các remote services. Hystrix tính thời gian gọi vượt quá ngưỡng quy định. Nó dùng circuit breaker pattern để client không phải chờ một cách không cần thiết khi một dịch vụ không phản hồi. Nếu tỷ lệ lỗi cho một dịch vụ vượt quá ngưỡng được chỉ định, Hystrix sẽ ngắt mạch và tất cả các request tới dịch vụ này thất bại ngay lập tức trong một khoảng thời gian nhất định. Hystrix cho phép xác định hành động dự phòng khi request không thành công, chẳng hạn như đọc từ bộ nhớ cache hoặc trả lại giá trị mặc định. Nếu đang sử dụng JVM, bạn chắc chắn nên xem xét sử dụng Hystrix. Và, nếu đang chạy trong một môi trường không JVM, bạn nên sử dụng một thư viện tương đương.

Tóm tắt

Đối với hầu hết các ứng dụng dựa trên microservices, có lý do consider đến API Gateway, hoạt động như một điểm đầu vào duy nhất của hệ thống. API Gateway chịu trách nhiệm định tuyến các request, tổng hợp và chuyển đổi giao thức. Nó cung cấp cho mỗi client một API tùy chỉnh. API Gateway cũng có các bạn pháp xử lý lỗi như thay thế các lỗi trong các service bằng cách trả về dữ liệu cache hoặc mặc định. Trong phần tiếp theo của loạt bài này, chúng ta sẽ xem xét về cách giao tiếp giữa các dịch vụ.

Bạn cũng có thể tải xuống bộ bài viết hoàn chỉnh, cùng với thông tin về việc triển khai microservices sử dụng NGINX Plus, dưới dạng ebook – Microservices: From Design to Deployment.

Read more:
https://www.nginx.com/blog/building-microservices-using-an-api-gateway/

Đây là loạt bài viết về Microservices mình đã đọc, đúc kết và sưu tầm được trong quá trình tìm hiểu về nó. Hi vọng nó cũng giúp ích được cho bạn trong quá trình architecture design và deploy các ứng dụng microservices.

Tổng hợp và dịch by edwardthienhoang