# 面試準備
###### tags: `Interview`
[TOC]
## 資料庫類問題
### 資料庫關聯(Database Relationships)
* 一對一(One-to-One):
* 在一對一關係中,每一個實體A的記錄僅能對應一個實體B的記錄,反之亦然。
* 例子:一個人(Person)只有一個身份證(ID Card),一個身份證只對應一個人。這樣可以將身份證資料獨立為另一個表,然後通過主鍵-外鍵的方式來維持關聯。
* 實現方式:可以通過在其中一個表中添加一個外鍵,或在兩個表中使用相同的主鍵來實現。
* 一對多(One-to-Many):
* 在一對多關係中,一個實體A的記錄可以對應多個實體B的記錄,但每個B的記錄只能對應一個A的記錄。
* 例子:一個客戶(Customer)可以擁有多個訂單(Orders),但每個訂單只屬於一個客戶。
* 實現方式:通常是在多的一方的表中添加一個外鍵來實現,比如在 orders 表中添加一個 customer_id 外鍵來指向 customers 表。
* 多對多(Many-to-Many):
* 在多對多關係中,多個實體A的記錄可以對應多個實體B的記錄,反之亦然。
* 例子:學生(Student)和課程(Course)之間是一種多對多關係,一個學生可以選多門課程,而一門課程可以有多名學生。
* 實現方式:通常使用一個中間表(Junction Table)來實現。這個中間表包含兩個外鍵,分別指向兩個表的主鍵。例如 student_courses 表中有 student_id 和 course_id,分別指向 students 表和 courses 表。
### SQL 指令的執行順序

### 資料庫正規化(Database Normalization)
> 正規化是一種分解數據的過程,將數據拆分為多個表,並通過鍵的關聯來消除數據冗餘和依賴,從而提高資料庫的完整性和一致性。正規化由一系列「正規形式」構成,從 第一正規形式(1NF) 到 第五正規形式(5NF),常用的有前三個。
主要的正規化階段:
* 第一正規形式(1NF):
* 要求每個欄位中的數據都是原子性的,不能有多值屬性,數據必須是不可再分的單一值。
* 例子:一個表格中不應該出現「電話號碼」欄位中包含多個號碼的情況,而應該將其拆分為多行或建立獨立的電話號碼表。
* 第二正規形式(2NF):
* 要求表格符合第一正規形式,且每個非主鍵欄位都完全依賴於主鍵(消除部分依賴)。
* 主要針對複合鍵的情況,確保每個非主鍵屬性依賴於整個複合主鍵,而不是其中一部分。
* 例子:一個訂單明細表中,如果複合主鍵是 order_id 和 product_id,而 product_name 只依賴於 product_id,就需要將 product_name 移到產品表中,這樣表只需存儲與訂單有關的數據。
* 第三正規形式(3NF):
* 要求表格符合第二正規形式,且所有非主鍵屬性相互之間沒有傳遞依賴(消除傳遞依賴)。
* 例如,若表中存在 student_id → department_id → department_name 的依賴關係,那麼應將 department_name 分離至 departments 表,以避免冗餘。
* Boyce-Codd 正規形式(BCNF):
* BCNF 是第三正規形式的強化版本,要求每個非主鍵屬性都依賴於候選鍵,而不只是主鍵。
* 這通常應用於複雜的多候選鍵情況下,以消除任何潛在的依賴性。
**正規化的優點和缺點**
* 優點:
* 減少數據冗餘:正規化通過分解表和消除依賴,減少了數據的重複存儲。
* 提升數據一致性:避免了重複數據的更新異常,保證數據的完整性。
* 提高查詢性能:通過更小的表和更具針對性的索引,優化了查詢性能。
* 缺點:
* 增加查詢複雜性:正規化後數據分散在多個表中,導致查詢時需要進行多表連接,增加了查詢的複雜性。
* 可能降低插入/更新性能:正規化的結構可能導致在插入和更新時需要更新多個表,特別是在需要維護關聯的情況下。
### DB Lock(資料庫鎖)
#### 概念
> 資料庫鎖(DB Lock)是一種機制,用於控制並發環境下對資料庫資源的訪問,防止數據不一致或衝突。鎖機制在多用戶或多進程情況下非常重要,確保數據的完整性和一致性。
#### 為什麼需要 DB Lock?
1. 髒讀(Dirty Read):一個事務讀取了另一個事務尚未提交的數據。
2. 不可重複讀(Non-repeatable Read):一個事務多次讀取同一數據,另一事務在兩次讀取之間修改了數據,導致讀取結果不一致。
3. 幻讀(Phantom Read):一個事務在多次查詢之間,另一個事務插入或刪除了符合條件的數據,導致數據集合不一致。
#### 按操作範圍分類
1. 行鎖(Row Lock):僅鎖定單行記錄。行鎖的粒度最小,因此並發性最高,但會消耗更多的資源。
* 行鎖適用於需要鎖定特定行,而不影響其他行的情況。
大部分關係型資料庫(如 MySQL InnoDB)支援行鎖。
2. 表鎖(Table Lock):鎖定整個表,當一個事務鎖定表後,其他事務無法對該表進行操作。
* 表鎖的粒度較大,並發性低,但實現相對簡單且佔用資源較少。
常用於資料更新頻率低的表或批量操作。
3. 頁鎖(Page Lock):鎖定資料庫的一個頁(通常是數據塊或數據頁)。頁鎖粒度介於行鎖和表鎖之間,並發性和資源佔用也介於兩者之間。
* 頁鎖在一些資料庫中(如 SQL Server)被使用,用於批量操作時的性能優化。
#### 按鎖定行為分類
1. 共享鎖(Shared Lock):允許多個事務讀取同一資源,但禁止寫入操作。共享鎖適合在多個讀操作的情況下使用。
* 又稱 讀鎖(Read Lock),一個事務加上共享鎖後,其他事務也可以加共享鎖,但不能加排他鎖。
2. 排他鎖(Exclusive Lock):當一個事務獲得排他鎖後,其他事務無法讀取或寫入該資源。排他鎖適合在進行數據寫入時使用。
* 又稱 寫鎖(Write Lock),用於保護寫操作,防止其他事務讀取或修改該數據。
#### 按鎖的模式分類
1. 意向鎖(Intent Lock):用於在表級別上標記事務的意圖。當一個事務需要鎖定某行時,先在表上設置意向鎖,這樣其他事務知道該表中有行已被鎖。
* 意向共享鎖(IS):事務意圖在表中某行上加共享鎖。
* 意向排他鎖(IX):事務意圖在表中某行上加排他鎖。
2. 死鎖(Deadlock):當兩個或多個事務因互相等待對方的鎖資源而進入死循環,造成資源無法釋放的情況。資料庫系統通常會檢測死鎖並中止其中一個事務。
* 這邊可以延伸到Update Lock,當兩個以上的transaction進行時都會獲得Shared Lock,而其中之一作完工作後應釋放Shared Lock生成Update Lock,這樣才不會造成Deadlock。
#### DB Lock 的隔離級別
1. 未提交讀(Read Uncommitted):允許事務讀取未提交的更改,會出現髒讀。幾乎不使用鎖。
> 這種隔離層級可能產生:
> 髒讀
> 不可重複讀
> 幻讀
2. 提交讀(Read Committed):事務只能讀取已提交的更改,解決了髒讀問題。通常使用共享鎖和排他鎖。
> 這種隔離層級可能產生:
> 不可重複讀
> 幻讀
3. 可重複讀(Repeatable Read):事務在範圍內的多次讀取結果一致,防止了不可重複讀。大多數實現行鎖。
> 這種隔離層級可能產生:
> 幻讀
4. 可序列化(Serializable):最高隔離級別,所有事務依序執行,避免所有並發問題,但性能較低,常用於行鎖或表鎖。
> 一個用效能換取一致性的隔離層級,讓所有 transaction 序列化執行,避免併發可能會造成的問題。
### B+ Tree
> 是一種平衡樹結構,廣泛應用於資料庫和文件系統中,作為索引結構來實現高效的數據存取。B+ Tree 是 B-Tree 的一種變體,專門設計用來優化磁碟 I/O 操作,特別適合大規模數據的範圍查詢和排序操作。
#### B+ Tree 的結構與特性
* 所有數據都儲存在葉節點:
在 B+ Tree 中,非葉節點僅存儲索引鍵(Key),而不包含具體的數據。所有的實際數據都存儲在葉節點中。這樣的設計可以讓非葉節點更小,從而在內存中存放更多的索引鍵,減少磁碟 I/O 操作。
* 平衡性:
B+ Tree 是一棵平衡樹,所有葉節點的高度相同,這意味著從根節點到任意葉節點的路徑長度是相同的。因此,查詢、插入和刪除操作的時間複雜度均為 𝑂(log𝑛)。
* 順序存取:
葉節點之間形成了鏈表,便於範圍查詢和順序遍歷。從一個葉節點開始,可以通過鏈表直接訪問後續的葉節點,這在進行範圍查詢和排序查詢時非常高效。
* 高效的磁碟 I/O:
B+ Tree 的節點數量設計可以讓每個節點剛好適合磁碟的一個頁面,從而減少 I/O 操作次數,提升查詢和插入性能。
#### B+ Tree 與 B-Tree 的主要區別
* 數據儲存位置:
在 B-Tree 中,數據既可以儲存在葉節點,也可以儲存在非葉節點。而在 B+ Tree 中,所有數據都儲存在葉節點,非葉節點只存儲索引鍵。
* 鏈表結構:
B+ Tree 的葉節點之間是鏈接的,形成了一條鏈表,因此支援範圍查詢。B-Tree 則沒有這個鏈表結構。
* 節點佔用空間:
B+ Tree 的非葉節點僅存儲索引鍵,因而佔用空間較少,B+ Tree 的層數通常比 B-Tree 更少,這在處理大量數據時可以提升效率。
#### B+ Tree 的基本操作
1. 搜索
過程:從根節點開始,根據索引鍵逐層向下搜索,直到到達葉節點。在葉節點中找到具體的數據。
範圍查詢:由於葉節點之間有鏈表結構,可以從查找到的葉節點開始,通過鏈表直接遍歷後續的葉節點,獲取範圍內的所有數據。
2. 插入
定位插入位置:首先通過索引鍵從根節點開始遍歷,找到應插入的葉節點。
* 插入數據到葉節點:
* 如果葉節點有空間,直接插入數據。
* 如果葉節點滿了,則進行「節點分裂」。
* 節點分裂:將葉節點分成兩個部分,並將中間鍵提升到父節點作為索引鍵。如果父節點也滿了,則遞歸向上分裂,直到整棵樹的高度平衡。
3. 刪除
* 定位刪除目標:通過索引鍵找到葉節點中的目標數據。
* 刪除數據:從葉節點中刪除該數據。
* 如果刪除後葉節點中數據量低於最小限制,則進行合併或重分配,以保持樹的平衡。
#### B+ Tree 的優點
* 高效的範圍查詢:B+ Tree 的葉節點之間形成了鏈表結構,這使得範圍查詢非常高效,適合資料庫中的範圍查詢需求。
* 更小的非葉節點:非葉節點不存儲具體數據,只存儲索引鍵,這意味著非葉節點的結構更小,能夠存儲更多的索引鍵,提升了查詢效率。
* 適合大規模數據:B+ Tree 能夠在磁碟上進行高效的數據讀寫操作,適合用於需要處理大量數據的系統中(如資料庫和文件系統)。
#### B+ Tree 的應用場景
* 資料庫系統:B+ Tree 是大多數資料庫(如 MySQL、PostgreSQL)的索引結構,適合範圍查詢、排序查詢等需求。
* 文件系統:在文件系統(如 NTFS)中,用 B+ Tree 來實現目錄查找和文件訪問。
### Index(索引)
> 索引是一種加速資料庫查詢的數據結構,通過為特定的列或多列建立索引,資料庫可以更快地查找匹配的記錄。
#### 常見的索引類型:
1. B-Tree 索引:大多數資料庫的默認索引,適合範圍查詢、排序查詢等。
1. Hash 索引:基於 Hash 表結構,適合精確匹配查詢,但不支援範圍查詢。
1. 全文索引(Full-text Index):用於全文搜索,在文本文檔或大文本欄位中特別有用。
1. 空間索引(Spatial Index):用於地理空間數據的索引,常見於 GIS 應用。
#### 使用場景:
1. 快速查找:當需要在大量數據中進行頻繁的查找操作時,使用索引可以顯著提升查詢速度。
1. 排序查詢:索引可以用於加速排序查詢,避免對整個表進行排序操作。
1. 範圍查詢:B-Tree 索引支援範圍查詢,如查找特定範圍內的數值
#### 注意事項:
1. 索引會佔用額外的儲存空間。
1. 過多的索引會影響插入和更新操作的性能。
### Secondary Index(二級索引)
> 資料庫中用來加速非主鍵(Non-Primary Key)列查詢的索引。二級索引是一種輔助索引,因為它不依賴於主鍵,主要用於加速特定列的查詢操作。
#### 概念
* Primary Index(主鍵索引):依據表的主鍵(Primary Key)來構建索引,每條記錄在資料庫中都有唯一的主鍵索引。
* Secondary Index(二級索引):根據非主鍵列(例如名稱、地區等)建立的索引,允許通過非主鍵列進行高效查詢。
:::success
二級索引的設計主要用於提升查詢性能。如果表中的某些欄位經常被查詢(如通過 WHERE 條件進行篩選),為這些欄位建立二級索引可以避免全表掃描,顯著提高查詢效率。
:::
#### Secondary Index 的類型
1. 單列索引:在一個非主鍵列上建立的索引,適合單一條件的查詢。
1. 複合索引(Composite Index):在多個列上建立的索引,適合多條件的查詢。
1. 唯一索引(Unique Index):保證索引列中的值是唯一的,有助於數據完整性。
1. 全文索引(Full-Text Index):用於全文搜索,通常用於查詢大文本數據。
#### Secondary Index 的工作原理
當查詢使用了二級索引的欄位時,資料庫會通過以下步驟進行查詢:
1. 定位索引鍵:二級索引將記錄的非主鍵欄位(如名稱、地區等)作為索引鍵進行查詢。
1. 獲取主鍵參照:二級索引儲存了該記錄的主鍵值。當二級索引找到匹配的索引鍵後,會返回主鍵值。
1. 回表(Row Lookup):資料庫會根據主鍵值回到主索引(通常是聚簇索引)中找到完整的數據行。
:::success
資料庫的索引是為了加速查詢性能而設計的,它們只包含部分列的資料,這樣可以節省存儲空間並加快查詢速度。然而,當查詢需要的列不全在索引中時,資料庫需要回到主表中獲取完整的行資料。這種查詢方式就是所謂的「回表」。
:::
這種回表的過程增加了一些 I/O 開銷,但仍然比全表掃描效率更高,特別是在查詢條件匹配大量行時。
#### Secondary Index 的優缺點
* 優點:
* 加速非主鍵查詢:對於頻繁查詢的非主鍵列,二級索引可以顯著提升查詢速度。
* 支持範圍查詢和排序查詢:B-Tree 結構的二級索引支持範圍查詢和排序查詢,適合用於進行篩選和排序。
* 減少 I/O 操作:二級索引減少了查詢時的資料掃描次數,節省了系統的 I/O 資源。
* 缺點:
* 增加寫操作的開銷:插入、更新、刪除操作都需要同步更新所有二級索引,因此會降低寫入性能。
* 佔用存儲空間:二級索引需要額外的空間來儲存索引結構,索引列越多,佔用的空間越大。
* 回表開銷:當查詢通過二級索引命中記錄後,需要回表查詢完整記錄,這增加了 I/O 操作。
### 樂觀與悲觀鎖
在資料庫或並發控制中,樂觀鎖(Optimistic Lock)和悲觀鎖(Pessimistic Lock)是兩種不同的鎖定機制,用於管理多個事務或線程對同一資源的訪問。這兩種鎖定策略的核心區別在於它們對資源衝突的預期態度:
* 樂觀鎖 : 假設資源衝突不常發生,因此允許多個事務同時進行操作,只有在提交數據時才進行衝突檢查。
* 悲觀鎖 : 假設資源衝突是常見的,因此在操作資源之前,會先加鎖,確保當前事務獲得資源的排他性,避免其他事務同時訪問。
#### 樂觀鎖(Optimistic Lock)
> 樂觀鎖假設資源衝突的概率較低,因此允許多個事務同時操作資源。當事務準備提交變更時,樂觀鎖會檢查在操作過程中是否有其他事務修改過同一資源。如果沒有衝突,則變更成功;如果有衝突,則根據應用需求進行重試或回滾操作。
##### 實現方式:
* 版本號(Version Number):在表中增加一個版本號字段,每次更新數據時,版本號自增。事務在更新時會先檢查版本號是否與讀取時一致,如果一致則更新並自增版本號;否則說明數據已被其他事務修改,更新失敗。
* 時間戳(Timestamp):使用時間戳記錄每次修改的時間,事務在提交時比較時間戳是否一致。
##### 使用場景:
* 高並發的讀取場景:樂觀鎖適合讀多於寫的情況下,衝突概率低,可以在不加鎖的情況下進行操作。
* 無需嚴格同步的應用:在一些應用中,對數據的同步要求不高,允許偶爾的衝突和重試。
##### 優點:
* 高並發性能:樂觀鎖不需要長時間的鎖住資源,因此在高並發場景下性能較好。
* 無死鎖問題:樂觀鎖不會加鎖,因此不會出現死鎖情況。
##### 缺點:
* 重試開銷:如果衝突發生,則需要進行重試,這可能會增加系統的處理開銷。
* 不適合高衝突場景:在頻繁更新的場景中,樂觀鎖可能導致過多的重試和失敗,降低性能。
#### 悲觀鎖(Pessimistic Lock)
> 悲觀鎖假設資源衝突的概率較高,因此在操作資源之前,會先獲取鎖,將其他事務排除在外,直到當前操作完成並釋放鎖。這樣可以確保在操作期間沒有其他事務修改資源,但可能會導致阻塞等待。
##### 實現方式:
* 資料庫鎖機制:許多資料庫提供原生的悲觀鎖機制。例如,在 MySQL 中可以使用 SELECT ... FOR UPDATE 來鎖定選定行,防止其他事務修改。
* 排他鎖(Exclusive Lock):在資料庫中,排他鎖是一種悲觀鎖,當一個事務獲得排他鎖後,其他事務必須等待該鎖釋放。
##### 使用場景:
* 高頻寫入場景:在大量更新或刪除的場景下,悲觀鎖可以避免衝突,確保資料的一致性。
* 強一致性需求的應用:如果應用對數據一致性有嚴格要求(例如銀行轉賬、訂單扣款等),可以使用悲觀鎖來確保一致性。
##### 優點:
* 確保數據一致性:悲觀鎖可以確保數據的一致性,適合並發修改的情況。
* 無需重試:悲觀鎖保證了資源的排他性,因此通常無需重試。
##### 缺點:
* 阻塞開銷:悲觀鎖會導致其他事務等待鎖釋放,因此在高併發場景下性能較差。
* 死鎖風險:當多個事務互相等待鎖釋放時,可能發生死鎖。
## ACID
> 是資料庫管理系統(DBMS)中用於描述事務(Transaction)性質的四個重要屬性,這些屬性保證了資料庫操作的可靠性和一致性。ACID 的四個特性分別是:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)。
### 原子性(Atomicity)
> 定義:原子性確保事務中的所有操作要麼全部成功,要麼全部失敗。在一個事務內,如果某一步操作失敗,事務中已經執行的所有操作都必須回滾(Rollback),使資料庫返回到事務開始之前的狀態。
:::success
示例:銀行轉帳操作 假設有一個事務用於將 A 帳戶的 100 元轉移到 B 帳戶。如果將 A 帳戶的金額減少了 100 元,但在將這 100 元添加到 B 帳戶時出現錯誤,原子性將保證 A 帳戶的扣款操作也會被回滾,使得兩個帳戶的金額都保持原始狀態。
:::
### 一致性(Consistency)
> 定義:一致性確保資料庫在事務執行之前和執行之後都處於一致的狀態。這意味著,事務必須從一個有效的資料庫狀態轉換到另一個有效的資料庫狀態,不會破壞資料庫的完整性約束(例如唯一性、外鍵、檢查約束等)。
:::success
示例:銀行帳戶餘額不能為負數 在轉帳事務中,如果 A 帳戶的餘額不足 100 元,則事務不應該成功。這樣可以保證資料庫中不會存在餘額為負的帳戶,從而保持一致性。
:::
### 隔離性(Isolation)
> 定義:隔離性確保多個事務同時執行時,各事務的操作互不影響。每個事務的中間狀態對其他事務是不可見的。隔離性使得事務看起來像是順序執行的,即使它們實際上是並行執行的。
**隔離級別:**
1. 讀未提交(Read Uncommitted):一個事務可以讀取到其他事務未提交的數據,可能會導致「髒讀」(Dirty Read)。
1. 讀已提交(Read Committed):一個事務只能讀取到其他事務已提交的數據,避免了髒讀,但可能出現「不可重複讀」(Non-repeatable Read)。
1. 可重複讀(Repeatable Read):一個事務在整個過程中多次讀取相同數據時,讀取結果保持一致,避免了不可重複讀,但可能出現「幻讀」(Phantom Read)。
1. 序列化(Serializable):最高的隔離級別,所有事務串行執行,避免了所有讀寫問題,但性能較低。

:::success
示例:避免髒讀 假設有兩個事務 T1 和 T2。如果 T1 在更新 A 帳戶的餘額但尚未提交時,T2 可以讀取到 T1 的未提交變更,這就是髒讀。隔離性確保 T2 無法讀取 T1 的未提交變更,從而防止髒讀。
:::
### 持久性(Durability)
> 定義:持久性確保一旦事務提交,其對資料庫的變更將永久保留,即使系統發生崩潰(例如斷電、系統故障)也不會丟失。這通常通過寫入日誌或快照的方式來保證,確保數據在崩潰後可以恢復。
:::success
示例:確保轉帳操作的安全性 在銀行轉帳事務中,一旦轉帳操作完成並提交,即使之後發生系統故障,也能保證轉帳的結果被正確保留,不會因故障而丟失。
:::
### ACID 的總結

#### ACID 的重要性
ACID 屬性在資料庫管理系統中非常重要,因為它們能夠確保多使用者、多事務併發環境下數據的一致性和可靠性。具體來說:
1. 保證數據一致性:ACID 可以確保在任何情況下,資料庫中的數據始終保持一致的狀態。
1. 提升資料庫穩定性:通過隔離性和持久性,可以避免數據競爭和崩潰時數據丟失的風險。
1. 簡化應用開發:開發者可以依賴資料庫的 ACID 屬性來簡化並發操作中的數據一致性問題,而無需手動管理多事務之間的同步。
### 總整理
ACID 是關於資料庫事務的四個核心特性,它保證了事務操作的可靠性和一致性:
1. 原子性:保證事務內所有操作的不可分割性。
1. 一致性:確保事務完成後資料庫處於有效狀態。
1. 隔離性:確保併發事務之間的操作互不干擾。
1. 持久性:確保事務提交後數據的永久保存。
這四個特性在傳統關係型資料庫中非常重要,是保證數據安全和一致性的基礎,但在分布式系統中,實現 ACID 可能會有較大的性能開銷。理解 ACID 特性有助於設計可靠的資料庫操作,特別是在多使用者、多事務併發環境中。
## 分布式資料庫實現 ACID 的挑戰與策略
> 在分布式資料庫中,網絡延遲、節點失效、網絡分區(Partition)等因素使得在多節點環境中保持 ACID 屬性變得複雜。以下是每個 ACID 屬性在分布式環境中的實現挑戰及解決方法:
* 原子性(Atomicity):
* 挑戰:分布式事務涉及多個節點,如何確保所有節點的操作要麼全部成功,要麼全部失敗?
* 解決方案:使用 兩階段提交(2PC) 或 三階段提交(3PC) 協議來實現原子性。這些協議會在多個節點間協調,以確保事務的原子性。2PC 主要由「準備階段」和「提交階段」組成,但在網絡分區或節點失敗時可能會導致事務卡住。3PC 在 2PC 基礎上增加了協調器,進一步減少了鎖死的風險。
* 一致性(Consistency):
* 挑戰:如何在多節點的分布式環境中確保數據一致性,尤其是網絡分區或節點異常時?
* 解決方案:通過 強一致性 機制,例如分布式鎖、事務提交協議等來保證一致性。這通常伴隨一定的性能開銷。此外,分布式資料庫也可以採用 最終一致性 策略,允許數據在短時間內不一致,但最終會達成一致。
* 隔離性(Isolation):
* 挑戰:多個事務並行執行時,如何確保它們之間不互相影響?
* 解決方案:採用多版本並發控制(MVCC)或分布式鎖機制來確保隔離性。MVCC 可以讓讀操作不受寫操作影響,從而提升併發性能。此外,分布式資料庫還可以使用事務的隔離級別來控制隔離性,但在分布式環境中通常需要在隔離性和性能之間進行權衡。
* 持久性(Durability):
* 挑戰:如何確保事務提交後的數據在所有節點中永久保留,即使發生系統崩潰?
* 解決方案:分布式資料庫通常會在多個節點間進行數據複製(Replication),並使用分布式日誌(如 Raft 或 Paxos 協議)來確保數據持久性。這樣,即使某個節點失效,數據仍然可以從其他副本中恢復。
## CAP 理論
> 用來描述分布式系統的三個特性,並指出分布式系統在網絡分區(Partition Tolerance)存在的情況下,無法同時滿足一致性(Consistency)和可用性(Availability)。CAP 三個特性分別是:
* 一致性(Consistency):
每次讀取操作都可以獲取到最新的數據。如果一個事務在一個節點上寫入了數據,那麼該數據在其他節點上立即可見。
* 可用性(Availability):
每次請求都能獲得響應(不一定是最新數據)。即使某些節點出現故障,系統仍然可以提供服務。
* 分區容錯性(Partition Tolerance):
系統能夠應對網絡分區的情況,即使節點之間的網絡斷開,系統仍能正常工作。
CAP 不可能定理:CAP 理論指出,在分佈式系統中,無法同時滿足一致性、可用性和分區容錯性三個特性,只能在其中兩個特性之間進行選擇:
* CP 系統:選擇一致性和分區容錯性,捨棄可用性。在網絡分區時保證數據一致性,但可能無法及時響應用戶請求。常見的 CP 系統包括 HBase、MongoDB 等。
* AP 系統:選擇可用性和分區容錯性,捨棄一致性。在網絡分區時保證系統可用性,但不同節點上的數據可能暫時不一致。常見的 AP 系統包括 Cassandra、DynamoDB 等。
## BASE 理論
> 對 CAP 理論的補充,特別針對分布式系統中對一致性要求較弱的場景。BASE 理論的核心思想是:在確保基本可用性的基礎上,允許數據暫時不一致,但最終達成一致。BASE 理論的三個特性如下:
* 基本可用(Basically Available):
系統保證基本的可用性,即在系統部分功能出現故障的情況下,仍然可以為用戶提供部分可用的服務。
* 軟狀態(Soft state):
允許系統中的數據狀態在一段時間內不是一致的,也就是說不同節點之間可以存在暫時的不一致。
* 最終一致性(Eventual consistency):
系統保證最終一致性,即隨著時間的推移,所有節點的數據將會達成一致。最終一致性適合於對一致性要求不高但可用性要求高的分布式系統。
### BASE 與 ACID 的區別:
* ACID:強調數據的一致性和可靠性,適合對數據一致性要求高的應用。
* BASE:允許數據在短時間內不一致,以換取系統的可用性和性能,適合分布式系統中對一致性要求較低的場景。
## ACID、CAP、BASE 比較

在分布式系統中,根據具體需求選擇合適的理論和技術:
* 如果應用需要強一致性(如金融系統),可選擇 CP 系統或使用 ACID 特性的資料庫。
* 如果應用對一致性要求不高,但需要高可用性(如社交網絡、網購等),則可以選擇 BASE 理論或 AP 系統,保證最終一致性。
## MVCC(Multi-Version Concurrency Control,多版本並發控制)
> 是一種資料庫並發控制技術,允許多個事務同時訪問數據,並在讀取和寫入之間提供隔離,以提高並發性能。MVCC 通過為每個數據行保存多個版本,使得讀操作無需等待寫操作完成,從而減少鎖定和衝突,並提供更高的並發性和讀寫效率。
### MVCC 的核心概念
1. 多版本存儲:
* MVCC 通過為每行數據保留多個版本,使得資料庫可以在不同的事務中同時讀取和修改數據。
* 每個事務在操作數據時,會讀取數據的特定版本,從而避免讀寫衝突。
2. 快照讀取:
* 每個事務在開始時會獲取一個一致性的數據快照。該快照包含事務開始時的數據狀態。
* 事務的所有讀操作都基於這個快照進行,不會受到其他事務寫入操作的影響。
3. 版本控制(讀取一致性):
* MVCC 通常使用版本號或時間戳來區分不同事務對數據的修改。
* 事務在開始時獲取當前版本的數據,並在提交新數據時自動更新數據版本,從而確保數據的一致性。
4. 避免鎖定:
* 由於每個事務讀取的是自己的快照,MVCC 可以避免傳統鎖機制帶來的等待,允許更高的併發度。
* 寫操作則依然需要進行鎖定來避免衝突,但僅限於修改的數據行,影響範圍較小。
### MVCC 的工作原理
1. 快照讀取(Snapshot Read):
* 當一個事務開始時,它會獲取資料庫當前的一個數據快照,並根據這個快照進行所有的讀操作。
* 每個事務只能讀取在該事務開始之前已提交的數據變更,事務進行期間不會讀到其他事務的未提交變更。
2. 提交讀取(Commit Read):
* 寫操作在提交時更新版本或時間戳,為該數據創建一個新的版本。這意味著在事務提交之前,其他事務無法讀到新版本。
* 當事務提交後,新的數據版本才對其他事務可見。
3. 寫衝突解決:
* 寫操作的事務在更新數據時,會檢查目標數據行的最新版本。如果版本不匹配,則說明有其他事務在更新這行數據,系統會觸發回滾或重試操作。
### MVCC 的優缺點
* 優點:
1. 提高並發性:MVCC 避免了讀寫操作的直接衝突,允許多個事務並發訪問,特別適合高讀取操作的場景。
1. 無鎖讀取:讀操作無需加鎖,減少了傳統鎖機制下的等待時間。
1. 更好的用戶體驗:MVCC 可以在不鎖定的情況下提供一致的讀取體驗,即使有事務在修改數據,其他事務仍然可以進行無衝突的讀取操作。
* 缺點:
1. 存儲開銷:MVCC 需要存儲數據的多個版本,可能會增加儲存空間的需求,並需要後續的垃圾回收來清理過期版本。
1. 寫操作開銷:寫操作需要創建新版本,並進行版本管理,對寫密集型的工作負載可能會影響性能。
1. 版本管理和回收:隨著時間推移,資料庫可能會積累許多過期版本,需要定期進行清理,否則會影響系統性能。
### MVCC 的應用場景
MVCC 通常應用於以下場景:
* 高讀取併發的系統:例如網絡應用、查詢密集的系統,MVCC 可以提供快速的無鎖讀取,提升系統的併發處理能力。
* 需要快照隔離的場景:在一些對隔離性有較高需求的應用中(如財務系統),MVCC 提供的快照隔離能保證數據的一致性。
* 長時間的查詢操作:長時間運行的查詢操作可能會影響其他事務,但使用 MVCC 可以避免查詢操作對其他事務的影響,從而提升查詢性能。
### MVCC 在資料庫中的實現
許多資料庫系統採用了 MVCC 來提升並發性能,以下是幾個常見資料庫中的實現:
1. MySQL(InnoDB 引擎):
* MySQL InnoDB 使用隱藏的版本號來實現 MVCC,為每行數據增加 DB_TRX_ID(事務 ID)和 DB_ROLL_PTR(回滾指針),每次操作都會更新這些隱藏欄位。
* InnoDB 提供了「快照讀取」(Snapshot Read)和「提交讀取」(Read Committed)兩種隔離級別,利用 MVCC 實現無鎖的讀取。
2. PostgreSQL:
* PostgreSQL 中的 MVCC 是基於多版本存儲控制的,每次更新會創建新版本的數據行,而不刪除舊版本。
* PostgreSQL 使用 VACUUM 操作清理過期版本,從而釋放存儲空間。
3. Oracle:
* Oracle 使用回滾段(Rollback Segments)來實現 MVCC,允許事務在進行快照讀取時,根據回滾段中的歷史版本來進行數據訪問。
### MVCC 與傳統鎖機制的對比

### 總結
MVCC 是一種提高資料庫並發性能的技術,通過為每個數據行保存多個版本,使得讀操作無需等待寫操作完成,有效減少了鎖定和衝突。MVCC 適合高併發、查詢密集的應用場景,但會增加存儲和寫操作的開銷。
主要特點包括:
1. 支援快照讀取,減少讀寫衝突。
1. 提供更高的併發性能,特別適合高讀取應用。
1. 需要處理存儲空間管理和過期版本清理。
MVCC 在 MySQL、PostgreSQL、Oracle 等主流資料庫中得到了廣泛應用,是一種有效的資料庫併發控制技術。
## JWT
> JWT(JSON Web Token) 是一種開放標準(RFC 7519),用於在不同服務之間作為 JSON 對象安全地傳遞信息。JWT 在許多應用中用於身份驗證和授權,尤其在無狀態的分佈式應用中得到廣泛使用。
### JWT 的結構
JWT 由三個部分組成,每個部分之間用點(.)分隔:
1. Header(標頭)
1. Payload(有效負載)
1. Signature(簽名)
這三部分合併成一個字串,最終形成一個形如 header.payload.signature 的 JWT 字串。
#### Header(標頭)
Header 是 JWT 的頭部,通常包含兩部分信息:
* Type:說明令牌的類型,一般是 "JWT"。
* Algorithm:加密算法,通常是 HS256(HMAC SHA-256)或 RS256(RSA SHA-256)。
```jsonld=
{
"alg": "HS256",
"typ": "JWT"
}
```
會被轉換成 Base64Url 編碼
#### Payload(有效負載)
Payload 是 JWT 的主體,包含具體的聲明(Claims)。Claims 是一些陳述,比如用戶的身份或權限。Payload 可以包含以下三種聲明:
1. Registered Claims(註冊聲明):一些建議的、通用的字段,例如 iss(簽發者)、exp(過期時間)、sub(主體)、aud(接收者)等。
1. Public Claims(公共聲明):可以自行定義,並且可以在 OpenID 等標準中定義,但需要避免與註冊聲明名稱的衝突。
1. Private Claims(私有聲明):自定義聲明,用於應用之間的特定信息。
```jsonld=
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"exp": 1516239022
}
```
會被轉換成 Base64Url 編碼
#### Signature(簽名)
Signature 用於驗證 JWT 的數據完整性,防止數據被篡改。簽名由以下三部分組成:
* 編碼後的 Header
* 編碼後的 Payload
* 秘鑰(Secret)
這三者通過 Header 中指定的算法進行加密,生成簽名部分。例如使用 HMAC SHA-256 的算法:
```jsonld=
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
```
### JWT 的生成和驗證
#### JWT 的生成
在服務器上生成 JWT 的過程包括以下步驟:
1. 建立 Header、Payload 並轉換為 JSON 格式。
1. 將 Header 和 Payload 轉換為 Base64Url 格式。
1. 根據 Header 中指定的加密算法,使用秘鑰生成簽名。
1. 最後將 Header、Payload 和 Signature 三部分組合成一個字串,即完成 JWT 的生成。
#### JWT 的驗證
當伺服器收到包含 JWT 的請求時,伺服器會進行以下驗證過程:
1. 提取出 JWT 的 Header 和 Payload,並根據 Header 中的算法和事先設置的秘鑰重新生成簽名。
1. 將生成的簽名與 JWT 中的簽名進行比較,確保 JWT 未被篡改。
1. 驗證 Payload 中的 exp(過期時間)、iss(簽發者)等聲明,確認 JWT 的有效性。
1. 如果簽名和聲明都合法,則通過驗證,允許該請求訪問受保護資源。
#### JWT 的使用場景
1. 身份驗證:JWT 最常用於身份驗證。在用戶登錄後,服務器生成一個包含用戶信息的 JWT,並將其返回給客戶端。客戶端在每次請求中附帶此 JWT,服務器通過驗證 JWT 來識別用戶身份。
1. 授權:JWT 也可以用於授權,客戶端攜帶 JWT 訪問不同的資源,服務器根據 JWT 中的權限進行授權。
1. 信息傳遞:JWT 也可以用於多系統之間的數據傳遞。JWT 是無狀態的,並且可以攜帶任何聲明,因此適合跨服務傳遞簡單的數據。
#### JWT 的優缺點
* 優點:
1. 無狀態:JWT 是無狀態的,伺服器無需存儲會話數據,可以減少伺服器的負擔,特別適合分佈式架構。
1. 攜帶自定義數據:JWT 可以攜帶任何聲明,並且自定義數據允許應用程式根據需要存儲任意數據。
1. 安全:JWT 通過簽名確保數據的完整性,並且可以使用私鑰進行簽名,保證數據的安全性。
* 缺點:
1. 無法撤銷:一旦 JWT 被簽發,無法撤銷或主動使其失效,這可能會帶來風險。
1. 大小限制:JWT 中包含的數據越多,令牌的大小就越大,這對帶寬和性能有一定的影響。
1. 安全風險:JWT 的安全性依賴於密鑰管理,如果密鑰洩露,會帶來數據風險。
#### JWT 與 Session 的對比

#### JWT 的實際應用案例
1. 用戶登錄系統:在用戶登錄後生成 JWT,然後將其存儲於客戶端。隨後每次請求攜帶該 JWT 作為身份驗證。
1. API 授權:在微服務架構中,每個服務驗證 JWT 來確保請求來自合法用戶,適合分佈式環境。
1. OAuth 2.0 和 OpenID Connect:JWT 常用於 OAuth 2.0 的存取令牌(Access Token),並在 OpenID Connect 中作為身份令牌(ID Token)。
### JWT的過期處理
#### 使用 exp(Expiration)聲明
> 在生成 JWT 時,可以在 Payload 中設置 exp(expiration)屬性。該屬性是一個 UNIX 時間戳,表示 JWT 的過期時間。服務器在驗證 JWT 時會檢查 exp 屬性,如果當前時間超過了 exp 指定的時間,則視為該令牌已過期
```jsonld=
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622 // 過期時間
}
```
#### 過期 Token 的處理策略
處理過期 Token 的策略主要有以下幾種方式:
##### 返回錯誤,要求重新登入
> 這是最簡單的處理方式。如果令牌過期,伺服器會返回一個 "Token Expired" 的錯誤,並要求用戶重新登入以獲取新的令牌。這種方式適合安全性要求高的應用,因為它確保了每次使用者登入都會獲取新的令牌。
* 優點:
* 安全性高,避免了過期令牌的風險。
* 缺點:
*用戶體驗可能不佳,因為過期後需要重新登入。
##### 使用 Refresh Token 機制
Refresh Token 機制是另一種常見的處理過期 Token 的方法,特別適合需要長時間保持登入狀態的應用。
流程如下:
1. 當用戶登入成功後,伺服器會同時生成Access Token(短期有效)和Refresh Token(長期有效)。
1. Access Token 有較短的有效期(例如 15 分鐘),而 Refresh Token 有較長的有效期(例如 7 天)。
1. 當 Access Token 過期後,客戶端使用 Refresh Token 向伺服器請求新的 Access Token。
1. 伺服器驗證 Refresh Token 的合法性,通過驗證後返回新的 Access Token。這樣,用戶無需重新登入即可繼續訪問資源。
1. Refresh Token 一旦過期,則需要用戶重新登入。
這種方法將用戶的會話控制權轉移到了伺服器上,通過 Refresh Token 延長用戶的登入狀態,同時不影響安全性
* 優點:
* 提供了無縫的用戶體驗,用戶無需頻繁重新登入。
* 安全性較高,即使 Access Token 過期,用戶也可以通過 Refresh Token 繼續使用。
* 缺點:
* 需要額外的機制來存儲和管理 Refresh Token,並防止 Refresh Token 遭到竊取。
##### 短期令牌加頻繁自動刷新
這種策略類似於 Refresh Token 機制,但沒有使用單獨的 Refresh Token。伺服器可以生成短期有效的 JWT(例如 5 分鐘有效),並在客戶端靠近過期時間前自動向伺服器請求刷新令牌。這樣,可以實現無縫體驗,且無需使用 Refresh Token。
客戶端在靠近過期時會自動請求新的令牌,如果用戶不活躍一段時間(沒有進行刷新請求),則 JWT 會自動過期。
* 優點:
* 簡化了 Refresh Token 的管理和存儲。
無需頻繁重新登入。
* 缺點:
* 須確保網絡連線穩定,否則可能導致令牌刷新失敗。
##### 安全性考慮
在處理過期 Token 時,還需注意以下安全性問題:
1. 定期清理 Refresh Token:在使用 Refresh Token 機制時,需要定期檢查和清理過期的 Refresh Token,以減少潛在的安全風險。
1. 防止 Refresh Token 被竊取:Refresh Token 的有效期長,應特別注意其存儲方式,避免存放在不安全的地方(例如不受保護的 Local Storage),建議將其存儲在安全的 Cookie 中,並設置 HttpOnly 和 Secure 標誌。
1. 單次使用的 Refresh Token(One-time Use Refresh Token):在每次使用 Refresh Token 獲取新的 Access Token 時,將原有的 Refresh Token 作廢,並生成新的 Refresh Token,從而防止舊的 Refresh Token 被重用,提升安全性。
## RBAC(Role-Based Access Control,基於角色的訪問控制)
> 是一種授權模型,用於控制系統中的用戶如何訪問資源。RBAC 通過角色來管理和分配權限,從而簡化權限管理流程,提高靈活性和安全性。
**在 RBAC 模型中,有三個主要元素:**
1. 用戶(User):系統中的個人或實體,每個用戶在系統中可以擁有多個角色。
1. 角色(Role):角色是一組權限的集合,代表某一類用戶的操作權限。例如,管理員、編輯者、訪客等。
1. 權限(Permission):具體的操作權限或資源訪問權限,通常指代對系統資源的具體操作(如讀取、寫入、刪除等)。
RBAC 通過將權限分配給角色,並讓用戶扮演角色來獲取權限。這樣,用戶並不是直接與權限綁定,而是通過角色來間接管理權限,從而提高了管理的靈活性。
**RBAC 的核心概念**
* 用戶分配角色(User Assignment of Roles):
* 每個用戶可以被分配一個或多個角色,這些角色決定了用戶擁有哪些權限。
* 例如,Alice 可以被分配為「管理員」,而 Bob 被分配為「訪客」。
* 角色分配權限(Role Assignment of Permissions):
* 每個角色可以擁有多個權限,這些權限是角色可以進行的操作。
* 例如,「管理員」角色可能擁有「讀取」、「寫入」、「刪除」的權限,而「訪客」角色可能只有「讀取」的權限。
* 用戶透過角色獲得權限:
* 用戶在被分配角色後,便可以通過角色來間接地獲得權限。
* 當系統進行權限檢查時,會確認用戶是否具備該角色,進而決定是否授予權限。
**RBAC 的優點**
* 簡化權限管理:RBAC 通過角色來管理權限,減少了直接為每個用戶分配權限的工作量,便於維護。
* 靈活性高:可以根據不同的業務需求創建角色,靈活配置每個角色的權限,適應不同角色的需求。
* 安全性高:RBAC 有助於控制誰可以執行哪些操作,從而增強了系統的安全性。
* 便於組織管理:在大型系統中,RBAC 可以對應不同的職位、職能來創建角色,使得權限管理更加貼合組織結構。
**RBAC 的應用場景**
RBAC 適用於大部分具有多層次權限需求的系統,特別是在企業級應用中。以下是常見的應用場景:
* 企業管理系統:例如 ERP、CRM 等系統,通常具有不同的角色(如管理員、員工、訪問者等),每個角色擁有不同的訪問和操作權限。
* 內容管理系統(CMS):在 CMS 中,編輯、審核、發布等角色可能擁有不同的權限,例如編輯只能創建和修改內容,審核者可以批准內容,發布者可以將內容公開。
* 教育系統:在學校的學習管理系統中,可能會有學生、老師、管理員等不同角色,每個角色擁有不同的功能權限。
**RBAC 的層次模型**
RBAC 模型根據其複雜度和功能進行了分層,從簡單到複雜分為以下三層:
1. RBAC0(基礎 RBAC):
* 基礎層級,包含了用戶、角色、權限三個概念,允許將角色分配給用戶,並將權限分配給角色。
2. RBAC1(角色層次結構):
* 增加了角色的層次結構,可以設置角色之間的繼承關係,例如高級角色可以繼承低級角色的權限。
* 例子:管理員角色可以繼承員工角色的權限,這樣管理員就自動擁有了員工的所有權限。
3. RBAC2(約束):
* 在 RBAC1 的基礎上引入了「約束」概念,可以定義角色的互斥條件或衝突,例如一個用戶不能同時擁有「審核員」和「被審核人」的角色。
* 約束可以防止權限過度授予,提高系統的安全性。
4. RBAC3(RBAC1 + RBAC2):
* 綜合了角色繼承和約束功能的最複雜模型,適合複雜的大型系統。
## Docker
> Docker 是一種容器化技術,用於創建、部署和運行應用程式的輕量級虛擬化環境。它通過將應用程式及其依賴打包在「容器(Container)」中,使得應用能夠在不同的環境中一致地運行,從而提高了應用的可移植性和開發效率。
### Docker 的核心概念
1. 容器(Container):
* 容器是 Docker 的核心單位,它包含了應用程式及其所有的依賴(如庫、工具、配置文件等),保證應用程式可以在任何支持 Docker 的平台上運行。
* 容器類似於輕量級的虛擬機,但更輕便高效,因為它共享了主機的操作系統內核。
2. 映像(Image):
* Docker 映像是一種只讀的模板,包含應用程序的文件系統和運行環境(如代碼、運行時、庫等)。
* 每個映像是層級化的,由多個「層」組成,每層代表了不同的文件和配置。Docker 使用聯合文件系統來組合這些層,以便快速構建和重用。
* 使用映像可以快速生成容器,並且一個映像可以創建多個容器。
3. Dockerfile:
* Dockerfile 是一種用於描述映像構建過程的腳本文件。通過編寫 Dockerfile,開發者可以定義應用所需的所有安裝步驟和配置,並最終生成一個 Docker 映像。
* Dockerfile 中的指令(如 FROM、RUN、COPY 等)可以指定基礎映像、運行命令、安裝依賴等。
4. Docker 引擎(Docker Engine):
* Docker 引擎是運行在主機上的 Docker 核心組件,負責映像的生成、容器的運行和資源管理等。
* 它包括 Docker 守護進程(daemon)和 Docker CLI(命令行工具),開發者可以通過 CLI 與 Docker 守護進程交互,管理容器和映像。
5. 倉庫(Registry):
* Docker 映像可以存儲在倉庫中,類似於代碼存儲庫。Docker Hub 是最流行的公共倉庫,用於存儲和分發映像。
* 用戶也可以搭建私有倉庫(如 Harbor),以便在企業內部存儲和管理映像。
### Docker 的核心特性
1. 輕量級虛擬化:
* Docker 容器與傳統的虛擬機相比更輕量,因為容器共享主機的操作系統內核,而不是模擬整個操作系統。
* 容器的啟動和停止速度非常快,通常在秒級,這樣可以大大提高應用的部署和擴展速度。
2. 可移植性:
* Docker 容器包含了應用所需的全部依賴,因此可以在任何支持 Docker 的環境中運行,無需重新配置。
* 開發人員可以在本地開發和測試,並確保應用可以無縫運行在生產環境中,解決了「在我電腦上可以正常運行」的問題。
3. 資源隔離:
* Docker 使用 Linux 的內核功能(如命名空間和控制組,Namespace 和 cgroups)來實現容器之間的資源隔離。每個容器都是獨立的,不會互相干擾。
* 這樣可以確保每個應用在自己的隔離環境中運行,並且可以根據需求配置 CPU、內存和存儲資源的使用量。
4. 版本控制和映像層:
* Docker 映像是分層構建的,每一層都代表了映像的一個修改。這樣,Docker 可以重用相同層,減少存儲需求並加快構建過程。
* 映像可以版本化,使得開發人員可以回滾到特定的版本。
5. 自動化:
* Dockerfile 可以自動化映像的構建過程,將映像的生成、依賴安裝、配置過程等全部定義在代碼中。
* 配合 CI/CD 工具,可以實現從代碼提交到生產部署的全流程自動化。
### Docker 的底層技術
#### 命名空間(Namespaces):
* 命名空間 是 Linux 提供的一種機制,用於隔離系統資源(如進程 ID、網絡、掛載點等),使每個容器擁有自己的「命名空間」,從而與主機和其他容器隔離。
* 常見的命名空間包括:
* PID 命名空間:隔離進程 ID,使容器內的進程擁有獨立的進程樹。
* NET 命名空間:隔離網絡資源,包括網卡、IP 地址、路由表等,使每個容器擁有獨立的網絡環境。
* IPC 命名空間:隔離進程間通信資源,使容器內的進程無法直接與其他容器通信。
* MNT 命名空間:隔離文件系統掛載點,使每個容器擁有自己的文件系統視圖。
* UTS 命名空間:隔離主機名和域名,使每個容器可以設置自己的主機名。
* 作用:通過命名空間,Docker 可以在單一操作系統內核上運行多個隔離的容器,達到類似虛擬機的效果,但佔用的資源更少
#### 控制組(Control Groups,cgroups)
* cgroups 是 Linux 的一項功能,用於限制、隔離和管理不同進程組的資源使用(如 CPU、內存、磁碟 I/O 等)。
* 資源限制:cgroups 允許 Docker 限制每個容器可以使用的 CPU、內存和磁盤 I/O 等資源,確保每個容器在系統資源的使用上彼此隔離。
* 資源統計:cgroups 提供精確的資源使用統計,便於監控和管理容器的資源消耗。
* 作用:通過 cgroups,Docker 可以確保容器之間的資源隔離,並防止某個容器消耗過多的系統資源,影響其他容器或主機的穩定性。
#### 聯合文件系統(Union FS):
* Union FS 是一種文件系統技術,允許將多個文件系統層疊合併為一個文件系統視圖。
* 分層映像(Layered Images):Docker 使用 Union FS 來構建分層的映像,每個 Docker 映像由多個層組成,每層只記錄改動部分(例如新增、修改的文件)。這樣可以提高映像的重用性和構建速度。
* 寫時複製(Copy-on-Write):容器啟動後會在映像的基礎上創建一個可寫層,對文件的修改僅影響容器內的可寫層,不會影響基礎映像。
* 作用:通過 Union FS,Docker 映像可以快速構建和部署,並且允許映像分層重用,提高存儲效率。
#### 容器格式(如 OCI 標準):
* OCI(Open Container Initiative) 定義了容器映像和運行時標準,確保不同的容器工具之間的互操作性。
* Docker 映像和 OCI 格式兼容:Docker 映像格式基於 OCI 標準,使得 Docker 映像可以在支持 OCI 的運行環境中執行,提高了可移植性。
#### 網絡虛擬化(Networking):
* Docker 支持多種網絡模式(bridge、host、overlay 等),每種模式通過虛擬網卡和網橋(bridge)等技術實現網絡隔離和互通。
* 橋接模式(Bridge Networking):每個容器都連接到一個虛擬網橋,容器之間可以相互通信,並且可以通過主機的 NAT 轉發訪問外部網絡。
* 主機模式(Host Networking):容器共享主機的網絡堆棧,性能較好但不具備隔離性。
* 覆蓋網絡(Overlay Networking):多主機之間的容器可以通過 overlay 網絡進行通信,通常用於跨主機的容器編排。
### VM與Docker的差異


#### 架構層次:
**虛擬機(VM):**
在 VM 架構中,每個虛擬機運行在一個 Hypervisor(虛擬機管理程序)上。
每個虛擬機都有一個完整的 Guest OS(客體操作系統),即每個 VM 需要自己的操作系統副本,這樣才能支持上層的應用程式。
**Docker 容器:**
Docker 的架構更輕量級,它直接在宿主機的操作系統上運行。所有的容器共享同一個操作系統核心(Host OS)。
Docker 通過 Docker Engine 來管理和運行容器,而不需要每個容器都有一個完整的操作系統副本。
#### 資源佔用:
**虛擬機(VM):**
* 每個 VM 都包含一個完整的操作系統,這就增加了大量的資源開銷(如內存和 CPU 使用)。此外,啟動每個 VM 時也需要分配足夠的資源來支持其運行的 OS。
**Docker 容器:**
* 容器不需要運行獨立的操作系統,而是共享主機的操作系統核心,因此比 VM 更輕量,佔用的資源更少。
* 容器的啟動速度非常快(通常在秒級),因為不需要加載完整的操作系統。
#### 啟動速度:
**虛擬機(VM):**
* VM 啟動相對較慢,因為需要加載整個操作系統。啟動時間通常在數十秒到數分鐘之間,具體取決於 OS 和應用的複雜性。
**Docker 容器:**
* Docker 容器啟動非常迅速,因為它們不需要啟動完整的操作系統。容器的啟動時間通常在數秒以內。
#### 隔離性與安全性:
**虛擬機(VM):**
* 每個 VM 有自己的操作系統內核,因此隔離性更強。如果一個 VM 出現故障或被攻擊,理論上不會直接影響到其他 VM 和主機系統。
**Docker 容器:**
* 容器共享主機的操作系統核心,因此隔離性略低於 VM。如果主機操作系統出現問題,可能會影響到所有容器。
* 但 Docker 也有自己的安全機制(如命名空間和控制組),以實現一定程度的隔離。
#### 可移植性:
**虛擬機(VM):**
* VM 在不同環境間的移動性不如容器靈活,因為 VM 包含整個操作系統,需要更高的計算資源。
**Docker 容器:**
* 容器包含應用和其依賴,可以輕鬆地在不同環境中移動和運行(例如從開發環境到生產環境),非常適合 DevOps 流程。
#### 運行效率與性能:
**虛擬機(VM):**
* 因為 VM 運行自己的操作系統,性能上略低,存在一定的性能開銷(例如 Hypervisor 的開銷)。
**Docker 容器:**
* 容器運行效率較高,因為它直接運行在主機的操作系統上,不需要 Hypervisor,減少了系統的資源消耗。
## 事件驅動
基於「事件」來驅動程式的執行流。這意味著應用程式的邏輯執行不是按照順序進行,而是由發生的事件來觸發對應的動作。這種模型廣泛應用於 GUI 應用程式、網絡服務、高併發 I/O 操作等場景,因為它可以高效地管理和響應大量並發請求。
:::danger
不適合CPU密集型的任務。
事件驅動模型主要適合 I/O 密集型任務,如網絡請求、文件讀寫等。如果應用程式包含大量的 CPU 密集型任務,單一執行緒可能會無法滿足需求。
:::
### 事件驅動模型的基本原理
事件驅動模型的核心思想是應用程式進入一個 事件循環(Event Loop) 中,等待並監控各種事件的發生。當事件發生時,事件循環會觸發對應的 回調函數(Callback) 來處理該事件。事件驅動通常包含以下幾個要素:
* 事件:
* 事件是系統中發生的動作或變化,如按下按鈕、網絡請求、定時器到期、文件可讀寫等。
* 事件可以是來自用戶的輸入操作,也可以是來自系統的 I/O 事件。
* 事件循環(Event Loop):
* 事件循環是事件驅動模型的核心組件,它會持續運行,等待和監控各種事件的發生。
* 當某個事件發生時,事件循環會將控制權交給對應的回調函數來處理該事件。
* 事件循環允許單個執行緒在非阻塞的情況下處理多個事件,因此適合高併發場景。
* 事件觸發器:
* 當事件發生時,事件觸發器會負責檢測和捕捉該事件,並通知事件循環去執行相應的回調。
* 回調函數(Callback Function):
* 回調函數是事先定義好的處理函數,當事件發生時會被調用。
* 不同的事件可以綁定不同的回調函數,這樣應用程式可以根據事件類型來執行對應的邏輯。
### 事件驅動模型的工作流程
1. 初始化事件循環:
* 應用程式啟動時會初始化一個事件循環,準備進入等待狀態。
2. 註冊事件與回調:
* 為各類事件(如網絡請求、文件 I/O、定時器)設置回調函數,並將這些事件註冊到事件循環中,讓事件循環知道在事件發生時應調用哪個回調函數。
3. 事件等待和檢測:
* 事件循環進入等待狀態,監控已註冊的事件。這通常是通過輪詢機制(如 select、poll、epoll)來實現的。
4. 事件觸發與執行:
* 當某個事件發生(如文件可讀、定時器觸發)時,事件循環會檢測到該事件並觸發相應的回調函數。
* 事件循環將控制權交給對應的回調函數來處理該事件。例如,讀取文件數據、處理網絡請求等。
5. 重複事件循環:
* 當所有事件處理完成後,事件循環繼續監控其他事件,直到程式退出。
## OOP
物件導向程式設計(OOP,Object-oriented programming)是一種程式設計範式,它將程式中的資料和操作進行封裝,並將其組織成物件。
1. 類別(Class)和物件(Object):
* OOP 將程式中的實體抽象為類別,類別定義了對應的屬性(資料)和方法(操作)。
* 類別的實例化則產生了物件,也就是類別的具體實例。每個物件都有自己的狀態(屬性值)和行為(方法)。
1. 封裝(Encapsulation):
* 可以使用訪問修飾符(public、protected、private)來控制類別成員的訪問權限,從而實現封裝。
* 封裝是指將資料和操作封裝在物件內部,對外部隱藏實現的細節,只提供公開的介面(方法)供外部使用。
* 封裝有助於保護資料的完整性,並隱藏了實現的細節,提高了代碼的安全性和可維護性。
1. 繼承(Inheritance):
* 繼承是指一個類別(子類別)可以繼承另一個類別(父類別)的屬性和方法,並且可以在其基礎上進行擴展和修改。
* 繼承允許代碼的重用,減少了重複編碼,同時使代碼更加結構化和模組化。
1. 多型(Polymorphism):
* 在 PHP 中,可以通過覆寫父類別的方法來實現多型。
* 多型是指相同的操作可以在不同的物件類型上具有不同的行為。這使得可以使用相同的介面來處理不同的對象。
* 多型提高了代碼的靈活性和可擴展性,使代碼更容易理解和維護。
OOP 的設計思想強調將複雜的系統分解為獨立的、可重用的組件,並通過組合這些組件來構建更大的系統。
## 工廠模式
旨在通過將對象的創建和初始化邏輯封裝在一個工廠類中,從而隔離客戶端程式碼與具體類別之間的相依性。工廠模式可以使系統更靈活、可擴展,並且易於維護和測試。
在 PHP 中,常見的工廠模式包括簡單工廠模式、工廠方法模式和抽象工廠模式。
* 簡單工廠模式(Simple Factory Pattern):
* 簡單工廠模式是一種最基本的工廠模式,通常由一個工廠類別負責根據客戶端的請求創建適當的對象。
* 客戶端不需要直接創建對象,而是通過呼叫工廠類別的靜態方法或者非靜態方法來取得對象。
```php=
class CarFactory {
public static function createCar($type) {
switch ($type) {
case 'SUV':
return new SUV();
case 'Sedan':
return new Sedan();
default:
throw new Exception("Invalid car type");
}
}
}
// 使用簡單工廠模式
$car1 = CarFactory::createCar('SUV');
$car2 = CarFactory::createCar('Sedan');
```
* 工廠方法模式(Factory Method Pattern):
* 工廠方法模式將對象的創建延遲到其子類別中,每個子類別都實現了一個工廠方法來創建對應的對象。
* 客戶端通過調用工廠方法來獲取對象,從而可以根據需要使用不同的對象。
```php=
interface CarFactory {
public function createCar();
}
class SUVFactory implements CarFactory {
public function createCar() {
return new SUV();
}
}
class SedanFactory implements CarFactory {
public function createCar() {
return new Sedan();
}
}
// 使用工廠方法模式
$suvFactory = new SUVFactory();
$car1 = $suvFactory->createCar();
$sedanFactory = new SedanFactory();
$car2 = $sedanFactory->createCar();
```
* 抽象工廠模式(Abstract Factory Pattern):
* 抽象工廠模式提供了一種創建相關或相依對象的方法,而不需要指定具體的類別。
* 通常由一個抽象工廠類別定義一組相關的工廠方法,每個具體工廠類別實現了這些工廠方法來創建一組相關的產品。
```php=
interface CarFactory {
public function createSUV();
public function createSedan();
}
class LuxuryCarFactory implements CarFactory {
public function createSUV() {
return new LuxurySUV();
}
public function createSedan() {
return new LuxurySedan();
}
}
class EconomicCarFactory implements CarFactory {
public function createSUV() {
return new EconomicSUV();
}
public function createSedan() {
return new EconomicSedan();
}
}
// 使用抽象工廠模式
$luxuryFactory = new LuxuryCarFactory();
$luxurySUV = $luxuryFactory->createSUV();
$luxurySedan = $luxuryFactory->createSedan();
$economicFactory = new EconomicCarFactory();
$economicSUV = $economicFactory->createSUV();
$economicSedan = $economicFactory->createSedan();
```
## 微服務
> 是一種將應用系統分解為一組小型、獨立、可部署的服務的設計模式。每個微服務負責完成特定的業務功能,並通過輕量級的通信協議(通常是 HTTP 或消息隊列)進行交互。這種架構提高了系統的靈活性、可擴展性和可維護性,使得開發團隊可以更快速地構建和迭代大型應用。
### 微服務架構的核心概念
1. 服務拆分:
* 在微服務架構中,應用程序被分解為一組獨立的服務,每個服務負責一個具體的業務功能,如用戶管理、訂單處理、支付處理等。
* 每個微服務都是獨立的應用單位,擁有自己的邏輯和數據存儲,並且可以獨立部署和更新。
2. 獨立部署:
* 微服務可以獨立於其他服務進行開發、測試和部署。這使得開發團隊能夠靈活地迭代和更新每個微服務,而不影響整個系統。
* 每個微服務都有自己的生命周期,可以根據業務需求獨立升級和擴展。
3. 輕量級通信:
* 微服務之間通過輕量級的通信協議來進行交互,通常是基於 HTTP 的 REST API 或基於消息的通信(如 RabbitMQ、Kafka 等)。
* 微服務之間的通信是無狀態的,這樣可以避免耦合,並且提高了服務的獨立性和可測試性。
4. 去中心化數據管理:
* 在微服務架構中,每個服務通常擁有自己的數據存儲,這樣避免了數據的集中化管理。
* 每個微服務的數據庫是獨立的,可以根據服務的需求選擇不同的數據庫技術(如 NoSQL 或 SQL),這樣可以提高數據存儲的靈活性和性能。
5. 容錯性和高可用性:
* 微服務架構允許每個服務在故障發生時不影響其他服務,這提高了系統的容錯能力和高可用性。
* 使用負載均衡、服務註冊和發現等技術,實現自動故障轉移和動態擴展,提高系統的穩定性和可用性。
### 微服務架構的優點
1. 可擴展性:
* 每個微服務可以獨立擴展,根據需求增加或減少資源。例如,流量大的服務可以獨立水平擴展,而不需要影響其他服務。
* 根據服務的需求,可以選擇合適的技術棧和數據庫,針對不同業務需求進行優化。
2. 靈活性和技術多樣性:
* 不同的微服務可以使用不同的編程語言、框架和數據庫,這樣可以根據每個服務的具體需求選擇最佳的技術棧。
* 開發團隊可以更加靈活地選擇技術,適應快速變化的業務需求。
3. 獨立部署和迭代:
* 微服務架構允許每個服務獨立部署,這樣可以更頻繁地進行發布,而不影響其他服務的穩定性。
* 每個團隊可以專注於自己的服務,實現持續交付和持續部署。
4. 容錯和高可用性:
* 微服務架構通過隔離每個服務的故障,提高了整體系統的容錯能力。
* 如果某個服務出現故障,其他服務仍然可以繼續運行,從而提高系統的高可用性。
5. 便於持續交付(CI/CD):
* 微服務架構允許各個服務獨立開發和部署,便於實現自動化測試和持續集成。
* 因為每個微服務都是相對獨立的,因此可以對其進行自動化測試和快速發布。
### 微服務架構的挑戰
1. 分布式系統的複雜性:
* 微服務是一種分布式系統,這意味著要面對網絡延遲、異常處理、分佈式數據一致性等挑戰。
* 對於開發團隊來說,這需要更高的技能來處理分佈式系統的問題。
2. 數據一致性問題:
* 在微服務架構中,每個服務擁有自己的數據存儲,這可能會導致數據一致性問題。
* 例如,訂單服務和支付服務分別管理不同的數據,必須使用分佈式事務或最終一致性策略來確保數據一致。
3. 運維和部署的複雜性:
* 微服務架構中有大量的獨立服務,這使得部署、運維和監控變得更為複雜。
* 需要引入容器化技術(如 Docker)、集群管理工具(如 Kubernetes)來進行微服務的管理和編排。
4. 通信和安全:
* 微服務之間的通信通常使用 HTTP、gRPC 或消息隊列,這需要額外的設計來處理通信安全、加密和認證等問題。
* 因為各個服務之間存在跨網絡的交互,需實施 API Gateway 和服務安全機制來保障通信的安全性。
5. 測試的複雜性:
* 在微服務架構中,單一服務的變更可能會影響整個系統,因此需要進行單元測試、集成測試和端到端測試來確保系統的穩定性。
* 測試和調試分佈式微服務可能比較困難,需要專門的測試環境和工具。
### 微服務架構的組件
在微服務架構中,常見的組件包括:
1. API Gateway:
* API Gateway 是微服務架構中的入口,負責路由請求、負載均衡、認證和授權、限流等功能。
* 它允許前端應用通過統一的接口訪問後端的不同微服務,簡化了客戶端與後端之間的通信。
2. 服務註冊與發現:
* 服務註冊和發現系統允許微服務之間自動檢測和連接。
* 例如,當一個新的微服務啟動時,它會向服務註冊中心註冊自己的位置,這樣其他微服務可以通過服務註冊中心找到它。
3. 配置管理:
* 在分佈式系統中,集中式配置管理可以方便地管理所有微服務的配置。
* 常見的配置管理工具如 Spring Cloud Config、Consul 等,可以讓微服務動態地加載和更新配置。
4. 消息隊列(Message Queue):
* 微服務之間的通信可以使用消息隊列來實現非同步處理和解耦。
* 常見的消息隊列有 RabbitMQ、Kafka、ActiveMQ 等,允許微服務以事件驅動的方式進行交互。
5. 監控與日誌:
* 在微服務架構中,需要對每個服務進行獨立的監控和日誌記錄,以便於快速診斷和解決問題。
* 如 Prometheus、ELK 堆疊(Elasticsearch, Logstash, Kibana)可以幫助收集和分析服務的性能數據和日誌。
### 我的論文 (AnserGateway)
基於workerman開發,透過workerman可替換的event loop機制,加上Swow所支援的coroutine機制,完成更高效的請求處理。
:::success
* 簡而言之,AnserGateway的base就是多process,每個process都只有一個thread,每個thread都有多個coroutine。
* 對於AnserGateway而言,每個request都會被coroutine處理,等於thread內會塞很多個coroutine,而coroutine在遭遇I/O(API 調用或read file)時,將會自動把控制權讓出並交給其他coroutine使用,而這段切換並不是CPU的Context switch,而是用戶態切換(User-mode switching),因為在做coroutine資源切換時,會將當前資料存在暫存器(記憶體)當中,等I/O做完後在切換回來,與System call的資源消耗相比,coroutine切換的cost會小很多。
* coroutine的切換是交給協程調度器去做的,而協程調度器,負責協程的創建、切換和銷毀。調度器會將所有協程放入一個「待執行隊列」,根據優先級或執行條件(如 I/O 等待)進行協程切換。Swow 支持多種調度策略,例如 FIFO 調度、循環調度等。
* Worker = process
:::
#### Coroutine的生命週期
協程的生命週期階段
* 創建(Created)
* 協程在被初始化和創建時處於「創建」狀態。此時,協程已經分配了所需的資源(如棧內存),並且已經設置了初始的上下文,但尚未執行。
* 創建過程通常涉及分配協程棧和上下文結構,並準備協程要執行的函數或任務。
* 就緒(Ready)
* 創建完成後,協程進入「就緒」狀態,等待調度器分配時間片來執行。
* 在這個狀態下,協程已經準備好執行,但尚未獲得 CPU 資源,因此還沒有執行過。
* 運行(Running)
* 當調度器將 CPU 資源分配給協程時,協程進入「運行」狀態,並開始執行其中的任務。
* 運行狀態下,協程的代碼正在被執行,協程可以進行各種操作,例如計算、I/O 操作等。
* 讓出(Suspended/Waiting)
* 協程可以在運行過程中主動讓出執行權,進入「讓出」或「等待」狀態,通常是因為協程遇到了阻塞操作(如 I/O 請求)或需要等待某個條件。
* 在讓出狀態下,協程會保存當前的上下文,並讓出 CPU 控制權,調度器會將 CPU 資源分配給其他協程。
* 協程可以在以下情況下讓出:
* 主動讓出:協程通過調用 yield 或類似方法主動讓出執行權。
* 被動等待:協程執行 I/O 操作或需要等待其他協程結果,此時進入等待狀態。
* 恢復(Resumed)
當讓出狀態的協程的條件滿足時(如 I/O 完成),調度器會將該協程放回就緒隊列,並在下一次調度時恢復該協程,進入「運行」狀態繼續執行。
恢復過程中,協程的上下文(如棧指針、寄存器)會被重新加載,以便協程能夠從上次中斷的地方繼續執行。
* 結束(Completed/Terminated)
當協程的任務執行完成後,協程進入「結束」狀態,釋放相關資源。
結束狀態意味著協程的生命周期已經完結,不再會被調度器調度。
在結束過程中,協程的棧空間和上下文結構會被釋放,協程的結果(如果有)會返回給調用者。
#### 解決什麼?
:::danger
Q1. PHP 在 Web 服務器環境中傳統上是單請求-單進程的模式,每個請求使用一個獨立的進程處理,並在請求結束後釋放所有資源。
:::
:::success
A. 在PHP的擴展中,PHP 可以使用 pcntl 等擴展來創建多進程應用,常見於 CLI 和一些常駐程序(如 Workerman)。
因此能提升不少效率,且Workerman的process本身就是個常駐process,因此在處理每個請求時,都可不必重新run一次依賴檔案。
:::
:::danger
Q2. 為何要在workerman中加上swow?
:::
:::success
A. 因為workerman本身有支援event loop不支援coroutine,但Swow程式庫有支援coroutine的event loop,可完美配合workerman的process進行事件處理,且Swow程式庫是基於C語言所撰寫的,因此在效能上會獲得不錯的表現。
:::
:::danger
Q3. Workerman是如何做到多process的
:::
:::success
A. 主要依靠 PHP 的 pcntl 擴展 提供的進程控制功能,具體來說,使用了 pcntl_fork() 函數來創建多個子進程。
**實現流程**
* Master 進程啟動:
* 當 Workerman 啟動時,首先創建一個主進程(Master 進程),這個主進程負責管理和監控子進程的狀態。
* 主進程不直接處理請求,它的主要職責是創建和管理工作進程(Worker 進程),並監控其健康狀況。
* 創建 Worker 進程:
* 主進程啟動後,根據配置的 count(進程數量)設置,通過 pcntl_fork() 創建指定數量的子進程,每個子進程即一個 Worker 進程。
* 這些 Worker 進程是相互獨立的,彼此之間不共享內存,因此每個進程都有自己的 PHP 執行環境和內存空間,從而避免了多線程環境中的資源爭用問題。
* Worker 進程處理請求:
* 每個 Worker 進程都會綁定一個監聽的端口,並進入事件循環,等待客戶端的請求。
* 當客戶端發來請求時,Worker 進程會接受該請求並處理,處理完成後返回結果,並準備接受下一個請求。
* 負載均衡:
* 在多進程模式下,不同的 Worker 進程可以同時處理多個客戶端請求,這樣能夠分攤負載,提高並發處理能力。
* 每個進程會獨立處理請求,因此不會互相阻塞,即便一個進程處理較慢,也不會影響其他進程。
* 進程管理與重啟:
* 主進程不會處理具體的業務邏輯,而是負責管理 Worker 進程的生命週期,包括啟動、監控和重啟。
* 如果某個 Worker 進程異常退出,主進程會自動監控到這一變化,並重新創建一個新的 Worker 進程來代替它,以保證服務穩定性。
* 這種自動重啟機制可以提高系統的可靠性和容錯能力。
* 事件循環與非阻塞 I/O:
* 每個 Worker 進程啟動後,會進入一個事件循環,等待並處理網絡 I/O 事件。Workerman 通常會使用 PHP 的 stream_select() 函數或底層的事件驅動擴展(如 libevent、ev 等)來監控和管理多個 I/O 事件。
* 這使得每個 Worker 進程可以同時處理多個 I/O 請求,而不需要為每個請求創建一個線程或進程,進一步提升了性能和資源利用率。
:::
:::danger
Q4. workerman + Swow的架構中的資源使用分配?
:::
:::success
CPU 分配和使用
* 多進程模式利用多核 CPU:每個 Worker 進程都是一個獨立的進程,可以運行在不同的 CPU 核心上。假設 Workerman 啟動 4 個 Worker 進程,那麼系統會將這些進程分配到多個 CPU 核心,從而可以同時利用多核的計算能力。
* 單線程內的協程切換:在每個 Worker 進程內,只有一個線程,但 Swow 的協程調度器會在同一個線程內調度多個協程運行。協程是輕量級的,並且執行是非阻塞的,即使有大量協程在運行,因為它們是「合作式」調度,所以不會導致線程阻塞。
* 協程切換的 CPU 開銷低:協程在執行過程中,遇到 I/O 操作(如讀寫數據庫、網絡請求等)時會讓出控制權,而協程之間的切換是輕量的,並不需要系統層面的上下文切換(不像多進程或多線程),因此 CPU 的開銷比較低。這種協程切換通常在用戶態完成,而不是內核態,大大減少了 CPU 的負擔。
* I/O 密集型應用的 CPU 利用率:協程通常更適合 I/O 密集型應用,因為這種應用的主要瓶頸是等待 I/O 完成而不是 CPU 計算。在 I/O 密集型應用中,CPU 的利用率會比較低,而協程的非阻塞特性可以有效地利用 CPU 來處理多個請求,避免 CPU 空閒等待。
記憶體分配
* 每個 Worker 進程都有自己的內存空間:每個 Worker 進程是獨立的,它們之間的內存空間不共享。這意味著每個進程需要獨立分配內存來存儲運行數據和協程的狀態。
* 協程的內存開銷小:協程比線程和進程更輕量級,它們的內存開銷主要來自於協程的堆疊(通常非常小)和協程內部的狀態數據(如局部變量)。Swow 的協程可以在單一進程中創建大量協程,內存佔用相對較小,因此在高併發場景中非常適合使用協程。
* 內存回收:當協程完成執行或不再需要時,Swow 會自動回收內存,釋放協程佔用的資源。這樣可以避免內存洩漏,使系統資源得到更好的利用。
I/O 資源使用
* 非阻塞 I/O:Swow 提供了非阻塞 I/O,這意味著當某個協程在等待 I/O 完成時(如等待網絡數據、文件讀寫等),協程會讓出控制權,讓其他協程繼續執行。這樣一來,不會因為一個 I/O 操作而阻塞整個進程,從而有效提高 I/O 資源的利用率。
* 事件循環管理 I/O 資源:事件循環負責監控所有的 I/O 事件,當 I/O 操作完成時會立即通知相關協程恢復執行。這樣的設計可以減少進程的阻塞等待,使系統能夠並行處理大量 I/O 操作。
系統資源的總體分配
* 多核 CPU 的利用:多個 Worker 進程可以運行在不同的 CPU 核心上,從而實現並行處理,充分利用多核 CPU 的性能。
* 單核內的高效切換:在每個進程內的單線程中,協程切換由 Swow 的協程調度器控制,協程切換開銷非常小。這樣即使在單個 CPU 核心上,也能實現高效的併發處理。
* 內存開銷和管理:協程的內存佔用小,因此在單個進程中可以創建大量協程,而不會帶來大量內存消耗。協程結束後內存會自動釋放,避免內存洩漏。
* I/O 資源的高效使用:非阻塞 I/O 使得在處理 I/O 操作時不會阻塞進程,使 I/O 資源得到了更高效的利用。
總結
* CPU 資源:多進程模式允許利用多核 CPU,每個進程內的協程切換開銷小,適合 I/O 密集型應用。協程的調度避免了繁重的上下文切換,使得 CPU 利用率更高。
* 內存資源:協程內存開銷小,可以在單一進程內創建大量協程。內存的自動回收避免了內存洩漏。
* I/O 資源:非阻塞 I/O 和事件循環提高了 I/O 資源的利用效率,使得進程在等待 I/O 時不會被阻塞。
:::