後端面試題總整理

後端

目錄


1. 資料庫相關

1.1 資料庫隔離級別 (Transaction Isolation Levels)

ACID 特性

資料庫事務需滿足 ACID 特性:

  • Atomicity (原子性):事務中的所有操作要麼全部完成,要麼全部不完成
  • Consistency (一致性):事務執行前後,資料庫都保持一致性狀態
  • Isolation (隔離性):並行執行的事務之間互不干擾
  • Durability (持久性):事務提交後,對資料庫的改變是永久性的

四種隔離級別

從最寬鬆到最嚴格:Read Uncommitted → Read Committed → Repeatable Read → Serializable

隔離級別 Dirty Read
(髒讀)
Non-repeatable Read
(不可重複讀)
Phantom Read
(幻讀)
說明
Read Uncommitted ✅ 可能發生 ✅ 可能發生 ✅ 可能發生 可以讀取其他事務尚未提交的資料,最不安全但速度最快
Read Committed ❌ 已避免 ✅ 可能發生 ✅ 可能發生 只能讀取已提交的資料 (PostgreSQL 預設)
Repeatable Read ❌ 已避免 ❌ 已避免 ✅ 可能發生 同一事務中相同查詢會得到相同結果 (MySQL InnoDB 預設)
Serializable ❌ 已避免 ❌ 已避免 ❌ 已避免 最嚴格的隔離級別,事務串行執行,性能最差

異常現象說明

  • Dirty Read (髒讀):讀取到其他事務尚未提交的資料
  • Non-repeatable Read (不可重複讀):同一事務中兩次讀取同一資料得到不同結果
  • Phantom Read (幻讀):同一事務中重新執行查詢,會出現新增或減少的資料列

不同資料庫的預設隔離級別

  • MySQL InnoDB: Repeatable Read
  • PostgreSQL: Read Committed

設定隔離級別 (MySQL)

sql
-- 設定 Session 級別
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
-- 執行查詢
COMMIT;

-- 設定全域級別
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 查看當前隔離級別
SELECT @@session.transaction_isolation;  -- Session 級別
SELECT @@global.transaction_isolation;   -- 全域級別

使用場景建議

使用情境 建議的隔離級別
分析報表 / 資料查詢 Read Committed
高並發服務 Read Committed / Repeatable Read + 重試機制
金融交易 (如銀行轉帳) Repeatable Read / Serializable
庫存系統 / 訂票系統 Serializable 或加鎖

1.2 MySQL 鎖機制

鎖的分類

MySQL 中的鎖主要分為三個層級:

  1. 全域鎖 (Global Lock)
  2. 表級鎖 (Table-Level Lock)
  3. 行級鎖 (Row-Level Lock)

1.2.1 全域鎖

  • 使用場景:資料庫備份
  • 效果:資料庫中所有表變為唯讀狀態
sql
-- 加全域鎖
FLUSH TABLES WITH READ LOCK;

-- 解鎖
UNLOCK TABLES;

1.2.2 表級鎖

類型:

  • 表鎖 (Table Lock)
  • 元數據鎖 (Metadata Lock, MDL)
  • 意向鎖 (Intention Lock)
  • AUTO-INC 鎖

表鎖特性:

  • 讀讀共享、讀寫互斥、寫寫互斥
  • 粒度大,開銷小,不會產生死鎖
  • 併發性能較差,適合 MyISAM 引擎

表鎖類型:

鎖類型 說明
讀鎖 持有讀鎖可以讀表但不能寫表;允許多個執行緒持有讀鎖;其他執行緒加寫鎖會被阻塞
寫鎖 持有寫鎖的執行緒可讀可寫;只有持有寫鎖的執行緒能訪問該表,其他執行緒被阻塞
sql
-- 加讀鎖
LOCK TABLES authors READ;

-- 加寫鎖
LOCK TABLES comment WRITE;

-- 解鎖當前 Session 所有表鎖
UNLOCK TABLES;

意向鎖:

  • 在使用 InnoDB 引擎的表中對某些記錄加行級鎖之前,需要先在表級加意向鎖
  • 意向鎖的目的是為了快速判斷表裡是否有記錄被加鎖
  • 意向鎖之間不會衝突,只會與表級的共享鎖和獨佔鎖衝突

1.2.3 行級鎖

類型:

  • Record Lock (記錄鎖)
  • Gap Lock (間隙鎖)
  • Next-Key Lock (記錄鎖 + 間隙鎖)

特性:

  • InnoDB 支援行級鎖,MyISAM 不支援
  • 讀讀共享、讀寫互斥、寫寫互斥
  • 粒度小,開銷大,可能產生死鎖
  • 併發性能好

Record Lock (記錄鎖):

sql
-- 對讀取的記錄加共享鎖 (S鎖)
SELECT * FROM authors WHERE id = 1 LOCK IN SHARE MODE;

-- 對讀取的記錄加獨佔鎖 (X鎖)
SELECT * FROM authors WHERE id = 1 FOR UPDATE;

Gap Lock (間隙鎖):

  • 鎖定兩個索引之間的間隙,防止其他事務在此範圍內插入或修改記錄
  • 可以避免幻讀現象

Next-Key Lock:

  • 概念上等於 Record Lock + Gap Lock
  • 建立一個前開後閉的索引間隙,避免其他事務在此間隙插入資料

1.2.4 死鎖 (Deadlock)

根本原因:兩個或多個事務加鎖順序不一致

處理方式:

  • 設定鎖等待超時時間
  • 使用死鎖檢測機制
  • 確保事務按相同順序獲取鎖

InnoDB vs MyISAM

特性 InnoDB MyISAM
預設版本 MySQL 5.5+ MySQL 5.1 及之前
事務支援 ✅ 支援 ❌ 不支援
行級鎖 ✅ 支援 ❌ 不支援,僅支援表鎖
外鍵 ✅ 支援 ❌ 不支援
崩潰恢復 ✅ 自動恢復 ❌ 不支援
儲存空間 較大 較小,可壓縮
適用場景 高並發、事務處理 大量查詢、篩選

1.3 SQL 查詢優化與執行計畫

什麼是執行計畫?

執行計畫 (Execution Plan) 是資料庫引擎執行 SQL 查詢的具體步驟,包括:

  • JOIN 順序
  • 索引使用情況
  • 表掃描方式
  • 預估成本

取得執行計畫

sql
-- MySQL / PostgreSQL
EXPLAIN SELECT * FROM orders WHERE status = 'shipped';

-- PostgreSQL - 包含實際執行時間
EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'shipped';

-- MySQL - JSON 格式,更詳細
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE status = 'shipped';

執行計畫關鍵指標

指標 說明 理想值 / 需注意的問題
type 訪問方式 (ALL, index, ref, eq_ref) ✅ 優先選擇 ref 或 eq_ref,避免 ALL (全表掃描)
key 使用的索引 ❗ 若為 NULL,表示未使用索引
rows 預估掃描的行數 ✅ 越低越好
filtered 傳遞到下一階段的行數百分比 ❗ 低於 30% 表示過濾效果差
Extra 額外資訊 ❗ “Using temporary” 或 “filesort” 表示性能差

優化步驟

1. 識別昂貴的操作

  • 全表掃描 (Full Table Scan) 而非索引掃描
  • 大數據集上的嵌套迴圈連接 (Nested Loop Join)
  • 排序操作 (Sort) 或 Hash 聚合
  • 使用臨時表或磁碟

2. 檢查索引使用

  • 確保 WHERE 條件和 JOIN 使用索引欄位
  • 複合索引的欄位順序要與查詢條件匹配
  • 添加缺失的索引
  • 移除未使用或低效的索引

3. 避免 SELECT *

  • 只查詢需要的欄位,減少 I/O

4. 分析 JOIN 順序

  • 將過濾條件推到 JOIN 之前執行
  • 使用 CTE (Common Table Expression) 隔離昂貴的分支

5. 重構 WHERE/HAVING 子句

  • 確保謂詞是 Sargable (Search Argument-able)
  • 避免 WHERE YEAR(date_col) = 2023,改用 WHERE date_col BETWEEN '2023-01-01' AND '2023-12-31'
  • 使用顯式 JOIN 而非相關子查詢

6. 檢查 GROUP BY / ORDER BY

  • 為 GROUP BY、ORDER BY 使用的欄位建立索引
  • 添加覆蓋索引 (Covering Index)

優化範例

問題查詢:

sql
SELECT o.id, o.order_date, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status = 'shipped' AND c.region = 'US'
ORDER BY o.order_date DESC
LIMIT 100;

執行 EXPLAIN 發現問題:

  • customers 表進行全表掃描 (type=ALL, key=NULL)
  • orders 表使用 filesort

優化方案:

sql
-- 添加索引
CREATE INDEX idx_customers_region ON customers(region);
CREATE INDEX idx_orders_status_date ON orders(status, order_date DESC);

-- 可選:覆蓋索引
CREATE INDEX idx_orders_covering ON orders(status, order_date DESC, customer_id, id);

常見問題與修復

症狀 修復方案
type=ALL 為 WHERE / JOIN 欄位添加索引
Extra 有 filesort 為 ORDER BY 欄位添加索引
Extra 有 Using temporary 重寫查詢或添加索引
rows 數量過高 重寫邏輯或拆分為子查詢
filtered < 30% 改進 WHERE 條件或在應用層預過濾

2. 快取相關

2.1 快取三大問題

快取在高並發系統中扮演重要角色,但也會遇到三大經典問題:

  1. 快取穿透 (Cache Penetration)
  2. 快取擊穿 (Hotspot Invalid)
  3. 快取雪崩 (Cache Avalanche)

2.1.1 快取雪崩 (Cache Avalanche)

現象:

  • 在某個時刻,所有的快取同時過期或 Redis 服務失效
  • 導致大量請求直接打到資料庫
  • 當流量巨大時,資料庫可能被打掛

解決方案:

  • ✅ 隨機過期時間:為每個快取 key 設定隨機的過期時間,避免同時失效
  • ✅ 互斥鎖:快取不存在時使用鎖機制,只允許一個執行緒查詢資料庫
  • ✅ 後台定期更新快取:在快取過期前自動更新
  • ✅ 服務熔斷或請求限流:避免資料庫過載
  • ✅ Redis 叢集:提高可用性

2.1.2 快取擊穿 (Hotspot Invalid)

現象:

  • 某個熱門的快取 key 過期
  • 高並發集中在此 key,流量直接打到資料庫

解決方案:

  • ✅ 將熱點 key 設為永不過期
  • ✅ 互斥鎖:在應用層加鎖,確保只有一個執行緒查詢資料庫並更新快取
    • 缺點:會降低系統吞吐量,阻礙其他執行緒

2.1.3 快取穿透 (Cache Penetration)

現象:

  • 客戶端請求的資料既不存在於快取,也不存在於資料庫
  • 每次請求都會穿過快取直接打到資料庫
  • 例如:查詢 id=-1 的資料,但資料庫從 id=1 開始

解決方案:

  • ✅ 過濾非法請求:在應用層驗證請求參數的合法性
  • ✅ 快取空值或預設值:將查詢不到的資料也快取起來(設定較短的過期時間)
  • ✅ 布隆過濾器 (Bloom Filter):判斷請求的 key 是否可能存在於資料集中
    • 存在則查詢 Redis
    • 不存在則直接返回預設訊息

2.2 Redis 分散式鎖

在分散式系統中,Redis 鎖用於確保同一時刻只有一個程序能訪問特定資源,防止競態條件 (race condition) 和資料不一致。

核心概念

  1. 原子性:Redis 支援原子操作,鎖的獲取和釋放不可中斷
  2. 過期時間:設定 TTL (Time To Live),避免死鎖
  3. 分散式:可跨多個實例或微服務使用

2.2.1 SETNX (Set if Not Exists)

最簡單的鎖實現方式:

python
import redis
r = redis.Redis()

lock_key = "lock:my_resource"

# 嘗試獲取鎖
if r.setnx(lock_key, 1):
    try:
        # 取得鎖,執行臨界區程式碼
        pass
    finally:
        # 釋放鎖
        r.delete(lock_key)
else:
    # 未取得鎖,稍後重試
    pass

問題:如果程式在釋放鎖之前崩潰,會造成死鎖

2.2.2 SET with Expiration (推薦)

使用 SET 命令搭配 NX 和 EX 選項,原子地設定鎖和過期時間:

python
import redis
r = redis.Redis()

lock_key = "lock:my_resource"
lock_timeout = 10  # 鎖在 10 秒後自動過期

# 嘗試獲取鎖
if r.set(lock_key, 1, nx=True, ex=lock_timeout):
    try:
        # 取得鎖,執行臨界區程式碼
        pass
    finally:
        # 釋放鎖
        r.delete(lock_key)
else:
    # 未取得鎖,稍後重試
    pass

2.2.3 Redlock 演算法

用於多個 Redis 實例的分散式鎖,提供容錯能力:

python
from redis import Redis
from redlock import Redlock

# 連接多個 Redis 實例
redis_instances = [Redis(host='localhost', port=6379) for _ in range(5)]
dlock = Redlock(redis_instances)

lock_key = "lock:my_resource"
lock = dlock.lock(lock_key, 10000)  # 鎖在 10 秒後過期

if lock:
    try:
        # 執行任務
        pass
    finally:
        dlock.unlock(lock)
else:
    # 未取得鎖
    pass

工作原理:

  • 嘗試在多個 Redis 節點 (如 5 個) 上獲取鎖
  • 當大多數節點 (如 3 個) 成功獲取鎖時,才認為鎖獲取成功
  • 提高可靠性,即使部分節點失效也能正常工作

最佳實踐

  1. 設定合理的過期時間:避免死鎖,但要足夠完成任務
  2. 確保釋放鎖:使用 try-finally 確保鎖被釋放
  3. 重試邏輯:使用指數退避策略 (exponential backoff)
  4. 原子性和隔離性:確保被鎖保護的操作是原子的
  5. 超時和失敗處理:考慮使用 Lua 腳本確保鎖和操作的原子性

2.3 Spring Boot + Redis 快取實踐

快取模式

模式 說明
Read-Through 應用先查快取,若未命中則查資料庫,並填充快取
Write-Through 寫入時同時更新快取和資料庫
Write-Behind (Async) 先寫快取,稍後非同步寫入資料庫(風險較高)
Cache-Aside (Lazy Load) 應用讀取快取,未命中時查資料庫並更新快取(最常見)

Spring Boot 快取實現 (Cache-Aside)

java
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    // 查詢時先查快取,未命中則查資料庫並快取結果
    @Cacheable(value = "user", key = "#userId")
    public User getUserById(Long userId) {
        return userRepository.findById(userId).orElseThrow();
    }

    // 更新資料後清除快取
    @CacheEvict(value = "user", key = "#user.id")
    public void updateUser(User user) {
        userRepository.save(user);
    }

    // 更新資料並同時更新快取
    @CachePut(value = "user", key = "#user.id")
    public User saveUser(User user) {
        return userRepository.save(user);
    }
}

快取一致性策略

策略 實現方式 優點 缺點
寫入時清除 (@CacheEvict) 更新資料庫後刪除快取項 簡單、一致性好 下次讀取需重新載入快取
寫入時更新 (@CachePut) 同時寫入資料庫和快取 讀取快速 可能部分失敗導致不一致
訊息佇列 (如 Kafka) 透過事件流非同步同步 解耦、可擴展 複雜度高、可能有延遲
雙寫事務 事務性地更新兩者 可靠 回滾管理困難

常見問題與解決方案

1. 快取風暴 (Cache Stampede)

問題:快取過期時大量請求同時打到資料庫

解決方案:

  • 使用隨機化的 TTL
  • 使用互斥鎖 (SETNX)
  • 使用 Redisson 或 Caffeine 等函式庫

2. 快取不一致

問題:資料庫已更新,但快取未更新

解決方案:

  • 使用 @CacheEvict 或 @CachePut
  • 避免並行更新,使用鎖或防抖

3. 冷啟動 (Cold Start)

問題:重啟後快取為空

解決方案:

  • 啟動時預熱快取 (可選的批次載入器)

4. 序列化格式

Redis 需要快速的序列化,建議使用 JSON 或 String 而非 Java 原生序列化:

java
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
    return template;
}

3. 分散式系統

3.1 全域事務 (Global Transaction)

在分散式系統中,全域事務用於確保跨多個服務或資料庫的操作具有 ACID 特性。

3.1.1 兩階段提交 (2PC - Two-Phase Commit)

適用場景:強一致性的分散式資料庫

工作流程:

  • 階段 1 - 準備 (Prepare):協調者詢問所有參與節點是否準備好提交
  • 階段 2 - 提交 (Commit):如果所有節點回覆「是」,協調者發出提交指令;否則回滾

優點:

  • ✅ 強一致性 (ACID)
  • ✅ 中央協調

缺點:

  • ❌ 阻塞協定,可能造成鎖競爭
  • ❌ 單點故障(協調者失效)
  • ❌ 在網路分區時可用性差
  • ❌ 不適合雲原生 / 微服務架構

3.1.2 Saga 模式 (推薦用於微服務)

適用場景:現代微服務和事件驅動架構

工作原理:

  • 一系列本地事務,每個服務更新自己的資料並發布事件觸發下一步
  • 如果某一步失敗,執行補償事務 (Compensating Transaction) 回滾之前的步驟

範例(訂票流程):

  • 步驟 1:預訂飯店 → 步驟 2:預訂機票 → 步驟 3:付款
  • 如果步驟 2 失敗,執行補償動作取消步驟 1 的飯店預訂

實現方式:

方式 說明 範例工具
編排式 Saga (Orchestration) 中央服務控制流程 Netflix Conductor, Temporal
編舞式 Saga (Choreography) 服務對事件做出反應,自行管理轉換 Apache Kafka + EventBridge

優點:

  • ✅ 最終一致性
  • ✅ 高可用性和回應性
  • ✅ 適合鬆散耦合的系統

缺點:

  • ❌ 補償邏輯複雜
  • ❌ 難以除錯和追蹤

注意事項:

  • 確保每個服務的操作具有冪等性 (idempotency)
  • 使用相關 ID (correlation ID) 進行跨系統追蹤
  • 應用超時和斷路器 (circuit breaker) 提高韌性

3.1.3 TCC (Try-Confirm-Cancel)

適用場景:資源可以預留的系統(如庫存、訂票)

工作流程:

  • Try:預留資源(如鎖定庫存)
  • Confirm:確認操作,提交事務
  • Cancel:取消操作,釋放資源

優點:

  • ✅ 對操作有細粒度控制
  • ✅ 顯式的補償模型

缺點:

  • ❌ 所有服務必須實現 try/confirm/cancel 邏輯
  • ❌ 實現複雜度高

3.1.4 最終一致性 + 事件驅動架構

適用場景:可以容忍一致性延遲的系統

優點:

  • ✅ 非同步
  • ✅ 高度可擴展
  • ✅ 服務解耦

缺點:

  • ❌ 失敗處理複雜
  • ❌ 需要冪等操作
  • ❌ 難以除錯和追蹤

3.1.5 可靠訊息 + Outbox 模式

適用場景:確保事件/訊息以事務方式傳遞

優點:

  • ✅ 確保資料庫和訊息的原子性
  • ✅ 服務解耦

缺點:

  • ❌ 需要額外基礎設施 (Kafka, Debezium 等)
  • ❌ 運維和基礎設施開銷較高

模式比較表

模式 一致性 可擴展性 複雜度 失敗處理 理想使用場景
Saga 最終一致 ✅ 高 ⚠️ 中等 需要補償邏輯 長時間運行、可分解的業務流程
2PC 強一致 (ACID) ❌ 低 ⚠️ 高 協調者是瓶頸 需要嚴格一致性的金融操作
TCC 強一致 ⚠️ 中等 ❌ 高 顯式取消邏輯 訂票系統、可預留資源
最終一致性 (EDA) 最終一致 ✅ 非常高 ⚠️ 中等 重試邏輯、冪等性 容忍延遲的微服務
Outbox 模式 最終一致 ✅ 高 ⚠️ 中等 可靠訊息傳遞 確保訊息和資料庫一致性

選擇建議

需求 最佳選擇
需要強一致性 2PC, TCC
高可擴展性 Saga, Outbox
需要資源預留 TCC
容錯能力 Saga, Outbox
簡單性 > 一致性 手動對帳

3.2 ZooKeeper 分散式鎖

ZooKeeper 是一個分散式協調服務,常用於實現分散式鎖、配置管理、服務發現等功能。

基本概念:

  • 使用臨時順序節點 (Ephemeral Sequential Node) 實現鎖
  • 客戶端創建節點,序號最小的獲得鎖
  • 其他客戶端監聽前一個節點,等待鎖釋放

優點:

  • 支援阻塞式鎖
  • 自動釋放(客戶端斷線時自動刪除臨時節點)
  • 避免羊群效應 (thundering herd)

缺點:

  • 需要部署和維護 ZooKeeper 叢集
  • 性能不如 Redis

4. 網路與協定

4.1 TCP vs UDP

TCP (Transmission Control Protocol)

特性:

  • 面向連接的協定
  • 提供可靠的資料傳輸
  • 錯誤檢查和重傳功能
  • 確保資料不會丟失

三次握手 (Three-Way Handshake):

  1. Client 向 Server 發送連接請求封包 (SYN)
  2. Server 接收並確認,回傳確認封包 (SYN-ACK)
  3. Client 收到後再回傳確認封包 (ACK),連接建立

特性:

  • 建立連接後,所有封包都會加上序號
  • 保證完整性、順序性、重傳處理
  • 使用滑動窗口提升傳輸效率

適用場景:

  • 網頁瀏覽 (HTTP/HTTPS)
  • 電子郵件 (SMTP, IMAP)
  • 檔案傳輸 (FTP)
  • 需要資料完整性的應用

UDP (User Datagram Protocol)

特性:

  • 面向非連接的協定
  • 不保證資料傳輸的可靠性
  • 傳輸速度快,通訊引擎簡單
  • 無需確認機制,表頭資料較少

適用場景:

  • 串流服務 (影片、音訊)
  • 線上遊戲
  • DNS 查詢
  • 即時通訊

TCP vs UDP 比較

特性 TCP UDP
連接 面向連接 無連接
可靠性 可靠 不可靠
速度 較慢 較快
順序 保證順序 不保證順序
錯誤檢查 有 有但簡單
重傳 支援 不支援
表頭大小 較大 (20 bytes) 較小 (8 bytes)
使用場景 需要完整性的應用 需要速度的應用

4.2 HTTP 相關

HTTP, TCP, Socket 關係

  • TCP/IP:傳輸控制協定/網路協定,指一系列協定族
  • HTTP:基於 TCP 的應用層協定,用於 Web 伺服器與瀏覽器之間傳輸超文本
  • Socket:TCP/IP 網路的 API,隱藏了複雜的 TCP/IP 協定細節

關係總結:

  • 需要 IP 協定來連接網路
  • TCP 是一種安全傳輸資料的機制
  • HTTP 使用 TCP 協定來傳輸資料
  • Socket 可以用來建立 TCP 連接

HTTP 長連接與短連接

短連接(HTTP/1.0 預設):

  • 瀏覽器和伺服器每進行一次 HTTP 操作,就建立一次連接
  • 任務結束後立即中斷連接
  • 每個資源(HTML、CSS、JS、圖片)都需要獨立的連接

長連接(HTTP/1.1 預設):

  • 使用 Keep-Alive 保持連接
  • 網頁開啟完成後,TCP 連接不會關閉
  • 客戶端再次訪問相同伺服器時,繼續使用已建立的連接
  • 有保持時間限制,可在伺服器配置中設定

實質:HTTP 協定的長短連接,實質上是 TCP 協定的長短連接

HTTP 304 (Not Modified)

  • 含義:已讀取過的資源,由瀏覽器快取 (cache) 中讀取
  • 作用:節省頻寬,加快頁面載入速度
  • 觸發條件:
    • 客戶端發送帶有 If-Modified-Since 或 If-None-Match 的請求
    • 伺服器判斷資源未修改,返回 304 狀態碼
    • 瀏覽器使用本地快取的版本

4.3 JWT (JSON Web Token)

什麼是 JWT?

JWT 全名為 JSON Web Token,是一種基於 JSON 的開放標準(RFC 7519)。

結構:由三部分組成,以 . 分隔

  • Header:包含 token 類型和加密演算法
  • Payload:包含聲明 (claims),如用戶 ID、過期時間等
  • Signature:使用 Header 中指定的演算法 (HMAC、RSA、ECDSA) 對 Header 和 Payload 進行簽名

範例:

text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT 的優點

  • ✅ 無狀態:伺服器不需要存儲 session
  • ✅ 可擴展:適合分散式系統和微服務
  • ✅ 跨域支援:可在多個網域間使用
  • ✅ 自包含:Token 本身包含用戶資訊

JWT 的缺點

  • ❌ Cross-site 攻擊:可能遭受 XSS 攻擊
  • ❌ Local storage 不安全:存儲在 Local Storage 中容易被竊取
  • ❌ 無法單獨銷毀:Token 在過期前無法撤銷,除非使用黑名單機制

使用時機

  • ✅ Token 生命期較短:讓擁有此 Token 的用戶能在時間內完成特定操作(如登入、下載檔案)
  • ✅ Token 僅單次使用:任何 Token 只用於一次後就會被拋棄,不存在於任何持久化狀態

安全建議

  • 使用 HTTPS 傳輸 JWT
  • 設定合理的過期時間
  • 敏感資訊不要放在 Payload 中(因為 Base64 可解碼)
  • 考慮使用 Refresh Token 機制
  • 實現 Token 黑名單(如 Redis)用於撤銷

5. 應用架構

5.1 Middleware (中介層)

什麼是 Middleware?

Middleware 是位於傳入請求和核心應用邏輯之間的程式碼層,用於攔截、修改或處理請求和回應,而不改變主要業務邏輯。

流程:

text
Client → Middleware → App → Middleware → Response → Client

為什麼使用 Middleware?

Middleware 讓你可以將常見關注點從核心應用邏輯中解耦:

使用案例 說明
✅ 認證 (Authentication) 驗證 token、session、API key
✅ 日誌 (Logging) 記錄請求/回應詳情
✅ 速率限制 (Rate Limiting) 基於 IP/用戶防止濫用
✅ CORS / Headers 添加/修改 header 處理跨域請求
✅ 錯誤處理 (Error Handling) 捕獲並回應異常
✅ 請求解析/驗證 確保資料乾淨且安全

Flask 範例

python
from flask import Flask, request

app = Flask(__name__)

# 請求前的日誌記錄
@app.before_request
def log_request():
    print(f"[{request.method}] {request.path}")

# 請求後添加自訂 header
@app.after_request
def add_custom_header(response):
    response.headers['X-App-Version'] = '1.0.0'
    return response

@app.route("/hello")
def hello():
    return "Hello, World!"

Flask 認證 Middleware 範例

python
from flask import Flask, request, jsonify, abort

app = Flask(__name__)

# 模擬的 token 儲存(生產環境應使用資料庫或外部認證服務)
VALID_API_TOKENS = {
    "token123": "user_a",
    "token456": "user_b"
}

@app.before_request
def authenticate():
    # 定義不需要認證的公開路徑
    public_paths = ['/health', '/login']

    if request.path in public_paths:
        return  # 跳過認證檢查

    auth_header = request.headers.get("Authorization")
    if not auth_header or not auth_header.startswith("Bearer "):
        abort(401, description="Missing or malformed Authorization header")

    token = auth_header.split("Bearer ")[1]
    user = VALID_API_TOKENS.get(token)
    if not user:
        abort(401, description="Invalid or expired token")

    # 將用戶資訊附加到全域 request context
    request.user = user

@app.route("/health")
def health_check():
    return {"status": "ok"}

@app.route("/protected")
def protected_resource():
    return {"message": f"Hello, {request.user}. You accessed a protected route."}

if __name__ == "__main__":
    app.run(debug=True)

Express (Node.js) 範例

javascript
const express = require('express');
const app = express();

// 日誌 middleware
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next(); // 傳遞控制權給下一個 middleware 或路由
});

// 認證 middleware
app.use((req, res, next) => {
  if (!req.headers['authorization']) {
    return res.status(401).send('Unauthorized');
  }
  next();
});

app.get('/api/data', (req, res) => {
  res.send({ message: 'Protected data' });
});

核心概念

  • Middleware 通常是無狀態的,獨立處理每個請求
  • Middleware 可以短路請求(如在到達路由前返回 401)
  • 通常是堆疊或組合的,形成管道
  • 可以是自訂的或來自框架/函式庫

各框架的 Middleware

框架 Middleware 術語
Express (Node.js) app.use() handlers
Flask (Python) @before_request, WSGI middleware
Django MIDDLEWARE setting in settings.py
ASP.NET Core Middleware pipeline with UseXXX()
Ruby on Rails Rack middleware

5.2 ORM (Object-Relational Mapping)

什麼是 ORM?

ORM 是一種程式設計技術,用於在關聯式資料庫和物件導向程式語言之間建立映射關係。

核心概念:

  • 將資料庫表映射為類別 (Class)
  • 將表中的行映射為物件實例 (Object Instance)
  • 將欄位映射為物件屬性 (Attribute)

優點:

  • ✅ 減少 SQL 程式碼,提高開發效率
  • ✅ 資料庫無關性,易於切換資料庫
  • ✅ 型別安全,減少 SQL 注入風險
  • ✅ 易於維護和測試

缺點:

  • ❌ 性能可能不如原生 SQL(特別是複雜查詢)
  • ❌ 學習曲線(需要理解 ORM 框架)
  • ❌ 可能產生低效的 SQL(N+1 問題)

常見 ORM 框架:

  • Java: Hibernate, JPA, MyBatis
  • Python: SQLAlchemy, Django ORM
  • Node.js: Sequelize, TypeORM, Prisma
  • .NET: Entity Framework

5.3 MVC vs MVVM

MVC (Model-View-Controller)

架構:

  • Model:資料和業務邏輯
  • View:使用者介面
  • Controller:處理用戶輸入,協調 Model 和 View

流程:

text
User → Controller → Model → Controller → View → User

優點:

  • ✅ 職責分離
  • ✅ 易於測試
  • ✅ 適合傳統 Web 應用

缺點:

  • ❌ Controller 可能變得臃腫
  • ❌ View 和 Model 可能存在耦合

MVVM (Model-View-ViewModel)

架構:

  • Model:資料和業務邏輯
  • View:使用者介面
  • ViewModel:View 的抽象,處理 View 的展示邏輯和狀態

流程:

text
User → View ↔ ViewModel ↔ Model

特點:

  • View 和 ViewModel 之間通過資料綁定 (Data Binding) 自動同步

優點:

  • ✅ View 和業務邏輯完全分離
  • ✅ 易於單元測試(ViewModel 可獨立測試)
  • ✅ 適合現代前端框架(Vue.js, Angular, React)

缺點:

  • ❌ 學習曲線較陡
  • ❌ 過度使用資料綁定可能影響性能

6. 常見面試題

6.1 如何建立全域事務?

回答要點:

  • 說明 2PC 適用於強一致性需求
  • 說明 Saga 適用於微服務架構
  • 提到 TCC、Outbox 模式等替代方案
  • 強調需考慮一致性、可用性、性能的權衡

詳見 3.1 全域事務


6.2 建立分散式 Redis 鎖時的考量?

回答要點:

  1. 原子性:使用 SET key value NX EX timeout 確保鎖的獲取和過期時間設定是原子的
  2. 過期時間:設定合理的 TTL,避免死鎖
  3. 鎖的釋放:確保在 finally 塊中釋放鎖
  4. 重試策略:使用指數退避 (exponential backoff)
  5. 唯一標識:使用 UUID 作為鎖的值,釋放時驗證是否為自己持有的鎖
  6. Redlock 演算法:多 Redis 實例場景下使用 Redlock 提高可靠性

詳見 2.2 Redis 分散式鎖


6.3 如何優化 SQL 查詢?如何使用執行計畫?

回答要點:

  1. 使用 EXPLAIN 或 EXPLAIN ANALYZE 查看執行計畫
  2. 識別昂貴的操作:全表掃描、filesort、using temporary
  3. 確保 WHERE 和 JOIN 使用索引
  4. 避免 SELECT *,只查詢需要的欄位
  5. 分析 JOIN 順序,將過濾條件前置
  6. 為 ORDER BY 和 GROUP BY 欄位建立索引

詳見 1.3 SQL 查詢優化與執行計畫


6.4 Spring Boot Cloud 主要組件有哪些?

常見組件:

  • Eureka:服務註冊與發現
  • Ribbon:客戶端負載平衡
  • Feign:聲明式 REST 客戶端
  • Hystrix:斷路器,實現服務降級和熔斷
  • Zuul / Gateway:API 閘道
  • Config Server:集中式配置管理
  • Sleuth + Zipkin:分散式追蹤

6.5 如何確保資料庫資料一致性?

回答要點:

  1. 事務隔離級別:根據需求選擇合適的隔離級別
  2. 樂觀鎖 vs 悲觀鎖:
    • 悲觀鎖:SELECT ... FOR UPDATE
    • 樂觀鎖:版本號或時間戳
  3. 分散式事務:使用 2PC、Saga、TCC 等模式
  4. 冪等性:確保操作可重複執行而不影響結果
  5. 補償機制:失敗時執行補償邏輯

詳見 1.1 資料庫隔離級別、3.1 全域事務


6.6 如何使用 Redis 快取和資料庫(MySQL),當 Redis 可能失效時?

回答要點:

  1. 快取模式:使用 Cache-Aside 模式
  2. 快取一致性:
    • 寫入時使用 @CacheEvict 清除快取
    • 或使用 @CachePut 同時更新快取
  3. 快取失效場景:
    • 快取穿透:使用布隆過濾器或快取空值
    • 快取擊穿:熱點資料設為永不過期或使用互斥鎖
    • 快取雪崩:使用隨機過期時間或 Redis 叢集
  4. 降級策略:Redis 不可用時直接查詢資料庫

詳見 2.1 快取三大問題、2.3 Spring Boot + Redis 快取實踐


6.7 Interface、Class 在編譯時、執行時的使用,以及與 Bytecode 的關係?

回答要點:

編譯時 (Compile Time):

  • Interface 定義契約,Class 實現契約
  • 編譯器檢查型別安全、方法簽名等
  • Java 編譯器將 .java 檔案編譯為 .class bytecode 檔案

執行時 (Runtime):

  • JVM 載入 bytecode 並執行
  • 使用多型 (Polymorphism) 時,JVM 根據實際物件類型調用對應方法
  • Interface 在執行時通過虛擬方法表 (Virtual Method Table) 實現動態綁定

與 Bytecode 的關係:

  • Interface 編譯後產生 .class 檔案,包含方法簽名但無實現
  • Class 編譯後產生包含實現的 bytecode
  • Bytecode 指令如 invokevirtual、invokeinterface 用於方法調用

參考資料


最後更新日期: 2026-02-14