第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 |
| 库存扣减 | 强一致性 | CP | Redis + Lua |
| 用户注册 | 强一致性 | CP | MySQL主从 |
| 社交点赞 | 最终一致性 | AP | Cassandra |
| DNS解析 | 最终一致性 | AP | DNS系统 |
| 购物车 | 最终一致性 | AP | DynamoDB |
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
保证机制:
- 读写时间戳
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
}
- 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
}
- 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)
定义:弱一致性的特例,保证最终会一致
变种:
- 因果一致性(Causal Consistency)
如果A操作导致B操作,则所有节点看到的顺序是A→B
示例:
User1: 发帖(Post A)
User2: 评论(Comment on A)
保证:所有用户先看到Post A,再看到Comment
- 会话一致性(Session Consistency)
同一个会话内保证一致性
示例:
User1: 修改昵称 → 立即刷新 → 看到新昵称
User2: 访问User1主页 → 可能看到旧昵称(暂时)
User1的会话内保证一致
- 单调读一致性(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(候选者):选举时的临时角色
核心机制:
- Leader选举
初始状态:所有节点都是Follower
选举触发:Follower超时未收到Leader心跳
↓
转为Candidate
↓
发起投票请求
↓
获得多数票 → 成为Leader
未获得多数票 → 重新选举
- 日志复制
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:
| 对比维度 | Paxos | Raft |
|---|---|---|
| 理解难度 | 难 | 易 |
| 实现复杂度 | 高 | 中 |
| 性能 | 优 | 良 |
| 应用 | 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协议 |
| DynamoDB | 1秒内 | 异步复制 |
| 微服务事件 | 几秒 | 消息队列 |
保证最终一致的方法:
- 定时对账:周期性比对数据
- 重试机制:失败自动重试
- 幂等性:重复操作结果相同
- 版本控制:解决冲突
Paxos和Raft有什么区别?应该选择哪个?
答案:
| 对比维度 | Paxos | Raft |
|---|---|---|
| 提出时间 | 1989年(Lamport) | 2013年(Stanford) |
| 理解难度 | 非常难 | 相对容易 |
| 实现复杂度 | 高 | 中等 |
| 角色 | Proposer/Acceptor/Learner | Leader/Follower/Candidate |
| Leader选举 | 无明确Leader | 强Leader |
| 日志复制 | 可乱序 | 严格有序 |
| 工业应用 | ZooKeeper(ZAB变种) | etcd, Consul, TiKV |
选择建议:
- 新项目:选Raft(易实现、易理解)
- 已有Paxos:继续使用(改造成本高)
- 教学:学Raft(概念清晰)
- 研究:学Paxos(理论基础)
如何在AP系统中保证数据不丢失?
答案:
虽然AP系统牺牲强一致性,但不等于数据丢失
保证机制:
- 多副本写入
// Cassandra写入(Quorum)
consistency := gocql.Quorum // W=2(写2个副本)
err := session.Query(
"INSERT INTO users (id, name) VALUES (?, ?)",
userID, name,
).Consistency(consistency).Exec()
// 只要多数节点确认,数据不会丢失
- Hinted Handoff(提示移交)
写入流程:
1. Node1, Node2正常(写入成功)
2. Node3宕机(写入失败)
3. Node1保存hint(提示)
4. Node3恢复后,Node1重新发送数据
结果:数据不丢失,只是延迟
- Read Repair(读修复)
读取流程:
1. 客户端读取3个副本
2. 发现数据不一致:
Node1: version=5
Node2: version=5
Node3: version=3
3. 返回最新数据(version=5)
4. 同时修复Node3(写入version=5)
- 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)
趋势:放弃强一致性,拥抱最终一致性