系统设计实战 181:关系型数据库

4 阅读10分钟

🚀 系统设计实战 181:关系型数据库

摘要:本文深入剖析系统的核心架构关键算法工程实践,提供完整的设计方案和面试要点。

你是否想过,设计关系型数据库背后的技术挑战有多复杂?

1. 系统概述

1.1 业务背景

关系型数据库是现代应用系统的核心组件,需要提供ACID事务保证、高性能查询、数据一致性和可靠性。系统需要支持SQL查询、索引优化、并发控制和故障恢复。

1.2 核心功能

  • 存储引擎:数据页管理、B+树索引、缓冲池
  • 查询优化器:SQL解析、执行计划优化、统计信息
  • 事务管理:ACID保证、锁机制、MVCC
  • 日志系统:WAL日志、检查点、故障恢复
  • 复制机制:主从复制、读写分离、高可用

1.3 技术挑战

  • 并发控制:多用户并发访问的一致性保证
  • 性能优化:查询优化、索引设计、缓存管理
  • 数据一致性:事务隔离级别、锁粒度控制
  • 故障恢复:数据持久化、崩溃恢复、备份还原
  • 扩展性:读写分离、分库分表、集群管理

2. 架构设计

2.1 整体架构

┌─────────────────────────────────────────────────────────────┐
│                    关系型数据库架构                          │
├─────────────────────────────────────────────────────────────┤
│  Client Layer                                               │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │
│  │ SQL客户端   │ │ JDBC驱动    │ │ 连接池      │           │
│  └─────────────┘ └─────────────┘ └─────────────┘           │
├─────────────────────────────────────────────────────────────┤
│  SQL Layer                                                  │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │
│  │ SQL解析器   │ │ 查询优化器  │ │ 执行引擎    │           │
│  └─────────────┘ └─────────────┘ └─────────────┘           │
├─────────────────────────────────────────────────────────────┤
│  Storage Engine Layer                                       │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │
│  │ 事务管理    │ │ 锁管理      │ │ 缓冲池      │           │
│  │ 索引管理    │ │ 日志管理    │ │ 页面管理    │           │
│  └─────────────┘ └─────────────┘ └─────────────┘           │
├─────────────────────────────────────────────────────────────┤
│  Physical Layer                                             │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │
│  │ 数据文件    │ │ 索引文件    │ │ 日志文件    │           │
│  └─────────────┘ └─────────────┘ └─────────────┘           │
└─────────────────────────────────────────────────────────────┘

3. 核心组件设计

3.1 存储引擎

// 时间复杂度:O(N),空间复杂度:O(1)

type StorageEngine struct {
    bufferPool    *BufferPool
    pageManager   *PageManager
    indexManager  *IndexManager
    lockManager   *LockManager
    logManager    *LogManager
}

type Page struct {
    PageID      PageID
    Data        []byte
    IsDirty     bool
    PinCount    int
    LastAccess  time.Time
    LSN         LogSequenceNumber
}

type BufferPool struct {
    pages       map[PageID]*Page
    freeList    *list.List
    lruList     *list.List
    maxPages    int
    mutex       sync.RWMutex
}

func (bp *BufferPool) GetPage(pageID PageID) (*Page, error) {
    bp.mutex.Lock()
    defer bp.mutex.Unlock()
    
    // 检查页面是否在缓冲池中
    if page, exists := bp.pages[pageID]; exists {
        page.PinCount++
        page.LastAccess = time.Now()
        bp.moveToFront(page)
        return page, nil
    }
    
    // 需要从磁盘加载页面
    if len(bp.pages) >= bp.maxPages {
        if err := bp.evictPage(); err != nil {
            return nil, err
        }
    }
    
    page, err := bp.loadPageFromDisk(pageID)
    if err != nil {
        return nil, err
    }
    
    page.PinCount = 1
    page.LastAccess = time.Now()
    bp.pages[pageID] = page
    bp.addToFront(page)
    
    return page, nil
}

func (bp *BufferPool) evictPage() error {
    // LRU策略选择要淘汰的页面
    for elem := bp.lruList.Back(); elem != nil; elem = elem.Prev() {
        page := elem.Value.(*Page)
        if page.PinCount == 0 {
            if page.IsDirty {
                if err := bp.flushPageToDisk(page); err != nil {
                    return err
                }
            }
            delete(bp.pages, page.PageID)
            bp.lruList.Remove(elem)
            return nil
        }
    }
    return errors.New("no page available for eviction")
}

3.2 B+树索引

type BPlusTree struct {
    root        *BPlusTreeNode
    order       int
    keyComparer KeyComparer
    mutex       sync.RWMutex
}

type BPlusTreeNode struct {
    IsLeaf      bool
    Keys        []Key
    Values      []Value      // 叶子节点存储值
    Children    []*BPlusTreeNode // 内部节点存储子节点指针
    Next        *BPlusTreeNode   // 叶子节点链表指针
    Parent      *BPlusTreeNode
}

func (bt *BPlusTree) Search(key Key) (Value, error) {
    bt.mutex.RLock()
    defer bt.mutex.RUnlock()
    
    node := bt.root
    
    // 从根节点向下搜索
    for !node.IsLeaf {
        childIndex := bt.findChildIndex(node, key)
        node = node.Children[childIndex]
    }
    
    // 在叶子节点中查找
    for i, k := range node.Keys {
        if bt.keyComparer.Equal(k, key) {
            return node.Values[i], nil
        }
    }
    
    return nil, ErrKeyNotFound
}

func (bt *BPlusTree) Insert(key Key, value Value) error {
    bt.mutex.Lock()
    defer bt.mutex.Unlock()
    
    if bt.root == nil {
        bt.root = &BPlusTreeNode{
            IsLeaf: true,
            Keys:   []Key{key},
            Values: []Value{value},
        }
        return nil
    }
    
    // 找到插入位置的叶子节点
    leaf := bt.findLeafNode(key)
    
    // 插入到叶子节点
    insertIndex := bt.findInsertPosition(leaf, key)
    leaf.Keys = append(leaf.Keys[:insertIndex], append([]Key{key}, leaf.Keys[insertIndex:]...)...)
    leaf.Values = append(leaf.Values[:insertIndex], append([]Value{value}, leaf.Values[insertIndex:]...)...)
    
    // 检查是否需要分裂
    if len(leaf.Keys) > bt.order {
        return bt.splitLeafNode(leaf)
    }
    
    return nil
}

func (bt *BPlusTree) splitLeafNode(node *BPlusTreeNode) error {
    mid := len(node.Keys) / 2
    
    // 创建新的右节点
    rightNode := &BPlusTreeNode{
        IsLeaf: true,
        Keys:   make([]Key, len(node.Keys)-mid),
        Values: make([]Value, len(node.Values)-mid),
        Next:   node.Next,
        Parent: node.Parent,
    }
    
    copy(rightNode.Keys, node.Keys[mid:])
    copy(rightNode.Values, node.Values[mid:])
    
    // 更新左节点
    node.Keys = node.Keys[:mid]
    node.Values = node.Values[:mid]
    node.Next = rightNode
    
    // 向父节点插入分割键
    promotedKey := rightNode.Keys[0]
    return bt.insertIntoParent(node, promotedKey, rightNode)
}

3.3 事务管理

type TransactionManager struct {
    transactions map[TransactionID]*Transaction
    lockManager  *LockManager
    logManager   *LogManager
    nextTxnID    TransactionID
    mutex        sync.Mutex
}

type Transaction struct {
    ID          TransactionID
    Status      TransactionStatus
    StartTime   time.Time
    LastLSN     LogSequenceNumber
    UndoList    []LogRecord
    LockList    []Lock
    IsolationLevel IsolationLevel
}

func (tm *TransactionManager) BeginTransaction(isolationLevel IsolationLevel) (*Transaction, error) {
    tm.mutex.Lock()
    defer tm.mutex.Unlock()
    
    txn := &Transaction{
        ID:             tm.nextTxnID,
        Status:         TransactionStatusActive,
        StartTime:      time.Now(),
        UndoList:       make([]LogRecord, 0),
        LockList:       make([]Lock, 0),
        IsolationLevel: isolationLevel,
    }
    
    tm.nextTxnID++
    tm.transactions[txn.ID] = txn
    
    // 记录事务开始日志
    logRecord := &BeginLogRecord{
        TxnID:     txn.ID,
        Timestamp: time.Now(),
    }
    lsn, err := tm.logManager.WriteLog(logRecord)
    if err != nil {
        return nil, err
    }
    txn.LastLSN = lsn
    
    return txn, nil
}

func (tm *TransactionManager) CommitTransaction(txnID TransactionID) error {
    tm.mutex.Lock()
    defer tm.mutex.Unlock()
    
    txn, exists := tm.transactions[txnID]
    if !exists {
        return ErrTransactionNotFound
    }
    
    if txn.Status != TransactionStatusActive {
        return ErrInvalidTransactionStatus
    }
    
    // 写提交日志
    commitLog := &CommitLogRecord{
        TxnID:     txnID,
        Timestamp: time.Now(),
        PrevLSN:   txn.LastLSN,
    }
    
    lsn, err := tm.logManager.WriteLog(commitLog)
    if err != nil {
        return err
    }
    
    // 强制刷新日志到磁盘
    if err := tm.logManager.FlushLog(lsn); err != nil {
        return err
    }
    
    // 释放所有锁
    for _, lock := range txn.LockList {
        tm.lockManager.ReleaseLock(lock)
    }
    
    txn.Status = TransactionStatusCommitted
    delete(tm.transactions, txnID)
    
    return nil
}

func (tm *TransactionManager) AbortTransaction(txnID TransactionID) error {
    txn, exists := tm.transactions[txnID]
    if !exists {
        return ErrTransactionNotFound
    }
    
    // 执行回滚操作
    for i := len(txn.UndoList) - 1; i >= 0; i-- {
        undoRecord := txn.UndoList[i]
        if err := tm.executeUndo(undoRecord); err != nil {
            return err
        }
    }
    
    // 写回滚日志
    abortLog := &AbortLogRecord{
        TxnID:     txnID,
        Timestamp: time.Now(),
        PrevLSN:   txn.LastLSN,
    }
    
    tm.logManager.WriteLog(abortLog)
    
    // 释放所有锁
    for _, lock := range txn.LockList {
        tm.lockManager.ReleaseLock(lock)
    }
    
    txn.Status = TransactionStatusAborted
    delete(tm.transactions, txnID)
    
    return nil
}

3.4 锁管理器

type LockManager struct {
    lockTable    map[ResourceID]*LockQueue
    waitForGraph *WaitForGraph
    mutex        sync.Mutex
}

type LockQueue struct {
    ResourceID ResourceID
    Granted    []Lock
    Waiting    []Lock
    mutex      sync.Mutex
}

type Lock struct {
    TxnID      TransactionID
    ResourceID ResourceID
    LockMode   LockMode
    Status     LockStatus
}

func (lm *LockManager) AcquireLock(txnID TransactionID, resourceID ResourceID, lockMode LockMode) error {
    lm.mutex.Lock()
    defer lm.mutex.Unlock()
    
    queue, exists := lm.lockTable[resourceID]
    if !exists {
        queue = &LockQueue{
            ResourceID: resourceID,
            Granted:    make([]Lock, 0),
            Waiting:    make([]Lock, 0),
        }
        lm.lockTable[resourceID] = queue
    }
    
    lock := Lock{
        TxnID:      txnID,
        ResourceID: resourceID,
        LockMode:   lockMode,
        Status:     LockStatusRequested,
    }
    
    // 检查是否可以立即授予锁
    if lm.canGrantLock(queue, lockMode) {
        lock.Status = LockStatusGranted
        queue.Granted = append(queue.Granted, lock)
        return nil
    }
    
    // 检查死锁
    if lm.wouldCauseDeadlock(txnID, resourceID) {
        return ErrDeadlockDetected
    }
    
    // 加入等待队列
    queue.Waiting = append(queue.Waiting, lock)
    lm.waitForGraph.AddEdge(txnID, lm.getBlockingTransactions(queue, lockMode))
    
    return ErrLockWaiting
}

func (lm *LockManager) canGrantLock(queue *LockQueue, requestedMode LockMode) bool {
    // 检查与已授予锁的兼容性
    for _, grantedLock := range queue.Granted {
        if !lm.isCompatible(grantedLock.LockMode, requestedMode) {
            return false
        }
    }
    
    // 检查等待队列中是否有更高优先级的请求
    for _, waitingLock := range queue.Waiting {
        if !lm.isCompatible(waitingLock.LockMode, requestedMode) {
            return false
        }
    }
    
    return true
}

func (lm *LockManager) isCompatible(mode1, mode2 LockMode) bool {
    compatibilityMatrix := map[LockMode]map[LockMode]bool{
        LockModeShared: {
            LockModeShared:    true,
            LockModeExclusive: false,
        },
        LockModeExclusive: {
            LockModeShared:    false,
            LockModeExclusive: false,
        },
    }
    
    return compatibilityMatrix[mode1][mode2]
}

4. 查询优化器

4.1 SQL解析器

type SQLParser struct {
    lexer     *Lexer
    tokens    []Token
    position  int
}

type QueryPlan struct {
    Root        PlanNode
    Cost        float64
    Cardinality int64
}

type PlanNode interface {
    GetType() NodeType
    GetChildren() []PlanNode
    GetCost() float64
    Execute(ctx *ExecutionContext) (ResultSet, error)
}

type TableScanNode struct {
    TableName   string
    Predicate   Expression
    Cost        float64
    Cardinality int64
}

func (tsn *TableScanNode) Execute(ctx *ExecutionContext) (ResultSet, error) {
    table := ctx.GetTable(tsn.TableName)
    result := NewResultSet()
    
    // 全表扫描
    for _, row := range table.GetRows() {
        if tsn.Predicate == nil || tsn.Predicate.Evaluate(row) {
            result.AddRow(row)
        }
    }
    
    return result, nil
}

type IndexScanNode struct {
    TableName   string
    IndexName   string
    ScanRange   *ScanRange
    Cost        float64
    Cardinality int64
}

func (isn *IndexScanNode) Execute(ctx *ExecutionContext) (ResultSet, error) {
    index := ctx.GetIndex(isn.TableName, isn.IndexName)
    result := NewResultSet()
    
    // 索引范围扫描
    iterator := index.GetRangeIterator(isn.ScanRange)
    for iterator.HasNext() {
        rowID := iterator.Next()
        row := ctx.GetRowByID(isn.TableName, rowID)
        result.AddRow(row)
    }
    
    return result, nil
}

4.2 查询优化

type QueryOptimizer struct {
    statisticsManager *StatisticsManager
    costModel         *CostModel
    ruleEngine        *RuleEngine
}

func (qo *QueryOptimizer) OptimizeQuery(query *ParsedQuery) (*QueryPlan, error) {
    // 1. 逻辑优化(基于规则)
    optimizedQuery := qo.ruleEngine.ApplyRules(query)
    
    // 2. 生成候选执行计划
    candidates := qo.generateCandidatePlans(optimizedQuery)
    
    // 3. 基于成本选择最优计划
    bestPlan := qo.selectBestPlan(candidates)
    
    return bestPlan, nil
}

func (qo *QueryOptimizer) generateCandidatePlans(query *ParsedQuery) []*QueryPlan {
    plans := make([]*QueryPlan, 0)
    
    // 为每个表生成访问路径
    for _, table := range query.Tables {
        // 全表扫描
        scanPlan := qo.createTableScanPlan(table, query.Predicate)
        plans = append(plans, scanPlan)
        
        // 索引扫描
        for _, index := range qo.getAvailableIndexes(table) {
            if qo.canUseIndex(index, query.Predicate) {
                indexPlan := qo.createIndexScanPlan(table, index, query.Predicate)
                plans = append(plans, indexPlan)
            }
        }
    }
    
    // 生成连接计划
    if len(query.Tables) > 1 {
        joinPlans := qo.generateJoinPlans(query.Tables, query.JoinConditions)
        plans = append(plans, joinPlans...)
    }
    
    return plans
}

func (qo *QueryOptimizer) selectBestPlan(candidates []*QueryPlan) *QueryPlan {
    var bestPlan *QueryPlan
    minCost := math.MaxFloat64
    
    for _, plan := range candidates {
        cost := qo.costModel.CalculateCost(plan)
        if cost < minCost {
            minCost = cost
            bestPlan = plan
        }
    }
    
    return bestPlan
}

5. 日志与恢复

5.1 WAL日志系统

type LogManager struct {
    logFile     *os.File
    logBuffer   []byte
    bufferPos   int
    nextLSN     LogSequenceNumber
    flushedLSN  LogSequenceNumber
    mutex       sync.Mutex
}

type LogRecord interface {
    GetLSN() LogSequenceNumber
    GetTxnID() TransactionID
    GetType() LogRecordType
    Serialize() []byte
    GetSize() int
}

type UpdateLogRecord struct {
    LSN       LogSequenceNumber
    TxnID     TransactionID
    PageID    PageID
    Offset    int
    OldValue  []byte
    NewValue  []byte
    PrevLSN   LogSequenceNumber
}

func (lm *LogManager) WriteLog(record LogRecord) (LogSequenceNumber, error) {
    lm.mutex.Lock()
    defer lm.mutex.Unlock()
    
    // 分配LSN
    lsn := lm.nextLSN
    lm.nextLSN++
    
    // 序列化日志记录
    data := record.Serialize()
    
    // 检查缓冲区空间
    if lm.bufferPos+len(data) > len(lm.logBuffer) {
        if err := lm.flushBuffer(); err != nil {
            return 0, err
        }
    }
    
    // 写入缓冲区
    copy(lm.logBuffer[lm.bufferPos:], data)
    lm.bufferPos += len(data)
    
    return lsn, nil
}

func (lm *LogManager) FlushLog(lsn LogSequenceNumber) error {
    lm.mutex.Lock()
    defer lm.mutex.Unlock()
    
    if lsn <= lm.flushedLSN {
        return nil // 已经刷新
    }
    
    return lm.flushBuffer()
}

func (lm *LogManager) flushBuffer() error {
    if lm.bufferPos == 0 {
        return nil
    }
    
    _, err := lm.logFile.Write(lm.logBuffer[:lm.bufferPos])
    if err != nil {
        return err
    }
    
    err = lm.logFile.Sync()
    if err != nil {
        return err
    }
    
    lm.flushedLSN = lm.nextLSN - 1
    lm.bufferPos = 0
    
    return nil
}

5.2 崩溃恢复

type RecoveryManager struct {
    logManager      *LogManager
    bufferPool      *BufferPool
    transactionManager *TransactionManager
}

func (rm *RecoveryManager) Recover() error {
    // 1. 分析阶段:扫描日志确定检查点
    checkpoint, err := rm.findLastCheckpoint()
    if err != nil {
        return err
    }
    
    // 2. 重做阶段:重做所有已提交事务的操作
    if err := rm.redoPhase(checkpoint); err != nil {
        return err
    }
    
    // 3. 撤销阶段:撤销所有未提交事务的操作
    if err := rm.undoPhase(); err != nil {
        return err
    }
    
    return nil
}

func (rm *RecoveryManager) redoPhase(startLSN LogSequenceNumber) error {
    iterator := rm.logManager.GetLogIterator(startLSN)
    
    for iterator.HasNext() {
        record := iterator.Next()
        
        switch record.GetType() {
        case LogRecordTypeUpdate:
            updateRecord := record.(*UpdateLogRecord)
            page, err := rm.bufferPool.GetPage(updateRecord.PageID)
            if err != nil {
                return err
            }
            
            // 检查页面LSN,决定是否需要重做
            if page.LSN < updateRecord.LSN {
                rm.applyUpdate(page, updateRecord)
                page.LSN = updateRecord.LSN
                page.IsDirty = true
            }
            
            rm.bufferPool.UnpinPage(page.PageID)
            
        case LogRecordTypeCommit:
            // 标记事务为已提交
            commitRecord := record.(*CommitLogRecord)
            rm.transactionManager.MarkCommitted(commitRecord.TxnID)
        }
    }
    
    return nil
}

func (rm *RecoveryManager) undoPhase() error {
    // 获取所有未提交的事务
    activeTransactions := rm.transactionManager.GetActiveTransactions()
    
    // 为每个活跃事务执行撤销操作
    for _, txnID := range activeTransactions {
        if err := rm.undoTransaction(txnID); err != nil {
            return err
        }
    }
    
    return nil
}

func (rm *RecoveryManager) undoTransaction(txnID TransactionID) error {
    // 从事务的最后一个日志记录开始向前撤销
    lastLSN := rm.transactionManager.GetLastLSN(txnID)
    
    for lastLSN != 0 {
        record := rm.logManager.GetLogRecord(lastLSN)
        
        if record.GetType() == LogRecordTypeUpdate {
            updateRecord := record.(*UpdateLogRecord)
            
            // 生成补偿日志记录
            clr := &CompensationLogRecord{
                TxnID:    txnID,
                PageID:   updateRecord.PageID,
                Offset:   updateRecord.Offset,
                OldValue: updateRecord.NewValue,
                NewValue: updateRecord.OldValue,
                UndoNext: updateRecord.PrevLSN,
            }
            
            // 应用撤销操作
            page, err := rm.bufferPool.GetPage(updateRecord.PageID)
            if err != nil {
                return err
            }
            
            rm.applyUndo(page, clr)
            rm.logManager.WriteLog(clr)
            
            rm.bufferPool.UnpinPage(page.PageID)
            lastLSN = updateRecord.PrevLSN
        }
    }
    
    return nil
}

关系型数据库通过精心设计的存储引擎、事务管理、查询优化和恢复机制,为应用提供了可靠、高性能的数据管理服务。


🎯 场景引入

你打开App,

你打开手机准备使用设计关系型数据库服务。看似简单的操作背后,系统面临三大核心挑战:

  • 挑战一:高并发——如何在百万级 QPS 下保持低延迟?
  • 挑战二:高可用——如何在节点故障时保证服务不中断?
  • 挑战三:数据一致性——如何在分布式环境下保证数据正确?

📈 容量估算

假设 DAU 1000 万,人均日请求 50 次

指标数值
数据总量10 TB+
日写入量~100 GB
写入 TPS~5 万/秒
读取 QPS~20 万/秒
P99 读延迟< 10ms
节点数10-50
副本因子3

❓ 高频面试问题

Q1:关系型数据库的核心设计原则是什么?

参考正文中的架构设计部分,核心原则包括:高可用(故障自动恢复)、高性能(低延迟高吞吐)、可扩展(水平扩展能力)、一致性(数据正确性保证)。面试时需结合具体场景展开。

Q2:关系型数据库在大规模场景下的主要挑战是什么?

  1. 性能瓶颈:随着数据量和请求量增长,单节点无法承载;2) 一致性:分布式环境下的数据一致性保证;3) 故障恢复:节点故障时的自动切换和数据恢复;4) 运维复杂度:集群管理、监控、升级。

Q3:如何保证关系型数据库的高可用?

  1. 多副本冗余(至少 3 副本);2) 自动故障检测和切换(心跳 + 选主);3) 数据持久化和备份;4) 限流降级(防止雪崩);5) 多机房/多活部署。

Q4:关系型数据库的性能优化有哪些关键手段?

  1. 缓存(减少重复计算和 IO);2) 异步处理(非关键路径异步化);3) 批量操作(减少网络往返);4) 数据分片(并行处理);5) 连接池复用。

Q5:关系型数据库与同类方案相比有什么优劣势?

参考方案对比表格。选型时需考虑:团队技术栈、数据规模、延迟要求、一致性需求、运维成本。没有银弹,需根据业务场景权衡取舍。



| 方案一 | 简单实现 | 低 | 适合小规模 | | 方案二 | 中等复杂度 | 中 | 适合中等规模 | | 方案三 | 高复杂度 ⭐推荐 | 高 | 适合大规模生产环境 |

🚀 架构演进路径

阶段一:单机版 MVP(用户量 < 10 万)

  • 单体应用 + 单机数据库,功能验证优先
  • 适用场景:产品早期验证,快速迭代

阶段二:基础版分布式(用户量 10 万 - 100 万)

  • 应用层水平扩展 + 数据库主从分离
  • 引入 Redis 缓存热点数据,降低数据库压力
  • 适用场景:业务增长期

阶段三:生产级高可用(用户量 > 100 万)

  • 微服务拆分,独立部署和扩缩容
  • 数据库分库分表 + 消息队列解耦
  • 多机房部署,异地容灾
  • 全链路监控 + 自动化运维

✅ 架构设计检查清单

检查项状态
缓存策略
分布式架构
数据一致性
高可用设计
性能优化
水平扩展

⚖️ 关键 Trade-off 分析

🔴 Trade-off 1:一致性 vs 可用性

  • 强一致(CP):适用于金融交易等不能出错的场景
  • 高可用(AP):适用于社交动态等允许短暂不一致的场景
  • 本系统选择:核心路径强一致,非核心路径最终一致

🔴 Trade-off 2:同步 vs 异步

  • 同步处理:延迟低但吞吐受限,适用于核心交互路径
  • 异步处理:吞吐高但增加延迟,适用于后台计算
  • 本系统选择:核心路径同步,非核心路径异步