Kiến trúc Microservices: Lợi ích và thách thức khi triển khai
Triển khai microservices mang lại lợi ích rõ ràng về tốc độ phát hành và sự tự chủ của các đội, nhưng chỉ khi tổ chức vượt qua được một loạt thách thức kỹ thuật và con người: chia ranh giới dịch vụ đúng, giao tiếp tin cậy qua mạng, giữ dữ liệu nhất quán, quan sát được hệ thống phân tán và bảo mật hàng chục điểm giao tiếp. Bài này tập trung vào phần "làm thế nào": những vấn đề bạn sẽ gặp khi bắt tay vào triển khai và các mẫu thiết kế đã được cộng đồng kiểm chứng để xử lý chúng.
Lợi ích chỉ đến khi đáp ứng điều kiện
Các lợi ích thường được nhắc tới, gồm deploy độc lập, mở rộng có chọn lọc, cô lập lỗi và đội tự chủ, không tự nhiên xuất hiện khi bạn tách mã thành nhiều kho. Chúng là kết quả của:
- Ranh giới dịch vụ phản ánh đúng ranh giới nghiệp vụ.
- Mỗi dịch vụ sở hữu dữ liệu riêng.
- Hạ tầng triển khai và giám sát tự động.
- Cấu trúc đội tương ứng với cấu trúc dịch vụ.
Thiếu một trong các điều kiện trên, bạn dễ rơi vào "monolithic phân tán": vẫn phải deploy nhiều dịch vụ cùng lúc, nhưng giờ thêm cả độ trễ mạng. Nếu bạn còn đang cân nhắc có nên chọn kiến trúc này hay không, hãy đọc trước bài so sánh ưu nhược điểm microservices và monolithic.
Thách thức 1: Vẽ ranh giới dịch vụ
Đây là quyết định khó sửa nhất. Cách tiếp cận phổ biến đến từ Domain-Driven Design (DDD): xác định các bounded context, tức vùng nghiệp vụ có ngôn ngữ và mô hình dữ liệu riêng. "Sản phẩm" trong ngữ cảnh danh mục (mô tả, hình ảnh) khác "sản phẩm" trong ngữ cảnh kho (số lượng, vị trí) và trong ngữ cảnh thanh toán (giá, thuế).
Dấu hiệu ranh giới sai:
- Hầu hết tính năng mới đều phải sửa nhiều dịch vụ cùng lúc.
- Hai dịch vụ gọi qua lại liên tục với nhau cho mọi thao tác.
- Một dịch vụ cần đọc trực tiếp dữ liệu của dịch vụ khác để hoạt động.
Kỹ thuật như event storming, nơi chuyên gia nghiệp vụ và kỹ sư cùng liệt kê các sự kiện nghiệp vụ trên bảng, giúp phát hiện ranh giới tự nhiên trước khi viết mã.
Thách thức 2: Giao tiếp giữa các dịch vụ
Đồng bộ hay bất đồng bộ
| Kiểu | Công nghệ | Phù hợp | Rủi ro |
|---|---|---|---|
| Đồng bộ | REST, gRPC | Truy vấn cần kết quả ngay | Chuỗi phụ thuộc: một dịch vụ chậm kéo chậm cả chuỗi |
| Bất đồng bộ | Kafka, RabbitMQ, SQS, Pub/Sub | Thông báo sự kiện, xử lý nền | Khó theo dõi luồng, cần xử lý trùng lặp và thứ tự |
Một nguyên tắc thực tế: hạn chế chuỗi gọi đồng bộ dài. Nếu đặt hàng phải gọi tuần tự kho, thanh toán, vận chuyển, khuyến mãi, thì độ sẵn sàng của luồng bằng tích độ sẵn sàng của từng dịch vụ. Phát sự kiện "đơn hàng đã tạo" để các dịch vụ khác tự phản ứng thường bền vững hơn.
Chịu lỗi
Mạng sẽ lỗi; câu hỏi là khi nào. Các mẫu cần có:
- Timeout cho mọi lời gọi ra ngoài.
- Retry có giới hạn với độ trễ tăng dần và yếu tố ngẫu nhiên, chỉ cho thao tác idempotent.
- Circuit breaker ngắt gọi tạm thời tới dịch vụ đang lỗi để tránh dồn tải.
- Bulkhead cô lập tài nguyên để một phụ thuộc chậm không chiếm hết luồng xử lý.
- Fallback trả kết quả suy giảm, ví dụ ẩn mục gợi ý sản phẩm khi dịch vụ gợi ý không phản hồi.
Thách thức 3: Dữ liệu phân tán
Nguyên tắc "mỗi dịch vụ một cơ sở dữ liệu" bảo vệ tính độc lập nhưng xóa bỏ transaction chung. Các mẫu thường dùng:
- Saga: chia giao dịch lớn thành chuỗi bước cục bộ, mỗi bước có thao tác bù trừ khi bước sau thất bại. Ví dụ thanh toán thất bại thì phát sự kiện để kho hoàn lại số lượng đã giữ.
- Transactional outbox: ghi thay đổi dữ liệu và sự kiện cần phát vào cùng một transaction cục bộ, sau đó một tiến trình riêng đọc bảng outbox và phát sự kiện. Tránh tình huống cập nhật dữ liệu thành công nhưng sự kiện bị mất.
- CQRS và bản sao đọc: dịch vụ cần dữ liệu của dịch vụ khác để hiển thị có thể giữ bản sao chỉ đọc, cập nhật qua sự kiện.
Hệ quả là nhất quán cuối cùng (eventual consistency): trong khoảnh khắc ngắn, các dịch vụ có thể thấy dữ liệu khác nhau. Sản phẩm và nghiệp vụ cần chấp nhận và thiết kế trải nghiệm phù hợp.
Thách thức 4: Quan sát hệ thống
Khi người dùng báo "trang thanh toán chậm", bạn cần biết yêu cầu đã đi qua những dịch vụ nào và chậm ở đâu. Ba trụ cột quan sát:
- Log tập trung có cấu trúc, kèm mã định danh yêu cầu (correlation ID) xuyên suốt các dịch vụ.
- Metrics cho từng dịch vụ: tỷ lệ lỗi, độ trễ, lưu lượng.
- Distributed tracing ghi lại hành trình của từng yêu cầu. OpenTelemetry là chuẩn mở được hỗ trợ rộng rãi để thu thập cả ba loại dữ liệu.
Thách thức 5: Triển khai và kiểm thử
Hai mươi dịch vụ nghĩa là hai mươi pipeline. Chuẩn hóa bằng mẫu pipeline dùng chung, container hóa mọi dịch vụ và một nền tảng điều phối như Kubernetes là cách nhiều tổ chức chọn; bài Kubernetes cho người mới bắt đầu giới thiệu các khái niệm cơ bản.
Về kiểm thử, end-to-end trên toàn hệ thống trở nên chậm và dễ vỡ. Contract testing (ví dụ với Pact) kiểm tra rằng mỗi cặp dịch vụ vẫn tuân thủ thỏa thuận giao tiếp, cho phép deploy độc lập với độ tin cậy cao hơn.
Thách thức 6: Bảo mật nhiều điểm giao tiếp
Trong monolithic, phần lớn giao tiếp là nội bộ tiến trình. Trong microservices, mỗi lời gọi đi qua mạng và là một điểm có thể bị nghe lén hoặc giả mạo. Các biện pháp cơ bản:
- API Gateway làm điểm vào duy nhất, xử lý xác thực người dùng, giới hạn tần suất.
- Mã hóa và xác thực lẫn nhau giữa dịch vụ bằng mTLS, thường qua service mesh như Istio hoặc Linkerd.
- Truyền danh tính người dùng bằng token có chữ ký (ví dụ JWT) và kiểm tra quyền tại từng dịch vụ.
- Mỗi dịch vụ chỉ được truy cập dữ liệu của chính nó, theo nguyên tắc phân quyền tối thiểu. Cách tổ chức quyền truy cập vào dữ liệu nhạy cảm càng quan trọng khi dữ liệu nằm rải rác ở nhiều kho.
Thách thức 7: Con người và tổ chức
Định luật Conway, quan sát rằng hệ thống phản ánh cấu trúc giao tiếp của tổ chức tạo ra nó, đặc biệt đúng với microservices. Nếu một đội phải sở hữu mười dịch vụ, hoặc một dịch vụ do ba đội cùng sửa, lợi ích tự chủ biến mất. Nhiều tổ chức áp dụng mô hình đội theo luồng giá trị kèm một đội nền tảng cung cấp công cụ chung như pipeline, giám sát, mẫu dịch vụ.
Lộ trình chuyển đổi từ hệ thống hiện có
- Chuẩn bị nền tảng: CI/CD, container, log tập trung, giám sát trước khi tách dịch vụ đầu tiên.
- Chọn ứng viên đầu tiên: một chức năng ít phụ thuộc, có giá trị rõ, ví dụ gửi thông báo.
- Áp dụng mẫu Strangler Fig: đặt lớp định tuyến phía trước, chuyển dần lưu lượng của chức năng đó sang dịch vụ mới.
- Tách dữ liệu: chuyển bảng liên quan sang cơ sở dữ liệu riêng, đồng bộ trong giai đoạn chuyển tiếp.
- Đánh giá và lặp lại: đo thời gian deploy, tỷ lệ lỗi, rồi mới quyết định tách tiếp.
Không cần, và thường không nên, tách toàn bộ. Nhiều hệ thống ổn định ở trạng thái lai: một lõi monolithic cùng vài dịch vụ tách riêng cho những phần thực sự cần.
Câu hỏi thường gặp
Bao nhiêu dịch vụ là quá nhiều?
Không có con số cố định. Dấu hiệu quá nhiều là khi số dịch vụ vượt xa khả năng sở hữu của các đội, hoặc mỗi tính năng nhỏ đều đòi sửa nhiều dịch vụ. Khi đó nên gộp các dịch vụ thường thay đổi cùng nhau.
Service mesh có bắt buộc không?
Không. Service mesh hữu ích khi có nhiều dịch vụ cần mTLS, định tuyến lưu lượng và quan sát đồng nhất, nhưng nó thêm một lớp phức tạp. Với số dịch vụ ít, thư viện trong ứng dụng và API Gateway thường đủ.
Làm sao xử lý một yêu cầu cần dữ liệu từ nhiều dịch vụ?
Có thể dùng API Gateway hoặc lớp Backend for Frontend để gom dữ liệu, hoặc để dịch vụ hiển thị giữ bản sao dữ liệu cần thiết cập nhật qua sự kiện. Tránh để dịch vụ truy vấn trực tiếp cơ sở dữ liệu của dịch vụ khác.
Microservices có phù hợp với startup không?
Thường là chưa phù hợp ở giai đoạn đầu, khi sản phẩm thay đổi nhanh và đội nhỏ. Một modular monolith với ranh giới rõ ràng giữ được tốc độ phát triển và vẫn cho phép tách dịch vụ sau này.
Bài liên quan

Bảo mật đám mây: DevSecOps và những nguyên tắc cốt lõi
DevSecOps đưa bảo mật vào từng bước phát triển và vận hành trên cloud. Tìm hiểu các nguyên tắc cốt lõi, công cụ và cách bắt đầu cho đội nhỏ.

CI/CD Pipeline: Tự động hóa quy trình deploy với GitHub Actions
Hướng dẫn xây pipeline CI/CD bằng GitHub Actions: cấu trúc workflow, job test và build, deploy theo môi trường, bảo mật secrets và mẹo tăng tốc.

CI/CD Pipeline: Tự động hóa quy trình phát triển và kiểm thử phần mềm
CI/CD là gì, khác nhau giữa Continuous Delivery và Deployment, nên đặt unit test, integration test, E2E ở đâu trong pipeline và cách đo hiệu quả.

Docker và Kubernetes: Containerization cho người mới bắt đầu
Container là gì, Docker giải quyết vấn đề gì, khi nào cần đến Kubernetes. Giải thích dễ hiểu kèm lộ trình học Docker và K8s cho người mới bắt đầu.