HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 分布式架构模式

    • 分布式架构模式手册
    • 第1章:分布式一致性
    • 第2章:分布式锁
    • 第3章:分布式协调
    • 第4章:服务发现与注册
    • 第5章:负载均衡
    • 第6章:熔断降级
    • 第7章:DDD领域驱动设计
    • 第8章:CQRS与Event Sourcing

第1章:分布式一致性

为什么需要分布式一致性

单机系统 vs 分布式系统

单机系统:

┌─────────────────┐
│   Application   │
│       ↓         │
│   Database      │  ← 单点,强一致性
└─────────────────┘

特点:
 强一致性(ACID)
 实现简单
 单点故障
 扩展性差

分布式系统:

┌──────────┐  ┌──────────┐  ┌──────────┐
│  Node 1  │  │  Node 2  │  │  Node 3  │
│  Data A  │  │  Data B  │  │  Data C  │
└────┬─────┘  └────┬─────┘  └────┬─────┘
     └─────────────┼─────────────┘
                   ↓
            Network Partition(网络分区)

挑战:
 网络延迟
 节点故障
 数据不一致
 高可用
 高性能
 可扩展

一致性问题示例

场景:银行转账

时刻T0: 账户A余额=100, 账户B余额=0

操作: A转账50元给B

理想情况(强一致性):
T1: A=50,  B=50   一致

实际情况(网络延迟):
T1: Node1读到 A=100
T2: Node2读到 B=0
T3: Node1写入 A=50
T4: 网络延迟...
T5: Node2还没收到更新,读到B=0   不一致!

核心问题:

  • 如何保证多个节点的数据一致?
  • 一致性和性能如何权衡?
  • 网络分区时如何处理?

CAP定理

定理描述

CAP定理(布鲁尔定理):分布式系统最多只能同时满足以下三个特性中的两个

        C (Consistency)
           一致性
            ╱ ╲
           ╱   ╲
          ╱     ╲
         ╱       ╲
        ╱    CAP   ╲
       ╱   不可能   ╲
      ╱    三角形    ╲
     ╱               ╲
    ╱─────────────────╲
   A                   P
(Availability)    (Partition tolerance)
  可用性              分区容错性

三个特性详解

1. 一致性(Consistency)

定义:所有节点在同一时刻看到的数据是一致的

// 强一致性示例
func Transfer(from, to string, amount int) error {
    // 所有节点必须看到相同的结果

    tx := db.Begin()

    // 1. 扣减from账户
    tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from)

    // 2. 增加to账户
    tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to)

    // 3. 提交事务(原子性)
    return tx.Commit()
}

// 读操作总能读到最新的写入
balance := GetBalance("account_A")  // 总是返回最新值

特征:

  • 读操作总能读到最新的写入
  • 类似单机数据库的ACID特性
  • 性能开销大(需要同步等待)
  • 可用性受影响(故障时可能不可用)

2. 可用性(Availability)

定义:系统在任何时候都能响应请求(非故障节点)

// 高可用示例
func GetBalance(accountID string) (int, error) {
    // 从任意节点读取(可能不是最新值)

    // 优先本地缓存
    if balance, ok := localCache.Get(accountID); ok {
        return balance, nil
    }

    // 从最近的节点读取
    for _, node := range nearbyNodes {
        if balance, err := node.Get(accountID); err == nil {
            return balance, nil  // 立即返回,不保证最新
        }
    }

    return 0, errors.New("all nodes failed")
}

特征:

  • 请求总能得到响应(成功或失败)
  • 响应时间快
  • 可能返回旧数据
  • 不保证数据一致性

3. 分区容错性(Partition Tolerance)

定义:系统在网络分区时仍能继续工作

网络分区场景:

正常情况:
Node1 ←→ Node2 ←→ Node3
                

网络分区:
Node1 ←→ Node2  ✗  Node3
                   
(左侧分区)      (右侧分区)

系统必须能在分区情况下继续提供服务

特征:

  • 网络分区时系统不中断
  • 分布式系统的必选项(网络故障不可避免)
  • 需要在C和A之间做权衡

CAP权衡:三选二

CP系统(牺牲可用性)

代表:ZooKeeper, etcd, HBase, MongoDB(强一致模式)

场景:网络分区发生

┌─────────┐     ✗     ┌─────────┐
│ Node1   │           │ Node2   │
│ Leader  │           │ Follower│
└─────────┘           └─────────┘

策略:
1. Node1无法与Node2通信
2. Node1发现自己不是majority(多数派)
3. Node1拒绝服务(保证一致性)
4. 系统暂时不可用(直到网络恢复)

特点:
 保证强一致性
 可用性降低

代码示例:

// ZooKeeper写入(CP系统)
func WriteToZK(path string, data []byte) error {
    conn, _, err := zk.Connect([]string{"localhost:2181"}, time.Second)
    if err != nil {
        return err
    }

    // 必须写入多数节点才返回成功
    _, err = conn.Create(path, data, 0, zk.WorldACL(zk.PermAll))
    if err != nil {
        // 网络分区时可能失败(牺牲可用性)
        return err  //  拒绝服务
    }

    return nil  //  保证强一致
}

适用场景:

  • 配置管理(需要强一致性)
  • 分布式锁(必须准确)
  • Leader选举(不能有两个Leader)
  • 元数据存储

AP系统(牺牲一致性)

代表:Cassandra, DynamoDB, Eureka, Redis Cluster

场景:网络分区发生

┌─────────┐     ✗     ┌─────────┐
│ Node1   │           │ Node2   │
│ Data: A │           │ Data: B │
└─────────┘           └─────────┘

策略:
1. Node1和Node2都继续提供服务 
2. 客户端可能读到不同的值
   - 连接Node1 → 读到A
   - 连接Node2 → 读到B
3. 网络恢复后,通过冲突解决机制合并

特点:
 高可用(总能响应)
 短期数据不一致
 最终一致性(eventually consistent)

代码示例:

// Cassandra写入(AP系统)
func WriteToCassandra(key string, value string) error {
    session := cluster.CreateSession()

    // 只需写入部分节点即可返回(牺牲一致性)
    err := session.Query(
        "INSERT INTO data (key, value) VALUES (?, ?)",
        key, value,
    ).Consistency(gocql.One).Exec()  // ONE:只需1个节点成功

    if err != nil {
        return err
    }

    return nil  //  高可用,但可能有节点数据不一致
}

// 读操作
func ReadFromCassandra(key string) (string, error) {
    var value string

    // 从任意节点读取(可能是旧数据)
    err := session.Query(
        "SELECT value FROM data WHERE key = ?",
        key,
    ).Consistency(gocql.One).Scan(&value)

    return value, err  // 可能读到旧值(最终一致性)
}

适用场景:

  • 社交网络(点赞、评论可以延迟)
  • 内容分发(CDN)
  • 购物车(短期不一致可接受)
  • 日志收集

CA系统(理论存在,实际不可行)

结论:在分布式环境下,CA系统不存在

原因:

网络分区是必然发生的:
- 网线被拔掉
- 交换机故障
- 机房断电
- 跨地域延迟

如果不支持P(分区容错性):
→ 网络分区时系统完全不可用
→ 不符合分布式系统的初衷

单机数据库是CA系统:

  • MySQL单机:强一致性 + 高可用
  • 但不是分布式系统(没有P的需求)

CAP实践建议

1. 优先保证P

P(分区容错性)是必选项:
  网络分区无法避免
  ↓
在C和A之间权衡:
  需要强一致性 → 选择CP
  需要高可用性 → 选择AP

2. 根据业务选择

业务场景一致性要求推荐选择代表系统
金融交易强一致性CP传统数据库 + 2PC
库存扣减强一致性CPRedis + Lua
用户注册强一致性CPMySQL主从
社交点赞最终一致性APCassandra
DNS解析最终一致性APDNS系统
购物车最终一致性APDynamoDB

3. 混合策略

// 核心数据用CP,非核心数据用AP
type OrderSystem struct {
    orderDB    *MySQL      // CP:订单数据(强一致)
    cacheDB    *Redis      // AP:缓存(最终一致)
    searchDB   *ES         // AP:搜索(最终一致)
}

func (s *OrderSystem) CreateOrder(order *Order) error {
    // 1. 写入MySQL(CP,保证一致性)
    if err := s.orderDB.Insert(order); err != nil {
        return err
    }

    // 2. 异步更新缓存(AP,允许短暂不一致)
    go func() {
        s.cacheDB.Set(order.ID, order)
        s.searchDB.Index(order)
    }()

    return nil
}

BASE理论

BASE vs ACID

ACID(传统数据库):

A - Atomicity(原子性)
C - Consistency(一致性)
I - Isolation(隔离性)
D - Durability(持久性)

特点:强一致性,适合单机系统

BASE(分布式系统):

BA - Basically Available(基本可用)
S  - Soft state(软状态)
E  - Eventually consistent(最终一致性)

特点:牺牲强一致性,换取可用性和性能

BASE三要素

1. 基本可用(Basically Available)

定义:系统在出现故障时,允许损失部分可用性(但不是完全不可用)

示例:

正常情况:
- 响应时间: 50ms
- 成功率: 99.99%

故障情况(基本可用):
- 响应时间: 200ms(降低性能)
- 成功率: 99%(部分失败)
- 降级服务(返回缓存数据)

完全不可用 :
- 所有请求都失败

代码示例:

// 基本可用:降级策略
func GetUserInfo(userID string) (*User, error) {
    // 尝试从主库读取
    user, err := masterDB.GetUser(userID)
    if err == nil {
        return user, nil
    }

    // 主库失败,尝试从库(可能数据延迟)
    user, err = slaveDB.GetUser(userID)
    if err == nil {
        return user, nil  // 基本可用:返回可能旧的数据
    }

    // 从库也失败,返回缓存
    user, err = cache.GetUser(userID)
    if err == nil {
        return user, nil  // 基本可用:返回缓存数据
    }

    // 完全失败
    return nil, errors.New("service unavailable")
}

2. 软状态(Soft State)

定义:系统中的数据存在中间状态,该状态不影响系统的整体可用性

示例:

电商订单状态流转:

创建订单 → 待支付(软状态)→ 支付中(软状态)→ 已支付
                ↓
              超时取消

软状态特点:
- 中间状态允许存在
- 最终会达到一致状态
- 不阻塞系统运行

代码示例:

// 订单状态机(软状态)
type OrderStatus string

const (
    StatusPending    OrderStatus = "pending"     // 软状态
    StatusPaying     OrderStatus = "paying"      // 软状态
    StatusPaid       OrderStatus = "paid"        // 最终状态
    StatusCancelled  OrderStatus = "cancelled"   // 最终状态
)

type Order struct {
    ID        string
    Status    OrderStatus
    CreatedAt time.Time
}

// 订单状态转换(允许中间状态)
func (o *Order) Process() {
    switch o.Status {
    case StatusPending:
        // 软状态:等待支付
        if time.Since(o.CreatedAt) > 30*time.Minute {
            o.Status = StatusCancelled  // 超时取消
        }

    case StatusPaying:
        // 软状态:支付处理中
        // 异步查询支付结果
        go func() {
            result := queryPaymentResult(o.ID)
            if result.Success {
                o.Status = StatusPaid  // 达到最终状态
            } else {
                o.Status = StatusCancelled
            }
        }()
    }
}

3. 最终一致性(Eventually Consistent)

定义:系统保证在一定时间窗口后,所有节点的数据最终会达到一致状态

时间轴:

T0: 写入Node1(A=100)
T1: 客户端1读Node1 → A=100 
T2: 客户端2读Node2 → A=50   不一致(复制延迟)
T3: 客户端3读Node3 → A=50   不一致
...
T10: 复制完成
T11: 所有节点 → A=100  最终一致

最终一致性窗口:T0 ~ T10

保证机制:

  1. 读写时间戳
type Data struct {
    Value     string
    Version   int64      // 版本号
    Timestamp time.Time  // 时间戳
}

// 冲突解决:Last Write Wins
func Merge(data1, data2 Data) Data {
    if data1.Timestamp.After(data2.Timestamp) {
        return data1  // 后写入的获胜
    }
    return data2
}
  1. Vector Clock(向量时钟)
type VectorClock map[string]int64

// 示例:
// Node1: {Node1: 5, Node2: 3, Node3: 2}
// Node2: {Node1: 4, Node2: 6, Node3: 2}

func (vc VectorClock) HappensBefore(other VectorClock) bool {
    // 判断因果关系
    for node, timestamp := range vc {
        if timestamp > other[node] {
            return false
        }
    }
    return true
}
  1. Gossip协议
// 节点间周期性同步数据
func (n *Node) GossipSync() {
    ticker := time.NewTicker(1 * time.Second)

    for range ticker.C {
        // 随机选择一个节点同步
        peer := n.SelectRandomPeer()

        // 交换数据
        myData := n.GetAllData()
        peerData := peer.GetAllData()

        // 合并数据(解决冲突)
        mergedData := Merge(myData, peerData)

        // 更新本地数据
        n.UpdateData(mergedData)
    }
}

BASE实践案例

案例1:电商库存扣减

// 使用BASE理论的库存系统
type InventorySystem struct {
    db    *MySQL      // 持久化存储
    cache *Redis      // 缓存
    mq    *Kafka      // 消息队列
}

// 1. 基本可用:优先扣减缓存
func (s *InventorySystem) DeductStock(productID string, quantity int) error {
    // 1. 从缓存扣减(快速响应)
    remaining, err := s.cache.DecrBy(productID, quantity)
    if err != nil {
        return err
    }

    if remaining < 0 {
        // 库存不足,回滚
        s.cache.IncrBy(productID, quantity)
        return errors.New("insufficient stock")
    }

    // 2. 发送消息到MQ(异步更新DB)
    event := StockDeductEvent{
        ProductID: productID,
        Quantity:  quantity,
        Timestamp: time.Now(),
    }
    s.mq.Publish("stock.deduct", event)

    return nil  // 基本可用:缓存扣减成功即返回
}

// 3. 最终一致性:异步消费MQ,更新DB
func (s *InventorySystem) ConsumeStockEvents() {
    s.mq.Subscribe("stock.deduct", func(event StockDeductEvent) {
        // 更新数据库
        err := s.db.Exec(
            "UPDATE inventory SET stock = stock - ? WHERE product_id = ?",
            event.Quantity, event.ProductID,
        )

        if err != nil {
            // 重试机制
            s.mq.Retry(event)
        }
    })
}

// 4. 定时对账(保证最终一致)
func (s *InventorySystem) Reconcile() {
    ticker := time.NewTicker(1 * time.Hour)

    for range ticker.C {
        // 对比缓存和数据库
        products := s.db.GetAllProducts()

        for _, product := range products {
            cacheStock := s.cache.Get(product.ID)
            dbStock := product.Stock

            if cacheStock != dbStock {
                // 发现不一致,以数据库为准
                s.cache.Set(product.ID, dbStock)
                log.Printf("Reconciled: %s, cache=%d, db=%d",
                    product.ID, cacheStock, dbStock)
            }
        }
    }
}

优势:

  • 高性能(缓存扣减)
  • 高可用(异步处理)
  • 最终一致(对账机制)

一致性模型

强一致性(Strong Consistency)

定义:任何时刻,所有节点看到的数据都是一致的

特点:

写入流程:
Client → Write Request → Node1
                          ↓
                    Sync to Node2
                          ↓
                    Sync to Node3
                          ↓
                    All nodes synced
                          ↓
                    Return Success

读取:总是返回最新值
延迟:高(需要同步等待)
一致性:强

实现:

  • 分布式事务(2PC、3PC)
  • Paxos、Raft共识算法
  • ZooKeeper、etcd

弱一致性(Weak Consistency)

定义:写入后,不保证立即能读到最新值

特点:

写入流程:
Client → Write Request → Node1
                          ↓
                    Return Success(立即返回)
                          ↓
                    Async Sync to Node2/3

读取:可能返回旧值
延迟:低
一致性:弱

实现:

  • DNS系统
  • CDN缓存
  • 浏览器缓存

最终一致性(Eventual Consistency)

定义:弱一致性的特例,保证最终会一致

变种:

  1. 因果一致性(Causal Consistency)
如果A操作导致B操作,则所有节点看到的顺序是A→B

示例:
User1: 发帖(Post A)
User2: 评论(Comment on A)

保证:所有用户先看到Post A,再看到Comment
  1. 会话一致性(Session Consistency)
同一个会话内保证一致性

示例:
User1: 修改昵称 → 立即刷新 → 看到新昵称 
User2: 访问User1主页 → 可能看到旧昵称(暂时)

User1的会话内保证一致
  1. 单调读一致性(Monotonic Read Consistency)
同一个用户的多次读取,不会读到更旧的值

T1: Read → Value = 100
T2: Read → Value = 150  (不会读到50)

共识算法

Paxos算法

问题:在分布式环境下,如何让多个节点对某个值达成一致?

角色:

  • Proposer(提议者):提出提案
  • Acceptor(接受者):投票表决
  • Learner(学习者):学习最终结果

流程(简化版):

Phase 1: Prepare阶段
Proposer → Prepare(n) → All Acceptors
                         ↓
Acceptors → Promise/Reject → Proposer

Phase 2: Accept阶段
Proposer → Accept(n, value) → All Acceptors
                                ↓
Acceptors → Accepted/Rejected → Proposer
                                 ↓
                    Majority Accepted → Consensus Reached

示例:

3个节点选举Leader:

Round 1:
Node1 propose: "Node1 as leader" (n=1)
Node2 promise: OK
Node3 promise: OK
→ Node1成为Leader

Round 2(Node1宕机):
Node2 propose: "Node2 as leader" (n=2)
Node3 promise: OK
→ Node2成为Leader

Raft算法

Paxos的简化版,更易理解和实现

角色:

  • Leader(领导者):处理所有请求
  • Follower(跟随者):被动接收日志
  • Candidate(候选者):选举时的临时角色

核心机制:

  1. Leader选举
初始状态:所有节点都是Follower

选举触发:Follower超时未收到Leader心跳
         ↓
    转为Candidate
         ↓
    发起投票请求
         ↓
  获得多数票 → 成为Leader
  未获得多数票 → 重新选举
  1. 日志复制
Client → Leader: Write Request
              ↓
         Append to Log
              ↓
    Replicate to Followers
              ↓
      Majority Replicated
              ↓
         Commit Entry
              ↓
     Return Success to Client

代码示例(简化):

type RaftNode struct {
    id       string
    state    NodeState  // Leader/Follower/Candidate
    term     int64      // 任期号
    log      []LogEntry
    commitIndex int
}

type NodeState int

const (
    Follower NodeState = iota
    Candidate
    Leader
)

// 选举超时,发起选举
func (n *RaftNode) StartElection() {
    n.state = Candidate
    n.term++
    votes := 1  // 投自己一票

    // 向其他节点请求投票
    for _, peer := range n.peers {
        go func(p *RaftNode) {
            if p.RequestVote(n.term, n.id) {
                votes++
            }
        }(peer)
    }

    // 获得多数票
    if votes > len(n.peers)/2 {
        n.state = Leader
        n.StartHeartbeat()
    }
}

// Leader定期发送心跳
func (n *RaftNode) StartHeartbeat() {
    ticker := time.NewTicker(100 * time.Millisecond)

    for n.state == Leader {
        <-ticker.C
        for _, peer := range n.peers {
            go peer.AppendEntries(n.term, n.log)
        }
    }
}

Raft vs Paxos:

对比维度PaxosRaft
理解难度难易
实现复杂度高中
性能优良
应用ZooKeeper(ZAB)etcd, Consul

实战案例

案例1:分布式配置中心

需求:多个服务共享配置,配置更新需要通知所有服务

方案:使用etcd(CP系统)

package main

import (
    "context"
    "go.etcd.io/etcd/client/v3"
    "log"
    "time"
)

// 配置中心客户端
type ConfigCenter struct {
    client *clientv3.Client
}

func NewConfigCenter(endpoints []string) (*ConfigCenter, error) {
    client, err := clientv3.New(clientv3.Config{
        Endpoints:   endpoints,
        DialTimeout: 5 * time.Second,
    })

    if err != nil {
        return nil, err
    }

    return &ConfigCenter{client: client}, nil
}

// 读取配置(强一致性)
func (c *ConfigCenter) GetConfig(key string) (string, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    // 从Leader读取,保证强一致性
    resp, err := c.client.Get(ctx, key, clientv3.WithSerializable())
    if err != nil {
        return "", err
    }

    if len(resp.Kvs) == 0 {
        return "", nil
    }

    return string(resp.Kvs[0].Value), nil
}

// 更新配置
func (c *ConfigCenter) SetConfig(key, value string) error {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    // 写入etcd(需要多数节点确认)
    _, err := c.client.Put(ctx, key, value)
    return err
}

// 监听配置变化
func (c *ConfigCenter) WatchConfig(key string, callback func(string)) {
    watchChan := c.client.Watch(context.Background(), key)

    for resp := range watchChan {
        for _, event := range resp.Events {
            newValue := string(event.Kv.Value)
            callback(newValue)  // 通知配置变更
        }
    }
}

// 使用示例
func main() {
    cc, _ := NewConfigCenter([]string{"localhost:2379"})

    // 设置配置
    cc.SetConfig("/app/db/host", "mysql.example.com")

    // 读取配置
    host, _ := cc.GetConfig("/app/db/host")
    log.Println("DB Host:", host)

    // 监听配置变化
    go cc.WatchConfig("/app/db/host", func(newValue string) {
        log.Println("Config changed:", newValue)
        // 重新加载配置
    })
}

特点:

  • 强一致性(所有服务读到相同配置)
  • 实时通知(Watch机制)
  • 可用性略低(网络分区时拒绝服务)

案例2:分布式缓存

需求:跨地域部署的缓存集群,允许最终一致性

方案:使用Redis Cluster(AP系统)

package main

import (
    "github.com/go-redis/redis/v8"
    "context"
    "time"
)

// 分布式缓存
type DistributedCache struct {
    cluster *redis.ClusterClient
}

func NewDistributedCache(addrs []string) *DistributedCache {
    cluster := redis.NewClusterClient(&redis.ClusterOptions{
        Addrs: addrs,

        // AP配置:优先可用性
        ReadOnly:       true,   // 允许从slave读取
        RouteByLatency: true,   // 路由到延迟最低的节点
    })

    return &DistributedCache{cluster: cluster}
}

// 写入缓存
func (c *DistributedCache) Set(key, value string, ttl time.Duration) error {
    ctx := context.Background()

    // 写入master(异步复制到slave)
    return c.cluster.Set(ctx, key, value, ttl).Err()
}

// 读取缓存(可能读到旧值)
func (c *DistributedCache) Get(key string) (string, error) {
    ctx := context.Background()

    // 从最近的节点读取(可能是slave,数据可能延迟)
    value, err := c.cluster.Get(ctx, key).Result()
    if err == redis.Nil {
        return "", nil  // 键不存在
    }

    return value, err
}

// 使用示例
func main() {
    cache := NewDistributedCache([]string{
        "redis-1:6379",
        "redis-2:6379",
        "redis-3:6379",
    })

    // 写入
    cache.Set("user:1001", "Alice", 1*time.Hour)

    // 立即读取(可能从master读)
    value1, _ := cache.Get("user:1001")
    println("Read1:", value1)  // "Alice"

    // 从另一个地域读取(可能从slave读,可能延迟)
    time.Sleep(10 * time.Millisecond)
    value2, _ := cache.Get("user:1001")
    println("Read2:", value2)  // 可能是空(复制延迟)

    // 等待复制完成
    time.Sleep(100 * time.Millisecond)
    value3, _ := cache.Get("user:1001")
    println("Read3:", value3)  // "Alice"(最终一致)
}

特点:

  • 高可用(任一节点都可读写)
  • 高性能(就近访问)
  • 短期数据不一致(复制延迟)
  • 最终一致性(100ms内)

面试问答

CAP定理中,为什么不能同时满足三个特性?

答案:

从数学角度证明:

假设存在一个系统同时满足C、A、P:

1. 网络分区发生(P):
   Node1 和 Node2 失去联系

2. 客户端写入Node1:
   value = 100

3. 为了满足A(可用性):
   Node2必须能响应读请求

4. 为了满足C(一致性):
   Node2必须返回value=100

5. 矛盾:
   Node2没有收到Node1的更新(网络分区)
   但必须返回最新值(一致性)

结论:在P存在时,C和A不可兼得

实际选择:

  • 金融系统:选择CP(宁可不可用,也不能数据错误)
  • 社交系统:选择AP(宁可数据延迟,也要保持服务)

最终一致性的"最终"是多久?

答案:

理论上:没有明确时间保证

实践中:取决于系统设计

系统最终一致时间机制
DNS几分钟到几小时TTL过期
CDN几秒到几分钟缓存刷新
Cassandra几百毫秒Gossip协议
DynamoDB1秒内异步复制
微服务事件几秒消息队列

保证最终一致的方法:

  1. 定时对账:周期性比对数据
  2. 重试机制:失败自动重试
  3. 幂等性:重复操作结果相同
  4. 版本控制:解决冲突

Paxos和Raft有什么区别?应该选择哪个?

答案:

对比维度PaxosRaft
提出时间1989年(Lamport)2013年(Stanford)
理解难度非常难相对容易
实现复杂度高中等
角色Proposer/Acceptor/LearnerLeader/Follower/Candidate
Leader选举无明确Leader强Leader
日志复制可乱序严格有序
工业应用ZooKeeper(ZAB变种)etcd, Consul, TiKV

选择建议:

  • 新项目:选Raft(易实现、易理解)
  • 已有Paxos:继续使用(改造成本高)
  • 教学:学Raft(概念清晰)
  • 研究:学Paxos(理论基础)

如何在AP系统中保证数据不丢失?

答案:

虽然AP系统牺牲强一致性,但不等于数据丢失

保证机制:

  1. 多副本写入
// Cassandra写入(Quorum)
consistency := gocql.Quorum  // W=2(写2个副本)

err := session.Query(
    "INSERT INTO users (id, name) VALUES (?, ?)",
    userID, name,
).Consistency(consistency).Exec()

// 只要多数节点确认,数据不会丢失
  1. Hinted Handoff(提示移交)
写入流程:
1. Node1, Node2正常(写入成功)
2. Node3宕机(写入失败)
3. Node1保存hint(提示)
4. Node3恢复后,Node1重新发送数据

结果:数据不丢失,只是延迟
  1. Read Repair(读修复)
读取流程:
1. 客户端读取3个副本
2. 发现数据不一致:
   Node1: version=5
   Node2: version=5
   Node3: version=3
3. 返回最新数据(version=5)
4. 同时修复Node3(写入version=5)
  1. Anti-Entropy(反熵)
// 定期Merkle Tree比对
func (n *Node) AntiEntropy() {
    for _, peer := range n.peers {
        myTree := n.BuildMerkleTree()
        peerTree := peer.BuildMerkleTree()

        diff := myTree.Diff(peerTree)

        // 同步差异数据
        for _, key := range diff {
            n.SyncKey(peer, key)
        }
    }
}

CAP和ACID有什么关系?

答案:

不同层面的概念:

ACID(事务特性):
- 单机数据库的事务保证
- 关注:正确性、隔离性

CAP(分布式系统):
- 分布式环境的权衡
- 关注:可用性、分区容错

关系:
- ACID的C(一致性)≈ CAP的C(一致性)
- 但ACID是强一致性,CAP允许弱一致性

分布式事务:

目标:在分布式环境实现ACID

方案:
1. 2PC/3PC:强一致性(CP),性能差
2. TCC:补偿机制,最终一致性(AP)
3. Saga:长事务,最终一致性(AP)
4. 本地消息表:最终一致性(AP)

趋势:放弃强一致性,拥抱最终一致性

Prev
分布式架构模式手册
Next
第2章:分布式锁