🐁with Microservice
Introduce, 10 example,...
Last updated
Introduce, 10 example,...
Last updated
Kiến trúc Microservice dựa trên lĩnh vực kinh doanh, bao gồm nhiều dịch vụ nhỏ độc lập. Trong kiến trúc này, mỗi thành phần đều tự chứa và liên quan đến một khả năng kinh doanh cụ thể.
Tại sao chúng ta lại cân nhắc dùng kiến trúc Microservice thay vì kiến trúc đơn khối? Dưới đây là bốn lý do chính về việc chọn kiến trúc Microservice hơn kiến trúc đơn khối.
Dễ quan sát - MSA cho phép theo dõi các dịch vụ dễ dàng hơn.
Tăng khả năng phục hồi - Cải thiện khả năng phục hồi mạng dịch vụ.
Rút ngắn thời gian sản xuất - Giảm thời gian từ ý tưởng đến sản phẩm hoàn thiện.
Tiết kiệm chi phí - Giảm chi phí cho việc thiết kế, triển khai và duy trì các dịch vụ IT.
Bây giờ bạn đã biết kiến trúc Microservice là gì và tại sao bạn cần xem xét kiến trúc Microservice để xây dựng ứng dụng bền vững theo thời gian và có thể mở rộng đủ để xử lý lưu lượng trong thế giới thực. Hãy cùng tìm hiểu các nguyên tắc cơ bản của Microservices và mẫu thiết kế mà bạn có thể sử dụng để giải quyết các vấn đề phổ biến liên quan đến kiến trúc microservice.
Hãy xem xét các nguyên tắc mà kiến trúc microservice được xây dựng dựa trên đó:
Khả năng mở rộng
Linh hoạt
Độc lập và tự chủ
Quản lý phân tán
Khả năng phục hồi
Cô lập các lỗi
Giao hàng liên tục thông qua DevOps
Trong khi tuân thủ các nguyên tắc trên, có thể sẽ có một số rủi ro khác mà các nhà phát triển có thể gặp phải. Để tránh điều này, chúng ta có thể sử dụng các mẫu thiết kế trong kiến trúc microservice.
Thiết kế cơ sở dữ liệu đang phát triển nhanh chóng, và có nhiều rào cản cần vượt qua trong quá trình phát triển một giải pháp dựa trên microservices. Kiến trúc cơ sở dữ liệu là một trong những khía cạnh quan trọng nhất của microservices.
Cách lưu trữ dữ liệu tốt nhất và nên lưu trữ ở đâu?
Có hai lựa chọn chính để tổ chức cơ sở dữ liệu khi sử dụng kiến trúc microservice.
Cơ sở dữ liệu cho mỗi dịch vụ
Cơ sở dữ liệu chung
Trong lựa chọn "Cơ sở dữ liệu cho mỗi dịch vụ", mỗi microservice sẽ có cơ sở dữ liệu riêng biệt của nó, giúp đảm bảo độc lập và không ảnh hưởng đến các microservice khác. Điều này giúp tăng khả năng mở rộng và cải thiện hiệu suất.
Ở lựa chọn "Cơ sở dữ liệu chung", nhiều microservice chia sẻ cùng một cơ sở dữ liệu. Mặc dù điều này có thể dễ dàng hơn trong việc quản lý nhưng lại tiềm ẩn nguy cơ khi các dịch vụ liên quan đến nhau và có thể gây ra vấn đề về hiệu suất.
Thông thường, lựa chọn "Cơ sở dữ liệu cho mỗi dịch vụ" được ưa chuộng hơn trong kiến trúc microservices vì ưu điểm về khả năng mở rộng, độc lập và hiệu suất. Tuy nhiên, tùy vào yêu cầu cụ thể của dự án, bạn có thể cân nhắc giữa hai lựa chọn này.
Ý tưởng rất đơn giản. Sẽ có một cơ sở dữ liệu cho mỗi microservice (toàn bộ lược đồ hoặc một bảng). Các dịch vụ khác không thể truy cập vào kho dữ liệu mà chúng không kiểm soát. Một giải pháp như vậy có rất nhiều ưu điểm.
Cơ sở dữ liệu độc lập, mặt khác, dễ dàng mở rộng. Hơn nữa, microservice đóng gói dữ liệu của lĩnh vực (domain). Do đó, việc hiểu dịch vụ và dữ liệu của nó như một thể thống nhất trở nên dễ dàng hơn. Điều này đặc biệt quan trọng đối với các thành viên mới của development team.
Họ sẽ mất ít thời gian và công sức hơn để nắm rõ lĩnh vực mà họ chịu trách nhiệm. Nhược điểm chính của dịch vụ cơ sở dữ liệu này là có nhu cầu về cơ chế bảo vệ trước lỗi trong trường hợp truyền thông bị gián đoạn.
Việc sử dụng cơ sở dữ liệu chung được xem là một anti-pattern. Tuy nhiên, điều này vẫn còn khiếm khuyết. Vấn đề là khi các microservice sử dụng cơ sở dữ liệu chung, chúng mất đi các đặc tính chính như khả năng mở rộng, ổn định và độc lập. Kết quả là, microservices hiếm khi sử dụng cơ sở dữ liệu chung.
Khi một cơ sở dữ liệu chung dường như là giải pháp tốt nhất cho một dự án microservices, chúng ta nên xem xét lại liệu microservices thực sự cần thiết hay không. Có thể kiến trúc đơn khối (monolith) lại là lựa chọn tốt hơn. Hãy xem xét cách hoạt động của một cơ sở dữ liệu chung.
Việc sử dụng cơ sở dữ liệu chung với microservices không phải là tình huống phổ biến. Một trạng thái tạm thời có thể được tạo khi chuyển đổi kiến trúc đơn khối sang microservices. Quản lý giao dịch là lợi ích cơ bản của cơ sở dữ liệu chung so với cơ sở dữ liệu cho mỗi dịch vụ. Không cần phải lan truyền giao dịch trên các dịch vụ.
Event sourcing có trách nhiệm cung cấp một chuỗi các sự kiện được sắp xếp. Trạng thái ứng dụng có thể được xây dựng lại bằng cách truy vấn dữ liệu, và để làm điều này, chúng ta cần xây dựng lại mọi thay đổi trong trạng thái của ứng dụng. Event sourcing dựa trên ý tưởng rằng bất kỳ thay đổi nào trong trạng thái của một entity đều nên được hệ thống ghi lại.
Việc persistence của một business item được thực hiện bằng cách lưu trữ một chuỗi các sự kiện thay đổi trạng thái. Mỗi khi trạng thái của một object thay đổi, một event mới được thêm vào chuỗi sự kiện. Điều này về cơ bản là atomic vì đó là một hành động. Bằng cách replay các event của một entity, trạng thái hiện tại của nó có thể được xây dựng lại.
Một event store được sử dụng để theo dõi tất cả các event của bạn. Event store đóng vai trò như một message broker cũng như một cơ sở dữ liệu của các event. Nó cung cấp cho các service khả năng subscribe các event thông qua một API. Event store gửi thông tin về mỗi event được lưu trữ trong cơ sở dữ liệu cho tất cả các subscriber quan tâm. Trong kiến trúc microservices dựa trên sự kiện, event store là nền tảng.
Mô hình này có thể được sử dụng trong các tình huống sau:
Việc giữ nguyên data storage hiện có rất quan trọng.
Không nên có thay đổi nào đối với codebase của data layer hiện có.
Các Transaction rất quan trọng đối với thành công của ứng dụng.
Vì vậy, từ cuộc thảo luận ở trên, rõ ràng chỉ ra rằng event sourcing giải quyết thách thức của việc triển khai một kiến trúc dựa trên sự kiện. Các microservices với shared database không thể dễ dàng scale. Database cũng sẽ là single point of failure. Thay đổi database có thể ảnh hưởng đến nhiều services.
Trước đây, chúng ta đã thảo luận về event sourcing. Bây giờ, chúng ta sẽ thảo luận về CQRS là gì? Chúng ta có thể chia chủ đề này thành hai phần với các lệnh và truy vấn.
Lệnh - Thay đổi trạng thái của đối tượng hoặc entity. Truy vấn - Trả về trạng thái của entity và không thay đổi bất cứ điều gì.
Trong hệ thống quản lý dữ liệu truyền thống, có một số vấn đề:
Nguy cơ xung đột dữ liệu
Quản lý hiệu năng và bảo mật phức tạp do các đối tượng tiếp xúc cả ứng dụng đọc và ghi.
Vì vậy, để giải quyết những vấn đề này, CQRS xuất hiện trong bối cảnh lớn hơn. CQRS có trách nhiệm thay đổi trạng thái của entity hoặc trả về kết quả.
Lợi ích của việc sử dụng CQRS được thảo luận bên dưới.
Độ phức tạp của hệ thống giảm xuống do các mô hình truy vấn và lệnh được tách biệt.
Cung cấp nhiều chế độ xem cho mục đích truy vấn.
Có thể tối ưu hóa phía đọc của hệ thống riêng biệt khỏi phía ghi.
Phía ghi của mô hình xử lý việc lưu trữ các event và hoạt động như một nguồn thông tin cho phía đọc. Mô hình đọc của hệ thống tạo ra các chế độ xem vật liệu hóa của dữ liệu, thường là các chế độ xem phi chuẩn hóa cao.
SAGA Pattern là pattern đảm bảo tính nhất quán của dữ liệu (bằng các quản lý transactions) giữa các microservices khác nhau.
Vì là hệ thống phân tán nên mỗi ông có thể xử lý một phần (nằm trên một transactions), nếu một cái bị fail thì SAGA lúc này có thể thực hiện một transactions thay thế bù vào cái transactions bị fail đó.
Về mặt ý tưởng, mỗi micro services khi nhận được yêu cầu thực hiện transactions, bản thân micro services đó sẽ thực hiện transactions tại chính services đó. Sau khi đã thực hiện xong, theo hướng của SAGA Pattern, services đó sẽ publish một thông báo (message) hoặc một events (sự kiện).
Từ thông báo hoặc sự kiện đó sẽ biết được transactions nào nên được thực hiện tiếp.
Như hình phía trên, Order Services sau khi thực hiện transactions tại service sẽ publish message hoặc event để biết kế tiếp sẽ phải thực hiện transactions tại Customer Service.
Customer Services sau đó publish tiếp để biết cần phải quay lại Order Service để thực hiện tiếp transactions.
Có hai cách để thực hiện saga:
Biên đạo (Choreography)
Dàn dựng (Orchestration) Trong saga biên đạo, không có sự dàn dựng trung tâm. Mỗi dịch vụ trong Saga thực hiện giao dịch của mình và xuất bản các sự kiện. Các dịch vụ khác đáp ứng những sự kiện đó và thực hiện các nhiệm vụ của họ. Ngoài ra, tùy thuộc vào tình huống, chúng có thể hoặc không xuất bản các sự kiện bổ sung.
Trong saga dàn dựng, mỗi dịch vụ tham gia trong saga thực hiện giao dịch của họ và xuất bản các sự kiện. Các dịch vụ khác đáp ứng những sự kiện này và hoàn thành nhiệm vụ của họ.
Lợi ích của việc sử dụng SAGA
Có thể được sử dụng để duy trì tính nhất quán dữ liệu giữa các dịch vụ mà không cần liên kết chặt chẽ.
Nhược điểm của việc sử dụng SAGA
Độ phức tạp của mẫu thiết kế SAGA cao từ góc nhìn của lập trình viên, và các nhà phát triển không quen với việc viết saga như transaction truyền thống.
Mẫu này được sử dụng để xác định cách dữ liệu được truy xuất giữa máy chủ và máy khách. Lý tưởng nhất, nhóm frontend sẽ chịu trách nhiệm quản lý BFF (Backend For Frontend).
Một BFF duy nhất có trách nhiệm xử lý giao diện người dùng đơn và nó sẽ giúp chúng ta giữ cho frontend đơn giản và thấy dữ liệu được xem tổng hợp thông qua backend.
Tại sao BFF cần trong ứng dụng microservice của chúng ta? Mục tiêu của kiến trúc này là tách biệt các ứng dụng front-end khỏi kiến trúc backend. Trong một kịch bản, hãy nghĩ về việc bạn có một ứng dụng bao gồm ứng dụng di động, ứng dụng web và cần giao tiếp với các dịch vụ backend trong kiến trúc microservices.
Việc này có thể được thực hiện thành công, nhưng nếu bạn muốn thay đổi một trong những dịch vụ frontend, bạn cần triển khai phiên bản mới thay vì chỉ cập nhật một dịch vụ.
Vì vậy, kiến trúc microservice xuất hiện và nó có khả năng hiểu những gì ứng dụng của chúng ta cần và cách xử lý các dịch vụ.
Đây là một cải tiến lớn trong kiến trúc microservice, vì nó cho phép cô lập backend của ứng dụng khỏi frontend. Một lợi ích khác mà chúng ta có thể nhận được từ BFF này là chúng ta có thể tái sử dụng mã nguồn, vì nó cho phép tất cả máy khách sử dụng mã nguồn từ backend.
Giữa máy khách và các API, dịch vụ bên ngoài khác, vv, BFF hoạt động tương tự như một máy chủ proxy. Nếu yêu cầu phải đi qua một thành phần khác, độ trễ chắc chắn sẽ tăng lên.
Kiến trúc microservice rất phù hợp cho các ứng dụng lớn với nhiều ứng dụng client, và nó chịu trách nhiệm cung cấp một điểm nhập duy nhất cho một nhóm các microservices.
API Gateway đặt giữa ứng dụng client và microservices, hoạt động như một reverse proxy (proxy ngược) chuyển tiếp yêu cầu của client đến các dịch vụ. Xác thực, SSL termination (kết thúc SSL) và caching (bộ nhớ đệm) là một số dịch vụ cross-cutting (xuyên suốt) khác mà API Gateway có thể cung cấp.
Vì sao chúng ta lại xem xét kiến trúc API Gateway thay vì sử dụng liên lạc trực tiếp từ client đến microservice? Chúng ta sẽ thảo luận điều này với các ví dụ sau:
Security issues (Vấn đề bảo mật) - Nếu không có gateway, tất cả các microservices sẽ phải tiết lộ ra "bên ngoài", làm tăng mức độ nguy cơ tấn công so với việc giữ kín các microservices nội bộ không được truy cập trực tiếp bởi ứng dụng client.
Cross-cutting concerns (Những vấn đề xuyên suốt) - Các microservice được xuất bản công khai phải xử lý riêng biệt việc ủy quyền và SSL. Trong nhiều trường hợp, những vấn đề này có thể được giải quyết ngay tại API Gateway, giảm số lượng microservices nội bộ.
Coupling issues (Vấn đề kết nối) - Client apps ràng buộc với các microservices nội bộ mà không cần API Gateway. Ứng dụng client cần hiểu cách các microservices phân chia các phần khác nhau của ứng dụng.
Cuối cùng, API Gateway phải có khả năng xử lý partial failures (thất bại từng phần). Lỗi của một microservice không phản hồi riêng lẻ không nên gây ra lỗi cho toàn bộ request.
API Gateway có thể xử lý các partial failures bằng nhiều cách, bao gồm:
Sử dụng dữ liệu từ yêu cầu trước đó đã được lưu trữ trong bộ nhớ đệm.
Trả lại mã lỗi nếu dữ liệu quan trọng đã hết hạn.
Cung cấp giá trị rỗng.
Dựa trên giá trị hardware top 10 (giá trị phần cứng hàng đầu).
Strangler (Nghẽn) là một mẫu thiết kế phổ biến giúp chuyển đổi ứng dụng đơn khối (monolithic) sang microservices một cách từng bước thông qua việc thay thế các chức năng cũ bằng dịch vụ mới. Khi thành phần mới đã sẵn sàng, thành phần cũ sẽ bị "nghẽn" (strangled) và thành phần mới sẽ được đưa vào sử dụng.
Giao diện facade, đóng vai trò là giao diện chính giữa hệ thống cũ (legacy system) và các ứng dụng, hệ thống khác sử dụng nó, là một trong những thành phần quan trọng nhất của mẫu thiết kế Strangler.
Các ứng dụng (apps) và hệ thống bên ngoài (external) sẽ có thể xác định mã lập trình liên quan đến một chức năng nhất định, trong khi mã lập trình của hệ thống lịch sử (historical system) sẽ bị che giấu bởi giao diện facade. Mẫu thiết kế Strangler giải quyết vấn đề này bằng cách yêu cầu các nhà phát triển cung cấp giao diện facade, cho phép họ tiết lộ các dịch vụ và chức năng khi chúng được tách ra khỏi đơn khối (monolith).
Bạn cần hiểu về chất lượng và độ tin cậy của hệ thống của mình, dù bạn đang làm việc với mã lập trình cũ (legacy code), bắt đầu quá trình "strangling" hệ thống cũ của bạn, hay đang chạy một ứng dụng được đóng gói trong container (containerized application) mới. Khi có bất kỳ sự cố nào xảy ra, bạn cần biết cách hệ thống đạt đến vị trí đó và lý do tại sao theo con đường đó. Trong quá trình này, bạn cần xem xét các vấn đề về Security issues (vấn đề bảo mật) để đảm bảo rằng hệ thống được nâng cấp và duy trì một cách an toàn.
Circuit breaker (Mạch ngắt) là giải pháp cho sự cố của các cuộc gọi từ xa hoặc treo không trả lời cho đến khi đạt đến giới hạn thời gian chờ. Nếu bạn có nhiều người gọi với nhà cung cấp không phản hồi, bạn có thể hết tài nguyên quan trọng và điều này sẽ dẫn đến sự cố trên nhiều hệ thống trong các ứng dụng.
Vì vậy, mẫu circuit breaker được giới thiệu nhằm gói các lệnh đối với một hàm được bảo vệ trong một đối tượng circuit breaker, đối tượng này sẽ giám sát lỗi. Khi số lượng lỗi đạt đến một mức độ nhất định, mạch ngắt hoạt động và tất cả các cuộc gọi sau đó đến circuit breaker kết quả trong một lỗi hoặc một dịch vụ khác hoặc thông điệp mặc định, thay vì lời gọi được bảo vệ được thực hiện.
Các trạng thái khác nhau trong mẫu circuit breaker:
Closed (Đóng): Khi mọi thứ hoạt động tốt theo cách thông thường, circuit breaker sẽ ở trạng thái đóng.
Open (Mở): Khi số lượng lỗi trong hệ thống vượt quá ngưỡng tối đa, điều này dẫn đến việc mở trạng thái mở. Điều này sẽ gây ra lỗi cho các cuộc gọi mà không thực thi hàm.
Half-Open (Mở một phần): Sau khi chạy hệ thống nhiều lần, circuit breaker sẽ chuyển sang trạng thái mở một phần để kiểm tra vấn đề gốc có vẫn tồn tại hay không.
Dưới đây, chúng ta sẽ có một ví dụ về mã lập trình được xây dựng bằng cách sử dụng Netflix Hystrix.
Client application code sẽ gọi hàm studentMethod()
, và nếu call tới API /student
không nhận được bất kỳ response nào trong thời gian quy định, thì sẽ thực hiện gọi một phương thức dự phòng (fallback) thay thế. Điều này được đề cập trong đoạn codedưới đây.
Có thể sử dụng mẫu circuit breaker để cải thiện tính chịu lỗi và khả năng phục hồi của kiến trúc microservices và ngăn chặn việc lan truyền lỗi sang các microservice khác.
Mẫu circuit breaker được sử dụng để giảm thiểu tác động của lỗi trong hệ thống phân tán. Khi một microservice gặp sự cố và không thể phản hồi, mẫu circuit breaker sẽ ngắt kết nối tới microservice đó và chuyển hướng gọi đến một phương thức dự phòng (fallback) thay thế. Việc này giúp tránh việc tiếp tục gọi một microservice không hoạt động, giảm thiểu thời gian chờ đợi và ngăn chặn việc lan truyền lỗi đến các microservice khác.
Mẫu circuit breaker cũng cung cấp khả năng giám sát và điều chỉnh lại kết nối tới microservice bị lỗi. Nếu microservice đã được sửa chữa và hoạt động lại, circuit breaker có thể mở kết nối lại và cho phép gọi lại tới microservice đó.
Sử dụng mẫu circuit breaker giúp tăng tính kiên định và khả năng chịu lỗi của hệ thống microservices và ngăn chặn lan truyền lỗi đến các thành phần khác.
Thông thường, các dịch vụ cần được chạy trên nhiều môi trường khác nhau. Để chạy trên mỗi môi trường, cần phải cung cấp cấu hình riêng cho từng môi trường, bao gồm các khóa bí mật, thông tin đăng nhập cơ sở dữ liệu và nhiều hơn nữa. Tuy nhiên, thay đổi dịch vụ cho mỗi môi trường có nhiều hạn chế.
Để giải quyết vấn đề này, ta có thể sử dụng mẫu cấu hình nhân rộng (Externalized Configuration Pattern). Mẫu này cho phép bên ngoài có thể đặt cấu hình của ứng dụng, bao gồm thông tin đăng nhập cơ sở dữ liệu và vị trí mạng.
Ví dụ, Spring Boot framework hỗ trợ cấu hình nhân rộng, cho phép đọc cấu hình từ nhiều nguồn và có thể thay đổi cài đặt cấu hình đã xác định trước dựa trên thứ tự đọc. Tương tự, FastAPI cũng hỗ trợ tính năng cấu hình nhân rộng tích hợp sẵn.
Hãy mở lớp ConfigServerApplication và kích hoạt discovery client và configuration server bằng cách sử dụng chú thích sau đây.
Remove the application.properties file and create a new application.yml file with the following content.
Đây là một tệp cấu hình YAML cho Config Server của Spring Cloud. Tệp này cấu hình server để lắng nghe trên cổng 8001, đặt tên ứng dụng là "config-server", và thiết lập URI để truy cập các tệp cấu hình được lưu trữ trên kho lưu trữ git ("https://github.com/alejandro-du/vaadin-microservices-demo-config.git").
Tệp cấu hình cũng cấu hình Eureka Client và Eureka Instance. Eureka Client được sử dụng để đăng ký và tìm kiếm các dịch vụ khác trong môi trường phân tán. Eureka Instance cấu hình các thông tin liên quan đến đăng ký đối với Config Server.
Khi một nhóm đang xây dựng nhiều dịch vụ liên quan đồng thời trong bối cảnh nỗ lực hiện đại hóa và nhóm của bạn hiểu "domain language" của mối liên hệ ràng buộc, nhưng không biết đến các thuộc tính riêng lẻ của từng tổng hợp (aggregate) và payload (dữ liệu mang theo) của sự kiện, thì hướng tiếp cận hợp đồng dựa trên người tiêu dùng (consumer-driven contracts) có thể hiệu quả. Hướng tiếp cận này giúp đảm bảo tính ổn định giữa các dịch vụ bằng cách triển khai hợp đồng dựa trên các yêu cầu của người tiêu dùng (các dịch vụ với vai trò là consumer). Mỗi dịch vụ sẽ xác định các yêu cầu và giả định về các dịch vụ khác mà chúng phụ thuộc vào, rồi thông qua quá trình phát triển, nhóm sẽ đảm bảo rằng các dịch vụ tuân theo hợp đồng đã thỏa thuận. Bằng cách sử dụng phương pháp consumer-driven contracts, bạn có thể tăng tính linh hoạt, giảm các vấn đề phát sinh khi thay đổi các dịch vụ liên quan, đồng thời thúc đẩy sự tiếp tục phát triển của các dịch vụ theo đúng "ngôn ngữ miền" trong một môi trường phức tạp.
Mẫu microservice này rất hữu ích đối với các ứng dụng kế thừa (legacy) có dữ liệu lớn và kích thước dịch vụ hiện trạng. Mẫu thiết kế này giải quyết các vấn đề sau:
Làm thế nào để bổ sung vào API mà không làm hỏng các client (khách hàng) hạ stream.
Cách tìm ra ai đang sử dụng dịch vụ của họ.
Cách thực hiện vòng đời phát hành ngắn với từng bước giao hàng liên tục.
Trong kiến trúc dựa trên sự kiện (event-driven architecture), nhiều microservices tiết lộ hai loại API:
API RESTful dựa trên HTTP
API dựa trên HTTP và thông điệp (message-based API)
API RESTful cho phép tích hợp đồng bộ với các dịch vụ này cũng như khả năng truy vấn mạnh mẽ cho các dịch vụ đã nhận sự kiện từ một dịch vụ khác.
Tóm lại, một cách tiếp cận theo người dùng (consumer-driven) đôi khi được sử dụng khi phân rã một ứng dụng đơn khối (monolithic legacy application). Việc sử dụng phương pháp này giúp tăng tính linh hoạt, quản lý API một cách hiệu quả hơn, tìm ra ai sử dụng dịch vụ và thúc đẩy continuous delivery process.