Thiết kế trong các hệ thống nhỏ và trong các hệ thống lớn Với các hệ thống nhỏ và tương đối đơn giản, thiết kế và kiến trúc của hệ thống không phải là một vấn đề quá phức tạp, do bản thâ
Trang 1ĐẠI HỌC QUỐC GIA TP HỒ CHÍ MINH TRƯỜNG ĐẠI HỌC CÔNG NGHỆ THÔNG TIN
KHOA CÔNG NGHỆ PHẦN MỀM
GIẢNG VIÊN ThS Nguyễn Công Hoan
TP HỒ CHÍ MINH, 2023
Trang 2với Spring Boot 2.0 Đặng Quốc Hùng – 20520194
Phùng Trần Đăng Khôi - 20520150
Trang 3MỤC LỤC
1.1 Thiết kế trong các hệ thống nhỏ và trong các hệ thống lớn 1
Trang 44.2.4 Sidecar cache 16
Trang 68.5.1 Prometheus 60
9.3.2 Giao diện trừu tượng bộ nhớ cache của Spring 67 9.3.3 Một số khái niệm quan trọng và các chú thích về cache 68
9.4.1 Các tình huống ứng dụng của middleware tin nhắn 71
9.4.2 Tổng quan về phần mềm trung gian dịch vụ tin nhắn 73
Trang 79.6.2 Các kịch bản ứng dụng Docker 79
9.12.2 Vấn đề giao dịch phân tán trong microservices 86
Trang 89.13.1 Giới thiệu 89 9.13.2 Phụ bản Bean Validation nhúng các chú thích ràng buộc vào 90
9.17 Spring Boot với Hystrix Turbine and Dashboard 92
Trang 9Chương 1: Tổng quan về thiết kế hệ thống
1.1 Thiết kế trong các hệ thống nhỏ và trong các hệ thống lớn
Với các hệ thống nhỏ và tương đối đơn giản, thiết kế và kiến trúc của hệ thống không phải là một vấn đề quá phức tạp, do bản thân các hệ thống này tương đối dễ tổ chức,
dễ vận hành, dễ bảo trì và nhờ đó nên không có nhiều thành phần phức tạp Điển hình
về các hệ thống ở quy mô này là các hệ thống quản lý kho, hệ thống quản lý hồ sơ, quản lý sản phẩm của riêng một công ty, tổ chức; hệ thống quản lý thư viện địa phương, Với các hệ thống này, có rất nhiều mô hình phát triển phần mềm đủ đáp ứng yêu cầu: kiến trúc Monolith, kiến trúc N-tier, mô hình 3 lớp, Clean architecture, Hexagonal architecture, Các mô hình kiến trúc này đều dựa trên một cơ sở để thực hiện là kiến trúc Client-server
Thiết kế điển hình của một hệ thống nhỏ
Trong các mô hình kiến trúc đã kể trên cũng như trong kiến trúc Client-server, người dùng (client) kết nối đến một chương trình được deploy trên một server cụ thể, server này có kết nối đến một database cụ thể để thực hiện lưu trữ Với các hệ thống có quy
mô nhỏ và độ phức tạp tương đối đơn giản, phương pháp này hoàn toàn có thể đáp ứng đầy đủ các yêu cầu nghiệp vụ
Tuy nhiên, với các hệ thống có quy mô lớn hơn, mô hình nghiệp vụ phức tạp hơn, nhiều người sử dụng hơn, các mô hình kiến trúc kể trên không đủ khả năng để cung cấp dịch vụ kịp thời, chính xác và ổn định Điều này do giới hạn phần cứng của một server không thể nào đáp ứng được một nghìn, mười nghìn, thậm chí là cả triệu yêu cầu tại một thời điểm Do đó các hệ thống lớn cần phải áp dụng những kiến trúc phức tạp, đặc biệt hơn để đáp ứng nhu cầu chịu tải lớn, đảm bảo độ ổn định, độ chính xác
Trang 10và có khả năng chịu lỗi Một hệ thống có các nhu cầu trên thông thường sẽ sử dụng nhiều server, mỗi server song song chạy chương trình và nhiều database để đáp ứng nhu cầu lưu trữ
Thiết kế điển hình của một hệ thống lớn
1.2 Các nguyên lý thiết kế hệ thống
1.2.1 Scalability
Scalability là khả năng tăng trưởng (scale) của một hệ thống để đáp ứng nhu cầu xử
lý, tức khả năng xử lý tối ưu hơn, xử lý được nhiều yêu cầu hơn của một hệ thống khi
có thêm tài nguyên (bộ nhớ, đĩa cứng, tài nguyên CPU, )
Có 2 cách để scale một hệ thống là vertical scaling và horizontal scaling Vertical
scaling là tăng trưởng hệ thống bằng cách nâng cấp các thành phần vật lý của server
như nâng cấp CPU, nâng cấp RAM, nâng ổ cứng, Ngược lại, horizontal scaling là
cách thêm nhiều server hơn vào hệ thống, dùng các server phối hợp nhau để xử lý
nguồn tải lớn bằng cách chia đều tải (cân bằng tải) cho mỗi server
Ta không thể áp dụng vertical scaling để scale một hệ thống vĩnh viễn được do hạn chế về phần cứng - chỉ có thể lắp đặt phần cứng tối tân nhất cho một server, khi yêu cầu xử lý vượt quá khả năng của các phần cứng này, hệ thống sẽ bắt đầu ngưng trệ
và suy thoái Đây là giới hạn của vertical scaling
Ngược lại, áp dụng horizontal scaling sẽ cho phép scale hệ thống không giới hạn chỉ bằng cách cài đặt thêm các server Song, horizontal scaling cũng phức tạp hóa mô hình kiến trúc của hệ thống do phải điều phối hoạt động của nhiều server riêng biệt
và vận hành như một bộ máy duy nhất Kiến trúc này được biết đến với tên gọi Hệ
thống phân tán (Distributed system) và là cơ sở cho ra đời một họ các mô hình kiến
trúc sau này như Distributed monolith, Microservices, cùng với đó là rất nhiều vấn
đề cực kỳ phức tạp và khó giải quyết khác
Khi nhắc đến scalability, hầu hết ta luôn muốn nói đến khả năng horizontal scaling của hệ thống
Trang 111.2.2 Availability
Availability là độ sẵn sàng xử lý yêu cầu của hệ thống Các hệ thống lớn có yêu cầu
về availability cao, tức hệ thống phải thường xuyên sẵn sàng xử lý yêu cầu Availability có thể được miêu tả bằng các thuật ngữ như:
● “Two-nines” (hai số chín), tức hệ thống phải sẵn sàng trong 99% thời gian tồn
tại, tương đương với hệ thống chỉ được phép sập tối đa 3.7 ngày/năm
● “Three-nines”, tức sẵn sàng trong 99.9% thời gian tồn tại, tương đương thời
gian không hoạt động 8.8 tiếng/năm
● “Four nines” - 99.99% available, sập 53 phút/năm
● “Five-nines” - 99.999% available, sập 5.3 phút/năm
Để tăng cường availability, ta sử dụng các phương pháp như failover và replication
Failover có 2 cách: chủ động-thụ động passive) và chủ động-chủ động active)
(active-Với failover chủ động-thụ động, heartbeat (nhịp tim) được gửi giữa server chủ động
và server thụ động ở chế độ chờ Nếu nhịp tim bị gián đoạn, server thụ động sẽ tiếp nhận địa chỉ IP của server đang hoạt động và tiếp tục dịch vụ Khoảng thời gian ngừng hoạt động được xác định bằng việc server thụ động đã chạy ở chế độ chờ 'nóng' hay cần khởi động từ chế độ chờ 'nguội' Chỉ có server chủ động xử lý yêu cầu Failover chủ động-thụ động cũng có thể được gọi là failover chủ-tớ (master-slave)
Trong trạng thái chủ động-chủ động, cả hai server đều xử lý yêu cầu, tải được cân bằng giữa chúng Nếu các server ở chế độ công khai, thì DNS sẽ cần biết về các IP
công khai của cả hai server để có thể hỗ trợ client-side load balancing Nếu các server
chỉ giao tiếp nội bộ, application logic sẽ cần biết về cả hai server Failover chủ chủ động cũng có thể được gọi là failover chủ-chủ (master-master)
động-Replication cũng có 2 cách: master-slave (master-replica) và master-master master)
(multi-Trong master-slave replication, master xử lý cả yêu cầu đọc và ghi, dữ liệu thay đổi
từ các yêu cầu ghi được sao chép (replicate) sang các slave; slave chỉ xử lý đọc Slave cũng có thể sao chép dữ liệu ghi sang các slave khác Nếu master gặp sự cố,
hệ thống có thể tiếp tục hoạt động ở chế độ read-only cho đến khi một slave được thăng thành master hoặc một master mới được thêm vào cluster
Trang 12Ngược lại, trong master-master replication, các master cùng xử lý cả yêu cầu đọc và ghi, phối hợp với nhau để đảm bảo nhất quán trong các yêu cầu ghi Khi một server master có sự cố, các master còn lại vẫn có thể tiếp tục xử lý đọc và ghi
1.2.3 Consistency
Consistency (tính nhất quán) là thuộc tính của hệ thống phân tán đảm bảo rằng mọi server trong cluster đều có cùng trạng thái, cùng dữ liệu tại một thời điểm nhất định, bất kể client nào đã cập nhật dữ liệu Tính nhất quán cao có nghĩa là hệ thống phân tán hội tụ trên một giá trị duy nhất và client luôn đọc được dữ liệu mới nhất
Tính nhất quán của một hệ thống bị chi phối bởi định lý CAP - nêu rằng một hệ thống chỉ có thể đảm bảo được 2 trong 3 tính chất: Consistency (tính nhất quán), Availability (tính sẵn sàng) và Partition Tolerance (chịu được phân vùng) Nói cách khác, khi xảy ra network partition thì một hệ thống chỉ có thể đảm bảo được 1 trong
2 - Consistency hoặc Availability Khi không có network partition thì một hệ thống
có thể đảm bảo cả hai
Có nhiều mô hình về yêu cầu tính nhất quán của một hệ thống:
● Strong consistency: là mô hình nhất quán mạnh nhất, cam kết rằng mọi thao
tác được thực hiện lên dữ liệu đều được phản ảnh ngay lập tức bởi tất cả server trong cluster và do đó client luôn quan sát được dữ liệu mới nhất Mô hình này hy sinh availability để đảm bảo sự nhất quán giữa các server
● Weak consistency (hoặc Eventual consistency): các thao tác sau khi thực hiện
có thể chưa được phản ánh bởi các server trong cluster, yêu cầu đọc của client có thể chưa nhìn thấy dữ liệu mới nhất, trả về dữ liệu cũ Mô hình này không đảm bảo tính nhất quán cao nhưng đổi lại availability cao
1.2.4 Fault tolerance
1.2.4.1 Các khái niệm về fault tolerance
● Failure: cả hệ thống bị gián đoạn, không hoạt động
● Fault: một phần của hệ thống không hoạt động
○ Node fault: sự cố tại một server node
■ Crash-stop: server sập và dừng hoàn toàn, không hồi phục
■ Crash-recovery: server sập và sau đó hồi phục trở lại
■ Byzantine fault: server gặp sự cố và hoạt động không theo
chương trình, xử lý một cách ngẫu nhiên, không đáng tin cậy
Trang 13Byzantine fault có thể do trục trặc trong các thiết bị điện tử chứ
không nhất thiết phải do sự can thiệp của tác nhân có ý đồ xấu
○ Network fault: sự cố về đường truyền, kết nối mạng, chẳng hạn như message bị mất hoặc delay, tắc nghẽn,
● Fault tolerance: khả năng chống fault - hệ thống vẫn tiếp tục hoạt động khi
có fault (số lượng fault dưới một ngưỡng tối đa)
● Single point of failure: khi fault ở một node dẫn tới failure trên toàn hệ thống
1.2.4.2 Byzantine fault
Lỗi Byzantine (Byzantine fault), hay còn gọi là Vấn đề các vị tướng Byzantine, là một
tình trạng của một hệ thống, đặc biệt là hệ thống phân tán, trong đó các component
có thể bị lỗi và không có thông tin chính xác về việc liệu component đó có bị lỗi hay không Thuật ngữ này lấy tên từ một câu chuyện ngụ ngôn, "Vấn đề các vị tướng Byzantine", được phát triển để mô tả một tình huống trong đó, để tránh sự thất bại thảm hại của hệ thống, các tác nhân của hệ thống phải đồng ý về một chiến lược phối hợp, nhưng một số tác nhân này là không đáng tin cậy
Trang 14Không có cách nhất định để phát hiện lỗi Byzantine, do các server bị lỗi hoàn toàn
có thể phản hồi lại các yêu cầu một cách bình thường nhưng thực chất không thực hiện yêu cầu theo đúng chương trình Node bị lỗi còn có thể phản hồi thông tin khác nhau với mỗi thiết bị quan sát khác nhau Do đó, các cơ chế phát hiện lỗi (fault detection) thông thường không thể nhận biết được liệu một node có bị lỗi Byzantine hay không
Không có cách để một tướng biết được trong số các tướng khác ai là người phản bội
Tên gọi Byzantine xuất phát từ một sự so sánh rằng tình trạng này giống với một trận đánh mà các tướng quân đang quyết định có đánh chiếm thành Byzantine không Các tướng quân cần thống nhất một kế hoạch đánh hay rút lui, nhưng trong số các vị tướng
có một số kẻ phản bội, chúng có thể biểu quyết đánh nhưng sau đó lại rút lui Trong một cluster có 3 node, nếu một node bị lỗi Byzantine, cả cluster sẽ không thể cung cấp dịch vụ do không thể đạt thống nhất về trạng thái và cũng không tìm ra được node
bị lỗi
Lỗi Byzantine chỉ có thể giải được trong một cluster có số node bị lỗi ít hơn ⅓ tổng
số node Lỗi Byzantine cũng có ảnh hưởng đến các thuật toán đồng thuận (Consensus)
Trang 15Chương 2: Load balancer
2.1 Load balancer
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
Load balancer có thể giải quyết một số vấn đề, bao gồm:
● 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: Load balancer 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
2.2 Phân loại
2.2.1 Hardware & Software
Cách phân loại thứ nhất của load balancer là phân loại theo cách thức deploy
và nó có hai loại:
Trang 16● 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 Hardware load balancer thường có giá thành cao và đòi hỏi phải có kiến thức chuyên môn để cài đặt và cấu hình
● Software load balancer: Là một ứng dụng phần mềm có thể được cài
đặt trên physical host hoặc virtual host 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ó Software load balancer linh hoạt hơn và có chi phí thấp hơn so với hardware load balancer
2.2.2 Tầng xử lý
Cách phân loại thứ hai là sử dụng tầng xử lý của load balancer:
● 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
Trang 17● 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à 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
● Global server load balancer: Global server load balancer (GSLB) là
một phương pháp phân phối lưu lượng truy cập trên các máy chủ được đặt tại nhiều địa điểm trên toàn cầu GSLB sử dụng DNS và kiến thức
về tài nguyên dịch vụ để điều hướng lưu lượng truy cập dựa trên logic kinh doanh được xác định GSLB giúp tăng độ tin cậy và giảm độ trễ cho người dùng
Trang 182.3 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
● 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
2.4 Patterns
Server-side load balancing và client-side load balancing đều là các phương pháp phân phối lưu lượng truy cập trên nhiều máy chủ, nhưng chúng khác nhau về cách thức hoạt động:
● Server-side load balancing: Server-side load balancing được thực hiện bởi
một thiết bị hoặc ứng dụng được đặt trước các máy chủ backend Load balancer sẽ nhận các yêu cầu từ client và sử dụng một thuật toán để chọn một máy chủ backend để xử lý yêu cầu Sau đó, load balancer sẽ chuyển tiếp yêu cầu đến máy chủ backend được chọn và trả về kết quả cho client
Trang 19● Client-side load balancing: Client-side load balancing được thực hiện bởi
client Client sẽ nhận thông tin về các máy chủ backend từ một dịch vụ danh mục (registry service) và sử dụng một thuật toán để chọn một máy chủ backend
để gửi yêu cầu Sau đó, client sẽ gửi yêu cầu trực tiếp đến máy chủ backend được chọn và nhận kết quả trả về
Cả hai loại load balancing đều có ưu và nhược điểm riêng Server-side load balancing đơn giản hơn và dễ triển khai hơn, nhưng có thể tạo ra bottleneck và tăng độ trễ Client-side load balancing linh hoạt hơn và giảm độ trễ, nhưng phức tạp hơn và đòi hỏi client phải có khả năng thực hiện load balancing
2.5 Tăng availability của hệ thống
Single point of failure (SPOF) là một điểm trong hệ thống mà nếu nó gặp sự cố, toàn
bộ hệ thống sẽ ngừng hoạt động Việc có một SPOF trong hệ thống có thể dẫn đến nhiều vấn đề, bao gồm:
Trang 20● Mất dữ liệu: Nếu SPOF là một thiết bị lưu trữ dữ liệu, sự cố có thể dẫn đến
mất dữ liệu quan trọng
● Thời gian chết: Khi SPOF gặp sự cố, toàn bộ hệ thống sẽ ngừng hoạt động,
dẫn đến thời gian chết và ảnh hưởng đến trải nghiệm người dùng
● Mất doanh thu: Thời gian chết có thể dẫn đến mất doanh thu, đặc biệt đối với
các doanh nghiệp phụ thuộc vào hệ thống để bán hàng hoặc cung cấp dịch vụ
● Mất uy tín: Sự cố liên tục có thể làm giảm uy tín của doanh nghiệp và ảnh
hưởng đến lòng tin của khách hàng
Để giảm thiểu rủi ro từ SPOF, ta có thể áp dụng một số phương pháp để tạo ra bản sao của các thành phần trong hệ thống rồi load balance giữa các instance đó
Ta có thể khắc phục SPOF cho database sử dụng Sharding và replication Sharding
và replication là hai phương pháp phân tán dữ liệu và tài nguyên trong các hệ thống lớn, không chỉ áp dụng cho cơ sở dữ liệu mà còn cho các dịch vụ khác
● Sharding: Sharding là một phương pháp phân chia dữ liệu hoặc tài nguyên
lớn thành nhiều phần nhỏ hơn và lưu trữ chúng trên nhiều máy chủ khác nhau Mỗi máy chủ sẽ lưu trữ một phần của dữ liệu hoặc tài nguyên và chỉ xử lý các yêu cầu liên quan đến phần đó Sharding giúp tăng khả năng mở rộng và cải thiện hiệu suất của hệ thống bằng cách giảm gánh nặng cho từng máy chủ và cho phép xử lý song song các request
● Replication: Replication là một phương pháp sao chép dữ liệu hoặc tài nguyên
từ một máy chủ sang một hoặc nhiều máy chủ khác Mục đích của replication
là tăng độ tin cậy và khả dụng của hệ thống bằng cách cung cấp các bản sao
dự phòng của dữ liệu hoặc tài nguyên Khi một máy chủ gặp sự cố, các máy chủ khác có thể tiếp tục hoạt động và đảm bảo availability của dịch vụ
Trang 21Cả hai phương pháp đều có ưu và nhược điểm riêng và có thể được sử dụng kết hợp với nhau để tăng cường khả năng mở rộng, hiệu suất, độ tin cậy và khả dụng của hệ thống
Trang 22Chương 3: Caching
3.1 Tổng quan về caching
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
Cache ở nhiều vị trí trong hệ thống
3.2 Các pattern trong caching
4.2.1 Local cache
Ưu điểm:
● Đơn giản, độ trễ thấp, không phải serialize/deserialize
Trang 23● Có khả năng chịu lỗi và phục hồi
● Có thể sử dụng các thuật toán băm (hash) để phân mảnh dữ liệu, lưu trên nhiều node
● Sử dụng các data store như Redis, Memcached, SQL Server làm backing data store
Nhược điểm:
● Tăng độ trễ so với local cache
Trang 244.2.3 Reverse proxy cache
Trang 25● Độ trễ thấp hơn cache phân tán do cache và chương trình nằm trong cùng một pod
● Cấu hình đơn giản
● Tách biệt chương trình và cache, giảm dependency
Nhược điểm:
● Chỉ thực sự tối ưu khi áp dụng trong môi trường Kubernetes
● Độ trễ cao hơn local cache
4.2.5 Reverse proxy sidecar cache
Ưu điểm:
● Độ trễ thấp do cache và chương trình nằm trong cùng một pod
● Thích hợp với kiến trúc Microservice, Kubernetes, Service mesh
● Có khả năng cache API response
Nhược điểm:
● Khó invalidate cache
Trang 263.3 Làm trống cache (Cache eviction)
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
3.4 Các pattern truy cập trong caching
4.4.1 Cache-aside
Chương trình luôn đọc dữ liệu từ cache trước, nếu cache không có dữ liệu đó thì chương trình sẽ đọc từ database rồi ghi vào cache để hỗ trợ những lần đọc sau; ngược lại, cache sẽ trả kết quả ngay lập tức cho chương trình
Trang 27Ưu điểm:
● Đơn giản, dễ thực hiện
● Data model trong cache có thể khác so với database
Nhược điểm:
● Cần 3 network trip khi cache miss
● Khi update database, dữ liệu trong cache sẽ bị stale
4.4.2 Read-through
Cache đóng vai trò như một lớp trung gian giữa chương trình và database Chương trình luôn đọc từ cache, nếu không có dữ liệu đó, cache sẽ tự đọc từ database và ghi vào bộ nhớ của mình, sau đó trả kết quả cho chương trình
Trang 28● Đảm bảo cache và database luôn nhất quán, dữ liệu trong cache không bị lạc hậu
Trang 29Chương 4: Microservices
4.1 Miêu tả microservice
Một hệ thống monolithic là một hệ thống phần mềm trong đó tất cả các chức năng và tính năng đều được tích hợp chặt chẽ với nhau Mặc dù kiến trúc monolithic có thể đơn giản hóa việc phát triển và triển khai ban đầu, nhưng nó cũng có thể gây ra một
số vấn đề khi hệ thống phát triển và mở rộng Một số vấn đề của một hệ thống monolithic bao gồm:
● Khó mở rộng: Khi lưu lượng truy cập tăng, việc mở rộng một hệ thống
monolithic có thể trở nên khó khăn do sự phụ thuộc chặt chẽ giữa các thành phần
● Khó bảo trì: Việc bảo trì và nâng cấp một hệ thống monolithic có thể trở nên
phức tạp và tốn kém do sự phụ thuộc chặt chẽ giữa các thành phần
● Khó tái sử dụng: Các thành phần trong một hệ thống monolithic thường khó
tái sử dụng trong các dự án khác do sự phụ thuộc chặt chẽ giữa chúng
● Khó kiểm thử: Việc kiểm thử một hệ thống monolithic có thể trở nên khó
khăn do sự phụ thuộc chặt chẽ giữa các thành phần
● Code base lớn: Các lập trình viên mới vào dự án sẽ khó nắm bắt được project
vì lượng thông tin rất lớn
● Không có tính cách ly: Các thành phần khác nhau có thể ảnh hướng đến nhau
do trong cùng một codebase nên việc xảy ra lỗi là điều rất khó tránh khỏi
Để giải quyết các vấn đề này, nhiều tổ chức đã chuyển sang sử dụng kiến trúc microservice Microservice là một kiến trúc phần mềm trong đó ứng dụng được xây dựng dưới dạng một tập hợp các service nhỏ và độc lập, có thể triển khai và quản lý một cách độc lập Kiến trúc microservice mang lại nhiều lợi ích, bao gồm:
● Khả năng mở rộng: Các dịch vụ có thể được mở rộng độc lập, giúp tăng
cường khả năng xử lý của hệ thống
● Dễ bảo trì: Việc bảo trì và nâng cấp các dịch vụ trở nên dễ dàng hơn do chúng
được quản lý một cách độc lập
● Tái sử dụng: Các dịch vụ có thể được tái sử dụng trong các dự án khác, giúp
tiết kiệm thời gian và chi phí phát triển
● Dễ kiểm thử: Việc kiểm thử các service trở nên dễ dàng hơn do chúng được
quản lý một cách độc lập
● Life cycle độc lập: Các service sẽ có life cycle riêng và không phụ thuộc lẫn
nhau giúp các team có thể thoải mái hơn trong việc đặt version và release
Trang 30● Code base nhỏ: Mỗi team sẽ chỉ cần quan tâm và quản lý codebase riêng của service mà họ đang phát triển
● Tính cách ly cao: Khi có lỗi xảy ra ta có thể dễ dàng xác định được là nó nằm
gọn trong dịch vụ nào mà không cần phải lo về việc lỗi xảy ra ở một dịch vụ
khác nhưng lại do update một dịch vụ khác
● Deploy nhanh: Do life cycle độc lập và các service có thể deploy riêng nên
việc dừng các tiến trình đang chạy để khởi tạo các tiến trình mới với các update
mới nhanh hơn nhiều so với monolithic system
4.2 Phương thức giao tiếp giữa các service
Khi chuyển từ một hệ thống monolithic sang một hệ thống microservice, một trong những thách thức lớn nhất là việc thiết lập giao tiếp giữa các dịch vụ Trong một hệ thống monolithic, các thành phần khác nhau của ứng dụng có thể giao tiếp với nhau một cách trực tiếp thông qua các lời gọi hàm hoặc các cơ chế khác Tuy nhiên, trong một hệ thống microservice, các dịch vụ được triển khai và quản lý một cách độc lập,
do đó chúng không thể giao tiếp trực tiếp với nhau
Để giải quyết vấn đề này, ta có thể sử dụng nhiều kỹ thuật khác nhau để thiết lập giao tiếp giữa các dịch vụ Một số kỹ thuật phổ biến bao gồm:
● RESTful API: Các dịch vụ có thể cung cấp các API theo chuẩn RESTful để
cho phép các dịch vụ khác gọi và trao đổi dữ liệu
● Message queue: Các dịch vụ có thể sử dụng message queue để gửi và nhận
các thông điệp không đồng bộ
Trang 31● Service mesh: Service mesh là một lớp trung gian giữa các dịch vụ, cung cấp
các tính năng như cân bằng tải, bảo mật và giám sát để giúp quản lý giao tiếp giữa các dịch vụ
4.3 Khuyết điểm của microservice
Microservices là một kiến trúc phần mềm phổ biến, nhưng cũng có một số khuyết điểm cần lưu ý Dưới đây là một số khuyết điểm của microservices:
● Phức tạp: Việc triển khai và quản lý một hệ thống microservices có thể phức
tạp hơn so với một ứng dụng monolithic Các nhà phát triển cần phải xử lý các vấn đề như phân tán dữ liệu, giao tiếp giữa các dịch vụ và quản lý phiên bản
● Chi phí: Việc triển khai và vận hành một hệ thống microservices có thể đắt
hơn so với một ứng dụng monolithic do yêu cầu nhiều tài nguyên hơn, chẳng hạn như máy chủ và bộ nhớ
● Thời gian triển khai ban đầu: Việc triển khai một hệ thống microservices có
thể mất nhiều thời gian hơn so với một ứng dụng monolithic do yêu cầu nhiều bước hơn, chẳng hạn như đóng gói và triển khai từng dịch vụ riêng biệt
Trang 32● Rủi ro bảo mật: Việc sử dụng nhiều dịch vụ riêng biệt có thể tạo ra nhiều
điểm yếu hơn cho các cuộc tấn công mạng Các nhà phát triển cần phải đảm bảo rằng tất cả các dịch vụ đều được bảo mật đúng cách
● Yêu cầu nhân lực nhiều hơn: Khi triển khai một hệ thống microservice thì
ta cần phải có một đội ngũ các DevOps làm việc với nhau để có thể đưa hệ
thống từ development lên production
Tuy nhiên, nếu được thiết kế và triển khai đúng cách, microservices có thể mang lại nhiều lợi ích cho doanh nghiệp, chẳng hạn như khả năng mở rộng linh hoạt, availability cao và khả năng phát triển nhanh chóng Do đó, việc lựa chọn giữa microservices và kiến trúc monolithic sẽ phụ thuộc vào yêu cầu và mục tiêu của doanh nghiệp
Trang 33Chương 5 : Service discovery & API Gateway
Service Discovery là một phần quan trọng trong việc quản lý các service trong một distributed system 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
Service Discovery giúp giải quyết vấn đề này bằng cách cho phép các service đăng
ký địa chỉ IP, port và tên của chúng với một registry Các service khác có thể registry này để lấy thông tin về các service khác trong hệ thống Nhờ vậy, các service có thể nói chuyện trực tiếp với nhau một cách dễ dàng hơn
Có hai phương thức áp dụng service discovery:
5.1 Phân loại service discovery
Client side: 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
Trang 34Server side: 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)
5.2 Phân loại hình thức register
Trong Service Discovery, có hai cách chính để đăng ký một service với registry: đăng
ký tự động và đăng ký thủ công
● Self registration: Trong trường hợp này, service sẽ tự động đăng ký với
registry khi nó khởi động Điều này thường được thực hiện bằng cách sử dụng một thư viện client tích hợp với service, cho phép service giao tiếp với registry
và cung cấp thông tin cần thiết để đăng ký
● Third party registration: Trong trường hợp này, service sẽ được tự động
đăng ký mà không cần phải sử dụng thư viện tích hợp Khi này việc phát hiện
ra service và register và của một phần khác của hệ thống và service sẽ không cần phải biết bất cứ thứ gì về registry Điều này giúp decoupled registry với service nhưng lại kho implement hơn hẳn so với self registration Mặt khác, ta không bị giới hạn bởi ngôn ngữ mà thư viện để register hỗ trợ
Trang 355.3 Sử dụng service
1.1 Direct
Cách thức đầu tiên ta có thể sử dụng service là sử dụng trực tiếp thông qua service discovery Nhưng điều này là không nên vì để làm như thế này thì ta cần phải expose các địa chỉ IP của service và dẫn đến một lỗ hổng trong bảo mật Mặt khác các request sẽ trở nên phức tạp hơn vì ta sẽ cần phải tổng hợp lại các thông tin mà ta thu thập từ nhiều service khác nhau, ta phải biết service nào cung cấp thông tin nào
1.2 Composite UI
Một cách khác là ta dùng composite UI, khi này ta sẽ thiết kế giao diện dựa trên các service trong kiến trúc microservice ở backend Thay vì ta có một monolithic UI thì bây giờ ta sẽ ánh xạ kiến trúc của backend và thiết kế quanh các service Việc thiết kế giao diện này sẽ yêu cầu nhiều kỹ năng hơn từ người lập trình nhưng sẽ giúp phân tách nhiệm vụ truy vấn dữ liệu ra Tuy nhiên cách này vẫn cần phải expose địa chỉ IP, ta có thể giải quyết bằng cách áp dụng một API gateway
Trang 361.3 API Gateway
API Gateway là một thành phần trung gian giữa các client và các service trong một distributed system Nó đóng vai trò như một cửa vào cho các yêu cầu từ client, xử lý các yêu cầu đó và redirect chúng đến service thích hợp
API Gateway giải quyết một số vấn đề quan trọng trong việc xây dựng và vận hành distributed system Một số vấn đề mà API Gateway giải quyết bao gồm:
● Định tuyến yêu cầu: API Gateway có thể định tuyến các yêu cầu từ
client đến service thích hợp dựa trên các tiêu chí như URL, phương thức HTTP hoặc thông tin trong header
● Authentication & Authorization: API Gateway có thể authorize và
authenticate các yêu cầu từ client trước khi chuyển chúng đến service Điều này giúp lập trình viên không cần phải authorize ở code của service mà có thể để API Gateway authorize rồi để service authenticate quyền hạn
● Giới hạn tốc độ: API Gateway có thể giới hạn số lượng yêu cầu từ một
client trong một khoảng thời gian nhất định để ngăn chặn các cuộc tấn công từ chối dịch vụ (DoS) hoặc giảm thiểu các request bất thường
● Chuyển đổi dữ liệu: API Gateway có thể chuyển đổi dữ liệu từ một
định dạng sang một định dạng khác để phù hợp với yêu cầu của client hoặc service
● TLS termination: Giúp tăng tốc độ giao tiếp giữa các service trong cùng datacenter rồi re-encrypt khi reply được gửi đến gateway
Trang 37● Decouple giao diện: Giao diện không dính với các service phía sau nên
ta có thể thay đổi thiết kế của hệ thống, chỉ cần đảm bảo api gateway
vẫn cung cấp thông tin đúng format là UI vẫn chạy
Tuy nhiên, Khi redirect thẳng các request từ api gateway đến microservice như hình trên thì có thể dẫn đến việc có quá nhiều service call
Thay vì redirect trực tiếp đến các microservice thì ta có thể thêm vào một trung gian gọi là aggregator
Đối với các request phức tạp cần thông tin từ nhiều services khác nhau thì ta
sẽ redirect đến aggregator, còn các request đơn giản thì ta sẽ redirect nó đến service
Trang 38Các thành phần chi tiết của một API Gateway được thể hiện ở sơ đồ bên dưới bao gồm các thành phần để xử lý lỗi và thu thập metrics
5.4 Envoy
5.4.1 Giới thiệu
Envoy Proxy là một L3/L4 và L7 proxy có thể được dùng như edge proxy, được thiết kế cho các ứng dụng cloud Được phát triển tại Lyft, Envoy là một proxy được viết bằng C++ với hiệu suất cao được thiết kế cho các hệ thống distributed và ứng dụng khác
Envoy được xây dựng trên những kiến thức có được từ các nền tảng đi trước như NGINX, HAProxy, envoy được deploy với các application và ẩn đi sự phức tạp của networking Khi ta áp dụng Envoy, ta có thể dễ dàng thu thập metrics để monitor hệ thống
Trang 39Có thể ứng dụng làm API gateway nhờ các tính năng chủ yếu:
● Listeners: Là các thành phần lắng nghe các kết nối đến từ client và
chuyển chúng đến các filter chain thích hợp
● Filters: Là các thành phần xử lý dữ liệu truyền qua Envoy Có nhiều
loại filter khác nhau, bao gồm network filter, HTTP filter và listener filter
● Filter chains: Là một chuỗi các filter được sắp xếp theo thứ tự để xử
lý dữ liệu truyền qua Envoy Mỗi listener có thể có nhiều filter chain khác nhau và một filter chain mặc định tùy chọn
● Routes: Là các quy tắc redirect request từ client đến các cluster thích
hợp
● Clusters: Là một nhóm các máy chủ cung cấp cùng một dịch vụ Envoy
sử dụng thông tin về cluster để redirect request từ client đến máy chủ thích hợp
● Hosts: Là các máy chủ trong một cluster Envoy sử dụng thông tin về
host để redirect request từ client đến máy chủ thích hợp
Trang 40Envoy sử dụng protocol buffer để định nghĩa các thuộc tính có thể được configure (listener, route, endpoint, filter, ), qua đó cho phép tùy ý extend các configuration đó
Các configuration có thể được fetch từ một management server bằng
gRPC/HTTP polling và áp dụng ở runtime
Ví dụ về file yaml để configure Envoy: