11 thành phần quan trọng trong kiến trúc Microservices
Hello các anh em, lại là Ethanol Trần đến từ Sunteco Tech Team đây 😆😆😆
Nhân dịp năm mới Giáp Thìn 2024, chúc anh em luôn ít bug, khỏe mạnh và cống hiến nhiều hơn nữa nhé! Nối tiếp chuỗi bài về Microservices, tôi xin gửi đến anh em bài viết liên quan đến các thành phần quan trọng của Microservices!
Với kiến trúc microservices, chúng ta sẽ triển khai một hệ thống phân tán (distributed system) bao gồm rất nhiều thành phần kĩ thuật xoay quanh các service về business chính. Hệ thống phân tán sẽ có lợi thế về tính linh hoạt giữa các dịch vụ nhưng sẽ gặp khó khăn khi các phân phối thay đổi. Bài viết này sẽ đưa ra các thành phần quan trọng của kiến trúc microservice và tầm ảnh hưởng của chúng trong xây dựng và triển khai hệ thống.
Containers và Orchestration
Container là thành phần compute chính trong kiến trúc microservices, có chức năng chạy các ứng dụng tương tự như Máy chủ ảo (VM) ở các kiến trúc cũ. Sự khác biệt cốt lõi giữa container và VM nằm ở công nghệ ảo hóa: Containerization và Virtualization. Tuy nói microservices là kiến trúc của ứng dụng, không phân biệt chạy trên Container hay VM, nhưng do giới hạn về khả năng provisioning, scaling và HA nên VM rất ít tương thích với các concept của microservices.
Container được thiết kế chủ yếu để cô lập các tiến trình hoặc các ứng dụng với nhau và với hệ điều hành. Cài đặt và chạy một ứng dụng trên container rất đơn giản, nhưng sẽ thế nào nếu cần cài đặt những container này cho một hệ thống, lúc này số container sẽ lên tới hàng chục cho tới hàng trăm và hơn nữa. Đi kèm với hệ thống là các cài đặt về mạng, lưu trữ cho database, lưu trữ các biến môi trường, config của ứng dụng, các nhu cầu về auto scaling, khôi phục tự động… Lúc này sẽ cần một “nhạc trưởng” để quản lý và điều phối (orchestrated) hoạt động của các container và hệ sinh thái đi kèm. Một số container orchestration có thể kể đến như K8S, Google container engine, Amazon ECS.
API gateway
API gateway là thành phần thiết yếu của kiến trúc microservices. Do trong một hệ thống phân tán có rất nhiều service, hệ thống cần giới hạn lại các entry point để có thể quản lý được các request ra vào hệ thống, đây chính là tác dụng quan trọng nhất của API gateway. Hệ thống microservices thường được tổ chức theo cách chỉ có API gateway được facing ra ngoài internet, còn các dịch vụ bên trong sẽ giao tiếp với nhau qua internal endpoint hoặc messaging system.
Với vai trò như cổng trung gian giữa hệ thống, network và system khác, API gateway có thể đảm nhiệm các chức năng:
- Routing: API gateway hoạt động bằng cách cấu hình mapping giữa địa chỉ của dịch vụ với một pattern URL của request gọi đến. Lúc này, các hệ thống bên ngoài sẽ chỉ nhìn thấy một endpoint duy nhất của API gateway. Gateway sẽ thực hiện forward request và response của request vào và ra khỏi hệ thống, tới các dịch vụ. Do đó nó cũng có tác dụng che giấu kiến trúc các thành phần bên trong.
- Quản lý truy cập tập trung: Hệ thống luôn có nhu cầu thực hiện authentication, auditing, logging…tập trung với các request vào hệ thống. API gateway trở thành bộ phận hoàn hảo để đặt các tính năng này.
- Quản lý quota truy cập: Tính năng giới hạn tần suất của request đến hệ thống, các yếu tố detect có thể là URL, IP, authentication factor, session… giúp ngăn chặn DDOS hệ thống hoặc crawl data.
- Handle request/response: API gateway có thể được sử dụng để detect các metadata của request hoặc status của response để thực hiện các công việc như custom response hoặc redirect.
- Caching: Một vài API thường rất ít khi thay đổi dữ liệu trả về, việc cache lại ở Gateway và response ngay lập tức mà ko phải request vào nguồn dữ liệu sẽ giảm tải cho hệ thống.
- Load balancing: Chia tải (request) tới các địa chỉ khác nhau để giảm tải cho một instance.
- API health monitoring: Nhóm chức năng này giúp gateway có thể detect được trạng thái available của dịch vụ, từ đó có thể chuyển hướng request tới các dịch vụ hoặc thực hiện các kịch bản dự phòng.
Một số giải pháp gateway: NGINX, Spring gateway, Kong gateway
Service Discovery
Service Discovery giúp quản lý các deployment và phân bố tải giữa các dịch vụ trong quá trình vận hành hệ thống. Trên thực tế, các instance của các dịch vụ được triển khai trên khắp các node của hệ thống, các IP/host của instance có thể bị thay đổi trong khi runtime, start một instance mới hay thay đổi instance khi tăng số instance, service request sẽ không biết được địa chỉ cụ thể của các instance để request đến. Service discovery sẽ giúp giải quyết vấn đề này, nó đóng vai trò như một nhà ga trung tâm, luôn quản lý danh sách các instance đang active. Service Discovery hoạt động với 3 thành phần chính:
- Service provider: Service cung cấp dịch vụ
- Service consumer: Ứng dụng/ dịch vụ đang yêu cầu cung cấp địa chỉ của một service để tiến hành request.
- Service registry: Tồn tại như một database giúp lưu trữ địa chỉ các instance đang available của dịch vụ.
Các hoạt động của Service Discovery:
- Service provider khi được sinh ra sẽ đăng kí dịch vụ với service registry location/address của instance
- Service consumer sẽ request đến Server registry để lấy địa chỉ của dịch vụ thông qua tên dịch vụ\
- Service registry trả về địa chỉ của Service provider đang active
- Service consumer sẽ request đến Service provider theo địa chỉ nhận được
Service discovery bao gồm hai pattern chính là server-side và client-side. Client-site hoạt động tương tự cách mô tả ở trên, còn server-side sẽ hoạt động hơi khác một chút, sau bước 2, sau khi đã định vị được địa chỉ của service provider, router sẽ forward request tới service tương ứng.
Với một số giải pháp microservices tự xây dựng, người dùng sẽ cần cài đặt riêng thành phần service discorvey. Ngoài ra, nếu sử dụng các giải pháp container orchestration thì người dùng không cần phải quan tâm đến thành phần này, hầu hết đã được build in trong giải pháp, mà chỉ cần quan tâm đến các container phục vụ business.
Service Mesh
Thuật ngữ Service mesh xuất hiện trong bối cảnh cùng với cloud application, containers và microservices. Trong hệ thống microservices, khi có nhiều dịch vụ và instance runtime được sinh ra, việc kiểm soát communication giữa các dịch vụ trở nên rất khó khăn, nếu không có giải pháp hay công cụ hỗ trợ, nhà phát triển hoàn toàn phải kiểm soát communication bằng cách thủ công bắt đầu từ việc liệt kê connection giữa các dịch vụ. Các nhu cầu bao gồm kiểm soát connection giữa các dịch vụ, lượng traffic phát sinh, metric và monitoring… Service mesh sẽ giải quyết vấn đề này, nhà phát triển chỉ cần tập trung vào các vấn đề của business. Layer quản lý connection trong mạng lưới dịch vụ phân tán sẽ trở nên transparent với code business.
Service mesh sử dụng sidecar pattern, pattern này hoạt động với nguyên lý tạo ra một proxy-container bên cạnh các container instance của dịch vụ, proxy container này sẽ định tuyến traffic tới các proxy-container của dịch vụ khác, nhờ đó handle được các connection mà không can thiệp tới hoạt động của container chính.
Load Balancing
Số lượng request đến hệ thống có thể thay đổi theo lượng sử dụng của người dùng, ví dụ như: theo các ngày đặc biệt trong năm, chương trình khuyến mãi hay các sự kiện tập trung nhiều truy cập… Lúc này hệ thống có thể lựa chọn phương án tăng thêm tài nguyên CPU/RAM (Vertical scaling) hoặc sẽ tạo ra thêm các instance (horizontal scaling) để đáp ứng tải. Với phương án horizontal scaling, hệ thống cần một thành phần để điều phối request tới các instance của cùng một dịch vụ. Việc điều phối này cần phải linh hoạt theo nhiều tiêu chí để đáp ứng được các bài toán thực tế chứ không phải chỉ là lần lượt chia đều tải trên số lượng instance. Load balancer chính là thành phần này. Không chỉ giúp tối ưu hóa xử lý compute, tăng tính ổn định và tin cậy, giảm độ trễ của hệ thống, Load balancer có thể phân phối tải đều tới tất cả các instance trên các node đang được triển khai. Thuật ngữ load balancing không chỉ ở trong phạm vi công nghệ container mà với công nghệ Virtual Machine cũng tương tự, ở đây thay vì phân phối tải tới các container thì load balancer sẽ phân phối tải tới các máy VM trong cùng một group.
Circuit Breaker
Đây là cơ chế giúp ngăn ngừa các lỗi có thể xảy ra khi request gửi đến một instance mà ko có phản hồi hoặc có tín hiệu unavailable. Service client khi request tới một service khác nên gọi qua một proxy có chức năng tương tự như một cầu dao diện (circuit break). Khi số lượng request thất bại liên tục đạt tới một ngưỡng, kết nối sẽ bị ngắt hoàn toàn ngay từ proxy. Khi kết nối bị ngắt, các request tới dịch vụ đích sẽ bị từ chối ngay lập tức. Sau một khoảng thời gian, một số lượng request test nhất định được phép đi qua, nếu những request này thành công thì cầu dao sẽ được đóng lại và khôi phục kết nối, còn ngược lại, một chu kì lỗi timeout sẽ tiếp tục diễn ra. Quá trình này giúp tránh những trường hợp như dịch vụ vừa khôi phục đã phải nhận một lượng lớn request, dẫn đến hệ thống tiếp tục bị treo, không thể phục hồi hoàn toàn.
Pattern này tạo điều kiện cho dịch vụ tự khôi phục và không xảy ra quá nhiều lỗi trên dịch vụ đích. Quá trình hoạt động của circuit break được tóm tắt qua 3 trạng thái: Closed State (Trạng thái ngắt), Open state (Trạng thái mở), Half-Open State (Trạng thái test).
Centralized Logging
Logging luôn luôn là yêu cầu cần thiết cho bất cứ hệ thống nào, chức năng phục vụ cho tracing dữ liệu, debugging và troubleshooting ứng dụng. Nhưng với sự phức tạp của hệ thống microservices, log đến từ hàng chục đến hàng trăm instance với các định dạng log của các ngôn ngữ khác nhau, debugging trở nên thật sự khó khăn. Nhà phát triển không thể access vào console log của từng ứng dụng và kéo hàng nghìn dòng log để tìm kiếm thứ mình cần. Khó khăn còn đến từ multiple instance của mỗi dịch vụ, người dùng không thể biết exception vừa bắn ra từ hệ thống đến từ instance nào. Việc logging từ ứng dụng container cũng trở nên khó khăn hơn từ Máy chủ ảo do nguồn phát sinh log đã trở nên đa dạng và phức tạp hơn rất nhiều.
Ý tưởng cho việc logging cần dễ dàng cho việc truy cập, lưu được theo retention dài, da dạng các chỉ số index cho việc tìm kiếm từ log (xuất phát từ dịch vụ nào, thời gian, nội dung, token, session, URL…). Việc lưu trữ và truy xuất cũng cần tập trung thay vì rải rác trên các dịch vụ hoặc thành phần khác nhau. Các công cụ phổ biến như ELK, EFK hay Splunk là ý tưởng cho việc tìm kiếm, lọc và query log. Trước đó còn cần có bước chuẩn hóa định dạng log trước khi đẩy dữ liệu vào các công cụ tìm kiếm.
Monitoring and Alert
Các thành phần hạ tầng của một hệ thống bao gồm CPU, RAM, Storage và Bandwidth
Thành phần này thường không được chú trọng trong thời gian đầu phát triển hệ thống, nhưng khi hệ thống đã đi vào hoạt động thì lại thực sự cần thiết. Monitoring là tính năng cho phép quản trị viên và nhà phát triển có thể theo dõi sức khỏe hệ thống realtime hoặc tổng hợp lại theo các khoảng thời gian (giờ, ngày, tháng, năm), từ đó có thể tracing các issue của hệ thống như bị tấn công, các function chưa tối ưu, thiếu tài nguyên (CPU/Ram/Storage/Bandwidth).
Alert cho phép người dùng cài đặt các mức cảnh báo hoặc các cài đặt về healthcheck sau đó gửi cảnh báo đến người dùng (Email, Slack, Discord…) cho phép người dùng nhận biết được sớm các vấn đề mà hệ thống đang gặp phải, từ đó đưa ra các quyết định phù hợp để giữ hệ thống hoạt động ổn định.
Tránh gặp phải các tính trạng như hệ thống bị treo do hết ram/cpu hoặc quá nhiều người truy cập dẫn đến bottle neck hay database lưu trữ hết dung lượng khiến hệ thống gặp lỗi…
Các giải pháp phục vụ cho Monitoring và Alert có thể kể đến: Prometheus, Grafana
Messaging System
Messaging phục vụ nhu cầu giao tiếp giữa các dịch vụ. Do kiến trúc microservices dựa trên sự phân tán, giao tiếp point to point sẽ gặp một số issue: các request có thể bị mất do lỗi logic code, network latency, service downtime… Bạn có thể đã từng nghe đến Message Queue và Message Broker.
Message Queue đảm bảo dữ liệu sẽ không bị mất trong các trường hợp kể trên, ngoài ra chức năng cơ bản nhất của Message Queue là vận chuyển dữ liệu đến các client/consumer đúng thứ tự như khi chúng được ghi vào queue (FIFO).
Message Broker được thiết kế để duy trì message cho đến khi consumer/subscribe có thể nhận được dữ liệu. Điều đó có nghĩa là Messaging system giúp giảm effort rất nhiều cho nhà phát triển khi không phải xử lý các vấn đề như message translation, message validation, topic/channel ACL, routing message từ producer đến consummer, đảm bảo tính persistence, vận chuyển và streaming dữ liệu… mà chỉ cần tập trung vào code business. Ngay cả với các hệ thống không dựa trên kiến trúc microservices thì Message Broker cũng là một thành phần phổ biến và có nhiều tác dụng.
Các giải pháp cho Messaging trong hệ thống microservice: Kakfa, RabbitMP, Active MQ
Single Page Application
Single Page Application ít được chú ý nhưng cũng rất phổ biến trong kiến trúc microservices. Đây là thành phần thuộc lớp ứng dụng frontend. Giống như sự tương thích giữa Container với microservices, SPA cũng phát triển và phù hợp với hệ sinh thái microservices. Thay vì render tất cả HTML DOM từ phía server như các công nghệ đã có từ lâu (Spring MVC, PHP, Java Servlet, ASP.NET MVC…), SPA chỉ trả về các resource và code logic, hầu hết được đóng gói trong các file .js, từ đó kết hợp với các dữ liệu để render ra HTML theo behavior của người dùng ngay trên client browser (client side rendering).
SPA được xây dựng dựa trên các component, vừa tăng tính tái sử dụng code, từ đó tăng tốc độ phát triển ứng dụng. Đi cùng với SPA thường là Rest API giúp giao tiếp với phía server. Với SPA, người dùng sẽ có trải nghiệm ứng dụng mượt mà, giảm thời gian tải trang giữa các lần truy cập và cũng giảm tải cho phía server. SPA cũng giúp tách riêng việc phát triển UI ứng dụng và xử lý logic backend phía sau, tăng tốc phát triển của dự án.
Các công nghệ SPA phổ biến: Angular, React, Vue.js
Configuration Server
Một hệ thống microservices chạy trên rất nhiều container, các container này lại cần rất nhiều cấu hình để phục vụ CI/CD hoặc runtime. Configuration server sẽ có tác dụng lưu trữ tập trung các dữ liệu cấu hình và có thể được truy cập từ các service hoặc các thành phần khác của hệ thống. Giải pháp lưu trữ tập trung cấu hình ngoài việc đảm bảo việc dễ dàng truy cập dữ liệu từ các service còn giải quyết những vấn đề xung quanh như ACL, Versioning dữ liệu, grouping by project/environment…
Kết
Bài viết đã đề cập đến các thành phần của kiến trúc Microservices và tầm quan trọng của mỗi thành phần. Nhìn nhận ở một góc độ khác, hầu hết các thành phần của hệ thống phân tán lại có chức năng quản lý tập trung và kết nối các dịch vụ, đây chính là điểm khác biệt giúp bù đắp lại những issue của kiến trúc microservices so với monolithic. Chúng ta sẽ đi sâu vào mỗi thành phần và cách sử dụng các thành phần trên lý thuyết và cả thực tế ở những bài viết sau.
Tác giả: Ethanol Tran - A member of Sunteco Team
All rights reserved