ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

分布式高性能高可用:核心知识点、面试要点与实战经验全解析

分布式高性能高可用:核心知识点、面试要点与实战经验全解析 做后端开发这么多年有个体会越来越深分布式、高性能、高可用这三个词几乎贯穿了从初级工程师到架构师的全部成长路径。无论你是在准备面试还是在排查线上故障或者是在设计一个新系统绕来绕去都离不开这三件事。这个标题其实是很多人的痛点因为知识太散。有人从分布式锁开始学有人先去背 CAP 理论有人埋头搭集群学完发现面试一问细节就卡壳回到工作中也不知道怎么落地。这篇内容我打算换一个思路不从理论定义开始而是从实际问题出发把分布式、高性能、高可用这三个维度的学习重点、面试要点和落地经验串成一条线。文章会比较长但保证都是我自己用过、踩过、验证过的东西不是概念堆砌。1. 先搞清楚知识边界三个词其实各管一摊很多人把分布式高性能高可用混在一起学结果越学越乱。我建议先把边界画清楚因为它们的侧重点完全不同学习路径和面试考察方式也不一样。1.1 分布式体系到底在解决什么问题分布式不是目的而是手段。核心目标只有一个用多台机器完成单台机器搞不定的事。可能是数据量太大单机存不下可能是并发太高单机扛不住也可能是怕机器宕机需要冗余。但引入分布式之后随之而来的是一堆单机时代不存在的问题。网络延迟、节点故障、数据一致性、时钟不同步、分布式事务、负载均衡、服务发现、配置管理、链路追踪……这些全部属于分布式体系的范畴。所以分布式更像是一个问题域它的学习重点不是某一个技术而是一整套解决这些问题的思路和工具。1.2 高性能和高可用分别是另一条线高性能关注的是单次请求有多快、单位时间能处理多少请求。它涉及并发编程、缓存设计、数据库索引与分库分表、消息队列削峰、网络IO模型、JVM调优如果是Java技术栈等等。高性能的核心是压榨资源和消除瓶颈。高可用关注的是系统全年能稳定运行多长时间遇到故障能多快恢复。它涉及冗余部署、故障转移、熔断降级、限流、备份恢复、容灾演练等。高可用的核心是预期故障和快速恢复。一句话总结分布式是系统架构形态高性能是处理能力的指标高可用是稳定性的指标。它们互相有关联但学习的时候一定要分层理解否则容易什么都看了什么都没抓住。1.3 我推荐的学习顺序基于踩过的弯路我个人比较推荐的学习顺序是这样的先学分布式基础理论CAP、BASE 这些是判断取舍的底层逻辑再学高性能手段因为大多数场景下先得让系统跑得快接着学分布式下的高性能延伸比如缓存一致性、分布式事务最后学高可用体系把系统搞不挂在此基础上再横向铺开具体工具ZooKeeper、Redis、Kafka、Nginx、Spring Cloud 全家桶等等这个顺序的好处是每个阶段解决的问题都很明确不会产生我在学什么的迷茫感。接下来我按这个思路把核心内容挨个展开。2. 分布式核心问题逐个拆解锁、事务、幂等分布式体系里最常被问到的也是工作中最容易出问题的集中在三个点分布式锁、分布式事务、幂等性。这三个问题一旦处理不好数据就乱线上就炸。2.1 分布式锁面试必问实现方案要能背更要懂为什么需要分布式锁很简单单机下synchronized或者ReentrantLock就能解决并发竞争问题但在多实例部署下每个 JVM 进程的锁互不感知必须有一个所有进程都能访问到的公共锁这就是分布式锁。业界方案有三种我把它们的适用场景和坑都列一下实现方案核心原理优点风险点数据库唯一索引插入一条唯一记录的记录作为锁实现最简单不需要额外组件性能差依赖数据库可用性Redis SETNX利用 Redis 单线程原子性写入性能极高实现简单锁超时、GC停顿导致锁失效ZooKeeper 临时顺序节点创建临时节点监听前一个节点可靠性高天然解决死锁性能不如 Redis客户端复杂实际生产中用 Redis 的居多因为性能好。但用 Redis 做分布式锁有几个细节非常关键一定要用SET lock_key unique_value NX PX 30000这种带过期时间的原子操作不能分两步走value 必须是一个唯一标识比如 UUID释放锁的时候只能删自己的锁释放锁要先用 Lua 脚本比对 value 再删除两步分开会存在误删问题这个 Lua 脚本样例可以直接抄if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end注意Redis 锁在极端场景下仍有问题比如 Master 节点宕机后数据未同步到 Slave另一个线程可能拿到同一把锁。Redis 官方后来推出的 Redlock 算法试图解决这个问题但争议很大。访谈里如果被问到可以提一嘴 Redlock 的优缺点会显得你研究得深。ZooKeeper 方案我没少用它天然通过临时顺序节点加 Watch 机制解决了持有锁的进程挂了锁不释放的问题。但代价是性能确实比 Redis 差而且在网络分区情况下也遇到过会话超时导致锁失效的问题。选型逻辑很简单高并发但能容忍极端情况选 Redis强一致优先选 ZooKeeper。2.2 分布式事务两阶段、三阶段、TCC、最终一致分布式事务是分布式系统里最让人头疼的问题。单库时代靠本地事务就能保证 ACID但微服务化之后一次业务操作要跨多个服务、多个数据库本地事务彻底失效。主流的方案有这些我一个个说两阶段提交2PC是经典方案引入协调者先让所有参与者 prepare全部成功再 commit。缺点是阻塞性太强协调者挂了就全卡住。现在用的已经很少了但面试常考。三阶段提交3PC是对 2PC 的改进引入了超时机制和准备阶段但同样没有彻底解决一致性问题工程落地也少。TCCTry-Confirm-Cancel是业务层面的补偿方案需要对每个操作实现 Try、Confirm、Cancel 三个方法。优点是灵活性高不依赖数据库缺点是侵入性强开发成本大。比如一个下单操作Try 阶段冻结库存Confirm 阶段扣减库存Cancel 阶段释放冻结库存。这套逻辑要写得很严谨否则冻结不对账就乱了。本地消息表是一个经典的最终一致方案核心思路是在业务库建一张消息表业务操作和写消息表在同一个本地事务里完成然后通过异步任务把消息发到 MQ消费方处理完后再回调确认。这个方案实现不算复杂但目前生产上更多还是直接用 MQ 事务消息。MQ 事务消息是 RocketMQ 支持的能力half message 先发送本地事务执行成功后再 commit 消息消费端才能真正看到。这套机制本质上也是最终一致性核心观点是不要追求分布式环境下的强一致要接受 BASE 理论保证最终一致。我在生产环境落地过基于 RocketMQ 事务消息的订单与库存一致性方案这是实践经验事务消息的生产者端的三个状态要处理好。RocketMQ 中sendMessageInTransaction返回LocalTransactionState.COMMIT_MESSAGE、ROLLBACK_MESSAGE或UNKNOW如果返回 UNKNOWBroker 会回查生产者接口你必须实现checkLocalTransaction来反查本地事务状态。这个回查机制是事务消息可靠性的核心很多人第一次做都容易忽略。2.3 幂等设计比分布式事务更常用也更容易被忽略幂等性是一个很朴素但极其重要的概念同一个操作执行一次和执行多次结果一致。在分布式系统里网络超时、重试、消息重复消费都会导致同一个请求被执行多次。如果不做幂等就会出现重复下单、重复扣款、重复发放优惠券等严重问题。最常用的幂等方案是唯一键约束比如订单号、业务流水号。在数据库层面建唯一索引重复插入直接报错业务捕获后当作已处理即可。还有一种方案是基于状态机的幂等比如订单状态从待支付到已支付是单向流转重复的状态变更请求直接忽略。结合我的实践我建议把幂等设计放到接口设计的源头而不是出问题后再补救。对外提供的每个写接口都应该要求调用方传入幂等键服务端根据幂等键做去重。这个习惯养成后能减少大量线上问题。3. 高性能体系实战重点缓存、拆分与异步高性能这块面试问得最多的是缓存和拆分。但很多人的理解停留在加个 Redis 就快了分个表就快了这个层面这是不够的。3.1 缓存设计穿透、击穿、雪崩三大坑必须填缓存可以说是高性能的第一利器没有任何一个高并发系统离得开缓存。但用不好反而会引发线上事故。缓存穿透指的是查询一个不存在的数据缓存和数据库都没有请求直接打到数据库。大量这种请求进来数据库压力巨大。解决思路缓存空值加短期过期时间或者用布隆过滤器先过滤一遍。缓存击穿指的是一个热点 key 在缓存过期的瞬间大量并发请求同时打进数据库。解决的经典做法是互斥锁只让一个线程去数据库加载并且重建缓存其他线程等待。另一种做法是逻辑过期缓存不设置物理过期时间而是存一个逻辑过期字段异步去刷新。缓存雪崩是指大量 key 在同一时间集中过期或者 Redis 集群直接宕机导致所有请求全部打到底层数据库。解决思路过期时间加随机值打散、热点数据不过期、Redis 集群高可用以及服务级别兜底比如空数据也返回默认值而不是报错。缓存这块我最想强调的一点是缓存和数据库的一致性是无法做到强一致的只能尽量减少不一致的窗口。业界常用 Cache Aside 模式读的时候先读缓存读不到再读数据库然后回填缓存写的时候先更新数据库再删除缓存。删除缓存而不是更新缓存是因为更新缓存存在并发写覆盖的问题而删除后下次读会自动回填反而更安全。3.2 消息队列削峰填谷是核心价值消息队列在分布式高性能体系里承担的角色说直白点就是缓冲。短时间的高峰流量全部打到后端服务很容易打垮系统但用 MQ 接住后消费端可以按自己的节奏处理。选型方面Kafka 适合大吞吐量日志采集和消息管道RocketMQ 在业务消息、事务消息场景更有优势RabbitMQ 轻量但吞吐相对低一些。使用 MQ 的必修课是消费幂等和顺序消息。消费幂等我上面讲过顺序消息这块要特别注意RocketMQ 可以用 MessageQueueSelector 把同一个业务 key 的消息发送到同一个队列消费端用单线程或者加锁的顺序消费模型处理保证消息消费的有序性。3.3 数据库拆分分库分表的时机与核心思路数据库是绝大多数高并发系统的最终瓶颈。但分库分表不是第一选择很多项目在完全没必要的情况下提前上了分库分表反而把架构搞复杂了。我个人的判断标准是单表数据量过了千万级别或者 QPS 到瓶颈或者磁盘/连接数受限再考虑拆分。分库分表有两个方向水平拆分和垂直拆分。垂直拆分是把不同业务的表拆到不同库比如订单库和用户库分开水平拆分是把同一张表的数据按规则分到多个表里。水平拆分的核心是分片键的选择这个直接决定后续所有查询的复杂度。订单表按用户 ID 分片是最常见的方案但按订单号查询就需要额外维护映射关系或者用冗余字段。所以分片键要结合业务里最核心的查询维度来定不能为了分而分。分库分表带来的一个附带问题是分布式 ID这个后面我会单独讲。3.4 分布式 ID 方案高并发下的基础组件在单机数据库时代自增主键就够用了。但一旦分库分表或者多服务并发生成数据全局唯一 ID 就成了必需品。我整理了几种常见方案直接对比方案实现思路优点缺点UUID本地生成性能极高、无网络IO太长、无序不适合做索引数据库自增单独一张序列表实现简单有单点风险性能一般Redis INCR利用 Redis 原子自增性能好、有序需要考虑 Redis 持久化和集群雪花算法时间戳机器ID序列号有序、高性能、无网络依赖机器时钟时钟回拨会重复雪花算法是我最推荐的一个方案很多开源组件都在用。它的核心结构是 64 位长整型1 bit 符号位 41 bit 毫秒时间戳 10 bit 机器 ID 12 bit 序列号。好处是生成 ID 不依赖网络、趋势递增、性能极高。我在生产环境里把雪花算法的机器 ID 分成了数据中心 ID 和节点 ID配合 ZooKeeper 做节点 ID 的自动注册解决多实例配置问题。注意雪花算法如果机器时钟发生回拨比如 NTP 同步可能会生成重复 ID。行业通用做法是记录上次生成 ID 的时间戳如果发现当前时间小于上次时间直接拒绝生成或者等待时间追上。4. 高可用体系从被动到主动系统稳不稳全看这里高可用是系统上线之后最被关注的指标一般用 SLA 来衡量。高可用不是玄学也不是靠运气是设计出来的。我把高可用相关的核心内容分成两个层面基础设施层面的冗余和故障转移以及应用层面的治理手段。4.1 冗余与故障转移消除单点是第一步任何系统的高可用首先都要回答一个问题我的系统里有没有不可替代的单点比如只有一个数据库实例、只有一个 Redis、只有一个 Nginx那它就是单点。单点一挂整个系统就瘫。消除单点的思路是冗余加故障转移。常见的方案有Nginx 多节点用 Keepalived 做虚拟 IP 漂移主挂了备自动接管数据库做主从复制加 MHA 或者等价的高可用方案主库故障自动切换从库Redis 用哨兵或者集群模式自动完成主从切换应用服务多实例部署前面挂负载均衡实例挂了对调用方无感知我经常给团队讲一句话故障一定会发生只是时间问题。我们要做的不是避免故障而是在故障发生时系统依然能对外提供正确服务。4.2 流量治理三板斧限流、熔断、降级高并发系统经常遇到一个问题平时的流量能扛住但偶尔会来一波尖峰流量比如秒杀、双十一。这时候如果系统硬扛大概率会把所有线程池打满、数据库连接池耗尽最后全部不可用。限流是给系统的入口设置一个阈值超过的部分直接拒绝。常用的限流算法有固定窗口、滑动窗口、漏桶和令牌桶。生产上用的最多的是令牌桶因为它允许一定的突发流量。实现上可以用 Guava RateLimiter、Resilience4j、Sentinel 等组件。熔断的思想来自电路熔断当一个下游服务的错误率达到阈值调用方直接快速失败不再继续请求下游避免拖垮自己。熔断通常有三个状态关闭、打开、半开。关闭状态正常调用错误率超标就打开打开状态下所有请求快速失败过一段时间进入半开状态放少量请求探测如果成功就恢复关闭。降级是指系统资源不足时主动关闭一些非核心功能保证核心功能可用。比如电商大促时可以把评论列表、历史订单这些非关键功能降级优先保障下单和支付流程。这三大手段是稳定性的底线。业内有个很形象的比喻限流是拒绝多余的水熔断是切断损坏的管道降级是关掉非紧要的水龙头最终想保住的是核心水源不被污染。4.3 监控告警与故障演练高可用体系的工业标准只顾着设计高可用架构不做监控等于闭眼开车。监控体系要能覆盖基础层CPU、内存、磁盘、网络、应用层接口QPS、RT、错误率、业务层订单量、支付成功率、用户登录数。在监控体系建设上我常用的组合是 Prometheus 加 Grafana配合 SkyWalking 做链路追踪。Prometheus 负责指标采集和告警规则引擎Grafana 负责可视化SkyWalking 解决分布式请求的调用链追踪问题。但比建设监控更重要的其实是告警有效性。很多团队监控面板做得很好看但告警噪音巨大人人麻木最终真正的故障反而没被发现。我的原则是告警要分级P0 级告警必须 7x24 通知到人P1/P2 级汇总到日报。宁可漏报也不要给一线同事制造噪音否则狼来了喊多了真正出事反而没人响应。故障演练是检验高可用体系的试金石。我喜欢用混沌工程的方式来做在预发环境定期杀一个 Pod、停一个数据库从库、把某个服务的超时时间调小然后观察系统表现。只有真正演练过你才知道你的故障切换脚本是否有 bug、应急预案是否可行。4.4 分布式任务调度与分布式监控选型经验高可用体系里还有两个经常被提到的组件一个是任务调度一个是监控。分布式任务调度解决的是多实例下定时任务重复执行的问题。我最早做项目时用 Quartz但 Quartz 是单机任务调度框架扩展性和高可用都不够。后来项目里引入了 XXL-JOB它通过调度中心加执行器的架构解决了任务分片、故障转移、日志可追溯的问题。部署上也比较简单调度中心本身支持集群部署执行器支持分组管理。分布式监控这块我在生产环境实际部署过 CAT大众点评开源的监控系统并把它容器化部署到 K8s 环境。CAT 的部署重点在于服务端三个角色cat-home、cat-consumer、cat-hadoop的职责划分和数据上报链路。在容器环境里最大的坑是网络和存储客户端的上报端口要能够路由到服务端消息队列和报表存储要有可靠的持久卷支撑。如果你也在做类似的事情我提醒一句先把 CAT 的本地模式和数据模型搞清楚再上生产否则排障会非常痛苦。5. 面试考察点汇总与回答方法面试准备是很多人看这篇文章的直接目的所以我单独拿出一个章节来整理。分布式、高性能、高可用三个方向的面试题非常多但高频的就是那么二三十道把握住核心回答框架就能稳定输出。5.1 高频面试题分维度盘点分布式基础类CAP 理论怎么理解为什么三选二BASE 理论是什么什么是 RPC怎么设计一个 RPC 框架分布式一致性协议了解哪些Raft 的原理是什么分布式锁与事务类分布式锁有哪些实现方案Redis 分布式锁的坑有哪些分布式事务有哪些解决方案怎样选择TCC 和 2PC 的区别是什么如何保证消息队列的幂等消费分布式缓存类缓存穿透、击穿、雪崩是什么怎么解决缓存和数据库一致性怎么保证Redis 集群的架构和选举机制是怎样的如何设计一个缓存键才能避免热点问题分库分表与分布式 ID 类分库分表方案怎么选分片键怎么定分布式 ID 有哪些方案雪花算法的原理和问题是什么高可用类如何设计一个高可用系统单点故障怎么消除限流算法有哪些它们的区别和应用场景熔断、降级、限流的区别是什么怎么做容量评估和性能压测线上突然 CPU 飙高和内存溢出怎么排查如果让你设计一个高可用架构你会怎么整体考虑5.2 面试回答方法论用总分总结构讲案例面试时最大的坑不是不知道而是知道但讲不出来。我总结了一套回答技术问题的框架第一步用自己的话一句话定义问题是什么、为什么会出现。第二步说出业界主流解决方案有哪些每个方案的优点和缺点是什么。这里不要只罗列名词要展开说适用场景。第三步根据自己的项目经历挑一个实际用过的方案详细展开。说说当时业务背景是什么技术选项过程中对比了哪些方案最后怎么决定的落地时踩过什么坑。第四步如果有精力补充一层这个方案还有哪些可以改进的空间或者如果数据量再大一倍你会怎么做。这套框架的好处是既有知识广度又有实践深度面试官最容易给高分。5.3 项目复盘比八股更重要我面过不少候选人八股背得很溜CAP、2PC、TCC 张口就来。但一问你项目里为什么选用 Redis 锁而不是 ZooKeeper就愣了。这说明他只有知识没有判断力。我建议每个人把自己的核心项目做一个整体梳理系统整体架构长什么样哪些环节用了分布式、哪些环节考虑了高可用系统的瓶颈在哪里如果流量再翻十倍你会怎么做。把这些东西想清楚比背一百道面试题都有用。面试官每年见太多背诵工见到一个有真实项目思考和沉淀的人印象分是完全不同的。6. 学习路线规划与实操建议最后这部分给不同阶段的人一些学习上的建议。这些是基于我带团队和培养新人的过程中积累下来的经验。6.1 按阶段规划学习路径初阶0-2年经验重点在会用。先把 Redis、ZooKeeper、Kafka、Spring Cloud 这些工具的基本使用搞明白理解典型的应用场景。这个阶段不需要追求原理精通但要动手最好自己照着教程搭一套完整的微服务架构出来。中阶2-5年经验重点在原理和调优。需要深入源码层面理解Redis 为什么快、Kafka 为什么吞吐高、Nacos 注册中心是怎么实现服务发现的、RocketMQ 事务消息的底层逻辑是什么。同时要在性能调优上积累实战经验会看监控指标会做压测会做 JVM 调优。高阶5年以上重点在架构设计。要有能力从零设计一个高可用、高性能的分布式系统能够根据业务特点做技术选型和技术取舍能够对现有系统的瓶颈做出准确判断并且能基于成本、硬件的约束给出合理的技术方案。6.2 实践路径建议纸上得来终觉浅。我非常建议通过动手来加深理解可以从这几个项目开始第一步搭一套 Hadoop 伪分布式集群或者 Hadoop3 分布式集群体会一下大数据集群的部署和配置逻辑。这套过程能够帮你理解节点协商、主从切换、状态同步这些概念。第二步把一个单体应用拆成微服务引入注册中心、配置中心、网关用 Docker 或者 K8s 部署。做一次服务间的远程调用并加上链路追踪。第三步给这个微服务系统加上缓存、消息队列、分布式锁制造一个并发扣减库存的场景验证数据一致性。第四步给系统加上限流熔断降级组件用压测工具模拟高峰流量观察系统行为和监控指标演练故障场景。6.3 最后再分享一个技术选型的小技巧技术选型的时候我建议只看当前业务最痛的点和团队最容易长期维护的方案流行的不一定适合你。有些团队在业务量极低的情况下就上了全套微服务加 K8s结果运维成本比业务量还大。技术的价值不在用了多新的东西而在于解决了多实际的问题。我自己的判断框架是优先选社区活跃、资料多、团队有人懂的技术优先选能渐进式落地、不要推倒重来的技术优先选在行业里有大量成功案例的技术。这个框架帮我避过很多坑。
返回列表