Tóm tắt đồ án Thiết kế hệ thống với các định nghĩa các thành phần và cách tích hợp chúng, các giao diện lập trình ứng dụng APIs, và mô hình dữ liệu data model để xây dựng kiến trúc Micro
GIỚI THIỆU ĐỀ TÀI
Động lực nghiên cứu
Trong bối cảnh Internet ngày càng được nhiều người dùng trên toàn thế giới sử dụng, vì vậy các ứng dụng trên toàn thế giới phải phát triển và xây dựng thành nhiều mô hình hệ thống mới và có khả năng đáp ứng số lượng nhu cầu người dùng lớn
Spring Boot là sự lựa chọn phù hợp để xây dựng các mô hình hệ thống vì độ phổ biến rộng rãi trong cộng đồng phát triển phần mềm Nền tảng mạnh mẽ và linh hoạt của Spring Boot giúp tối ưu hiệu suất, dễ dàng triển khai ứng dụng và cung cấp công cụ hỗ trợ đáng tin cậy Khả năng học hỏi liên tục từ cộng đồng và tiềm năng việc làm cao làm cho việc nghiên cứu và áp dụng Spring Boot mang lại lợi ích lớn, đặc biệt là khi cung cấp kiến thức và kỹ năng cần thiết cho ngành công nghiệp công nghệ thông tin ngày càng đòi hỏi.
Mục tiêu và phạm vi nghiên cứu
- Nắm rõ cấu trúc, kiến thức về các thành phần trong Spring Boot
- Xây dựng ứng dụng đơn giản áp dụng kiến trúc Microservice với Spring Boot Phạm vi:
- Tìm hiểu các thành phần trong Spring Boot
- Tìm hiểu và phân tích các thành phần của kiến trúc Microservice.
Tóm tắt đồ án
Thiết kế hệ thống với các định nghĩa các thành phần và cách tích hợp chúng, các giao diện lập trình ứng dụng (APIs), và mô hình dữ liệu (data model) để xây dựng kiến trúc Microservice đáp ứng các yêu cầu về chức năng và phi chức năng
Xây dựng ứng dụng áp dụng kiến trúc Microservice có độ tin cậy (reliable), hiệu quả, và dễ bảo trì cùng với những đặc điểm khác.
Bố cục báo cáo
Phần còn lại của báo cáo được trình bày theo bố cục sau:
THIẾT KẾ HỆ THỐNG
Tổng quan về thiết kế hệ thống
Thiết kế hệ thống sử dụng các khái niệm trong lĩnh vực mạng máy tính (Computer Networking), tính toán song song (Parallerl Computing) và hệ thống phân tán (Distributed systems) để xây dựng những hệ thống có khả năng mở rộng và hiệu suất cao
Hình 2.1: Thiết kế hệ thống
Thiết kế hệ thống có mục đích xây dựng những hệ thống có độ tin cậy (reliable), hiệu quả, và dễ bảo trì cùng với những đặc điểm khác
- Hệ thống hiệu quả là hệ thống đáp ứng tất cả các nhu cầu của người dùng và yêu cầu kinh doanh
- Hệ thống đáng tin cậy là hệ thống có khả năng xử lý lỗi, sự cố và sai sót
- Hệ thống dễ bảo trì là hệ thống linh hoạt và dễ dàng mở rộng hoặc thu nhỏ Khả năng thêm tính năng mới cũng nằm trong phạm vi của tính dễ bảo trì.
BỘ CÂN BẰNG TẢI
Khái niệm bộ cân bằng tải
Bộ cân bằng tải “Load Balancer” là một thiết bị phân phối lưu lượng mạng hoặc ứng dụng trên nhiều máy chủ Load balancer được sử dụng để tăng khả năng xử lý và availability của ứng dụng
Chúng cải thiện hiệu suất tổng thể của ứng dụng bằng cách giảm gánh nặng cho máy chủ liên quan đến việc quản lý và duy trì phiên ứng dụng và mạng, cũng như thực hiện các tác vụ cụ thể của ứng dụng
Hình 3.1: Cách bộ cân bằng tải hoạt động
Bộ cân bằng tải là điểm tiếp xúc đầu tiên trong một trung tâm dữ liệu sau khi yêu cầu vượt qua tường lửa Một máy cân bằng tải có thể không cần thiết nếu dịch vụ chỉ phục vụ một vài trăm hoặc thậm chí vài nghìn yêu cầu mỗi giây Tuy nhiên, khi số lượng yêu cầu từ khách hàng tăng lên, máy cân bằng tải cung cấp những khả năng sau đây:
- Khả năng mở rộng: Khi lưu lượng truy cập tăng, load balancer có thể phân phối lưu lượng truy cập trên nhiều máy chủ, giúp dễ dàng mở rộng theo chiều ngang
- Dự phòng: Load balancer có thể giúp đảm bảo tính sẵn sàng của ứng dụng bằng cách chuyển hướng lưu lượng truy cập đến các máy chủ khác khi một máy chủ gặp sự cố
- Hiệu suất: Load balancer có thể giúp cải thiện hiệu suất của ứng dụng bằng cách phân phối lưu lượng truy cập đến các máy chủ ít bận rộn hơn
- Bảo mật: Bộ cân bằng tải có thể giúp bảo vệ ứng dụng khỏi các cuộc tấn công từ chối dịch vụ (DoS) và các cuộc tấn công khác bằng cách chặn lưu lượng truy cập độc hại
Bộ cân bằng tải thường đặt ở đâu ?
Thường thì, bộ cân bằng tải (LBs) đặt ở giữa client và server Yêu cầu từ client được chuyển qua server và phản hồi lại cho client thông qua bộ cân bằng tải Tuy nhiên, trong thực tế đó không phải là điểm duy nhất mà bộ cân bằng tải được sử dụng
Hãy xem xét ba nhóm máy chủ phổ biến: máy chủ web, máy chủ ứng dụng và máy chủ cơ sở dữ liệu Để phân chia tải lưu lượng truy cập giữa các máy chủ có sẵn, máy cân bằng tải có thể được sử dụng giữa các phiên bản máy chủ của ba dịch vụ này theo cách sau:
- Đặt máy cân bằng tải giữa người dùng cuối của ứng dụng và máy chủ web/cổng ứng dụng
- Đặt máy cân bằng tải giữa máy chủ web và máy chủ ứng dụng chạy logic kinh doanh/ứng dụng
- Đặt máy cân bằng tải giữa máy chủ ứng dụng và máy chủ cơ sở dữ liệu
Hình 3.2: Cách sử dụng khả thi cân bằng tải trong cấu trúc 3 lớp
Trong thực tế, bộ cân bằng tải có thể được sử dụng giữa bất kỳ hai dịch vụ nào có nhiều biến thể (instances) trong thiết kế của một hệ thống.
Phân loại bộ cân bằng tải
Tùy thuộc vào yêu cầu, cân bằng tải có thể được thực hiện ở tầng mạng/vận chuyển hoặc tầng ứng dụng của mô hình Open systems interconnection (OSI)
Layer 3/4 load balancer: Layer 3/4 load balancer hoạt động ở tầng mạng (network layer) và tầng vận chuyển (transport layer) của mô hình OSI và sử dụng thông tin về địa chỉ IP và
16 cổng TCP/UDP để đưa ra quyết định định tuyến Layer 3/4 load balancer thường không kiểm tra nội dung gói tin và chỉ chuyển tiếp các gói tin TCP/UDP đến các máy chủ backend
Layer 7 load balancer: Layer 7 load balancer hoạt động ở tầng ứng dụng (application layer) của mô hình OSI và sử dụng thông tin từ giao thức ứng dụng (chủ yếu là HTTP) để đưa ra quyết định định tuyến Layer 7 load balancer hoạt động như một proxy, duy trì hai kết nối TCP: một với client và một với server.
Các thuật toán
Để điều hướng lưu lượng, load balancer có thể áp dụng một trong các thuật toán dưới đây để quyết định địa chỉ mà request sẽ được điều hướng tới:
- Round Robin: Phân phối lưu lượng truy cập đến các máy chủ theo lượt
- Weighted Round Robin: Tương tự như Round Robin, nhưng cho phép gán trọng số cho các máy chủ để ưu tiên một số máy chủ hơn
- Least Connections: Chuyển hướng lưu lượng truy cập đến các máy chủ có ít kết nối nhất
- Least response time: Chuyển hướng đến server với thời gian phản hồi ít thời gian nhất
- Weighted Least Connections: Tương tự như Least Connections, nhưng cho phép gán trọng số cho các máy chủ để ưu tiên một số máy chủ hơn
- IP Hash: Sử dụng địa chỉ IP nguồn và đích của lưu lượng truy cập để tính toán một giá trị băm và gán kết nối đến một máy chủ cụ thể dựa trên giá trị băm này
- URL hash: Thuật toán này hoạt động bằng cách lấy URL mà máy khách yêu cầu và biến đổi nó thành một giá trị số học (thường là một chuỗi hash) bằng cách băm URL Giá trị số học này được sử dụng để xác định máy chủ hoặc cụm máy chủ nào sẽ phục vụ yêu cầu từ máy khách
Ngoài ra còn có các thuật toán khác, như thuật toán “randomized” hoặc “weighted least connections”.
Triển khai bộ cân bằng tải
3.4.1 Bộ cân bằng tải phần cứng
Bộ cân bằng tải phần cứng (hardware load balancer) là một thiết bị phần cứng vật lý được cài đặt trong trung tâm dữ liệu Nó được thiết kế để xử lý lưu lượng truy cập lớn và có hiệu suất cao
Tuy nhiên bộ cân bằng tải phần cứng có những hạn chế nhất định:
- Đòi hỏi kiến thức chuyên môn cao: Việc cấu hình của một bộ cân bằng tải phần cứng cần đòi hỏi một nguồn nhân lực lớn có kiến thức chuyên môn để cài đặt và cấu hình
- Vấn đề về tính khả dụng: Tính khả dụng có thể là một vấn đề với cân bằng tải phần cứng Khi xảy ra sự cố, chúng ta cần chuẩn bị thiết bị phần cứng để thay thế trong trường hợp gặp lỗi
- Chi phí vận hành và bảo trì cao: Các cân bằng tải phần cứng có thể có chi phí bảo trì và vận hành cao Ngoài ra, có thể phát sinh vấn đề về sự tương thích và tích hợp với các phần mềm và cơ sở hạ tầng khác
- Ràng buộc với nhà cung cấp: Cân bằng tải phần cứng thường kèm theo các ràng buộc của nhà cung cấp, đồng nghĩa với việc bạn có thể bị ràng buộc vào một nhà cung cấp cụ thể và khó lòng chuyển đổi sang giải pháp của nhà cung cấp khác Dưới những khía cạnh này, cân bằng tải phần cứng không phải lúc nào cũng là sự lựa chọn hàng đầu, ngay cả đối với các doanh nghiệp lớn có khả năng tài chính Sự linh hoạt kém, chi phí vận hành cao và các vấn đề về tương thích làm cho chúng ít phù hợp trong môi trường công nghiệp hiện đại, trong đó các giải pháp cân bằng tải dựa trên phần mềm và điện toán đám mây đã trở nên phổ biến hơn
Hình 3.3: Bộ cân bằng tải phần cứng
Ví dụ Bộ cân bằng tải phần cứng:
- F5 BIG-IP load balancer (Setup HTTPS load balance on F5)
3.4.2 Bộ cân bằng tải phần mềm
Bộ cân bằng tải phần mềm: Là một ứng dụng phần mềm có thể được cài đặt trên máy chủ vật lý hoặc máy chủ ảo Nó có thể hoạt động như một ứng dụng độc lập hoặc được tích hợp vào hệ thống hiện có Bộ cân bằng tải phần mềm linh hoạt hơn và có chi phí thấp hơn so với Bộ cân bằng tải phần cứng
Dưới đây là một số điểm quan trọng về cân bằng tải phần mềm:
- Mở rộng tốt: Cân bằng tải dựa trên phần mềm có khả năng mở rộng tốt khi yêu cầu tăng lên Bạn có thể dễ dàng thêm máy chủ hoặc tài nguyên máy tính để xử lý khối lượng lớn yêu cầu mà không cần phải mua hoặc triển khai các thiết bị phần cứng đắt tiền
- Tính khả dụng được đảm bảo: Cân bằng tải dựa trên phần mềm thường đảm bảo tính khả dụng cao Điều này có nghĩa là bạn có thể thêm các máy chủ “shadow load balancers” trên phần cứng thông thường với chi phí bổ sung nhỏ Khi một cân bằng tải gặp lỗi hoặc gặp vấn đề, “shadow load balancers” có thể tiếp tục xử lý yêu cầu mà không gây gián đoạn dịch vụ
- Phân tích dự đoán: Cân bằng tải phần mềm có khả năng cung cấp phân tích dự đoán Điều này có nghĩa là nó có thể theo dõi và phân tích dữ liệu về lưu lượng hiện tại để dự đoán các mô hình giao thông (traffic pattern) tương lai Thông tin này giúp chuẩn bị cho các biến động trong tải và đảm bảo rằng hệ thống được cấu hình để xử lý chúng một cách hiệu quả
Hình 3.4: Bộ cân bằng tải phần mềm
CƠ SỞ DỮ LIỆU
Tổng quan về cơ sở dữ liệu
Khái niệm cơ sở dữ liệu là một tập hợp dữ liệu được tổ chức một cách có hệ thống để quản lý và truy cập dễ dàng Cơ sở dữ liệu được tạo ra với mục tiêu giúp lưu trữ, truy xuất, sửa đổi và xóa dữ liệu một cách hiệu quả trong quá trình xử lý dữ liệu khác nhau
Hình 4.1: Cơ sở dữ liệu quan hệ và không quan hệ
Có hai loại cơ sở dữ liệu chính:
- Cơ sở dữ liệu quan hệ: Có một cấu trúc được xác định rõ ràng như các thuộc tính (các cột trong bảng)
- Cơ sở dữ liệu không quan hệ: Như cơ sở dữ liệu tài liệu (document databases), thường có cấu trúc dữ liệu được xác định bởi ứng dụng.
GIAO DỊCH PHÂN TÁN
Tổng quan về giao dịch
Giao dịch là một đơn vị công việc được thực hiện trong hệ thống cơ sở dữ liệu, đại diện cho một sự thay đổi bao gồm nhiều hoạt động
Giao dịch cơ sở dữ liệu là một trừu tượng được tạo ra để đơn giản hóa công việc và giúp kỹ sư không cần phải xử lý tất cả các sự cố có thể xuất hiện do tính không đáng tin cậy của phần cứng Đảm bảo tính ACID trong một hệ thống:
- Tính nguyên tử - Atomacity: Các giao dịch được thực hiện đồng thời khi tất cả các node đều đã cam kết hoàn thành hoặc được hủy bỏ khi có một node hủy bỏ
- Tính nhất quán – Consistency: Tại bất kỳ thời điểm nào, mọi dữ liệu trên toàn bộ các node phải duy trì ở một trạng thái nhất quán, và nó cũng phải đảm bảo trạng thái nhất quán sau một giao dịch
- Tính cách ly – Isolation: Trong trường hợp có nhiều giao dịch đang chạy thì chúng không nên ảnh hưởng đến nhau
- Tính bền vững – Durability: Hệ thống nên đảm bảo rằng các giao dịch đã hoàn thành sẽ được lưu trữ trong hệ thống dữ liệu và không được mất đi kể cả khi hệ thống gặp sự cố Đối với hệ thống phân tán, tính bền vững có thể đạt được khi chúng ta lưu trữ các bản sao dữ liệu để đảm bảo khi một thành phần sụp đổ thì thành phần thay thế có thể truy cập bản sao dữ liệu đó và tiếp tục nhiệm Còn đối với tính nhất quán, chúng ta có thể sử dụng các giải pháp như khóa ngoại, cascades, triggers, …
Nhưng ngược lại việc để hệ thống đảm bảo tính cách ly và nguyên tử là một thử thách và khó triển khai.
Xác nhận 2 pha “2-Phase Commit” (2PC)
Trong một hệ thống phân tán với mạng không đáng tin cậy, việc chỉ gửi một thông điệp đến các nút liên quan không đủ để thực hiện một giao dịch phân tán
Nút khởi tạo giao dịch sẽ không biết liệu các nút khác đã cam kết thành công hay đã hủy bỏ do một số sự cố, để đưa ra quyết định cuối cùng về kết quả của giao dịch
Giao thức Xác nhận 2 pha (2PC) bao gồm 2 vai trò:
- Người điều phối (Coordinator): chịu trách nhiệm điều phối các giai đoạn khác nhau của giao thức
- Những người tham gia (Paticipants): tương tác với tất cả các nút trong giao dịch Lưu ý: Một trong những người tham gia có thể đóng vai trò như người điều phối
Giao thức 2PC bao gồm 2 pha: Pha biểu quyết (Voting phase) và Pha xác nhận (Commit phase)
Pha biểu quyết (Voting phase)
Khi bắt đầu, client sẽ yêu cầu coordinator khởi động transaction, coordinator sẽ tạo mới transaction và bắt đầu theo dõi tình trạng của nó Các thao tác sau đó của client sẽ không đi qua coordinator mà sẽ trực tiếp gọi đến các server trong hệ thống Các thao tác đọc của client ở giai đoạn này sẽ tạo khóa đọc (read lock) lên tất cả các entry được đọc để đảm bảo dữ liệu không bị thay đổi trong khi transaction đang tiến hành Một entry bị khóa đọc sẽ không cho phép các transaction khác cập nhật (ghi) đè lên entry đó, chỉ có thể đọc Các khóa này đảm bảo tính Isolation của transaction Các thao tác ghi được thực hiện ở giai đoạn này sẽ ở trạng thái chờ và chưa được commit, không được nhìn thấy bởi các thao tác đọc sau đó
Trong pha chuẩn bị, coordinator sẽ gửi yêu cầu chuẩn bị cho tất cả các server tham gia transaction để tạo các khóa ghi (write lock) trên tất cả các dữ liệu bị ảnh hưởng bởi các thao tác ghi của transaction Một entry bị khóa ghi sẽ không cho phép bất cứ transaction nào khác thực hiện đọc hoặc ghi entry đó
Nếu lấy được tất cả khóa ghi thành công, các servers sẽ phản hồi thành công với coordinator, đồng nghĩa đã chuẩn bị mọi thứ sẵn sàng để commit Coordinator lúc này sẽ mặc định server đó chắc chắn thành công trong pha commit Nếu nhận được phản hồi thành công từ tất cả server tham gia transaction, coordinator sẽ tiến đến pha commit Ngược lại, nếu có ít nhất một server phản hồi không thành công hoặc timeout, coordinator sẽ hiểu rằng server đó không thể commit local transaction của mình Lúc này coordinator sẽ hủy bỏ transaction và gửi yêu cầu rollback đến các server tham gia
Hình 5.2: Coordinator rollback transaction sau khi nhận thấy một server không chuẩn bị thành công
Pha xác nhận (Commit phase) Ở pha commit, coordinator gửi yêu cầu commit đến tất cả server tham gia transaction Server nhận được yêu cầu sẽ thực hiện commit local transaction của mình và gửi phản hồi thành công cho coordinator Nếu có một server sập sau khi đã chuẩn bị hoặc không thể hoàn tất pha commit local transaction của nó, coordinator sẽ gửi lại yêu cầu cho đến khi thành công, do ở pha chuẩn bị server đã đảm bảo rằng nó sẽ commit thành công
29 Để tăng fault tolerance, ta có thể sử dụng Write-ahead log lưu trên ổ cứng và sử dụng để lưu trạng thái, tiến độ của các transaction để phòng trường hợp coordinator sập Bằng cách đó, các transaction có thể được phục hồi từ WAL khi có một server mới được chọn làm coordinator Các cluster cũng có thể sử dụng WAL và áp dụng Replicated log (chương 8, Consensus) để lưu trạng thái của các transaction xảy ra trên các server thuộc cluster đó và đồng bộ các thay đổi giữa các node trong cùng một cluster Thêm vào đó, các server cũng cần có tính idempotent khi xử lý các yêu cầu từ coordinator, để đáp ứng việc coordinator mới sau khi phục hồi các transaction từ WAL có thể gửi lại các yêu cầu prepare/commit
Hình 5.3: Coordinator và các cluster sử dụng Write-ahead log để lưu trạng thái và tiến độ của các transaction
5.2.2 Xử lý lỗi người tham gia
Lỗi của người tham gia trong giai đoạn biểu quyết (Voting phase)
Nếu một người tham gia gặp sự cố trong giai đoạn bỏ phiếu trước khi trả lời cho người điều phối, người điều phối sẽ tính là hết thời gian chờ đợi và giả định một phiếu "Không" thay mặt cho người tham gia này Điều này có nghĩa là phương thức sẽ kết thúc bằng việc hủy bỏ giao dịch
Lỗi của người tham gia trong giai đoạn xác nhận (Commit phase)
Trong tình huống này, một người tham gia bỏ phiếu trong giai đoạn bỏ phiếu nhưng sau đó gặp sự cố trước khi nhận thông điệp từ người điều phối và hoàn tất giao dịch (bằng cách cam kết hoặc hủy bỏ)
Trong trường hợp này, giao thức sẽ kết thúc mà không bao gồm nút này Nếu nút này khôi phục sau này, nó sẽ xác định giao dịch đang chờ xử lý và liên lạc với người điều phối để biết kết quả là gì và hoàn tất nó theo cùng một cách
Vì vậy, nếu kết quả của giao dịch là thành công, bất kỳ người tham gia nào gặp sự cố thì cuối cùng cũng được biết khi họ khôi phục và cam kết nó Giao thức không cho phép hủy bỏ nó một mình Do đó, tính nguyên tử được bảo quản
Lỗi mạng cũng sẽ cho kết quả tương tự với những gì được mô tả trước, bởi vì việc hết hạn thời gian chờ sẽ làm cho chúng tương đương với việc nút xảy ra sự cố
5.2.3 Tính chặn (Blocking) trong giao thức 2PC
Thuật toán Two-phase commit tuy đã chứng minh được hiệu quả trong đa số trường hợp nhưng vẫn còn nhiều hạn chế Một vấn đề lớn chưa được 2PC giải quyết đó là sự thiếu ổn định của các server và của đường truyền Trường hợp xấu nhất có thể xảy ra là coordinator ngừng hoạt động sau pha chuẩn bị và một số server trong cluster cũng đồng thời ngừng hoạt động Lúc này, các server còn lại sẽ không có cách nào để biết liệu coordinator đã quyết định commit hay rollback - cho dù chúng có thể giao tiếp và biết được quyết định của nhau thì cũng không có cách nào để biết quyết định của các server đã ngưng hoạt động cùng coordinator Trong trường hợp này, các server còn hoạt động không thể commit mà cũng không thể rollback, do các server đã ngưng hoạt động có thể đã thực hiện ngược lại Tình trạng này không thể thoát khỏi mất đồng bộ, mất tính nhất quán giữa các server trong cluster Điều này có nghĩa rằng sự cố của người điều phối có thể làm giảm tính sẵn sàng của hệ thống một cách đáng kể Hơn nữa, nếu dữ liệu từ ổ đĩa của người điều phối không thể được phục hồi (ví dụ, do hỏng ổ đĩa), kết quả của các giao dịch đang chờ xử lý không thể được tìm thấy, và có thể cần phải can thiệp thủ công để mở khóa giao thức
Hình 5.4: Coordinator sập cùng một số server khác, khiến cho các server còn lại không thể quyết định tiếp ở pha commit
Xác nhận 3 pha “3-Phase Commit”
5.3.1 Các vấn đề của 2PC
Như đã mô tả ở phần trên, vấn đề chính của 2PC là sự số của người điều phối dẫn đến hệ thống bị chặn Cách giải quyết lý tưởng là người tham gia có khả năng tiếp quản và tiếp tục thực hiện giao thức trong trường hợp này, nhưng điều này không dễ dàng
Nguyên nhân cơ bản là trong giai đoạn cam kết, các người tham gia không biết trạng thái của các người tham gia khác - chỉ có người điều phối biết Vì vậy, việc tiếp tục mà không cần chờ đợi người điều phối có thể dẫn đến việc vi phạm tính nguyên tử
5.3.2 Giải quyết vấn đề 2PC bằng 3PC
Vấn đề của giao thức 2PC có thể được giải quyết bằng cách chia giai đoạn đầu tiên (giai đoạn bỏ phiếu) thành 2 giai đoạn con, trong đó người điều phối trước tiên thông báo kết quả phiếu bầu cho các nút, chờ sự xác nhận, và sau đó tiến hành với thông điệp cam kết hoặc hủy bỏ
Trong trường hợp này, các người tham gia sẽ biết kết quả từ các phiếu bầu và hoàn thành giao thức độc lập trong trường hợp người điều phối gặp sự cố Điều này chính là giao thức 3-phase commit (3PC)
Lợi ích chính của giao thức này là người điều phối không còn là một điểm thất bại duy nhất (Single Point of Failure – SPOF) Trong trường hợp người điều phối gặp sự cố, các người tham gia có khả năng tiếp quản và hoàn thành giao thức
Trong giao thức 3PC, khi người điều phối (coordinator) gặp sự cố và không thể tiếp tục quá trình quản lý giao dịch, một thành viên khác trong quá trình đóng vai trò người điều phối (coordinator) thay thế
Người tham gia tiếp quản có thể thực hiện giao dịch nếu nhận được thông báo prepare- to-commit, biết rằng tất cả những người tham gia đã bỏ phiếu “Có” Nếu nó không nhận được thông báo prepare-to-commit, nó có thể hủy giao dịch trong trường hợp không có người tham gia nào đã cam kết mà không cần bất cứ những người tham gia nhận được thông báo prepare- to-commit trước
Kết quả là, giao thức 3PC tăng khả năng khả dụng và ngăn người điều phối trở thành một điểm thất bại duy nhất Tuy nhiên, điều này đi kèm với chi phí về tính chính xác, vì giao thức dễ bị ảnh hưởng bởi sự cố như phân mảnh mạng
Hình 5.5: Quy trình thực hiện thuật toán Three-phase commit
5.3.4 Lỗi Phân mảnh mạng trong 3PC
Hình 5.6: Lỗi phân mảnh mạng
Trong ví dụ này, một phân mảnh mạng xảy ra tại điểm mà người điều phối chỉ gửi thông điệp "prepare-to-commit" đến một số nút tham gia Trong khi đó, người điều phối gặp sự cố ngay sau điểm này, do đó, các nút tham gia vượt quá thời gian chờ và phải hoàn thành quá trình giao thức một cách tự động Ở một phía của phân mảnh, các nút tham gia nhận được thông điệp "prepare-to-commit" và tiếp tục thực hiện việc xác nhận giao dịch Tuy nhiên, các nút tham gia ở phía khác của phân mảnh không nhận được thông điệp "prepare-to-commit", do đó, chúng tự ý hủy bỏ giao dịch mà không cần đợi thông điệp "prepare-to-commit".
Saga
Khác với thực hiện một transaction đơn nhất trên nhiều node như 2PC và 3PC, Saga là một chuỗi các local transaction nối tiếp nhau Mỗi service thực hiện local transaction của mình và gửi message/event để thông báo thực hiện cho một service khác transaction kế tiếp Khi local transaction không thành công, service sẽ gửi thông báo để các service đã thực hiện local transaction trước đó thực hiện compensating transaction Compensating transaction là một transaction riêng biệt được service node thực hiện để hoàn tác local transaction đã thực hiện trước đó Các service giao tiếp với nhau thông qua một cơ chế thông báo lightweight như message bus, event bus (VD: RabbitMQ, Apache Kafka, )
Hình 5.7: Một saga bao gồm 4 local transaction nối tiếp nhau
Có 2 phương pháp thực hiện saga:
- Choreography: Mỗi service listen trên các channel event của message bus và thực hiện transaction của chúng khi có event được gửi Logic của saga choreography phân bố giữa các service và mỗi service phải xử lý event từ tất cả các saga mà nó tham gia Do đó, khó để duy trì các saga hiện có và khó tích hợp các saga mới, dẫn đến mỗi team phải implement logic saga trên service của mình
Hình 5.8: Thực hiện Saga dựa trên phương pháp Choreography
- Orchestration: Sử dụng một object saga orchestrator để điều phối các service thực hiện transaction của chúng qua các command Sau khi hoàn tất transaction, service sẽ gửi event xác nhận vào message bus Orchestrator quan sát thấy event, xác nhận local transaction của service đã thành công và tiếp tục local transaction ở service tiếp theo trong saga Logic của saga orchestration tập trung ở orchestrator nên các service không cần phải phản ứng với event của tất cả saga, mà chỉ cần phản hồi các command yêu cầu thực hiện transaction Chỉ cần 1 service có vai trò chính trong saga đóng vai trò là orchestrator Tuy nhiên, orchestrator là một điểm single point of failure; nếu service có vài trò làm orchestrator ngừng hoạt động có thể gây ra dữ liệu không nhất quán và khiến saga không hoàn tất được
Hình 5.9: Thực hiện Saga dựa trên phương pháp Orchestrator
Một vấn đề quan trọng trong Saga là đảm bảo bước thực hiện local transaction và bước gửi message được thực hiện một cách đơn nhất (atomic), để không có tình trạng service ngừng hoạt động sau khi thực hiện local transaction nhưng lại trước khi gửi message, dẫn tới mất nhất quán giữa service đó - cũng như các service đã thực hiện local transaction trước nó - và các service còn lại trong saga Để đảm bảo yêu cầu này, ta có thể áp dụng các phương pháp sau:
- Transactional outbox: ghi message vào một table trong database (table outbox), table này dùng để ghi nhớ các message được phát đi bởi service; khi này, ta hoàn toàn có thể gói gọn các thao tác của local transaction và thao tác ghi message trong cùng một database transaction Sau đó, có 2 cách để gửi message tới message bus
Hình 5.10: Transaction log tailing o Transaction log tailing: subscribe vào các thay đổi trong transaction log của database, mỗi khi có transaction commit vào table outbox, ta sẽ gửi đi message tương ứng được lưu trong table outbox Ưu điểm của cách này là độ chính xác cao, tuy nhiên nhược điểm là khó tránh khỏi việc publish một message nhiều lần, đồng thời, transaction log của mỗi hệ quản trị database là khác nhau nên giải pháp cũng sẽ phụ thuộc vào công nghệ sử dụng
Hình 5.11: Polling publisher o Polling publisher: Thay vì theo dõi transaction log như Transaction log tailing, cách này liên tục đọc table outbox để nhận biết các message mới được thêm vào Ưu điểm dễ nhận thấy của cách này là không bị phụ thuộc vào công nghệ database, có thể hoạt động với bất kỳ RDBMS nào Ngược lại, cách này có một số khuyết điểm là không dễ để publish các message theo trình tự và không được tất cả database NoSQL hỗ trợ
- Event sourcing: là một kiến trúc lưu trữ trong đó database không lưu các domain object ở trạng thái hiện tại mà thay vào đó là lưu các sự kiện (event) đã xảy ra với các domain object đó Từ đó, ta có thể tái tạo một domain object tại một thời điểm bất kỳ trong suốt quá trình sống của hệ thống bằng cách thực hiện lần lượt các event đã diễn ra trên domain object đó Có một số framework hỗ trợ xây dựng hệ thống event sourcing như Eventuate Tram, Eventuate Local Service có thể subscribe vào event store để nhận được thông báo khi có event được thêm vào store, qua đó xử lý một cách tương ứng
Hình 5.12: Lưu trữ database thông thường so với kiến trúc Event sourcing
BỘ NHỚ ĐỆM
Tổng quản về bộ nhớ đệm
Caching là phương pháp lưu kết quả của các thao tác, các phép tính trước vào một bộ nhớ dễ truy cập và có độ trễ nhỏ, để phục vụ cho việc tái sử dụng trong tương lai Các công dụng chính của caching:
- Giảm thời gian phản hồi, tăng performance, scalability, availability
- Giảm tải lên các server
- Giảm nguy cơ quá tải các server từ tấn công DoS
Caching là phương pháp lưu kết quả của các thao tác, các phép tính trước vào một bộ nhớ dễ truy cập và có độ trễ nhỏ, để phục vụ cho việc tái sử dụng trong tương lai Các công dụng chính của caching:
- Giảm thời gian phản hồi, tăng performance, scalability, availability
- Giảm tải lên các server
- Giảm nguy cơ quá tải các server từ tấn công DoS
Caching có thể được thực hiện ở nhiều vị trí trong hệ thống:
Hình 6.1: Cache ở nhiều vị trí trong hệ thống
Cơ chế lưu trữ
Việc lưu trữ dữ liệu trong bộ đệm thường ít được quan tâm nhưng không phải lúc nào cũng đơn giản, bởi vì bộ đệm phân tán có nhiều máy chủ bộ đệm Khi sử dụng nhiều máy chủ bộ đệm, chúng ta phải đặt những câu hỏi thiết kế sau:
- Dữ liệu nào nên được lưu trữ trên máy chủ bộ đệm nào?
- Cấu trúc dữ liệu nào nên được sử dụng để lưu trữ dữ liệu?
Chức năng Hash
Hàm băm (hash function) được sử dụng theo 2 cách khác nhau:
- Xác định máy chủ bộ đệm trong một hệ thống bộ đệm phân tán để lưu trữ và truy xuất dữ liệu: Trong tình huống này, ta thường sử dụng các thuật toán băm khác nhau để xác định máy chủ bộ đệm cụ thể mà dữ liệu nên được lưu trữ và truy xuất
- Định vị các mục bộ đệm bên trong từng máy chủ bộ đệm: Trong tình huống này, ta sử dụng hàm băm để xác định một mục cụ thể trong máy chủ bộ đệm mà ta muốn đọc hoặc ghi Ưu điểm:
- Cho phép xác định mục cụ thể một cách nhanh chóng
- Không đề cập đến việc lưu trữ dữ liệu
- Không đề cập đến việc cấu trúc dữ liệu để lưu trữ.
Danh sách liên kết
Danh sách liên kết kép (doubly linked list) được sử dụng phổ biến và đơn Trong trường hợp này, thời gian cho việc thực thi thêm và xóa dữ liệu từ danh sách liên kết kép sẽ luôn cố định Bởi vì trong danh sách liên kết một là loại bỏ một mục cụ thể từ đuôi danh sách liên kết (tail), và hai là di chuyển một mục đến đầu danh sách liên kết kép Ưu điểm:
- Phù hợp cho các chiến lược loại bỏ dữ liệu ít được truy cập
- Tốn tài nguyên lưu trữ
- Không phù hợp với các trường hợp cần cấu trúc dữ liệu nhanh chóng như bảng băm.
Bộ nhớ đệm vô hiệu hóa
Ngoài việc loại bỏ dữ liệu ít được truy cập, một số dữ liệu nằm trong bộ đệm có thể trở nên lỗi thời hoặc đã cũ theo thời gian Các mục bộ đệm như vậy không còn hiệu lực và cần được đánh dấu để xóa đi Để giải quyết vấn đề này, chúng ta phải áp dụng thời gian gian sống (time to live - TTL) hoặc hạn sử dụng đến những khóa của bộ nhớ đệm
Chúng ta có thể sử dụng hai phương pháp khác nhau để xử lý các dữ liệu đã cũ bằng cách sử dụng TTL (thời gian sống):
- Hết hạn hoạt động (Active expiration): Phương pháp này đòi hỏi kiểm tra TTL của các mục trong bộ đệm một cách tích cực thông qua một tiến trình hoặc luồng dịch vụ đa nhiệm (daemon process hoặc thread)
- Hết hạn không hoạt động (Passive expiration): Phương pháp này kiểm tra TTL của một mục trong bộ đệm tại thời điểm truy cập, tức là khi mục đó được truy cập.
Nguyên lý xóa bỏ
Cache là một bộ nhớ có độ trễ truy cập nhỏ hơn nhiều lần so với các database, nhưng đổi lại thì dung lượng của cache cũng nhỏ hơn nhiều so với các ổ đĩa cứng sử dụng để lưu trữ dữ liệu trong các database Nguyên nhân là do phần cứng hỗ trợ của cache thường là bộ nhớ chính (RAM) hoặc các register có dung lượng nhỏ hơn nữa Do đó nên ta cần sử dụng dung lượng trong cache để ưu tiên cho các dữ liệu quan trọng, được truy cập nhiều hoặc có chi phí tính toán cao
Có nhiều thuật toán được sử dụng để xác định mức ưu tiên của dữ liệu, gọi chung là các thuật toán làm trống cache (cache eviction policy):
- Least recently used (LRU): loại bỏ các dữ liệu được truy cập ít nhất trong khoảng thời gian gần nhất để ưu tiên cho các dữ liệu được sử dụng tức thì
- Least frequently used (LFU): loại bỏ các dữ liệu được sử dụng ít thường xuyên nhất để ưu tiên cho các dữ liệu được sử dụng nhiều hơn; thuật toán này ghi nhớ tần suất truy cập của mỗi entry dữ liệu
- Most recently used (MRU): loại bỏ các dữ liệu được sử dụng gần đây nhất, ưu tiên các dữ liệu chưa được sử dụng; thuật toán này thường được ứng dụng trong các hệ thống gợi ý dữ liệu mới, chẳng hạn như Tinder
- Random replacement: loại bỏ dữ liệu một cách ngẫu nhiên
Có các phương pháp khác như “đầu vào đầu ra” (FIFO) Sự lựa chọn của từng thuật toán phụ thuộc vào bộ đệm - cache được phát triển trên hệ thống
SERVICE DISCOVERY & API GATEWAY
Tổng quan về Service Discovery
Khi có nhiều service chạy cùng lúc, việc tìm địa chỉ IP và port của một service cụ thể có thể trở nên phức tạp Đặc biệt khi chạy trên cloud, địa chỉ IP của các service có thể thay đổi thường xuyên do việc tạo và xóa máy ảo hay container là chuyện thường trong môi trường cloud Để giải quyết vấn đề trên, Service Discovery được sinh ra
Service Discovery là một khái niệm trong kiến trúc phân tán, dùng để quản lý và tìm kiếm các dịch vụ trong một hệ thống phân tán Nó giúp các thành phần của hệ thống biết được đâu là dịch vụ cần kết nối để có thể giao tiếp với nhau
Service Discovery có ba thành phần:
- Cung cấp dịch vụ (Service Provider): Cung cấp dịch vụ thông qua mạng
- Đăng ký dịch vụ (Service Registry): Cơ sơ dữ liệu chứa tất cả thông tin các service đã được đăng ký, là nơi cho phép các dịch vụ đăng ký địa chỉ IP, port và tên của chúng
- Sử dụng dịch vụ (Service Consumer): Service cần giao tiếp với Service khác, nó truy cập Service Registry để tìm kiếm thông tin về service đó và thiết lập kết nối.
Phân loại Service Discovery
Client-side Service Discovery, client hoặc API gateway thực hiện yêu cầu chịu trách nhiệm xác định vị trí của service instance và định tuyến yêu cầu đến nó Client bắt đầu bằng cách truy vấn service registry để xác định vị trí của các instance của service có sẵn và sau đó xác định instance nào sẽ sử dụng
Hình 7.1: Ảnh minh họa Client side Service Discovery Ưu Điểm:
- Client có thể định tuyến chính xác theo yêu cầu
- Quyết định về cân bằng tải có thể được tùy chỉnh cho ứng dụng cụ thể, như sử dụng hashing
- Nó ít phức tạp và có thể xử lý dễ dàng
- Nó không cần định tuyến ứng dụng hoặc yêu cầu qua bộ định tuyến hoặc cân bằng tải
- Tránh các bước phụ thêm giữa và dẫn đến thời gian phản hồi nhanh chóng
- Cần có sự chặt chẽ của client với registry dịch vụ, điều này có nghĩa là service discovery phải được triển khai cho mỗi ngôn ngữ lập trình và framework được sử dụng bởi các service clients
- Nó làm cho quản lý ứng dụng phức tạp khi kiến trúc microservices làm việc với các công nghệ, framework và công cụ đa dạng của các ngôn ngữ khác nhau
- Client phải thực hiện hai lần yêu cầu để có thể lấy tài nguyên từ dịch vụ mà nó cần
Trong khi đó, với Server-side Service Discovery, client không cần phải biết về service registry Các yêu cầu được thực hiện thông qua một router, sau đó router tìm kiếm service registry Ví dụ về Server-side Discovery là AWS Elastic Load Balancer (ELB)
Hình 7.2: Mô hình minh hoa về service discovery server side Ưu điểm:
- Nó không đòi hỏi clients nắm các thông tin về các dịch vụ có sẵn
- Nó tạo ra một lớp trừu tượng giữa client và server side
- Nó loại bỏ nhu cầu triển khai service discovery riêng lẻ cho từng ngôn ngữ và framework mà service client sử dụng
- Nó có sẵn miễn phí trong một số môi trường triển khai
- Server side Service Discovery cần được thiết lập và quản lý trừ khi được cung cấp sẵn một giải pháp hoặc công cụ service discovery Server side
- Cần triển khai cơ chế cân bằng tải cho máy chủ trung tâm
- Nó không cho phép máy khách lựa chọn một phiên bản dịch vụ phù hợp.
Phân loại hình thức đăng ký
Trong hình thức này, các service tự quản lý việc đăng ký và hủy đăng ký với Service Registry Nó cũng có thể gửi yêu cầu heartbeat để ngăn đăng ký hết hạn trong khi nó vẫn còn hoạt động
Tự đăng ký đơn giản và không phụ thuộc vào bất kỳ thành phần hệ thống nào khác Nó đăng ký địa chỉ của mình với Service Registry và ngay lập tức hủy đăng ký khi phiên bản kết thúc Tuy nhiên, nó kết nối từng service với Service Registry, điều này có nghĩa bạn phải triển khai đăng ký trong mọi ngôn ngữ lập trình và framework được sử dụng trong mỗi service
Hình 7.3: Minh họa Self-registration
Trong hình thức này, các service tự quản lý việc đăng ký và hủy đăng ký với Service Registry Nó cũng có thể gửi yêu cầu heartbeat để ngăn đăng ký hết hạn trong khi nó vẫn còn hoạt động
Tự đăng ký đơn giản và không phụ thuộc vào bất kỳ thành phần hệ thống nào khác Nó đăng ký địa chỉ của mình với Service Registry và ngay lập tức hủy đăng ký khi phiên bản kết thúc Tuy nhiên, nó kết nối từng service với Service Registry, điều này có nghĩa bạn phải triển khai đăng ký trong mọi ngôn ngữ lập trình và framework được sử dụng trong mỗi service
Hình 7.4: Minh họa Self-registration
CONSENSUS
Vấn đề Consensus
Một vấn đề cốt lõi trong các hệ thống phân tán là đảm bảo tính nhất quán về dữ liệu giữa các node trong cluster server (cluster), cũng như đảm bảo độ tin cậy, tính sẵn sàng của toàn hệ thống Để đáp ứng các tiêu chí trên đòi hỏi các node trong hệ thống đạt được sự thống nhất về trạng thái Vấn đề về sự thống nhất giữa các node gọi là Consensus, cụ thể vấn đề thống nhất về trạng thái để đảm bảo tính nhất quán gọi là State machine replication State machine replication được ứng dụng vào các pattern như Replicated log, Write-ahead log,
8.1.1 Định nghĩa về vấn đề Consensus
Giả sử chúng ta có một hệ thống phân tán gồm k nút (n1, n2, …, nk), trong đó mỗi nút có thể đề xuất một giá trị khác nhau
Vấn đề thống nhất (Consensus problem) là vấn đề làm cho tất cả các nút này đồng ý về một giá trị duy nhất Đồng thời, các tính chất sau phải được thỏa mãn:
- Kết thúc (Termination): Mọi nút không bị lỗi cuối cùng phải quyết định
- Thỏa thuận (Agreement): Quyết định cuối cùng của mọi nút không bị lỗi phải giống nhau
- Độ chính xác (Validity): Giá trị được đồng thuận phải được đề xuất trước đó bởi một trong các nút.
8.1.2 Các use-case của sự thống nhất
56 a Bầu chọn leader Đây là một vấn đề phổ biến khi các nút trong một hệ thống phân tán cần bầu chọn một nút từ trong số họ để hoạt động như người điều phối (leader) và điều phối hoạt động của toàn bộ hệ thống
Một ví dụ về vấn đề này là single-master replication schema
Schema này dựa trên việc một nút được chỉ định là chính (primary), sẽ chịu trách nhiệm thực hiện các hoạt động cập nhật dữ liệu Các nút khác, được chỉ định là phụ (secondaries), sẽ thực hiện các hoạt động tương tự
Tuy nhiên, trước tiên hệ thống cần chọn nút chính thông qua một quy trình gọi là bầu chọn leader Bởi vì tất cả các nút thực tế đều đồng ý về một giá trị duy nhất, danh tính của leader, vấn đề này có thể dễ dàng được mô hình hóa như một vấn đề thống nhất b Khóa phân phối
Một vấn đề phổ biến khác là khóa phân tán (distributed locking) Hầu hết các hệ thống phân tán tiếp nhận đồng thời nhiều yêu cầu và cần thực hiện kiểm soát đồng thời để ngăn chặn sự không nhất quán dữ liệu do sự can thiệp giữa các yêu cầu này
Một trong các phương pháp kiểm soát đồng thời này là sử dụng khoá (locking) Tuy nhiên, việc sử dụng khoá trong hệ thống phân tán đem lại nhiều trường hợp rủi ro
Tất nhiên, khoá phân tán cũng có thể được mô hình hóa như một vấn đề thống nhất, trong đó các nút của hệ thống đồng ý về một giá trị duy nhất, đó là nút nắm giữ khoá c Truyền tải nguyên tử
Một vấn đề thường được đề cập là việc truyền tải nguyên tử (atomic broadcast), liên quan đến việc cho phép một tập hợp các nút truyền tải các thông điệp đồng thời, trong khi đảm bảo rằng tất cả các điểm đến đều phân phối chúng theo cùng một tuần tự mặc dù có thể có sự xuất hiện của các nút bị lỗi Vấn đề này cũng tương đương vấn đề thống nhất.
Thuật toán Raft
Raft đặt ra khái niệm về máy trạng thái sao chép (replicated state machine) và nhật ký sao chép (replicated log of commands) là những thực thể hàng đầu (first-class citizens) trong hệ thống
Nó yêu cầu một tập hợp các nút tạo thành nhóm thống nhất (consensus group), được gọi là cụm Raft (Raft cluster) Mỗi nút có thể trong ba trạng thái sau:
- Ứng cử viên (Candidate) a Trạng thái nút (Node states)
Cluster có một node leader, gọi là leader, sẽ gửi heartbeat (nhịp tim) đến tất cả các node khác trong cluster, các node này gọi là những follower, để thông báo cho chúng về sự hiện diện của một leader Mỗi follower có một timer, được reset về một khoảng thời gian ngẫu nhiên khi nhận được heartbeat từ leader Chỉ có thể có một leader tại một thời điểm Một server có thể ở một trong ba trạng thái: follower, candidate (candidate), leader
Hình 8.2: Trạng thái các nút trong Raf t b Nhiệm kỳ (terms) Để ngăn chặn việc xuất hiện đồng thời hai người điều phối, Raft có một khái niệm về nhiệm kỳ (terms)
Thời gian được chia thành các nhiệm kỳ, một nhiệm kỳ được đánh số bằng các số nguyên liên tiếp Mỗi nhiệm kỳ bắt đầu bằng một cuộc bầu cử trong đó một hoặc nhiều ứng cử viên (candidate) cố gắng trở thành người điều phối (leader) Để trở thành người điều phối (leader), ứng cử viên cần nhận được phiếu bầu từ đa số các nút Bên cạnh đó, Mỗi nút bầu cho tối đa một ứng cử viên trong một nhiệm kỳ theo nguyên
58 tắc cơ bản "first-come-first-served" Do đó, tối đa chỉ có một nút có thể thắng cuộc trong cuộc bầu cử cho một nhiệm kỳ cụ thể quyết định bầu cử của mỗi server được lưu giữ trên disk để tránh bầu lại do sự cố
Nếu một ứng cử viên thắng trong cuộc bầu cử, nó sẽ trở thành người điều phối trong suốt phần còn lại của nhiệm kỳ đó Bất kỳ người điều phối từ các nhiệm kỳ trước đây sẽ không thể sao chép các mục nhật ký (log entries) mới đến các nhóm vì những người bỏ phiếu cho người điều phối mới sẽ từ chối các yêu cầu của người điều phối cũ, và cuối cùng nó sẽ phát hiện mình đã bị thay thế
Nếu không có ứng cử viên nào đạt có được đa số phiếu trong một nhiệm kỳ, nhiệm kỳ này sẽ kết thúc mà không có người điều phối, và một nhiệm kỳ mới (với một cuộc bầu cử mới) bắt đầu ngay sau đó
8.2.2 Giao tiếp giữa các nút Raft a Cơ chế giao tiếp
Các nút giao tiếp thông qua cuộc gọi thủ tục từ xa (Remote Procedure calls – RPCs), và Raft có hai loại RPC cơ bản:
- RequestVote: Được gửi bởi ứng cử viên trong quá trình bầu cử
- AppendEntries: Được gửi bởi người điều phối (leader) để sao chép các log entries và cung cấp một tín hiệu "heartbeat" (tim đập)
Các lệnh được lưu trữ trong một nhật ký – “log” được sao chép cho tất cả các nút trong cụm.
Các log entries được đánh số thứ tự, và chúng chứa nhiệm kỳ mà chúng được tạo ra và các lệnh liên quan cho state machine
Hình 8.3: Cấu trúc của Replicated log
Một mục được xem là đã committed nếu nó có thể được áp dụng vào state machine của các nút Raft đảm bảo rằng các committed entries là bền vững và cuối cùng sẽ được thực hiện bởi tất cả các state machine có sẵn, đồng thời đảm bảo rằng không có mục khác sẽ được committed cho cùng một chỉ mục (index) Bên cạnh đó, nó cũng đảm bảo rằng tất cả các mục trước đó đã committed Trạng thái này về cơ bản thông báo rằng thống nhất đã được đạt trong mục này
Mỗi server trong cluster duy trì một write-ahead log chỉ được thêm được sử dụng để lưu trữ các lệnh mà server nhận được, mỗi entry log lưu trữ nhiệm kỳ nhận được lệnh và lệnh (thay đổi trạng thái) Log được lưu trên disk để chống lỗi
Như đã đề cập trước đây, người điều phối có trách nhiệm nhận các lệnh từ “khách hàng” và sao chép chúng qua các cụm Quá trình diễn ra theo thứ tự sau:
- Khi người điều phối nhận được một lệnh mới, nó ghi thêm mục vào nhật ký riêng của mình và sau đó gửi một yêu cầu AppendEntries đồng thời đến các nút khác, nó sẽ lặp lại khi nó không nhận được phản hồi trong một khoảng thời gian từ một trong số các node chưa phản hồi
- Nếu entry được sao chép thành công trên đa số follower Người điều phối sẽ đánh dấu nó là đã commit, thực hiện lệnh và trả lại kết quả cho client
- Người điều phối áp dụng lệnh vào state machine của mình và thông báo cho các người theo (follower) rằng họ cũng có thể commit bằng cách kèm thông tin cần thiết trong các committed entries trong các thông điệp AppendEntries tiếp theo
Khi gửi yêu cầu AppendEntries cho follower, leader cũng bao gồm cặp [nhiệm kỳ, index] của entry cuối cùng của nó Nếu cặp [nhiệm kỳ, index] không khớp với entry cuối cùng của follower, nó sẽ từ chối yêu cầu và gửi lại phản hồi không thành công Leader sẽ quay lui log về entry trước và gửi các cặp [nhiệm kỳ, index] tương ứng cho đến khi khớp với entry cuối cùng của follower hoặc đã đi đến đầu của log, trong trường hợp đó, log của follower hoàn toàn trống
DEPLOYMENT
Các vấn đề khi triển khai một hệ thống
Có một số vấn đề khi triển khai một hệ thống áp dụng kiến trúc microservices bao gồm:
- Security: Khi hệ thống trên môi trường đám mây, ta có thể sẽ gặp những nhân tố xấu muốn đánh cắp thông tin
- Monitoring: Khi triển khai hệ thống microservices thì ta sẽ phải triển khai thêm một hệ thống monitoring phức tạp để có thể thu thập đủ các metrics cần thiết cho việc vận hành hệ thống
- Độ phức tạp: Vận hành một hệ thống microservices có thể sẽ phức tạp hơn so với một hệ thống monolithic
- Giao tiếp: Việc giao tiếp giữa các services có thể gặp khó khăn do sự phân tán của chúng
- Nhiều cách triển khai: Việc chọn cách triển khai phù hợp với hệ thống, đòi hỏi rất nhiều kiến thức từ người thiết kế.
Các mẫu triển khai
9.2.1 Multiple Service Instances per Host
Triển khai các service instance của hệ thống lên mỗi host, một host có thể là physical, virtual machine hoặc container
Hình 9.1 Mô hình mẫu Multiple Service Instances per Host Ưu điểm:
- Sử dụng tài nguyên hiệu quả
- Rủi ro xung đột trong yêu cầu tài nguyên
- Rủi ro xung đột trong instance phụ thuộc
- Khó giới hạn tài nguyên tiêu phụ bởi một service instance
- Nếu nhiều instance được triển khai trong cùng một quá trình, việc theo dõi việc tiêu thụ tài nguyên của mỗi instance là khó khăn
9.2.2 Single Service Instance per Host
Mỗi service instance của hệ thống được triển khai lên mỗi host, một host có thể là physical, virtual machine hoặc container Ưu điểm:
- Cách service instance được cô lập riêng biệt
- Không có khả năng xung đột trong yêu cầu tài nguyên hoặc instance phụ thuộc
- Một instance chỉ tiêu thụ tối đa tài nguyên của một host duy nhất
- Dễ dàng theo dõi, quản lý, và retriển khai mỗi service instance
- Triển khai khó khăn hơn
- Sử dụng tài nguyên không hiệu quả bởi vì có nhiều host
Hình 9.2 Mô hình mẫu Single Service Instance per Virtual Machine
Hình 9.3 Mô hình mẫu Single Service Instance per Container
Serverless là một trong những xu hướng trong công nghệ đám mây Một mô hình phát triển được xây dựng để tạo và chạy các ứng dụng mà không cần quản lý máy chủ Triển khai dùng serverless dựa trên hai thành phần chính: Function as a Service (FaaS) và Backend as a Service (BaaS) FaaS là một dịch vụ tính toán cần thiết để chạy các hàm độc lập được trong môi trường đám mây Ưu điểm:
- Loại bỏ việc quản lý cơ sở hạ tầng cấp thấp, tập trung vào phát triển serivce
- Cơ sở hạ tầng linh hoạt, tự động mở rộng service để xử lý tải
- Chỉ tính phí cho mỗi request thay vì cung cấp máy ảo hoặc container có thể không được sử dụng hiệu quả
- Hạn chế và ràng buộc đáng kể - Môi trường triển khai serverless thường có nhiều hạn chế hơn so với cơ sở hạ tầng dựa trên máy ảo hoặc container Ví dụ, AWS Lambda chỉ hỗ trợ vài ngôn ngữ Nó chỉ phù hợp để triển khai ứng dụng không lưu
67 trạng thái chạy dựa trên yêu cầu thay vì triển khai ứng dụng lưu trạng thái hoạt động lâu dài như database hoặc message broker
- Hạn chế nguồn đầu vào – Lambda chỉ có thể phản ứng với yêu cầu từ một số nguồn đầu vào hạn chế
- Ứng dụng phải khởi động nhanh
- Rủi ro về độ trễ cao.
Docker
Docker là một nền tảng mở cho việc phát triển, vận chuyển, và chạy các ứng dụng Docker cho phép tách các ứng dụng khỏi cơ sở hạ tầng để vận chuyển và triển khai phần mềm nhanh chóng Với Docker, ta có thể quản lý cơ sở hạ tầng của mình theo cách tương tự quản lý các ứng dụng Bằng cách tận dụng các phương pháp của Docker để vận chuyển, kiểm thử và triển khai mã, giúp ta có thể giảm đáng kể thời gian trễ giữa việc viết mã và chạy mã đó trong môi trường thực tế
Một số tính năng quan trọng của Docker:
- Containers: Tính năng chính của Docker, cho phép đóng gói các ứng dụng và các dependency của nó vào một container, tách biệt với môi trường máy chủ
- Tích hợp và hỗ trợ đa nền tảng: Docker có thể chạy trên nhiều nền tảng khác nhau bao gồm máy tính cá nhân, máy chủ vật lý, cloud public (như AWS, Azure, Google Cloud), private cloud, và cả môi trường hybrid
- Quản lý tài nguyên: Docker cung cấp cách quản lý tài nguyên hiệu quả hơn bằng cách chia sẻ tài nguyên từ host và giữ container nhẹ nhàng, tối ưu hóa sử dụng tài nguyên
- Cơ chế mạng và lưu trữ: Docker cung cấp cơ chế mạng và lưu trữ để kết nối các container với nhau hoặc với bên ngoài
- Quản lý vòng đời ứng dụng: Docker cung cấp công cụ để phát triển, thử nghiệm và triển khai ứng dụng, tạo quy trình dễ quản lý từ khi phát triển cho đến khi đi vào sản xuất
- Tích hợp và tính di động: Docker có thể tích hợp dễ dàng với các công cụ và hệ thống hiện có, cũng như di chuyển ứng dụng từ một môi trường sang môi trường khác một cách dễ dàng
- An toàn và bảo mật: Docker cung cấp các công cụ để kiểm tra và cung cấp môi trường bảo mật cho các container, ngăn chặn các rủi ro bảo mật tiềm ẩn
9.3.2 Triển khai microservices với Docker
Các bước để để đóng gói và triển khai ứng dụng với Docker:
- Tạo Dockerfile cho từng service, mô tả cách xây dựng image
- Sử dụng lệnh “docker build” để tạo image từ Docker cho từng Microservice
- Tạo file “docker-compose.yml” để cấu hình các service từ các image được build
- Sử dụng lệnh “docker-compose up” để khởi động các service.
THIẾT KẾ VÀ XÂY DỰNG HỆ THỐNG SPRING BOOT
Tổng quan về Spring
10.1.1 Tổng quan về Spring Boot Đầu tiên chúng ta cần biết Spring là gì? Spring được xem là một Framework có vai trò phát triển cho các ứng dụng Java Trong số đó, phổ biến nhất là Java Enterprise và nó được sử dụng bởi hàng triệu lập trình viên khác nhau Framework này được phát triển đầu tiên bởi Rod Johnson và được ban hành giấy phép Apache 2.0
Spring Boot là một trong số những module của Spring Framework chuyên cung cấp các tính năng Rapid Application Development (RAD) để tạo ra và phát triển nhanh các ứng dụng độc lập dựa trên Spring Nó cung cấp một cách nhanh chóng và dễ dàng để xây dựng các ứng dụng Java chất lượng cao, hiệu quả và có thể mở rộng Spring Boot giúp giải quyết các vấn đề thông thường trong phát triển ứng dụng backend, bao gồm cấu hình, quản lý phụ thuộc, bảo mật, kiểm thử và triển khai Nó cung cấp cho lập trình viên một cấu trúc dự án đơn giản và sẵn sàng, tự động cấu hình nhiều thứ và đưa ra các quy ước phát triển
10.1.2 Các tính năng chính của Spring Boot
AutoConfiguration: Tự động cấu hình là một cách tiếp cận Spring Boot cốt lõi để giảm số lượng hành động mà nhà phát triển phải thực hiện Nó tự động cấu hình một ứng dụng Spring dựa trên các thành phần bạn đã thêm trước đó Tự động cấu hình Spring Boot cung cấp các tính năng mặc định mạnh mẽ đồng thời tính linh hoạt cao
Opinionated Dependencies: Opinionated có nghĩa là Spring Boot tự quyết định tập hợp các bean được định cấu hình mặc định mà bạn có thể ghi đè nếu cần Hơn nữa, framework này chọn các gói để cài đặt cho các phần dependencies mà bạn có thể cần Bằng cách này, các nhà phát triển Spring Boot bắt đầu xây dựng ứng dụng của họ ngay lập tức, tập trung nhiều hơn vào logic nghiệp vụ vì phần lớn công việc được thực hiện bởi chính framework
Embedded Servers: Máy chủ nhúng là một phần của ứng dụng Điều đó có nghĩa là bạn không phải cài đặt trước nó trong môi trường triển khai Spring Boot cung cấp một máy chủ nhúng Tomcat theo mặc định, nhưng bạn có thể thay đổi nó thành Jetty hoặc Undertow Máy chủ được nhúng giúp triển khai hiệu quả hơn và giảm thời gian khởi động lại ứng dụng
Standalone applications: Spring Boot cho phép các nhà phát triển thiết lập và chạy các ứng dụng Spring độc lập, cấp sản xuất mà không cần triển khai chúng lên máy chủ web Để chạy một ứng dụng Java, bạn cần phải đóng gói ứng dụng đó, chọn, tải xuống và định cấu hình máy chủ web cũng như tổ chức quá trình triển khai Ngược lại, một ứng dụng Java Spring Boot chỉ cần đóng gói, sau đó nó đã sẵn sàng để chạy, sử dụng các lệnh đơn giản
10.1.3 Kiến trúc của Spring Boot
Có bốn tầng chính trong kiến trúc Spring Boot:
- Tầng Presentation: Đây là tầng mà nơi người dùng cuối sử dụng để tương tác với hệ thống
- Tầng Data Access: Các hoạt động CRUD (tạo, truy xuất, cập nhật, xóa) trên cơ sở dữ liệu thuộc loại này
- Tầng Service: Tầng này bao gồm các lớp dịch vụ và sử dụng các dịch vụ được cung cấp bởi tầng truy cập dữ liệu
- Tầng Tích Hợp Integration: Nó bao gồm các dịch vụ web khác nhau (bất kỳ dịch vụ nào có sẵn trên internet và sử dụng hệ thống truyền tin XML)
Sau đó, chúng ta có các lớp tiện ích (utility classes), lớp kiểm tra hợp lệ (validator classes) và lớp xem (view classes) Tất cả các dịch vụ do các lớp cung cấp được triển khai trong các lớp tương ứng của chúng và được lấy ra bằng cách triển khai sự phụ thuộc vào các giao diện đó
Hình 10.1: Kiến trúc Spring Boot
Spring Boot starter
Trước khi Spring Boot được giới thiệu, các developer Spring thường phải dành nhiều thời gian cho quản lý Dependency Spring Boot Starters được giới thiệu để giải quyết vấn đề này, giúp developer dành thời gian nhiều hơn cho việc lập trình logic hơn là cho Dependencies Spring Boot Starters là mô tả dependency có thể được thêm vào phần
trong tệp pom.xml Hiện nay, Có khoảng 50+ Spring Boot Starters cho các công nghệ khác nhau của Spring và liên quan đến nó Những Starters này cung cấp tất cả các dependency dưới một tên duy nhất Ví dụ, nếu bạn muốn sử dụng Spring Data JPA để truy cập cơ sở dữ liệu, bạn có thể bao gồm dependency spring-boot-starter-data-jpa
Các ưu điểm của việc sử dụng Starters như sau:
- Tăng năng suất bằng cách giảm thời gian cấu hình cho nhà phát triển
- Quản lý POM dễ dàng hơn vì số lượng dependencies cần thêm giảm đi
- Cấu hình dependencies đã được kiểm thử, sẵn sàng cho sản xuất và được hỗ trợ
- Không cần phải nhớ tên và phiên bản của các dependencies
Spring Boot Starter Data JPA được minh họa dưới đây:
Hình 10.2: Dependency Spring Starter Data JPA Điều này cung cấp tất cả các dependencies cần thiết và có thể thấy trong Maven tab của IntelliJ IDEA
Hình 10.3: Các Dependency starter được thêm vào trong maven tab
Spring Data JPA
Spring Boot JPA là một dependency phổ biến trong hệ thống Spring Boot, hoạt động ở giữa tầng service và database với những cấu hình tự động giúp chúng ta giảm thiểu giữa việc tương tác với cơ sở dữ liệu Để sử dụng Spring Data JPA, bạn cần thêm dependency tương ứng vào file pom.xml (cho Maven) hoặc build.gradle (cho Gradle) của dự án Dưới đây là ví dụ cách cài đặt Spring Data JPA thông qua Maven:
Hình 10.4 : Dependency của Spring Data JPA
Spring Config Server
Spring Cloud Config là một dự án trong hệ sinh thái Spring Cloud, cung cấp giải pháp quản lý cấu hình cho các ứng dụng phân tán Nó cho phép các ứng dụng lấy cấu hình của mình từ một nguồn cấu hình tập trung, giúp tạo ra một cách hiệu quả để quản lý cấu hình trong các môi trường phức tạp với nhiều ứng dụng và môi trường khác nhau
Các đặc điểm chính của Spring Cloud Config bao gồm:
- Cấu hình Tập Trung: Spring Cloud Config cho phép lưu trữ cấu hình tập trung trong một kho dữ liệu (repository), thường là Git Điều này giúp quản lý và theo dõi lịch sử thay đổi cấu hình theo thời gian
- Tương thích Đa Ngôn Ngữ: Mặc dù được thiết kế chủ yếu để tích hợp với ứng dụng Spring, nhưng Spring Cloud Config cũng có thể sử dụng được với các ứng dụng chạy bằng bất kỳ ngôn ngữ lập trình nào
- Quản lý Cấu Hình Đa Môi Trường: Spring Cloud Config hỗ trợ quản lý cấu hình cho các môi trường khác nhau như phát triển (development), kiểm thử (test), và sản xuất (production) Điều này giúp đảm bảo rằng ứng dụng có đủ cấu hình để chạy đúng cách trong từng môi trường
- Tích hợp với Spring Boot: Spring Cloud Config tích hợp mạnh mẽ với Spring Boot, giúp các ứng dụng Spring Boot dễ dàng tích hợp và sử dụng cấu hình từ Spring Cloud Config Server
- Bảo mật: Nó cung cấp các tùy chọn bảo mật để đảm bảo rằng thông tin cấu hình quan trọng được bảo vệ và chỉ có những ứng dụng được ủy quyền mới có thể truy cập cấu hình
Cài đặt và sử dụng Đầu tiên chúng ta cần thêm dependency “spring-cloud-config-server” vào file pom.xml của config server service:
Hình 10.5: Dependency “spring-cloud-config-server”
Phần chính của ứng dụng là một lớp cấu hình (config class), cụ thể là một lớp được đánh dấu bằng @SpringBootApplication, nó chọn lựa tất cả các cài đặt cần thiết thông qua chú thích tự động cấu hình @EnableConfigServer
Hình 10.6: Đặt @EnableConfigServer Ở config server, bản triển khai mặc định của EnvironmentRepository sử dụng một hệ thống lưu trữ Git, điều này rất thuận tiện để quản lý việc nâng cấp, môi trường vật lý và để kiểm tra các thay đổi Vì vậy chúng ta cần cấu hình cho config server để có thể kết nối đến repository chúng ta đã tạo trên Github
Hình 10.7: Ví dụ minh họa về cấu hình file yml cho ConfigServer
Spring Boot với Eureka
Eureka là một dịch vụ đăng ký và khám phá dịch vụ được phát triển bởi Netflix, được sử dụng trong kiến trúc Microservices để quản lý và tìm kiếm các dịch vụ khác nhau Cụ thể, Eureka chủ yếu chia thành hai phần:
- Eureka Server: Là một máy chủ chính đóng vai trò như một điểm trung tâm, nơi tất cả các dịch vụ Microservices đăng ký thông tin của mình (như địa chỉ IP và cổng) và trích dẫn thông tin về các dịch vụ khác Eureka Server giúp duy trì một danh sách các dịch vụ hiện đang hoạt động trong hệ thống
- Eureka Client: Là một thành phần tích hợp vào từng dịch vụ Microservices Mỗi khi một dịch vụ khởi chạy, nó đăng ký với Eureka Server để thông báo rằng nó
77 đã sẵn sàng hoạt động Ngoài ra, dịch vụ cũng có thể truy vấn Eureka Server để biết về các dịch vụ khác mà nó cần tương tác
Dịch vụ Eureka giúp giải quyết một số vấn đề trong môi trường Microservices, như quản lý và theo dõi các dịch vụ, khám phá dịch vụ tự động, và đảm bảo khả năng mở rộng của hệ thống Nó là một trong những công cụ quan trọng trong cơ sở hạ tầng cho các ứng dụng phân tán Để tạo Eureka server, chúng ta sẽ dùng Maven để quản lý các dependencies, và thêm dependency của nó như hình minh họa ở dưới:
Hình 10.8: Dependency “spring-cloud-starter-netflix-eureka-server”
Tại thành phần đảm nhiệm việc Service Discovery chúng ta sẽ thêm chú thích để Spring Boot có thể tự hiểu và khai báo đây chính là Eureka Server
Hình 10.9: Khai báo Eureka Server bằng chú thích @EnableEurekaServer
Sping Cloud Gateway
Spring Cloud Gateway là một dự án trong hệ sinh thái Spring Cloud, cung cấp một cổng (gateway) điều hướng (routing) và lọc (filtering) để xử lý các yêu cầu HTTP trong môi trường phân tán Nó được thiết kế để làm cầu nối giữa các microservices và cung cấp một số tính năng như điều hướng động, lọc, giảm độ trễ (latency), và các khả năng mở rộng
Dưới đây là một số đặc điểm và khái niệm quan trọng của Spring Cloud Gateway:
- Route (Đường Đi): Mỗi yêu cầu HTTP được xử lý thông qua một hoặc nhiều routes Mỗi route được xác định bằng một ID, một URI đích, và một số lượng các điều kiện để áp dụng route
- Predicate (Predicate): Một điều kiện để xác định xem một route nào sẽ được áp dụng cho một yêu cầu cụ thể Các predicate được sử dụng để kiểm tra các yêu cầu và quyết định xem chúng có khớp với một route nào hay không
- Filter (Lọc): Các lọc được sử dụng để thực hiện các thao tác trên yêu cầu và phản hồi Chúng có thể thay đổi nội dung, thêm tiêu đề, thực hiện đăng nhập, và thực hiện các chức năng khác Các lọc là những thành phần quan trọng để tùy chỉnh hành vi của gateway
- Route Locator: Là một giao diện cung cấp phương thức để định cấu hình các routes Mặc định, Spring Cloud Gateway cung cấp một Route Locator dựa trên các property files hoặc beans của Java
- Gateway Handler: Là một thành phần chịu trách nhiệm xử lý một yêu cầu cụ thể và quyết định xem nó sẽ đi đâu
- Global Filters: Là các lọc được áp dụng trên tất cả các routes, giúp thực hiện các chức năng chung như đăng nhập, quản lý lỗi, và đo lường thời gian xử lý
Hình 10.10: Kiến trúc của Spring Cloud Gateway
Trong hệ thống, Discovery Service giải quyết vấn đề đặt tên cho các dịch vụ thay vì hardcode địa chỉ IP của chúng
Tuy nhiên, chúng ta vẫn có thể có nhiều hơn một dịch vụ (instances) đang chạy trên các cổng khác nhau
- Spring Cloud Gateway sẽ ánh xạ giữa một tiền tố đường dẫn, ví dụ như /service02/ và một dịch vụ Service02 Nó sử dụng Eureka server để định tuyến dịch vụ được yêu cầu
- Nó cân bằng tải (sử dụng Ribbon) giữa các phiên bản của dịch vụ đang chạy trên các cổng khác nhau
80 Để sử dụng Spring Cloud Gateway, bạn cần thêm dependency tương ứng vào file pom.xml (cho Maven) hoặc build.gradle (cho Gradle) của dự án Dưới đây là ví dụ cách cài đặt Spring Cloud Gateway thông qua Maven:
Hình 10.11: Dependency của Spring Cloud Gateway
Spring Boot Cache
Kể từ phiên bản 3.1, Spring Framework cung cấp hỗ trợ cache vào ứng dụng Spring hiện có một cách rõ ràng Trong Spring, cache abstraction là một cơ chế cho phép sử dụng nhất quán các phương thức bộ đệm khác nhau với việc giảm thiểu viết code
Cache Abstraction: Cơ chế cache abstraction được áp dụng cho các phương thức trong Java Mục tiêu chính của việc sử dụng cơ chế cache abstraction là giảm số lần thực thi dựa trên thông tin có sẵn trong bộ nhớ đệm Do đó, cơ chế này được áp dụng cho các phương thức đòi hỏi nhiều tài nguyên như các phương thức liên quan đến CPU hoặc IO
Cơ chế cache abstraction trong Spring cung cấp các cách hoạt động của cache, bao gồm cập nhật nội dung trong cache và loại bỏ các mục cụ thể hoặc tất cả các mục Những hoạt động này rất hữu ích khi xử lý dữ liệu có thể thay đổi trong ứng dụng
Trong trường hợp của cache abstraction, có hai khía cạnh quan trọng mà developer cần chú ý:
- Khai báo cache: Khi nói đến khai báo cache, người phát triển cần xác định các phương thức cần được lưu trữ trong bộ nhớ đệm và cách thức mà chúng được quản lý
- Cấu hình cache: Khi đến bước cấu hình cache, developer phải quyết định nơi dữ liệu được lưu trữ và đọc từ đó
Lưu ý rằng giống như các dịch vụ khác trong Spring Framework, dịch vụ caching (bộ nhớ đệm) là một trừu tượng (không phải là một cài đặt cụ thể của bộ nhớ đệm) và yêu cầu sử dụng một lưu trữ thực tế để lưu trữ dữ liệu bộ nhớ đệm
Dưới đây là một số khái niệm cơ bản và cách sử dụng Spring Cache dựa vào chú thích:
- Cache Manager: Là một giao diện quản lý các cache và cung cấp các phương thức để tạo và quản lý cache
- Cache: Là một vùng lưu trữ dữ liệu tạm thời với khóa và giá trị, nơi mà dữ liệu có thể được lưu trữ và truy xuất nhanh chóng
- @Cacheable: Là một chú thích được sử dụng để báo cho Spring rằng kết quả của một phương thức nên được lưu trữ trong cache để tái sử dụng trong các lần gọi sau đó
- @CacheEvict: Chú thích này được sử dụng để xóa các mục khỏi cache, có thể được áp dụng trước hoặc sau khi phương thức được gọi
- @CachePut: Chú thích này đặt dữ liệu vào cache ngay cả khi phương thức không được gọi Nó giống như @Cacheable, nhưng không kiểm tra cache trước khi thực hiện phương thức Để cài đặt và sử dụng Spring Cache chúng ta thêm dependency vào file pom.xml (hoặc build.gradle nếu sử dụng Gradle) để bao gồm thư viện Spring Cache:
Hình 10.12: Dependency để khai báo Sping Cache
Spring Boot Actuator
Spring Boot Actuator là một bộ công cụ cung cấp thông tin và khả năng quản lý cho các ứng dụng Spring Boot Actuator cung cấp các giao diện RESTful để truy cập thông tin về ứng dụng, chẳng hạn như:
- Tình trạng ứng dụng: Actuator cung cấp thông tin về trạng thái của ứng dụng, chẳng hạn như trạng thái hoạt động, phiên hoạt động và tài nguyên
- Cấu hình ứng dụng: Actuator cung cấp thông tin về cấu hình của ứng dụng, chẳng hạn như cấu hình Spring Boot, cấu hình ứng dụng và cấu hình môi trường
- Thống kê ứng dụng: Actuator cung cấp thông tin thống kê về ứng dụng, chẳng hạn như thống kê về CPU, bộ nhớ và mạng
- Thông tin nhật ký ứng dụng: Actuator cung cấp thông tin nhật ký từ ứng dụng
82 Để sử dụng Spring Boot Actuator, chúng ta cần thêm phụ thuộc sau vào dự án:
Hình 10.13: Dependency “spring-boot-starter-actuator”
Sau khi thêm dependency, chúng ta có thể truy cập các giao diện RESTful của Actuator bằng cách sử dụng URL: http://localhost:8080/actuator
Các giao diện RESTful của Actuator được chia thành các nhóm:
- Health: Cung cấp thông tin về trạng thái của ứng dụng
- Info: Cung cấp thông tin về cấu hình của ứng dụng
- Metrics: Cung cấp thông tin thống kê về ứng dụng
- Logging: Cung cấp thông tin nhật ký từ ứng dụng
- Endpoints: Cung cấp khả năng quản lý các điểm cuối của ứng dụng
Spring Boot Actuator mang lại một số lợi ích cho ứng dụng, bao gồm:
- Tính hiệu quả: Spring Boot Actuator cung cấp các giao diện RESTful để truy cập thông tin và quản lý ứng dụng
- Tính linh hoạt: Spring Boot Actuator cung cấp khả năng tùy chỉnh cao
- Tính bảo mật: Spring Boot Actuator cung cấp các tính năng bảo mật tích hợp, chẳng hạn như xác thực và ủy quyền.
Spring Boot Security, OAuth2, JWT
Spring Security là một framework mạnh mẽ và có thể tùy chỉnh cao về xác thực và kiểm soát truy cập Đây được coi là tiêu chuẩn không chính thức để bảo vệ các ứng dụng dựa trên Spring
Spring Security là một framework tập trung vào việc cung cấp cả xác thực và ủy quyền cho các ứng dụng Java Như tất cả các dự án Spring khác, sức mạnh thực sự của Spring Security nằm ở khả năng mở rộng một cách dễ dàng để đáp ứng các yêu cầu tùy chỉnh
Các tính năng bảo mật của Spring Boot Security:
- Xác thực người dùng: Spring Boot Security cung cấp nhiều phương pháp xác thực người dùng khác nhau, bao gồm xác thực bằng username và password, xác thực bằng LDAP, xác thực bằng OAuth2, v.v
- Ủy quyền người dùng: Spring Boot Security cung cấp cách để ủy quyền cho người dùng truy cập các tài nguyên khác nhau trong ứng dụng
- Bảo vệ chống lại các cuộc tấn công phổ biến: Spring Boot Security cung cấp cách để bảo vệ ứng dụng của bạn khỏi các cuộc tấn công phổ biến, chẳng hạn như tấn công CSRF, tấn công XSS,…
Cách cài đặt Để sử dụng Spring Boot Security, chúng ta cần thêm phụ thuộc sau vào pom.xml:
Hình 10.14: Dependency “spring-boot-starter-security”
Sau đó, chúng ta cần cấu hình Spring Security trong lớp cấu hình của ứng dụng Để làm điều này, chúng ta cần sử dụng annotation @EnableWebSecurity
OAuth2 là một framework xác thực và ủy quyền được sử dụng rộng rãi trong các ứng dụng web và di động OAuth2 cung cấp một cách để các ứng dụng web có thể cấp quyền cho người dùng truy cập các tài nguyên của ứng dụng khác
Spring Boot cung cấp hỗ trợ cho OAuth2 thông qua các phụ thuộc sau:
Hình 10.16: Dependency “spring-boot-start-oauth2-client”
Thêm annotation @EnableOAuth2Client để sử dụng OAuth2
Hình 10.17: Thêm annotation @EnableOAuth2Client
JSON Web Token (JWT) là một tiêu chuẩn xác thực và ủy quyền dựa trên JSON JWT được sử dụng để truyền thông tin xác thực giữa các ứng dụng
Cách thức hoạt động của Spring Boot Security với JWT:
- Khi người dùng đăng nhập, Spring Boot Security sẽ tạo một mã thông báo JWT cho người dùng Mã thông báo JWT này sẽ chứa thông tin xác thực của người dùng, chẳng hạn như tên người dùng, vai trò và thời hạn hết hạn
- Sau khi người dùng đăng nhập, Spring Boot Security sẽ thêm mã thông báo JWT vào header của các yêu cầu HTTP của người dùng
- Các ứng dụng của bên thứ ba có thể xác minh mã thông báo JWT bằng cách sử dụng khóa công khai của ứng dụng
Cách cài đặt Để sử dụng JWT, chúng ta cần thêm phụ thuộc sau vào pom.xml:
- Sử dụng JwtTokenFilter để xác minh mã thông báo JWT trong các yêu cầu HTTP
- Sử dụng JwtEncoder để tạo mã token JWT cho người dùng
- Sử dụng JwtDecoder để xác minh mã token JWT trong các yêu cầu HTTP.
Spring Cloud Resilience4j
Spring Cloud Resilience4j là một dự án trong hệ sinh thái Spring Cloud, được thiết kế để cung cấp tính năng chống chọi (resilience) cho các ứng dụng microservices Nó tích hợp với thư viện Resilience4j, mang lại các giải pháp để xử lý lỗi, giảm thiểu tác động của lỗi và tăng tính ổn định của hệ thống
Spring Cloud Resilience4j cung cấp hỗ trợ cho các mô hình resilience như Circuit Breaker, Rate Limiter, Retry, Bulkhead, và có thể tích hợp với Spring Cloud Circuit Breaker Dưới đây là một số dịch vụ mà Spring Cloud Resilience4j bao gồm:
- Circuit Breaker: giúp giảm tác động của lỗi trong hệ thống bằng cách ngắt một số lượng yêu cầu đến một dịch vụ khi số lượng lỗi vượt qua một ngưỡng nhất định
- Rate Limiter: giúp kiểm soát tốc độ gọi dịch vụ, đặt ra giới hạn số lượng yêu cầu có thể được thực hiện trong một khoảng thời gian cố định
- Retry: cung cấp khả năng thực hiện lại một phương thức nếu nó thất bại, giúp đối phó với các lỗi tạm thời
- Bulkhead: giúp hạn chế số lượng tài nguyên (ví dụ: số luồng) mà một phương thức có thể sử dụng, giúp tránh tình trạng quá tải
86 Để sử dụng, thêm dependency vào file pom.xml (hoặc build.gradle nếu sử dụng Gradle) để bao gồm thư viện Spring Cloud Resilience4j:
Hình 10.19: Dependency của Spring Cloud Resilience4j
Spring Boot với Feign
OpenFeign là một thư viện khách hàng HTTP giúp đơn giản hóa việc viết khách hàng dịch vụ web trong các ứng dụng microservices Nó là một phần của dự án Spring Cloud và được tích hợp sẵn với Spring Cloud
OpenFeign giúp bạn tạo các khách hàng dịch vụ web bằng cách sử dụng các interface và chú thích, giảm độ phức tạp của việc tạo và quản lý khách hàng HTTP truyền thống Các chú thích được sử dụng để mô tả các yêu cầu HTTP và phản hồi, và OpenFeign tự động tạo ra triển khai cho các interface này Để sử dụng, thêm dependency vào file pom.xml (hoặc build.gradle nếu sử dụng Gradle) để bao gồm thư viện Spring Cloud OpenFeign:
Hình 10.20: Dependency của Spring Cloud OpenFeign
DEMO VÀ TỔNG KẾT
Demo
Demo của nhóm là một hệ thống đặt đồ ăn theo kiến trúc Microservices sử dụng các công nghệ sau:
- Spring Data: JDBC, JPA, MongoDB, Redis
- Spring Cloud: Circuit Breaker, Gateway, Config, Netflix, OpenFeign,…
Tổng kết
Tổng quan về thiết kế
Khi hệ thống khởi động, các service như Api Gateway, Eureka Service, Auth Service, User Service,… sẽ yêu cầu các tệp file cấu hình được lưu trữ ở Config Server Sau đó các Service đều trong tình trạng đã khởi động thành công và chúng sẽ tự đăng ký dịch vụ với Eureka Server
Nếu có một yêu cầu được gửi đến hệ thống, để có thể truy cập và sử dụng API của hệ thống thì yêu cầu phải bao gồm API-Key và Access Token được hệ thống cấp từ trước, nếu không có thì yêu cầu đó sẽ bị từ chối
Sau khi các yêu cầu đảm bảo về sự xác thực bởi API Gateway, thì API Gateway sẽ thông qua dịch vụ cân bằng tải OpenFeign Client để có thể tìm service thích hợp để thực hiện yêu cầu
Hình 11.1: Tổng quan hệ thống
Nhóm chúng tôi sẽ mô phỏng xử lý bao gồm hai chức năng tạo đơn hàng và thanh toán
Hình 11.2: Activity diagram mô tả quá trình đặt đơn hàng
Hình 11.3: Activity diagram mô tả quá trình thanh toán
Dashboard khi khởi động các service:
Hình 11.4: Zipkin Dashboard khi các service được khởi tạo
Theo dõi quá trình tạo Order:
Hình 11.5: Zipkin Dashboard khi thực hiện tạo đơn hàng
Theo dõi quá thanh toán đơn hàng:
Hình 11.6: Zipkin Dashboard khi thực hiện thanh toán
Theo dõi các trạng thái của mỗi service thông qua Prometheus
Hình 11.7: Theo dõi trạng thái thông qua Prometheus
Thông qua đồ án, nhóm đã đạt được một số kết quả như sau:
- Hiểu và áp dụng các nguyên lý trong thiết kế hệ thống
- Nắm được lý thuyết về microservices, kiến thức nền tảng về các building blocks trong một hệ thống
- Xây dựng các thành phần cần có của một hệ thống, triển khai các mô hình tăng cao khả năng xử lý lỗi, mở rộng
- Xử lý các lỗi, vấn đề cơ bản trong một hệ thống Đảm bảo sự ổn định của hệ thống khi có gặp sự cố hoặc xuống cấp của một dịch vụ trong hệ thống
Tuy nhiên nhóm vẫn còn nhiều chủ đề khác chưa tìm hiểu:
- Backends for Frontends (BFF) Pattern
- Xây dựng các CI/CD Pipelines
Mặc dù nhóm nhận thấy vẫn còn một số điểm cần cải thiện, nhưng quá trình tìm hiểu về thiết kế và xây dựng hệ thống đã mang lại cho nhóm rất nhiều kiến thức quý báu Nhóm đã áp dụng các công nghệ phức tạp vào việc phát triển demo một hệ thống đặt đồ ăn, đồng thời tiếp tục khám phá và nghiên cứu sâu hơn để hiểu rõ hơn về cách xây dựng hệ thống Những kiến thức cơ bản này sẽ là nền tảng vững chắc, sẽ giúp nhóm áp dụng chúng một cách hiệu quả trong các dự án thực tế tiếp theo
11.3 Hướng dẫn cài đặt và chạy ứng dụng
Link Github: https://github.com/nvtuanqt212uit/bistro-microservice
Gõ từng câu lệnh trên terminal để chạy ứng dụng:
- git clone https://github.com/nvtuanqt212uit/bistro-microservice
Lưu ý: đã khỏi động Docker trước khi chạy lệnh docker compose up
Sau khi chạy thành công, các service sẽ chạy trên các port như sau: