系統設計機制完整比較指南
目錄
I/O 處理機制
1. 中斷 vs 輪詢 (Interrupt vs Polling)
| 比較項目 | 中斷 (Interrupt) | 輪詢 (Polling) |
|---|---|---|
| 運作方式 | 事件發生時硬體通知CPU | CPU定期檢查是否有事件 |
| 白話比喻 | 像門鈴:有客人按門鈴才去開門 | 像不停看門:每隔幾秒就去門口看有沒有人 |
| CPU使用率 | 低(只在事件發生時處理) | 高(持續檢查浪費資源) |
| 回應時間 | 可能有中斷延遲 | 立即(但有檢查間隔) |
| 適用場景 | 鍵盤輸入、滑鼠點擊、硬碟完成通知 | 高速網卡、即時遊戲、感測器讀取 |
| 優點 | 效率高,CPU可做其他事 | 實作簡單,延遲可預測 |
| 缺點 | 有切換開銷,頻繁中斷效能差 | 浪費CPU資源,不適合低頻事件 |
| 實際範例 | USB裝置插入時通知系統 | 高頻交易系統每微秒檢查市場資料 |
詳細說明:
- 中斷機制:當你在電腦上按下鍵盤,鍵盤會發送一個中斷訊號給CPU,CPU暫停當前工作,處理鍵盤輸入,然後繼續原本的工作。
- 輪詢機制:遊戲引擎每16毫秒檢查一次手把按鈕狀態,確保即時回應玩家操作。
- 混合策略:Linux的NAPI在網路封包少時用中斷,封包多時切換到輪詢避免中斷風暴。
2. 同步 vs 異步 (Synchronous vs Asynchronous)
| 比較項目 | 同步 (Synchronous) | 異步 (Asynchronous) |
|---|---|---|
| 運作方式 | 等待操作完成才繼續 | 發起操作後立即返回,稍後通知 |
| 白話比喻 | 像排隊買咖啡:站在櫃台等咖啡做好才離開 | 像點外賣:下單後可以做其他事,做好了送來 |
| 執行流程 | 阻塞式,依序執行 | 非阻塞式,可併行處理 |
| 程式複雜度 | 簡單,邏輯順序清晰 | 複雜,需要callback或Promise |
| 適用場景 | 檔案讀取、資料庫查詢(簡單應用) | 網頁伺服器、聊天應用、大量I/O操作 |
| 優點 | 易於理解和除錯 | 高效率,不浪費等待時間 |
| 缺點 | 效率低,等待時間無法做其他事 | 錯誤處理複雜,除錯困難 |
| 實際範例 | file.read() 必須等檔案讀完 | Node.js的 fs.readFile() 繼續執行其他程式碼 |
詳細說明:
- 同步範例:你打電話給客服,必須等電話接通、問題處理完才能掛斷,這段時間什麼都不能做。
- 異步範例:你發email給客服,發送後可以繼續工作,收到回覆時再處理。
- JavaScript範例:
// 同步 const data = readFileSync('file.txt'); // 等待完成 console.log(data); // 異步 readFile('file.txt', (err, data) => { console.log(data); // 稍後執行 }); console.log('繼續執行'); // 立即執行
3. 阻塞 vs 非阻塞 (Blocking vs Non-blocking)
| 比較項目 | 阻塞 (Blocking) | 非阻塞 (Non-blocking) |
|---|---|---|
| 運作方式 | 呼叫會等待直到有結果 | 立即返回,可能還沒準備好 |
| 白話比喻 | 像ATM提款:插卡後必須等到錢吐出來 | 像查詢ATM餘額:按了馬上顯示,不用等 |
| 返回時機 | 操作完成時返回 | 立即返回(可能返回錯誤碼) |
| 錯誤處理 | 成功或失敗明確 | 需要重複嘗試(EAGAIN) |
| 適用場景 | 簡單的單一操作 | 需要同時處理多個連接 |
| 優點 | 邏輯簡單,結果確定 | 可同時監控多個操作 |
| 缺點 | 無法同時處理多個任務 | 需要額外邏輯處理「未準備好」狀態 |
| 實際範例 | recv() 等到收到資料才返回 | recv(O_NONBLOCK) 沒資料就返回EAGAIN |
詳細說明:
- 阻塞範例:你打電話訂餐,必須等店員接電話、記錄完訂單,電話才能掛斷。
- 非阻塞範例:你用App訂餐,點擊送出後App立即回到主畫面,訂單在背景處理。
- Socket範例:
# 阻塞模式 data = socket.recv(1024) # 等到收到資料 # 非阻塞模式 socket.setblocking(False) try: data = socket.recv(1024) # 立即返回 except BlockingIOError: # 資料還沒準備好 pass
4. DMA vs PIO (Direct Memory Access vs Programmed I/O)
| 比較項目 | DMA | PIO |
|---|---|---|
| 運作方式 | 硬體直接存取記憶體,CPU不參與資料搬移 | CPU負責在記憶體和I/O裝置間搬移資料 |
| 白話比喻 | 像快遞直接送貨到府:你不用親自去搬 | 像自己去郵局領包裹:要親自搬運 |
| CPU使用率 | 低(CPU可做其他事) | 高(CPU忙著搬資料) |
| 傳輸速度 | 快(硬體直接存取) | 慢(經過CPU) |
| 適用場景 | 大量資料傳輸(硬碟、網卡、音效卡) | 少量資料、簡單裝置(舊式鍵盤、簡單感測器) |
| 優點 | 效率高,不佔用CPU | 實作簡單,硬體成本低 |
| 缺點 | 需要專用硬體支援 | 佔用CPU資源 |
| 實際範例 | 硬碟讀取1GB檔案到RAM | 舊式印表機逐字元傳輸 |
詳細說明:
- DMA範例:你下載電影時,網卡收到的資料直接寫入記憶體,CPU可以同時執行其他程式。
- PIO範例:早期電腦讀取軟碟時,CPU要一個一個位元組搬移資料,這時電腦會變得很慢。
- 效能差異:
- DMA:1GB資料傳輸,CPU使用率 < 5%
- PIO:1GB資料傳輸,CPU使用率接近 100%
5. 零拷貝 vs 傳統拷貝 (Zero-Copy vs Traditional Copy)
| 比較項目 | 零拷貝 (Zero-Copy) | 傳統拷貝 (Traditional Copy) |
|---|---|---|
| 運作方式 | 資料直接從來源到目的地,不經過CPU | 資料經過多次CPU拷貝(Kernel→User→Kernel) |
| 白話比喻 | 像傳送帶:貨物直接從倉庫到卡車 | 像人工搬運:貨物先搬到暫存區再搬上車 |
| CPU使用率 | 極低(幾乎不佔用) | 高(CPU忙著拷貝資料) |
| 拷貝次數 | 0-1次 | 4次(Disk→Kernel→User→Kernel→Network) |
| 適用場景 | 大檔案傳輸、影片串流、Proxy伺服器 | 需要處理資料內容的場景 |
| 優點 | 效能極佳,減少CPU和記憶體頻寬佔用 | 可以在User space處理資料 |
| 缺點 | 無法修改資料內容 | 效能差,多次拷貝浪費資源 |
| 實際範例 | Nginx sendfile、Kafka檔案傳輸 | 傳統HTTP伺服器read+write |
詳細說明:
- 傳統拷貝流程:發送檔案給客戶端
1. read(): Disk → Kernel buffer (DMA) 2. read(): Kernel buffer → User buffer (CPU拷貝) 3. write(): User buffer → Socket buffer (CPU拷貝) 4. write(): Socket buffer → Network card (DMA) 總共:2次DMA + 2次CPU拷貝 + 4次上下文切換 - 零拷貝流程:使用sendfile()
1. sendfile(): Disk → Kernel buffer (DMA) 2. sendfile(): Kernel buffer → Network card (DMA或直接描述符傳遞) 總共:2次DMA + 0次CPU拷貝 + 2次上下文切換 - 實際程式碼對比:
// 傳統方式 char buffer[4096]; while ((n = read(fd, buffer, 4096)) > 0) { write(socket, buffer, n); // 2次CPU拷貝 } // 零拷貝 sendfile(socket, fd, NULL, file_size); // 0次CPU拷貝 - 效能提升:
- 傳統方式:100MB檔案,CPU使用率30%,吞吐量500MB/s
- 零拷貝:100MB檔案,CPU使用率5%,吞吐量2GB/s
6. 記憶體映射 I/O vs 標準 I/O (Memory-Mapped I/O vs Standard I/O)
| 比較項目 | 記憶體映射 I/O (mmap) | 標準 I/O (read/write) |
|---|---|---|
| 運作方式 | 將檔案映射到記憶體位址,像存取陣列 | 使用系統呼叫讀寫檔案 |
| 白話比喻 | 像把整本書攤開在桌上:隨便翻哪一頁 | 像看書:從第一頁依序看 |
| 存取方式 | 記憶體指標存取 | 系統呼叫(read/write) |
| 效能特性 | 隨機存取快,大檔案優勢明顯 | 順序存取快,小檔案開銷小 |
| 適用場景 | 大檔案隨機存取、資料庫、共享記憶體 | 順序讀寫、小檔案、串流資料 |
| 優點 | 隨機存取效率高,減少系統呼叫 | 實作簡單,適合順序操作 |
| 缺點 | 需要虛擬記憶體支援,小檔案開銷大 | 隨機存取慢,系統呼叫開銷 |
| 實際範例 | 資料庫索引(B-tree)、進程間共享記憶體 | 日誌寫入、檔案複製 |
詳細說明:
- mmap範例:隨機存取大檔案
// 映射檔案到記憶體 int fd = open("data.db", O_RDWR); char* addr = mmap(NULL, file_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); // 像存取陣列一樣存取檔案 addr[1000] = 'A'; // 直接修改offset 1000 addr[999999] = 'B'; // 跳到offset 999999,無需seek munmap(addr, file_size); - 標準I/O範例:順序讀取
FILE* fp = fopen("data.txt", "r"); char buffer[4096]; while (fread(buffer, 1, 4096, fp) > 0) { process(buffer); } fclose(fp); - 效能對比(100MB檔案,隨機存取10000次):
- mmap:約100ms(頁面錯誤後幾乎零開銷)
- read/write + lseek:約5000ms(每次都要系統呼叫)
- 共享記憶體應用:多程序通訊
// 程序A void* shared = mmap(NULL, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); fork(); // 子程序繼承映射 // 父子程序共享這塊記憶體,無需pipe或socket - 注意事項:
- mmap檔案修改不會立即寫入磁碟,需要msync()強制同步
- 映射大檔案可能耗盡虛擬記憶體位址空間(32位元系統)
- 適合大檔案(>幾MB),小檔案反而比read/write慢
記憶體管理
5. 分頁 vs 分段 (Paging vs Segmentation)
| 比較項目 | 分頁 (Paging) | 分段 (Segmentation) |
|---|---|---|
| 運作方式 | 將記憶體分成固定大小的頁(如4KB) | 依邏輯單元分成不同大小的段 |
| 白話比喻 | 像停車格:每個格子一樣大,不管車型 | 像衣櫃分區:上衣區、褲子區、大小不同 |
| 記憶體單位 | 固定大小(4KB、2MB等) | 可變大小(依據程式邏輯) |
| 碎片問題 | 內部碎片(頁內浪費) | 外部碎片(段間浪費) |
| 適用場景 | 現代作業系統(Windows、Linux) | 早期系統、或需要邏輯分離的場景 |
| 優點 | 管理簡單,無外部碎片 | 符合程式邏輯結構,保護容易 |
| 缺點 | 有內部碎片,查表開銷 | 有外部碎片,需要壓縮整理 |
| 實際範例 | 程式佔用100頁(400KB) | Code段50KB、Data段30KB、Stack段20KB |
詳細說明:
- 分頁範例:一個程式需要17KB記憶體,在4KB分頁系統中會分配5頁(20KB),浪費3KB。
- 分段範例:程式的程式碼、資料、堆疊分別放在不同的段,每段有自己的起始位址和長度。
- 混合使用:現代x86-64系統同時支援分頁和分段,但主要使用分頁。
6. 寫回 vs 寫透 (Write-back vs Write-through)
| 比較項目 | 寫回 (Write-back) | 寫透 (Write-through) |
|---|---|---|
| 運作方式 | 先寫快取,稍後批次寫回記憶體 | 同時寫入快取和記憶體 |
| 白話比喻 | 像筆記本:先記在本子上,有空再整理到電腦 | 像雙寫:同時寫筆記本和打電腦 |
| 寫入速度 | 快(只寫快取) | 慢(等記憶體寫完) |
| 資料一致性 | 可能不一致 | 一定一致 |
| 適用場景 | 一般應用程式、資料庫快取 | 關鍵系統、需要即時持久化 |
| 優點 | 效能最佳,減少記憶體存取 | 資料安全,不會遺失 |
| 缺點 | 斷電可能遺失資料 | 效能較差 |
| 實際範例 | CPU L1 Cache | 銀行交易系統 |
詳細說明:
- 寫回範例:你在Word編輯文件,修改先存在記憶體,過一段時間或關閉時才存到硬碟。
- 寫透範例:每次輸入都立即存檔,確保不會遺失任何內容,但存檔頻繁會變慢。
- 混合策略:資料庫的Write-Ahead Log(WAL)先寫日誌(寫透),資料頁面用寫回。
7. 靜態分配 vs 動態分配 (Static vs Dynamic Allocation)
| 比較項目 | 靜態分配 | 動態分配 |
|---|---|---|
| 運作方式 | 編譯時決定記憶體大小和位置 | 執行時根據需要分配記憶體 |
| 白話比喻 | 像固定座位:買票時就決定坐哪 | 像自由座:上車後找空位坐 |
| 分配時機 | 編譯期間 | 執行期間 |
| 彈性 | 無彈性,大小固定 | 彈性高,依需求調整 |
| 適用場景 | 全域變數、陣列大小確定時 | 鏈結串列、動態陣列、物件建立 |
| 優點 | 速度快,無overhead,可預測 | 彈性使用記憶體,適應不同需求 |
| 缺點 | 浪費記憶體,無法調整大小 | 有分配/釋放開銷,可能記憶體洩漏 |
| 實際範例 | int arr[100]; | int* arr = malloc(n * sizeof(int)); |
詳細說明:
- 靜態範例:宣告
char buffer[1024];直接在stack上配置1024 bytes,編譯時就決定了。 - 動態範例:
std::vector初始可能只配置8個元素空間,使用時自動擴充到16、32、64... - 記憶體區域:
- 靜態:放在 Stack(區域變數)或 Data Segment(全域變數)
- 動態:放在 Heap
排程策略
8. 搶佔式 vs 非搶佔式 (Preemptive vs Non-preemptive)
| 比較項目 | 搶佔式 (Preemptive) | 非搶佔式 (Non-preemptive) |
|---|---|---|
| 運作方式 | 可以強制中斷正在執行的任務 | 等任務自願放棄CPU |
| 白話比喻 | 像急診室:重症病人可以插隊 | 像銀行排隊:必須等前面的人辦完 |
| 任務切換 | 時間到就強制切換 | 只能等任務完成或主動讓出 |
| 回應時間 | 快,高優先權任務可立即執行 | 慢,可能等很久 |
| 適用場景 | 現代多工系統、即時系統 | 簡單系統、批次處理 |
| 優點 | 回應快,公平,適合互動式應用 | 實作簡單,無切換開銷 |
| 缺點 | 有切換開銷,實作複雜 | 一個任務可能霸佔CPU |
| 實際範例 | Linux、Windows的程序排程 | 早期MS-DOS、簡單嵌入式系統 |
詳細說明:
- 搶佔式範例:你在看影片時下載檔案,作業系統每10毫秒切換一次,兩個程式都能執行。
- 非搶佔式範例:舊系統執行一個程式,必須等它執行完或主動sleep才能執行下一個。
- 即時系統:飛機控制系統用搶佔式,確保緊急任務(如引擎故障)能立即處理。
9. FCFS vs SJF vs RR (First-Come First-Served vs Shortest Job First vs Round Robin)
| 比較項目 | FCFS | SJF | Round Robin |
|---|---|---|---|
| 運作方式 | 依到達順序執行 | 先執行最短的工作 | 輪流執行,每次固定時間片 |
| 白話比喻 | 排隊:先到先服務 | 快速通關:短工作優先 | 輪流玩遊戲:每人玩5分鐘 |
| 平均等待時間 | 可能很長 | 最短 | 中等 |
| 公平性 | 公平(但可能飢餓) | 不公平(長工作可能等很久) | 最公平 |
| 適用場景 | 批次系統、簡單任務 | 已知工作時間的系統 | 多工互動式系統(Linux、Windows) |
| 優點 | 實作簡單 | 最佳化平均等待時間 | 公平,回應時間可預測 |
| 缺點 | 長工作會阻塞後面所有工作 | 長工作可能餓死 | 有切換開銷 |
| 實際範例 | 印表機佇列 | 作業批次處理 | CPU時間分配 |
詳細說明:
- FCFS範例:3個工作:A(24秒)、B(3秒)、C(3秒),B和C必須等A完成,平均等待時間=(0+24+27)/3=17秒
- SJF範例:同樣3個工作,執行順序變成B(3秒)、C(3秒)、A(24秒),平均等待時間=(0+3+6)/3=3秒
- RR範例:時間片4秒,執行順序:A→B→C→A→A→A→A→A→A,每個工作輪流執行
10. 時間分享 vs 空間分享 (Time-sharing vs Space-sharing)
| 比較項目 | 時間分享 | 空間分享 |
|---|---|---|
| 運作方式 | 資源輪流給不同使用者 | 資源分割給不同使用者 |
| 白話比喻 | 像共用一台車:輪流開 | 像停車場:每人有固定車位 |
| 資源使用 | 同一資源,不同時間使用 | 不同部分同時使用 |
| 隔離性 | 時間隔離 | 空間隔離 |
| 適用場景 | CPU、網路頻寬 | 記憶體、硬碟空間 |
| 優點 | 資源利用率高 | 效能穩定,無競爭 |
| 缺點 | 需要切換機制 | 可能浪費未使用的空間 |
| 實際範例 | 多個程序共用一顆CPU | 每個程序有獨立的記憶體空間 |
詳細說明:
- 時間分享範例:一顆4核CPU同時運行100個程序,每個程序輪流使用CPU。
- 空間分享範例:8GB記憶體分給4個程式,每個程式固定使用2GB。
- 混合使用:現代系統CPU用時間分享,記憶體用空間分享。
並行控制
11. 鎖 vs 無鎖 (Lock-based vs Lock-free)
| 比較項目 | 鎖 (Lock-based) | 無鎖 (Lock-free) |
|---|---|---|
| 運作方式 | 使用互斥鎖保護共享資源 | 使用原子操作(CAS)避免鎖 |
| 白話比喻 | 像廁所門鎖:進去就上鎖 | 像搶座位:看到空位就快速坐下 |
| 阻塞情況 | 會阻塞其他執行緒 | 不會阻塞,失敗就重試 |
| 死鎖風險 | 可能死鎖 | 無死鎖 |
| 適用場景 | 一般應用、資料庫 | 高效能系統、即時系統 |
| 優點 | 實作簡單,容易理解 | 效能好,無死鎖 |
| 缺點 | 可能死鎖、效能瓶頸 | 實作複雜,難以除錯 |
| 實際範例 | pthread_mutex_lock() | Java的 AtomicInteger.compareAndSet() |
詳細說明:
- 鎖範例:銀行帳戶轉帳時,先鎖定兩個帳戶,完成轉帳後解鎖。
mutex.lock(); balance += amount; mutex.unlock(); - 無鎖範例:多個執行緒更新計數器,使用CAS操作。
do { old_value = counter; new_value = old_value + 1; } while (!compare_and_swap(&counter, old_value, new_value)); - 效能比較:無競爭時鎖快;高競爭時無鎖快。
12. 悲觀鎖 vs 樂觀鎖 (Pessimistic vs Optimistic Locking)
| 比較項目 | 悲觀鎖 | 樂觀鎖 |
|---|---|---|
| 運作方式 | 假設會衝突,先鎖住再操作 | 假設不會衝突,衝突時才重試 |
| 白話比喻 | 像保險箱:先鎖起來,確保沒人動 | 像購物車:放進去,結帳時才檢查還有沒有貨 |
| 鎖定時機 | 讀取時就鎖定 | 更新時才檢查 |
| 衝突處理 | 預防衝突 | 偵測衝突後重試 |
| 適用場景 | 寫多讀少、衝突頻繁 | 讀多寫少、衝突少 |
| 優點 | 確保不會衝突 | 效能好,無需等待 |
| 缺點 | 效能差,可能造成等待 | 衝突多時需要重試 |
| 實際範例 | SELECT ... FOR UPDATE | 資料庫版本號機制 |
詳細說明:
- 悲觀鎖範例:電影訂票系統,選座位時立即鎖定,避免同時被兩人選到。
BEGIN TRANSACTION; SELECT * FROM seats WHERE id = 10 FOR UPDATE; UPDATE seats SET booked = true WHERE id = 10; COMMIT; - 樂觀鎖範例:Wiki編輯,編輯時不鎖定,提交時檢查版本號是否變更。
UPDATE articles SET content = '新內容', version = version + 1 WHERE id = 100 AND version = 5; -- 如果version已經變更,UPDATE會失敗 - 選擇原則:衝突率低選樂觀鎖,衝突率高選悲觀鎖。
13. 粗粒度鎖 vs 細粒度鎖 (Coarse-grained vs Fine-grained Locking)
| 比較項目 | 粗粒度鎖 | 細粒度鎖 |
|---|---|---|
| 運作方式 | 鎖住大範圍(整個資料庫/表) | 鎖住小範圍(單一記錄) |
| 白話比喻 | 像整棟樓的大門鎖:進樓就鎖門 | 像每個房間的鎖:只鎖使用的房間 |
| 並發程度 | 低 | 高 |
| 實作複雜度 | 簡單 | 複雜 |
| 適用場景 | 低並發、簡單應用 | 高並發、大型系統 |
| 優點 | 實作簡單,死鎖機率低 | 並發高,效能好 |
| 缺點 | 並發差,效能瓶頸 | 實作複雜,可能死鎖 |
| 實際範例 | 鎖整個資料表 | 鎖單一資料列 |
詳細說明:
- 粗粒度鎖範例:整個購物網站共用一個庫存鎖,任何人修改任何商品庫存都要等待。
with global_lock: update_inventory(product_id, quantity) - 細粒度鎖範例:每個商品有自己的鎖,修改A商品不影響B商品。
with product_locks[product_id]: update_inventory(product_id, quantity) - 死鎖風險:細粒度鎖如果同時鎖定多個資源需注意順序,避免死鎖。
資料結構選擇
14. 陣列 vs 鏈結串列 (Array vs Linked List)
| 比較項目 | 陣列 (Array) | 鏈結串列 (Linked List) |
|---|---|---|
| 運作方式 | 連續記憶體空間儲存元素 | 節點透過指標連接 |
| 白話比喻 | 像公寓:房間連續排列 | 像尋寶遊戲:每個線索指向下一個 |
| 記憶體配置 | 連續 | 分散 |
| 隨機存取 | O(1) | O(n) |
| 插入/刪除 | O(n) | O(1) |
| 適用場景 | 需要快速查詢、大小固定 | 頻繁增刪、大小不定 |
| 優點 | 存取快、cache友善 | 插入刪除快、動態大小 |
| 缺點 | 插入刪除慢、大小固定 | 存取慢、需要額外指標空間 |
| 實際範例 | int arr[100] | struct Node { int data; Node* next; } |
詳細說明:
- 陣列範例:學生成績表,可以直接查詢第50個學生
grades[49],但插入新學生需要移動後面所有元素。 - 鏈結串列範例:播放清單,可以輕鬆插入新歌曲,但要找第50首歌需要從頭走訪。
- 效能對比:
- 陣列存取:
arr[1000]一次記憶體存取 - 鏈結串列存取:需要走訪1000次指標
- 陣列存取:
- 記憶體使用:鏈結串列每個節點需要額外8 bytes(64位元)儲存指標
15. 堆疊 vs 佇列 (Stack vs Queue)
| 比較項目 | 堆疊 (Stack) | 佇列 (Queue) |
|---|---|---|
| 運作方式 | LIFO(後進先出) | FIFO(先進先出) |
| 白話比喻 | 像盤子堆疊:最後放的最先拿 | 像排隊:先到的先服務 |
| 操作 | push(放)、pop(取) | enqueue(放)、dequeue(取) |
| 適用場景 | 函式呼叫、括號匹配、回溯演算法 | 任務排程、廣度優先搜尋、緩衝區 |
| 優點 | 簡單、適合遞迴 | 公平、順序處理 |
| 缺點 | 不適合需要順序處理的場景 | 不適合需要回溯的場景 |
| 實際範例 | 瀏覽器上一頁功能 | 印表機列印佇列 |
詳細說明:
- 堆疊範例:函式呼叫
main() → funcA() → funcB(),funcB執行完回到funcA,funcA執行完回到main。void funcA() { int x = 10; // 壓入stack funcB(); } // x彈出stack - 佇列範例:訊息系統,使用者依序發送訊息1、2、3,伺服器依序處理1、2、3。
queue.put("訊息1") queue.put("訊息2") msg = queue.get() # 取得"訊息1" - 變體:
- 雙端佇列(Deque):兩端都可以插入和刪除
- 優先佇列(Priority Queue):依優先權而非順序取出
16. Hash Table vs Tree (雜湊表 vs 樹)
| 比較項目 | Hash Table | Tree (如BST、B-tree) |
|---|---|---|
| 運作方式 | 用hash函數計算位置直接存取 | 有序結構,用比較找到位置 |
| 白話比喻 | 像字典:知道單字直接翻到該頁 | 像圖書館分類:一層層找下去 |
| 查詢時間 | O(1) 平均 | O(log n) |
| 範圍查詢 | 不支援 | 支援 |
| 排序 | 無序 | 有序 |
| 適用場景 | 快速查詢、資料無序 | 範圍查詢、需要排序 |
| 優點 | 查詢最快 | 支援範圍查詢、有序 |
| 缺點 | 可能碰撞、無序 | 查詢較慢、實作複雜 |
| 實際範例 | Python的dict、資料庫索引 | MySQL的B+tree索引 |
詳細說明:
- Hash Table範例:查詢學生成績,用學號hash後直接找到。
grades = {"A001": 85, "A002": 92} score = grades["A001"] # O(1) - Tree範例:查詢薪水在50K-80K的員工,可以用樹快速範圍查詢。
SELECT * FROM employees WHERE salary BETWEEN 50000 AND 80000; -- 使用B-tree索引 - 碰撞處理:Hash Table碰撞時用鏈結串列(Chaining)或開放定址法(Open Addressing)。
- 實務應用:資料庫同時使用Hash Index(點查詢)和B-tree Index(範圍查詢)。
快取策略
17. LRU vs LFU vs FIFO (替換演算法)
| 比較項目 | LRU | LFU | FIFO |
|---|---|---|---|
| 運作方式 | 移除最近最少使用的 | 移除最不常使用的 | 移除最早進來的 |
| 白話比喻 | 像整理書桌:丟掉很久沒用的 | 像整理衣櫃:丟掉最少穿的 | 像堆疊雜誌:丟掉最舊的 |
| 考量因素 | 最近使用時間 | 使用頻率 | 進入時間 |
| 適用場景 | 一般應用、網頁快取 | 熱門資料重要的場景 | 簡單系統 |
| 優點 | 符合時間局部性 | 保留熱門資料 | 實作最簡單 |
| 缺點 | 實作複雜 | 新資料不易進入 | 效能差 |
| 實際範例 | Redis、作業系統分頁 | CDN快取 | 早期快取系統 |
詳細說明:
- LRU範例:瀏覽器快取,最近看過的網頁保留,很久沒看的清除。
快取容量3,存取順序:A B C A D - 初始:[] - A:[A] - B:[A, B] - C:[A, B, C] - A:[B, C, A](A移到最前) - D:[C, A, D](移除B) - LFU範例:影片推薦系統,記錄觀看次數,保留熱門影片。
存取次數:A(5次) B(2次) C(8次) D(1次) 快取滿時,移除D(最少被存取) - FIFO範例:簡單的緩衝區,先進先出。
快取容量3,存取順序:A B C D - D進入時,移除A(最早的) - 結果:[B, C, D]
18. 直接映射 vs 全相聯 vs 組相聯 (Cache Mapping)
| 比較項目 | 直接映射 | 全相聯 | 組相聯 |
|---|---|---|---|
| 運作方式 | 每個區塊只能放一個固定位置 | 可放任何位置 | 分成多組,組內可任意放 |
| 白話比喻 | 像固定車位:你的車只能停A區 | 像自由停車:任何空位都能停 | 像分區停車:A區內任何位置都可以 |
| 查找速度 | 最快(直接計算) | 最慢(全部檢查) | 中等 |
| 命中率 | 最低 | 最高 | 中等 |
| 適用場景 | 簡單快取 | 小容量快取 | CPU快取(最常用) |
| 優點 | 硬體簡單、快速 | 彈性最大、命中率高 | 折衷效能和成本 |
| 缺點 | 衝突多 | 硬體複雜、查找慢 | 折衷方案 |
| 實際範例 | 簡單微控制器 | TLB(Translation Lookaside Buffer) | CPU L1/L2/L3 Cache |
詳細說明:
- 直接映射範例:記憶體位址0x1000只能映射到快取位置0,位址0x2000也映射到位置0,會產生衝突。
快取8個位置(0-7) 位址0x08 → 位置0 位址0x10 → 位置0(衝突!) - 全相聯範例:任何記憶體位址都能放入任何快取位置,需要比較所有tag。
位址0x1000可以放在位置0-7的任何一個 查找時需要檢查全部8個位置 - 組相聯範例:8-way組相聯,快取分成多組,每組8個位置。
64個快取位置,分成8組 位址0x1000 → 映射到組0,可放組0的任何8個位置 查找時只需檢查組0的8個位置
網路與通訊
19. Push vs Pull (推送 vs 拉取)
| 比較項目 | Push(推送) | Pull(拉取) |
|---|---|---|
| 運作方式 | 伺服器主動推送資料給客戶端 | 客戶端主動向伺服器請求資料 |
| 白話比喻 | 像新聞通知:有新聞主動推送給你 | 像查郵件:你自己去查看有沒有新信 |
| 資料流向 | Server → Client | Client ← Server |
| 即時性 | 高(立即通知) | 低(需要定期查詢) |
| 適用場景 | 即時通訊、股票報價、社群通知 | 電子郵件、RSS訂閱、定期同步 |
| 優點 | 即時、不需輪詢 | 客戶端可控制頻率 |
| 缺點 | 需要保持連接、伺服器負擔大 | 有延遲、可能錯過更新 |
| 實際範例 | WebSocket、Firebase | HTTP polling、Email POP3 |
詳細說明:
- Push範例:Line訊息,朋友傳訊息時伺服器立即推送到你的手機。
// WebSocket Push socket.on('message', (data) => { displayMessage(data); // 收到訊息立即顯示 }); - Pull範例:Email客戶端每15分鐘自動檢查一次新郵件。
while True: emails = fetch_new_emails() # 主動拉取 time.sleep(900) # 等15分鐘 - 混合模式:
- Long Polling:Pull但保持連接直到有新資料
- Server-Sent Events(SSE):單向Push
20. TCP vs UDP
| 比較項目 | TCP | UDP |
|---|---|---|
| 運作方式 | 可靠、有序、連接導向 | 快速、無連接、不保證送達 |
| 白話比喻 | 像掛號信:確保收到、有順序 | 像廣播:快速發送、不管有沒有收到 |
| 連接 | 需要三次握手建立連接 | 無需建立連接 |
| 可靠性 | 保證送達、有順序 | 不保證送達、可能亂序 |
| 適用場景 | 網頁、檔案傳輸、Email | 線上遊戲、視訊通話、DNS |
| 優點 | 可靠、有序、流量控制 | 快速、低延遲、開銷小 |
| 缺點 | 慢、開銷大 | 不可靠、需要應用層處理 |
| 實際範例 | HTTP/HTTPS、SSH、FTP | 線上遊戲、Zoom、DNS查詢 |
詳細說明:
- TCP範例:下載檔案,必須確保每個byte都正確且依序到達。
三次握手: 1. Client → Server: SYN 2. Server → Client: SYN-ACK 3. Client → Server: ACK 然後才能傳輸資料 - UDP範例:視訊通話,掉幾個封包無所謂,重要的是即時性。
sock.sendto(video_frame, (ip, port)) # 直接發送 # 不等待確認,不重傳遺失的封包 - 效能對比:
- TCP:100ms延遲(含握手、確認)
- UDP:10ms延遲(直接發送)
- 實務應用:
- HTTP/3使用QUIC(基於UDP但加入可靠性)
- 遊戲混用:重要資料用TCP,位置更新用UDP
21. 短連接 vs 長連接 (Short-lived vs Persistent Connection)
| 比較項目 | 短連接 | 長連接 |
|---|---|---|
| 運作方式 | 每次請求建立連接,完成後關閉 | 建立一次連接,重複使用 |
| 白話比喻 | 像計程車:每次叫新車 | 像包車:司機一直等你 |
| 連接次數 | 多(每次請求) | 少(只建立一次) |
| 伺服器負擔 | 建立連接的開銷大 | 維持連接的資源佔用 |
| 適用場景 | 低頻請求、簡單服務 | 高頻請求、即時通訊 |
| 優點 | 實作簡單、無需維護連接 | 減少建立連接開銷、效能好 |
| 缺點 | 每次建立連接有開銷 | 佔用伺服器資源、需要心跳機制 |
| 實際範例 | HTTP/1.0 | WebSocket、資料庫連接池 |
詳細說明:
- 短連接範例:每次瀏覽網頁都建立新連接。
1. 建立TCP連接(三次握手) 2. 發送HTTP請求 3. 接收HTTP回應 4. 關閉TCP連接(四次揮手) - 長連接範例:聊天應用,建立一次WebSocket連接後持續使用。
const ws = new WebSocket('wss://chat.example.com'); ws.send('Hello'); // 使用已建立的連接 ws.send('World'); // 重複使用同一連接 - HTTP演進:
- HTTP/1.0:短連接
- HTTP/1.1:引入
Connection: keep-alive支援長連接 - HTTP/2:多路複用,一個連接處理多個請求
- 心跳機制:長連接需要定期發送心跳包,確保連接存活。
while True: send_heartbeat() time.sleep(30)
儲存策略
22. 順序讀寫 vs 隨機讀寫 (Sequential vs Random Access)
| 比較項目 | 順序讀寫 | 隨機讀寫 |
|---|---|---|
| 運作方式 | 連續存取資料 | 跳躍式存取資料 |
| 白話比喻 | 像看書:從第一頁依序看到最後 | 像查字典:直接翻到需要的頁 |
| 存取方式 | 連續位址 | 任意位址 |
| 效能差異 | HDD快、SSD差異小 | HDD慢、SSD快 |
| 適用場景 | 日誌寫入、影片播放、備份 | 資料庫索引、作業系統分頁 |
| 優點 | 效能最佳(尤其HDD) | 彈性高、符合應用需求 |
| 缺點 | 不彈性 | HDD效能差 |
| 實際範例 | 影片檔案讀取 | 資料庫B-tree查詢 |
詳細說明:
- 順序讀寫範例:寫日誌檔案,每次追加到檔案尾端。
with open('log.txt', 'a') as f: f.write('新的日誌\n') # 順序寫入 - 隨機讀寫範例:資料庫更新,可能要存取不同位置的資料。
UPDATE users SET name = 'Alice' WHERE id = 1000; UPDATE users SET name = 'Bob' WHERE id = 50; -- 存取位置可能相距很遠 - 效能差異(HDD):
- 順序讀取:100-200 MB/s
- 隨機讀取:0.5-2 MB/s(慢100倍!)
- SSD優勢:
- 順序讀取:500-3500 MB/s
- 隨機讀取:300-3000 MB/s(差異小很多)
- 優化策略:資料庫使用寫前日誌(WAL),隨機寫轉換為順序寫。
23. 集中式 vs 分散式 (Centralized vs Distributed)
| 比較項目 | 集中式 | 分散式 |
|---|---|---|
| 運作方式 | 單一節點處理所有請求 | 多個節點協同處理 |
| 白話比喻 | 像總公司:所有決策一個地方做 | 像連鎖店:各分店獨立運作 |
| 單點故障 | 有(節點故障全停) | 無(部分節點故障仍可運作) |
| 複雜度 | 簡單 | 複雜(需要協調機制) |
| 適用場景 | 小型應用、單一地區 | 大型應用、全球服務 |
| 優點 | 實作簡單、一致性容易保證 | 高可用、可擴展、容錯 |
| 缺點 | 單點故障、擴展性差 | 複雜、一致性難保證 |
| 實際範例 | 傳統資料庫(單機) | Google搜尋、Facebook |
詳細說明:
- 集中式範例:小型電商網站,單一伺服器處理所有訂單。
[所有使用者] → [單一伺服器] → [單一資料庫] 伺服器故障 = 整個網站停擺 - 分散式範例:YouTube影片存儲,分散在全球數千台伺服器。
[美國使用者] → [美國伺服器] [台灣使用者] → [台灣伺服器] 任一伺服器故障不影響其他地區 - CAP定理:分散式系統無法同時滿足一致性、可用性、分區容錯性。
- 分散式挑戰:
- 資料同步:如何保持各節點資料一致
- 時鐘同步:不同伺服器時間可能不一致
- 網路分區:節點間無法通訊時如何處理
24. 複製 vs 分片 (Replication vs Sharding)
| 比較項目 | 複製 (Replication) | 分片 (Sharding) |
|---|---|---|
| 運作方式 | 相同資料存多份到不同節點 | 資料切分到不同節點 |
| 白話比喻 | 像備份:每台電腦都有完整資料 | 像圖書館分館:每館只有部分書 |
| 資料分佈 | 每個節點都有全部資料 | 每個節點只有部分資料 |
| 目的 | 提高可用性和讀取效能 | 提高容量和寫入效能 |
| 適用場景 | 讀多寫少、需要高可用性 | 資料量大、需要水平擴展 |
| 優點 | 容錯好、讀取快 | 容量無限、寫入分散 |
| 缺點 | 寫入需要同步、儲存成本高 | 跨分片查詢慢、複雜度高 |
| 實際範例 | MySQL主從複製、Redis Sentinel | MongoDB分片、分散式資料庫 |
詳細說明:
- 複製範例:電商網站有3個資料庫副本。
Master DB(主):處理寫入 Slave DB 1(從):處理讀取 Slave DB 2(從):處理讀取 → 讀取效能提升3倍,任一故障不影響服務 - 分片範例:社群網站依使用者ID分片。
Shard 1:使用者 ID 0-999999 Shard 2:使用者 ID 1000000-1999999 Shard 3:使用者 ID 2000000-2999999 → 每個分片只處理部分資料,容量可無限擴展 - 混合使用:每個分片內部做複製。
Shard 1 Master + 2 Slaves Shard 2 Master + 2 Slaves → 既能擴容又能容錯 - 分片鍵選擇:
- 好的分片鍵:使用者ID(均勻分佈)
- 壞的分片鍵:註冊日期(新使用者集中在一個分片)
一致性模型
25. 強一致性 vs 最終一致性 (Strong vs Eventual Consistency)
| 比較項目 | 強一致性 | 最終一致性 |
|---|---|---|
| 運作方式 | 寫入後立即所有節點看到相同資料 | 寫入後過一段時間才一致 |
| 白話比喻 | 像廣播:所有人同時聽到消息 | 像傳話遊戲:消息逐漸傳開 |
| 延遲 | 高(需等待所有節點確認) | 低(不等待) |
| 可用性 | 較低(節點故障可能無法寫入) | 高(總是可以寫入) |
| 適用場景 | 金融交易、庫存系統 | 社群媒體、快取、DNS |
| 優點 | 資料一定正確 | 效能好、高可用 |
| 缺點 | 效能差、可用性低 | 短期內可能不一致 |
| 實際範例 | 銀行轉帳 | Facebook按讚數 |
詳細說明:
- 強一致性範例:銀行轉帳,A轉1000元給B,必須確保兩邊同時生效。
1. A帳戶 -1000(所有副本都確認) 2. B帳戶 +1000(所有副本都確認) 3. 交易完成 如果任一步驟失敗,整個交易回滾 - 最終一致性範例:Facebook貼文按讚。
時間 0秒:Alice按讚,美國伺服器記錄 時間 1秒:按讚數顯示+1(在美國) 時間 5秒:資料同步到亞洲伺服器 時間 5秒:按讚數顯示+1(在台灣) → 短期內不同地區看到不同數字,但最終會一致 - 一致性級別(由強到弱):
- 線性一致性(Linearizability):最強
- 順序一致性(Sequential Consistency)
- 因果一致性(Causal Consistency)
- 最終一致性(Eventual Consistency):最弱
26. CP vs AP (CAP定理)
| 比較項目 | CP(一致性+分區容錯) | AP(可用性+分區容錯) |
|---|---|---|
| 運作方式 | 網路分區時犧牲可用性保證一致性 | 網路分區時犧牲一致性保證可用性 |
| 白話比喻 | 像嚴格的考試:寧可不考也不給錯答案 | 像開卷考試:先給答案,對錯之後再說 |
| 網路分區時 | 拒絕服務或返回錯誤 | 繼續服務但可能不一致 |
| 適用場景 | 金融系統、庫存管理、選舉 | DNS、快取、社群媒體 |
| 優點 | 資料絕對正確 | 服務永遠可用 |
| 缺點 | 可能暫時無法使用 | 可能讀到舊資料 |
| 實際範例 | 銀行系統、訂票系統 | Amazon購物車、Twitter |
詳細說明:
- CAP定理:一致性(Consistency)、可用性(Availability)、分區容錯性(Partition Tolerance)三者只能取其二。
- CP範例:訂票系統,網路分區時寧可停止售票,也不能超賣。
情境:兩個資料中心失去連接 CP做法:停止售票,等網路恢復 → 確保不會把同一個座位賣給兩個人 - AP範例:購物車,網路分區時繼續加商品,稍後合併。
情境:使用者在美國加商品A,網路分區 AP做法:先加入本地購物車 → 稍後網路恢復,與其他資料中心的購物車合併 可能出現:短期內不同地區看到不同購物車內容 - 實務選擇:
- 金融:選CP(寧可暫停服務,不能出錯)
- 社群:選AP(暫時不一致沒關係,服務可用更重要)
執行模式
27. 單執行緒 vs 多執行緒 vs 多程序 (Single-threaded vs Multi-threaded vs Multi-process)
| 比較項目 | 單執行緒 | 多執行緒 | 多程序 |
|---|---|---|---|
| 運作方式 | 一次只做一件事 | 多個執行緒共享記憶體 | 多個程序獨立記憶體 |
| 白話比喻 | 像一人廚房:一次煮一道菜 | 像團隊廚房:共用鍋碗瓢盆 | 像多間廚房:各自獨立 |
| 記憶體 | 共享全部 | 共享同一程序記憶體 | 完全獨立 |
| 切換開銷 | 無 | 低 | 高 |
| 適用場景 | 簡單任務、I/O密集(Node.js) | CPU密集、需要共享資料 | 需要隔離、安全性高 |
| 優點 | 簡單、無同步問題 | 效能好、共享資料容易 | 隔離好、穩定 |
| 缺點 | 無法利用多核 | 需要同步、可能死鎖 | 通訊慢、記憶體佔用大 |
| 實際範例 | JavaScript(瀏覽器) | Java應用、科學計算 | Chrome瀏覽器分頁、Nginx |
詳細說明:
- 單執行緒範例:Node.js使用事件迴圈處理多個請求。
// 雖然只有一個執行緒,但可以處理多個請求 server.on('request', (req, res) => { // 非阻塞I/O,不會卡住其他請求 fs.readFile('file.txt', (err, data) => { res.end(data); }); }); - 多執行緒範例:影片處理軟體,每個執行緒處理一段影片。
import threading def process_chunk(start, end): # 處理影片的一部分 pass threads = [] for i in range(4): # 4個執行緒 t = threading.Thread(target=process_chunk, args=(i*100, (i+1)*100)) threads.append(t) t.start() - 多程序範例:Chrome每個分頁一個程序,一個分頁崩潰不影響其他。
from multiprocessing import Process def worker(data): # 完全獨立的處理 pass processes = [] for i in range(4): p = Process(target=worker, args=(data[i],)) processes.append(p) p.start() - 效能對比:
- 單執行緒:1核心100%,其他核心閒置
- 多執行緒:4核心各100%(4倍效能)
- 多程序:4核心各100%,但記憶體佔用是多執行緒的4倍
28. 事件驅動 vs 多執行緒 (Event-driven vs Multi-threaded)
| 比較項目 | 事件驅動 | 多執行緒 |
|---|---|---|
| 運作方式 | 單執行緒+事件迴圈處理多個連接 | 每個連接一個執行緒 |
| 白話比喻 | 像櫃台人員:一個人服務多個客戶 | 像私人助理:每個客戶一個助理 |
| 並發方式 | 協作式多工(cooperative) | 搶佔式多工(preemptive) |
| 資源佔用 | 低(單執行緒) | 高(每個執行緒需要stack) |
| 適用場景 | I/O密集(網頁伺服器、聊天) | CPU密集(影片編碼) |
| 優點 | 低開銷、高並發連接數 | 實作簡單、符合直覺 |
| 缺點 | 不適合CPU密集 | 執行緒多時開銷大 |
| 實際範例 | Node.js、Nginx | Apache(傳統模式) |
詳細說明:
- 事件驅動範例:Node.js處理10000個同時連接。
// 單執行緒處理多個連接 const server = http.createServer(); server.on('request', async (req, res) => { const data = await db.query(); // 非阻塞 res.end(data); }); // 10000個連接只需要一個執行緒 - 多執行緒範例:Apache為每個連接建立執行緒。
連接1 → 執行緒1 連接2 → 執行緒2 連接3 → 執行緒3 ... → 10000個連接需要10000個執行緒(記憶體爆炸) - C10K問題:如何讓一台伺服器處理10000個同時連接。
- 傳統多執行緒:失敗(記憶體不足)
- 事件驅動:成功(低記憶體佔用)
- 混合模式:
- Node.js:主執行緒事件驅動 + Worker執行緒處理CPU密集
- Nginx:事件驅動處理連接 + Worker程序平行處理
29. 同步執行 vs 平行執行 vs 並發執行 (Synchronous vs Parallel vs Concurrent)
| 比較項目 | 同步執行 | 平行執行 | 並發執行 |
|---|---|---|---|
| 運作方式 | 依序執行,一次一個 | 真正同時執行(多核) | 快速切換,看似同時 |
| 白話比喻 | 一人排隊辦事:辦完一件再辦下一件 | 多個櫃台同時辦事 | 一人快速處理多件事 |
| 硬體需求 | 單核即可 | 需要多核心 | 單核即可 |
| 真正同時 | 否 | 是 | 否(輪流) |
| 適用場景 | 簡單任務 | CPU密集、科學計算 | I/O密集、多任務 |
| 優點 | 簡單、可預測 | 最快(利用硬體) | 高效使用單核 |
| 缺點 | 慢 | 需要多核、同步複雜 | 不能加速CPU密集 |
| 實際範例 | 簡單腳本 | 影片渲染、機器學習訓練 | 作業系統多工 |
詳細說明:
- 同步執行範例:煮飯流程。
煮飯() # 30分鐘 炒菜() # 20分鐘 煮湯() # 15分鐘 總時間:65分鐘 - 平行執行範例:三個人同時煮。
# 3個CPU核心 Thread1: 煮飯() # 30分鐘 Thread2: 炒菜() # 20分鐘 Thread3: 煮湯() # 15分鐘 總時間:30分鐘(最長的任務) - 並發執行範例:一個人快速切換。
# 單核心,快速切換 0-10分鐘:煮飯 10-15分鐘:炒菜 15-20分鐘:煮湯 20-25分鐘:煮飯 ...(不斷切換) 總時間:接近65分鐘(因為有切換開銷) - Python範例:
# 同步 result1 = task1() result2 = task2() # 並發(I/O) import asyncio result1, result2 = await asyncio.gather(task1(), task2()) # 平行(CPU) from multiprocessing import Pool pool = Pool(4) results = pool.map(task, data)
設計模式
30. 單體 vs 微服務 (Monolithic vs Microservices)
| 比較項目 | 單體 (Monolithic) | 微服務 (Microservices) |
|---|---|---|
| 運作方式 | 所有功能在一個應用程式 | 功能拆分成獨立服務 |
| 白話比喻 | 像百貨公司:所有部門在一棟樓 | 像商店街:每個店面獨立營運 |
| 部署單位 | 整個應用一起部署 | 每個服務獨立部署 |
| 技術棧 | 統一 | 可以不同 |
| 適用場景 | 小型專案、快速開發 | 大型專案、需要彈性擴展 |
| 優點 | 開發簡單、部署容易 | 獨立開發、獨立擴展、容錯 |
| 缺點 | 難以擴展、一個bug影響全部 | 複雜度高、需要服務治理 |
| 實際範例 | 傳統電商網站 | Netflix、Uber |
詳細說明:
- 單體範例:傳統電商系統。
單一應用程式包含: - 使用者管理 - 商品管理 - 訂單處理 - 付款處理 - 物流追蹤 → 改一個功能要重新部署整個應用 - 微服務範例:Netflix架構。
獨立服務: - User Service(使用者服務) - Video Service(影片服務) - Recommendation Service(推薦服務) - Billing Service(計費服務) → 每個服務可以獨立開發、部署、擴展 - 演進過程:
- 小型新創:單體(快速開發)
- 業務成長:單體開始變慢、難維護
- 拆分服務:逐步轉為微服務
- 挑戰:
- 服務間通訊:需要API Gateway
- 資料一致性:分散式交易
- 服務發現:如何找到其他服務
- 監控除錯:需要分散式追蹤
31. 拉模型 vs 推模型 (Pull vs Push Model - 資料流)
| 比較項目 | 拉模型 (Pull) | 推模型 (Push) |
|---|---|---|
| 運作方式 | 消費者主動拉取資料 | 生產者主動推送資料 |
| 白話比喻 | 像去餐廳點餐:你決定吃什麼 | 像外送訂閱:固定送來你家 |
| 控制權 | 消費者控制 | 生產者控制 |
| 背壓處理 | 天然支援(拉取速度自己控制) | 需要額外機制 |
| 適用場景 | Kafka消費者、批次處理 | WebSocket、即時通知 |
| 優點 | 消費者可控制速率、避免過載 | 即時性好、低延遲 |
| 缺點 | 可能有延遲 | 容易壓垮消費者 |
| 實際範例 | Kafka Consumer拉取訊息 | Firebase即時資料庫推送 |
詳細說明:
- 拉模型範例:Kafka消費者。
# 消費者主動拉取 while True: messages = consumer.poll(timeout=1.0) for message in messages: process(message) # 處理完一批再拉取下一批,速度自己控制 - 推模型範例:WebSocket推送。
// 伺服器主動推送 socket.on('connect', () => { socket.emit('newMessage', message); // 有訊息就推送 }); // 消費者被動接收,無法控制速度 - 背壓問題:推模型可能壓垮慢速消費者。
生產者:每秒產生10000條訊息 消費者:每秒只能處理1000條 → 推模型:消費者記憶體爆炸 → 拉模型:消費者慢慢拉,不會過載 - 混合模式:
- RabbitMQ:支援推和拉兩種模式
- gRPC Streaming:可以雙向推送
選擇決策樹
選擇技術時的考量順序:
1. 業務需求
├─ 一致性要求高? → 強一致性、CP
└─ 可用性要求高? → 最終一致性、AP
2. 資料特性
├─ 資料量大? → 分片
├─ 讀多寫少? → 複製 + 快取
└─ 寫多讀少? → 寫優化的資料結構
3. 效能需求
├─ 低延遲? → 快取、異步
├─ 高吞吐? → 平行處理、批次
└─ 即時性? → Push、WebSocket
4. 系統規模
├─ 小型? → 單體、集中式
├─ 中型? → 垂直擴展、複製
└─ 大型? → 微服務、分散式
5. 團隊能力
├─ 經驗少? → 選擇簡單方案
└─ 經驗多? → 可選複雜但效能好的方案
實務建議
1. 不要過度設計
- 從簡單開始,需要時再優化
- 單體 → 垂直擴展 → 水平擴展 → 微服務
2. 測量再優化
- 先測量瓶頸在哪
- 不要憑感覺優化
3. 權衡取捨
- 沒有完美方案,只有適合的方案
- 考慮維護成本、開發時間、效能
4. 關注變化
- 技術持續演進
- 保持學習,適時調整
5. 文件化決策
- 記錄為什麼選擇這個方案
- 未來回顧時有依據
總結對照表
| 分類 | 機制對比 | 核心差異 | 選擇關鍵 |
|---|---|---|---|
| I/O | 中斷 vs 輪詢 | 主動通知 vs 被動檢查 | 事件頻率 |
| I/O | 同步 vs 異步 | 等待 vs 回調 | 並發需求 |
| I/O | 阻塞 vs 非阻塞 | 等結果 vs 立即返回 | 多工需求 |
| I/O | DMA vs PIO | 硬體搬移 vs CPU搬移 | 資料量 |
| 記憶體 | 分頁 vs 分段 | 固定 vs 可變 | 管理複雜度 |
| 記憶體 | 寫回 vs 寫透 | 延遲寫 vs 立即寫 | 資料安全性 |
| 記憶體 | 靜態 vs 動態 | 編譯期 vs 執行期 | 彈性需求 |
| 排程 | 搶佔 vs 非搶佔 | 強制切換 vs 自願讓出 | 即時性 |
| 排程 | FCFS vs SJF vs RR | 順序 vs 最短 vs 輪流 | 公平性 |
| 並行 | 鎖 vs 無鎖 | 互斥 vs 原子操作 | 競爭程度 |
| 並行 | 悲觀 vs 樂觀 | 預防 vs 偵測 | 衝突率 |
| 並行 | 粗粒度 vs 細粒度 | 大鎖 vs 小鎖 | 並發需求 |
| 資料結構 | 陣列 vs 鏈結串列 | 連續 vs 分散 | 存取模式 |
| 資料結構 | 堆疊 vs 佇列 | LIFO vs FIFO | 處理順序 |
| 資料結構 | Hash vs Tree | 無序快速 vs 有序範圍 | 查詢類型 |
| 快取 | LRU vs LFU vs FIFO | 時間 vs 頻率 vs 順序 | 存取模式 |
| 快取 | 直接映射 vs 全相聯 vs 組相聯 | 固定 vs 任意 vs 分組 | 硬體成本 |
| 網路 | Push vs Pull | 推送 vs 拉取 | 即時性 |
| 網路 | TCP vs UDP | 可靠 vs 快速 | 資料重要性 |
| 網路 | 短連接 vs 長連接 | 用完丟 vs 重複用 | 請求頻率 |
| 儲存 | 順序 vs 隨機 | 連續 vs 跳躍 | 資料存取模式 |
| 儲存 | 集中式 vs 分散式 | 單點 vs 多點 | 規模 |
| 儲存 | 複製 vs 分片 | 備份 vs 分割 | 目的 |
| 一致性 | 強一致 vs 最終一致 | 立即 vs 延遲 | 業務需求 |
| 一致性 | CP vs AP | 一致性 vs 可用性 | CAP取捨 |
| 執行 | 單執行緒 vs 多執行緒 vs 多程序 | 一個 vs 共享 vs 隔離 | 隔離需求 |
| 執行 | 事件驅動 vs 多執行緒 | 單執行緒多工 vs 多執行緒 | I/O vs CPU |
| 執行 | 同步 vs 平行 vs 並發 | 依序 vs 同時 vs 輪流 | 硬體資源 |
| 設計 | 單體 vs 微服務 | 一體 vs 拆分 | 系統規模 |
| 資料流 | Pull vs Push | 拉取 vs 推送 | 控制權 |
最後提醒: 選擇技術方案沒有絕對的對錯,關鍵是:
- 理解每種方案的優缺點
- 評估你的實際需求和限制
- 做出合理的權衡
- 持續監控和優化
希望這份整理能幫助你在系統設計時做出更好的決策! 🚀