Chương 1: Tổng quan về thiết kế hệ thống 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 1 và 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.
Các nguyên lý thiết kế hệ thống 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.
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.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 (active-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ủ động- chủ động cũng có thể được gọi là failover chủ-chủ (master-master). Replication cũng có 2 cách: master-slave (master-replica) và master-master (multi- master). 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. 3 Ngượ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. 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.
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. 4 Byzantine 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.
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. Vấn đề các vị tướng Byzantine Một server trong một cluster có thể bị lỗi vì nhiều lý do, mà khiến cho server hoạt động không đúng theo chương trình, lệch khỏi chương trình, khiến cho mọi phản hồi từ server này trở nên không đáng tin cậy. 5 Khô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.