一、分布式系统基础:CAP理论、一致性模型、Raft协议简介、分布式锁、服务发现

说实话,分布式系统这个话题,我当年刚接触时也觉得头大。你想想看,一台机器干活好好的,为什么要搞那么多台?但等你真正面对高并发、海量数据时,就会发现——单机再强也有天花板。今天这一章,咱们就把分布式系统里最核心的几个概念掰开揉碎讲清楚。

1.1 CAP理论:分布式系统的“不可能三角”

CAP理论是分布式系统的基石。它告诉我们:一个分布式系统,最多只能同时满足以下三个特性中的两个:

  • C(Consistency)一致性:所有节点在同一时刻看到的数据是一样的。
  • A(Availability)可用性:每个请求都能得到一个非错误的响应。
  • P(Partition Tolerance)分区容错性:系统允许网络分区(节点间通信中断)的存在。

为什么说这是“不可能三角”?我举个例子你就明白了。假设你有两个节点A和B,网络突然断了。这时候:

  • 如果你选择CP(一致+分区容忍),那么A和B必须保持数据一致,但网络断了没法同步,所以只能拒绝写请求——可用性就没了。
  • 如果你选择AP(可用+分区容忍),那么A和B各自接受写请求,数据就不一致了。
  • 如果你选择CA(一致+可用),那网络分区时系统直接崩溃——这在分布式系统中几乎不可接受。

核心结论:在分布式系统中,P(分区容错)是必须选的。你真正要权衡的是C和A。比如ZooKeeper偏CP,Eureka偏AP。

我在项目中遇到过一个问题:早期我们用了强一致性的方案,结果网络抖动时整个服务不可用,用户疯狂投诉。后来改成最终一致性+补偿机制,虽然数据短暂不一致,但系统可用性大幅提升。嗯,这里要注意——没有银弹,只有取舍。

1.2 一致性模型:从强到弱

一致性模型说白了就是“数据同步的松紧程度”。我习惯把它们分成三类:

模型 特点 典型场景
强一致性 写完后,所有读都能看到最新数据 银行转账、库存扣减
最终一致性 数据会在一段时间后达成一致 DNS解析、社交动态
弱一致性 不保证后续读能看到写的结果 实时日志、监控指标

你想想看,强一致性听起来很完美,但代价是什么?性能下降、可用性降低。我做过一个秒杀系统,最开始用强一致性,结果QPS上不去。后来改成最终一致性+异步对账,性能直接翻了三倍。

我的建议:能用最终一致性解决的问题,就别上强一致性。系统越简单,越不容易出幺蛾子。

1.3 Raft协议简介:分布式共识的“白话版”

Raft协议是分布式共识算法里最容易理解的一个。它把复杂的共识问题拆成了三个子问题:

  1. Leader选举:选出一个老大来协调写操作。
  2. 日志复制:Leader把写操作广播给所有Follower。
  3. 安全性:保证只有拥有最新日志的节点才能成为Leader。

Raft的核心思想其实很简单——多数派同意。只要超过一半的节点确认了写操作,这个操作就算成功了。

我曾经在etcd的源码里看到Raft的实现,说实话,代码写得非常优雅。它把状态机、日志、快照都封装得清清楚楚。如果你要自己实现一个分布式系统,我强烈建议直接基于Raft,别自己造轮子。

注意:Raft假设网络是异步的,但节点故障是独立的。如果你的网络经常出现分区,Raft可能会频繁触发Leader选举,导致系统不可用。

1.4 分布式锁:别让资源打架

分布式锁解决的是“多个节点抢同一个资源”的问题。常见的实现方式有三种:

  • 基于数据库:用唯一索引或行锁实现。简单但性能差。
  • 基于Redis:用SETNX + 过期时间。性能好,但要注意锁超时和RedLock。
  • 基于ZooKeeper/etcd:用临时顺序节点。可靠性高,但性能略低。

我个人的习惯是:如果对性能要求极高,用Redis;如果对可靠性要求极高,用etcd。曾经有一次,我用Redis做分布式锁,结果业务处理时间超过了锁的过期时间,锁自动释放了,其他节点抢到了锁,导致数据重复处理。后来我加了一个“锁续期”的机制,才彻底解决这个问题。

// 一个简单的Redis分布式锁示例(伪代码)
bool tryLock(string key, string value, int expireMs) {
    // SET NX 保证原子性
    return redis.set(key, value, "NX", "PX", expireMs);
}

void unlock(string key, string value) {
    // Lua脚本保证原子性:只有持有锁的才能释放
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    redis.eval(script, 1, key, value);
}

避坑指南:分布式锁一定要设置过期时间,防止死锁。但过期时间不能太短,否则业务没处理完锁就没了。我一般设置为业务预估时间的3倍。

1.5 服务发现:让服务找到彼此

服务发现解决的是“服务A怎么知道服务B的IP和端口”的问题。常见的方案有:

  • 客户端发现:服务启动时注册到注册中心,客户端从注册中心拉取列表。比如Eureka、Consul。
  • 服务端发现:客户端请求负载均衡器,负载均衡器从注册中心获取后端服务列表。比如Kubernetes的Service。

我个人更推荐客户端发现模式,因为它少了一层代理,延迟更低。但缺点是需要每个客户端都集成服务发现逻辑。

etcd和Consul是目前最流行的两个服务发现工具:

特性 etcd Consul
一致性协议 Raft Raft
健康检查 需要自己实现 内置
KV存储 核心功能 支持
DNS接口 不支持 支持
多数据中心 有限支持 原生支持

我在项目中用过etcd做服务发现,配合gRPC的负载均衡,效果非常好。Consul我也用过,它的健康检查和DNS接口确实方便,但部署起来比etcd重一些。

总结一下:如果你追求轻量、高性能,选etcd;如果你需要丰富的服务治理功能,选Consul。

知识体系总览

下面这张图把本章的核心知识点串了起来,方便你理解它们之间的关系:

分布式系统核心知识体系 分布式系统 CAP理论(C/A/P三角) 一致性模型 Raft协议(共识算法) 分布式锁(Redis/etcd) 服务发现(etcd/Consul) etcd(Raft + KV) Consul(健康检查 + DNS) 应用场景 配置中心 分布式协调 服务注册与发现

这张图把CAP理论、一致性模型、Raft协议、分布式锁和服务发现串在了一起。你会发现,它们其实都围绕着同一个核心问题——如何在不可靠的网络环境下,构建可靠的分布式系统


公众号:蓝海资料掘金营,微信deep3321