Hiện nay, Microservice đã trở thành một mô hình kiến trúc phổ biến và được ưa chuộng trong việc xây dựng các hệ thống phần mềm phức tạp. Microservice mang lại nhiều lợi ích như tính linh hoạt, khả năng mở rộng và dễ dàng quản lý. Tuy nhiên, việc triển khai Microservice không phải lúc nào cũng suôn sẻ. Có nhiều lỗi sai hay còn gọi là Anti-Pattern mà các nhóm phát triển thường gặp phải, gây ra những vấn đề nghiêm trọng cho hệ thống.

Trong bài viết này, hãy cùng VietnamWorks inTECH tìm hiểu 8 sai lầm phổ biến khi triển khai Microservice và cách để tránh chúng. 

1. Monolith trong Microservices

Nếu bạn cố gắng xây dựng các microservices trong khi vẫn giữ lại kiến trúc monolith, bạn sẽ gặp phải các vấn đề về khả năng mở rộng, sự chịu lỗi và nhiều vấn đề khác. Điều này thường xảy ra do:

  • Sử dụng Chung Cơ sở dữ liệu: Khi xây dựng microservices, thường nên có một cơ sở dữ liệu cho mỗi dịch vụ. Điều này giúp bạn kiểm soát loại cơ sở dữ liệu, quy tắc schema và khả năng IOPS (Input/Output Operations Per Second) theo từng đặc thù dịch vụ, từ đó giúp bạn có kiểm soát tốt hơn khi mở rộng. Tuy nhiên, nếu sử dụng chung một cơ sở dữ liệu duy nhất cho tất cả các dịch vụ, bạn sẽ khiến cho ứng dụng khó phát triển.

  • Triển khai phức tạp: Mặc dù mô hình được phân tách thành các dịch vụ nhỏ hơn, quá trình triển khai vẫn phức tạp và tốn thời gian, đòi hỏi sự phối hợp giữa các nhóm khác nhau. Điều này sẽ hạn chế tính linh hoạt mà microservices mang lại.

  • Ranh giới dịch vụ không phù hợp: Ranh giới dịch vụ được xác định kém sẽ dẫn đến chức năng chồng chéo và quyền sở hữu không rõ ràng. Điều này có thể dẫn đến trùng lặp công việc và khó khăn trong việc kiểm soát và cải thiện kiến trúc.

Vì vậy, để tránh có các monolith trong microservice, bạn nên sử dụng một cơ sở dữ liệu riêng cho mỗi microservice cùng với việc xác định quyền sở hữu rõ ràng cho các microservice của bạn thông qua Domain Driven Design (DDD).

2. Microservices “giao tiếp” quá nhiều

Do các microservice được tách rời nhau, chúng thường giao tiếp với nhau để xử lý khối lượng công việc của ứng dụng. Mặc dù giao tiếp là yếu tố then chốt, nhưng việc giao tiếp quá mức hoặc trao đổi thông tin không hiệu quả giữa các microservice sẽ khiến chúng giảm hiệu suất. Hãy xem một số tình huống ví dụ sau:

  • Giao tiếp thường xuyên giữa các dịch vụ: Đây là trường hợp các microservices trong hệ thống gửi một số lượng lớn các yêu cầu tới các microservices khác để thực hiện các nhiệm vụ nhỏ hoặc lấy dữ liệu nhỏ. Điều này có thể tạo ra lưu lượng mạng lớn và làm tăng thời gian phản hồi.

  • API chi tiết: Các microservices cung cấp các API chi tiết đòi hỏi nhiều cuộc gọi để hoàn thành một yêu cầu người dùng hoặc giao dịch kinh doanh. Mỗi cuộc gọi có thể cần phải trình tự hóa, tăng overhead mạng và thậm chí là các hoạt động I/O chặn, gây ra vấn đề về hiệu suất.

  • Hiệu ứng lây lan: Một yêu cầu hoặc giao dịch của người dùng duy nhất khởi chạy một loạt các cuộc gọi giữa nhiều microservice khác nhau, mỗi dịch vụ phụ thuộc vào các dịch vụ khác để hoàn thành quá trình xử lý của nó. Điều này có thể gây ra hiệu ứng domino, trong đó sự cố hoặc chậm trễ của một dịch vụ lan sang các dịch vụ khác, dẫn đến suy giảm toàn hệ thống.

Như vậy, để tránh vấn đề này, bạn nên thiết kế kiến trúc sao cho các dịch vụ của bạn không liên kết chặt chẽ với nhau và có khả năng mở rộng tốt hơn. Bạn có thể áp dụng các giải pháp như sử dụng hàng đợi tin nhắn, event busses hoặc event topics.

3. Distributed monolith

Anti-pattern này đề cập đến một ứng dụng được thiết kế và triển khai như một hệ thống phân tán nhưng bao gồm nhiều thành phần hoặc dịch vụ được kết nối chặt chẽ, do đó các microservice không có tính độc lập thực sự.

Một số đặc điểm chính của Anti-pattern này bao gồm:

  • Không có tính tự chủ của dịch vụ: Mỗi thành phần trong hệ thống phân tán đều thiếu tính tự chủ hoàn toàn vì chúng phụ thuộc nhiều vào các thành phần khác để hoạt động. Sự thiếu độc lập này khiến việc mở rộng, triển khai và phát triển các yếu tố khác nhau một cách độc lập trở nên khó khăn.

  • Sự phụ thuộc phức tạp: Các thành phần của hệ thống phân tán có sự phụ thuộc lẫn nhau phức tạp, một thành phần phụ thuộc vào nhiều thành phần khác để hoạt động chính xác. Điều này dẫn đến một mạng lưới các mối quan hệ khó quản lý và khó hiểu, làm tăng tính phức tạp và rủi ro.

  • Trạng thái chia sẻ: Các thành phần của hệ thống phân tán chia sẻ trạng thái hoặc dữ liệu trực tiếp, thông qua cơ sở dữ liệu chia sẻ, bộ nhớ cache hoặc giao tiếp trực tiếp. Trạng thái chia sẻ này có thể gây ra các thách thức về tính nhất quán của dữ liệu, điều kiện và khó khăn về khả năng mở rộng.

Ví dụ, hãy xem xét kiến trúc sau:

Chúng ta đang gọi ba dịch vụ — OrderService, PaymentService và InvoiceService. Các dịch vụ này được thiết kế để hoạt động độc lập, nhưng với chuỗi gọi như thế, bạn đang liên kết các dịch vụ này với một hoạt động duy nhất. Bằng cách này, bạn đưa ra các vấn đề như tỷ lệ tuyến tính và tạo ra các sự phụ thuộc không cần thiết.

Một lần nữa, bạn có thể khắc phục vấn đề này bằng cách giải phóng các microservices của bạn với các dịch vụ như SNS, SQS.

4. Quá nhiều Microservices

Một trong những quan niệm sai lầm phổ biến nhất khi thiết kế kiến trúc dựa trên microservices là tách rời mỗi chức năng thành một microservice, ngay cả những chức năng đơn giản nhất!

Điều này tạo ra các microservices không cần thiết và vượt quá những gì cần thiết, gây ảnh hưởng xấu đến hiệu suất tổng thể của ứng dụng. Việc phân chia microservices nên dựa trên những gì cần thiết và tuân theo các nguyên tắc của Domain-Driven Design. Các đặc điểm chính của anti-pattern này bao gồm:

  • Phân mảnh quá mức: Hệ thống được chia thành một số lượng lớn các microservices, có thể dẫn đến hàng chục hoặc thậm chí hàng trăm dịch vụ. Mỗi microservice có thể chỉ bao gồm một phần nhỏ của chức năng, dẫn đến phân mảnh quá chi tiết.

  • Thiếu gắn kết: Các microservices riêng lẻ thiếu sự gắn kết, có nghĩa là chúng có thể không bao gồm các tập hợp chức năng logic hoặc nhất quán. Điều này có thể dẫn đến logic kinh doanh bị rải rác và phân tán, làm cho hệ thống trở nên khó hiểu và quản lý.

  • Liên kết quá chặt chẽ: Mặc dù được chia thành các microservices, các dịch vụ có thể liên kết chặt chẽ do tương tác và phụ thuộc lẫn nhau đáng kể. Thay đổi ở một microservice có thể yêu cầu thay đổi ở nhiều microservice khác, tăng độ phức tạp và rủi ro.

Một cách để khắc phục vấn đề này là áp dụng Domain Driven Design vào các microservice của bạn và tạo các microservice cho các domain (miền) cụ thể của ứng dụng. 

5. Single Responsibility Violation (S.R.V.) hay Vi phạm nguyên tắc trách nhiệm đơn lẻ 

Đây là một vi phạm cơ bản đối với trách nhiệm của hàm trong thiết kế hướng đối tượng. Điều này xảy ra khi một hàm đơn hoặc microservice đảm nhận quá nhiều trách nhiệm. Ví dụ: khi một microservice xử lý thanh toán cũng đồng thời xử lý việc đăng ký người dùng.

Một số tình huống dẫn đến sai lầm này:

  • Thiếu hiểu biết về Nguyên tắc Thiết kế: Các nhà phát triển có thể không hoàn toàn nhận thức hoặc hiểu rõ các khái niệm thiết kế như Single Responsibility Principle (SRP), hay họ có thể không ưu tiên áp dụng nguyên tắc này trong codebase của mình.

  • Lập kế hoạch không đầy đủ: Lập kế hoạch hoặc phân tích không đầy đủ trong giai đoạn đầu của thiết kế phần mềm có thể dẫn đến việc phân định trách nhiệm cho các thành phần không rõ ràng, gây ra việc trộn lẫn nhiều vấn đề khác nhau trong cùng một thành phần.

  • Hiểu sai Yêu cầu: Hiểu sai hoặc giải thích sai các yêu cầu có thể đưa vào các chức năng không cần thiết hoặc không liên quan trong một thành phần.

6. Kiến trúc Spaghetti

Đây là kiểu thiết kế phần mềm thiếu cấu trúc và tổ chức rõ ràng, dẫn đến một mớ bòng bong các thành phần, module hoặc lớp lang kết nối hỗn độn. Đặc điểm chính của Kiến trúc Spaghetti:

  • Thiếu Tách biệt Các Mối quan tâm (Separation of Concerns): Kiến trúc này không tách biệt các mối quan tâm hay trách nhiệm khác nhau, dẫn đến việc trộn lẫn logic nghiệp vụ, logic hiển thị, logic truy cập dữ liệu và các tính năng khác trong cùng một thành phần hoặc module.

  • Luồng Kiểm soát Phức tạp (Complex Control Flow): Luồng kiểm soát của kiến trúc này rất phức tạp và rối rắm, với các phụ thuộc và tương tác giữa các thành phần khó theo dõi hoặc hiểu. Điều này có thể dẫn đến hành vi không thể dự đoán và dẫn đến hậu quả không mong muốn.

  • Phụ thuộc Mạnh (High Coupling): Tương tự như những gì chúng ta đã xem xét trước đó, các thành phần hoặc module trong kiến trúc này được kết nối chặt chẽ, nghĩa là chúng phụ thuộc rất nhiều vào nhau. Thay đổi một thành phần đôi khi cần thay đổi nhiều thành phần khác, gây ra hiệu ứng domino lan truyền khắp hệ thống.

Để giải quyết vấn đề này bạn có thể áp dụng một số phương pháp sau:

  • Phân chia microservice bằng cách chia tách hệ thống thành microservice nhỏ, độc lập; hoặc áp dụng SRP cho mỗi microservice.

  • Tái cấu trúc mã, chẳng hạn như: Sắp xếp, loại bỏ mã thừa, sử dụng quy ước đặt tên chuẩn, viết chú thích rõ ràng, . . .

  • Sử dụng công cụ để phân tích tĩnh, kiểm tra đơn vị, giám sát.

  • Áp dụng nguyên tắc thiết kế: OOP, mẫu thiết kế, microservice.

7. Sự không nhất quán của dữ liệu phân tán

Đây là trường hợp dữ liệu được sao chép trên nhiều nút hoặc dịch vụ khác nhau, và sự không nhất quán có thể phát sinh do sự chậm trễ hoặc lỗi trong việc đồng bộ hóa các cập nhật trên các bản sao này.

Đặc điểm chính của anti pattern này bao gồm:

  • Cập nhật không đồng bộ: Các cập nhật dữ liệu được truyền bá không đồng bộ trên tất cả các bản sao hoặc nút của hệ thống phân tán. Điều này có thể gây ra độ trễ giữa việc thực hiện thay đổi và việc thay đổi đó được phản ánh trên tất cả các bản sao của dữ liệu.

  • Phân vùng mạng: Phân vùng mạng hoặc lỗi mạng có thể xảy ra do các lý do không lường trước được, ngăn cản các cập nhật truyền đến tất cả các bản sao hoặc dẫn đến sự khác biệt giữa các bản sao do cập nhật không đầy đủ.

  • Các hoạt động xung đột: Các hoạt động đồng thời trên cùng một dữ liệu từ nhiều nút có thể gây ra xung đột không được xử lý thỏa đáng, dẫn đến dữ liệu không nhất quán hoặc bị hỏng.

Để khắc phục các vấn đề như vậy, điều quan trọng là tận dụng các mô hình Microservice như Saga Pattern để tạo và quản lý các giao dịch phân tán trong các microservice.

8. Thiếu khả năng quan sát

Đây là trường hợp ứng dụng không cung cấp đủ thông tin chi tiết về trạng thái bên trong, hoạt động và hiệu suất của nó. Điều này khiến cho các nhà phát triển hoặc quản trị viên khó khăn trong việc quan sát hiệu suất của ứng dụng hoặc thậm chí là khắc phục sự cố một cách hiệu quả. Đặc điểm chính của anti pattern này bao gồm:

  • Ghi log hạn chế: Hệ thống không có cơ chế ghi log toàn diện để ghi lại các sự kiện quan trọng, lỗi và hành động xảy ra bên trong nó. Điều này làm cho việc theo dõi luồng thực thi và xác định vấn đề trở nên khó khăn hơn.

  • Chỉ số không đầy đủ: Hệ thống không cung cấp các số liệu hoặc dữ liệu telemetry hữu ích về hiệu suất, sử dụng tài nguyên và các chỉ số quan trọng khác. Không có thông tin này, việc đánh giá tình trạng của hệ thống và xác định các điểm nghẽn tiềm ẩn hoặc vị trí cần cải thiện trở nên khó khăn.

  • Theo dõi phân tán thưa thớt: Hệ thống thiếu khả năng theo dõi phân tán để theo dõi luồng các yêu cầu và giao dịch trên nhiều dịch vụ hoặc thành phần. Điều này khiến việc phát hiện các đột biến hiệu suất, các vấn đề về độ trễ và sự cố trong các hệ thống phân tán trở nên khó khăn hơn.

Lời kết

VietnamWorks inTECH hy vọng qua bài viết này, bạn đã có thêm kiến thức và nhận thức về những sai lầm thường gặp khi triển khai Microservice và cách để tránh chúng. Hãy áp dụng những nguyên tắc và kinh nghiệm được chia sẻ để cải thiện hệ thống của bạn, giúp nó vận hành mượt mà hơn và đáp ứng tốt hơn các yêu cầu kinh doanh.

VietnamWorks inTECH