☰
AI全栈实战 | 1.7-04 架构方向:分布式系统的 5 个核心问题,从程序员走向架构师
2026/9/28 21:14:23 网站建设 项目流程

上篇回顾:1.7-03 建立了「观测→定位→验证」的调优闭环,强调没有度量的优化是玄学。本篇是 Java 高阶最后一篇——从「写功能、调性能」的程序员视角,升级到「控制系统复杂度」的架构师视角。

一、开篇:程序员与架构师的区别

程序员思考:「这个功能怎么实现?」
架构师思考:「这个系统在 10 倍流量下会怎么崩?崩了怎么兜底?数据会不会错?」

架构师不是更高级的程序员,而是关注点不同——程序员关注「能不能跑」,架构师关注「会不会崩、崩了怎样、数据对不对」。

分布式系统的复杂度集中在 5 个核心问题:分布式 ID、分布式锁、幂等设计、限流降级熔断、流量演进路径。本篇一一道来。

二、分布式 ID:全局唯一怎么保证

2.1 为什么需要分布式 ID

单机用自增主键就够了。分库分表后,两个库可能同时生成相同的自增 ID——冲突。需要全局唯一 ID。

2.2 主流方案对比

方案原理优点缺点
UUID随机生成 128 位无中心、简单无序、占空间、不可读
数据库自增段批量取号简单、有序中心化瓶颈
Redis INCR原子自增高性能依赖 Redis、持久化风险
雪花算法时间+机器+序列无中心、有序、高性能时钟回拨问题
Leaf美团方案号段+雪花双模式运维复杂

2.3 雪花算法详解

0 | 41bit 时间戳 | 10bit 机器ID | 12bit 序列号
  • 41bit 时间戳:41 位能用 69 年
  • 10bit 机器 ID:1024 台机器
  • 12bit 序列号:每毫秒 4096 个 ID
publicclassSnowflake{privatefinallongepoch=1704067200000L;// 2024-01-01privatefinallongmachineId;privatelongsequence=0;privatelonglastTimestamp=-1;publicsynchronizedlongnextId(){longnow=System.currentTimeMillis();if(now<lastTimestamp){thrownewRuntimeException("时钟回拨");}if(now==lastTimestamp){sequence=(sequence+1)&0xFFF;// 12bitif(sequence==0){now=waitNextMillis(now);}}else{sequence=0;}lastTimestamp=now;return((now-epoch)<<22)|(machineId<<12)|sequence;}}

2.4 时钟回拨问题

机器时钟回拨会导致生成重复 ID。解决方案:

  • 回拨小(<5ms):等待回拨恢复
  • 回拨大:报警 + 拒绝生成
  • 终极方案:用 NTP 同步 + 报警兜底

三、分布式锁:Redis vs Zookeeper

3.1 需求场景

  • 库存扣减:防止超卖
  • 定时任务:防止多实例重复执行
  • 缓存重建:防止击穿时多线程同时查库

3.2 Redis 分布式锁

// 加锁(SET NX EX)StringlockKey="lock:order:"+orderId;StringrequestId=UUID.randomUUID().toString();Booleanlocked=redis.opsForValue().setIfPresent(lockKey,requestId,30,TimeUnit.SECONDS);if(Boolean.TRUE.equals(locked)){try{// 业务逻辑}finally{// 释放锁(Lua 脚本保证原子性)Stringscript="if redis.call('get', KEYS[1]) == ARGV[1] then "+" return redis.call('del', KEYS[1]) "+"else return 0 end";redis.execute(newDefaultRedisScript<>(script,Long.class),Collections.singletonList(lockKey),requestId);}}

关键点:

  1. requestId 用 UUID——释放锁时验证是不是自己的锁,防止误删别人的锁
  2. 释放用 Lua 脚本——保证「判断+删除」原子性
  3. TTL 30s——防止持锁线程崩溃导致锁永不释放

3.3 Redis 锁的两大问题

问题一:锁过期 + 业务未完成

业务执行超过 30s,锁自动释放,其他线程拿到锁,两个线程同时执行。

解决:Redisson 看门狗(WatchDog)——每 10s 续期一次。

问题二:主从切换丢锁

主节点加锁成功,还没同步到从节点就宕机,从节点升主后锁丢失。

解决:Redlock 红锁——向 N 个独立 Redis 节点加锁,多数成功才算成功。但 Redlock 有争议(Martin Kleppmann 批评),实践中多数场景用 Redisson 单节点锁 + 看门狗够了。

3.4 Zookeeper 分布式锁

1. 创建临时顺序节点 /lock/node-0001 2. 判断自己是否最小节点 → 是则获取锁 3. 不是则监听前一个节点的删除事件 4. 前一个节点删除 → 自己成为最小 → 获取锁 5. 业务完成 → 删除自己的节点

vs Redis 锁:

维度RedisZookeeper
性能高(内存)中(磁盘+网络)
可靠性主从切换可能丢锁临时节点+强一致
复杂度低中
适用高并发、容忍偶发问题强一致要求高

推荐:99% 场景用 Redisson,强一致要求(如金融)用 Zookeeper。

四、幂等设计:分布式场景的第一原则

4.1 为什么幂等是第一原则

网络不可靠 → 重试 → 同一请求可能执行多次。如果接口不幂等,重复执行会导致数据错误(重复扣款、重复下单)。

4.2 幂等的三层保障

层级手段实现
接口层唯一请求 ID + 去重表前端生成 requestId,后端查去重表
业务层状态机订单状态只能单向流转
数据层唯一索引UNIQUE KEY (biz_id)防重复插入

4.3 通用幂等模板

@TransactionalpublicResult<?>process(BizRequestreq){StringbizId=req.getRequestId();// 1. 查去重表if(idempotentMapper.exists(bizId)){returnResult.ok(idempotentMapper.getResult(bizId));// 返回上次结果}// 2. 分布式锁防并发StringlockKey="idempotent:"+bizId;try{if(!redisLock.tryLock(lockKey,10)){Thread.sleep(50);returnprocess(req);// 重试}// 3. 双重检查if(idempotentMapper.exists(bizId)){returnResult.ok(idempotentMapper.getResult(bizId));}// 4. 执行业务Result<?>result=doBusiness(req);// 5. 记录去重表(同一事务)idempotentMapper.insert(bizId,JsonUtil.toJson(result));returnresult;}finally{redisLock.unlock(lockKey);}}

五、限流降级熔断三板斧

5.1 限流:控制入口流量

算法原理特点
计数器固定窗口内计数简单、有边界突刺
滑动窗口细分窗口平滑计数比计数器平滑
漏桶固定速率出水平滑、无法应对突发
令牌桶固定速率发令牌可应对突发、主流
// Guava 令牌桶RateLimiterlimiter=RateLimiter.create(100);// 每秒 100 个publicResult<?>api(){if(!limiter.tryAcquire(1,500,TimeUnit.MILLISECONDS)){returnResult.fail(429,"限流");}// 业务}

5.2 降级:牺牲非核心保核心

@SentinelResource(value="getRecommendations",fallback="getRecommendationsFallback")publicList<Product>getRecommendations(LonguserId){returnrecommendService.getFromRemote(userId);// 远程调用}publicList<Product>getRecommendationsFallback(LonguserId){returnCollections.singletonList(Product.defaultProduct());// 降级返回默认}

5.3 熔断:保护整体不雪崩

参见 1.5-02 的 Sentinel 部分。核心思想:下游不可用时快速失败,不要让请求堆积拖垮自己。

六、高并发系统的流量演进路径

面对流量洪峰,架构演进的通用路径:

1. 加缓存 → 拦截 80% 读请求 2. 异步化 → 写请求进队列削峰 3. 分库分表 → 分散 DB 压力 4. 读写分离 → 读走从库 5. 微服务拆分 → 分散应用压力 6. 弹性扩缩 → 按需扩容

注意:不要一步到位。先用缓存扛住 80%,扛不住再异步化,再扛不住才分库分表。过早分库分表是过度设计。

七、架构师的职责边界

该做不该做
定义技术规范与边界代替开发写代码
评估技术选型风险替开发做所有技术决策
设计容灾与降级方案只设计不验证
控制系统复杂度引入不必要的新技术
培养团队技术能力独占所有知识

架构师的价值不在「画高大上的图」,而在让系统简单到普通人也能维护。

八、技术深度与架构广度的关系

  • 深度:某领域挖到底(如 JVM 调优、MySQL 索引原理)
  • 广度:跨领域知识面(前端、后端、运维、DBA)

两者不矛盾:深度是广度的地基,广度是深度的延伸。没有深度的广度是浮于表面,没有广度的深度是孤岛。

推荐路径:先在 1~2 个领域挖深(如 Java + MySQL),再横向扩展(Redis、MQ、Docker、前端),最后形成 T 型能力。

九、小结表

问题核心方案关键点
分布式 ID雪花算法时钟回拨
分布式锁Redisson + 看门狗锁过期 + 主从切换
幂等唯一ID + 去重表 + 状态机三层保障
限流令牌桶Guava / Sentinel
降级熔断Sentinel快速失败防雪崩

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询