2026/9/6 4:57:15

聊聊三高架构:高并发、高可用、高性能到底是怎么回事

聊聊三高架构:高并发、高可用、高性能到底是怎么回事 做了十来年 Java 后端带过几个从零到上线的系统也踩过不少上线那天服务器就跪了的坑。回头看很多架构问题最后都能归到同一个话题——三高高并发、高可用、高性能。这三个词几乎是后端面试的必考题但真正带过项目的人都知道它们不是三个孤立的知识点而是同一件事情的三个侧面。这篇文章尽量用大白话把这几件事捋清楚不堆术语能落地的地方尽量给思路和代码。一、什么是三高架构先说结论三高不是三个并列的目标而是三个互相牵制的约束条件。高并发系统能不能扛住同时涌进来的大量请求比如秒杀、大促、突发流量。高可用系统能不能在部分组件挂掉的情况下整体依然对外提供服务不至于一挂全挂。高性能单次请求的响应速度够不够快资源利用率够不够高。这三者经常是相互拉扯的关系。举个例子为了高可用你可能要做多副本、跨机房部署这就带来了数据同步的开销反过来又会拖慢性能为了扛住高并发你可能会加缓存、加异步队列但缓存又带来了数据一致性的风险一致性做得越强性能往往就越差。所以做三高架构本质上不是把三个指标都做到极致而是根据业务场景找一个合理的平衡点。支付类系统对一致性要求极高可以在性能上做一定让步而一个内容推荐系统短暂的数据不一致完全可以接受换来的是更好的并发能力。先想清楚业务对哪个维度更敏感再决定往哪边倾斜这一步比具体用什么技术方案更重要。二、如何设计高性能系统高性能说白了就是两件事把没必要的等待去掉把能复用的资源攒起来。具体拆开看有这么几个方向。缓存分层。数据库是整个系统里最容易成为瓶颈的一环能不查库就不查库。本地缓存Caffeine/Guava Cache解决高频小数据的读取分布式缓存Redis解决跨节点共享的问题两层配合使用命中率能提升一大截。但缓存不是万能药缓存穿透、击穿、雪崩这几个经典问题绕不开后面数据库那节会细说。异步化。凡是不需要用户同步等待结果的操作都应该丢到异步里去做。比如下单成功后发短信通知、写日志、更新统计报表这些完全可以通过消息队列解耦主流程只需要保证核心链路扣库存、创建订单同步完成其他都可以事后处理。这样做的直接效果是接口响应时间大幅下降因为你把原本串行的操作并行化或者延后化了。连接池化。数据库连接、HTTP 连接、线程这些创建成本都不低频繁创建销毁是隐性的性能杀手。用好连接池HikariCP、Druid、线程池把创建这个动作的成本摊薄到整个生命周期里去。这里有个容易被忽略的点线程池的参数不是抄来的模板越大越好核心线程数、队列长度、拒绝策略要结合业务的 QPS 和响应时间实测调整盲目调大线程数反而可能因为上下文切换开销让性能变差。减少锁竞争。能用无锁结构就不用锁比如用AtomicLong代替synchronized计数器用ConcurrentHashMap代替加锁的HashMap。实在需要加锁的地方尽量缩小锁的粒度比如分段锁的思路把一把大锁拆成多把小锁减少线程互相等待的时间。JVM 层面的调优也不能漏。GC 停顿是很多系统偶尔卡一下的元凶选对垃圾回收器G1 或者 ZGC看你的堆大小和延迟敏感度、合理设置堆内存大小、避免频繁的 Full GC这些都属于高性能系统的基本功。日常开发中养成看 GC 日志的习惯比出问题之后再排查要划算得多。三、高并发下如何解决数据库性能问题数据库几乎永远是高并发场景下第一个被打垮的环节因为它是有状态的没法像应用层那样简单地加机器就完事。先从慢 SQL 和索引下手。这是投入产出比最高的一步很多时候系统扛不住压力根本原因不是架构问题就是一条没走索引的 SQL 在拖后腿。用EXPLAIN看执行计划把全表扫描的查询揪出来该加索引加索引该拆分复杂查询就拆分。这一步做扎实了很多时候能省掉后面一大堆复杂的架构改造。读写分离。大部分业务场景都是读多写少把读请求分流到只读副本上写请求留在主库主库压力能明显降下来。需要注意主从同步延迟的问题如果业务对实时性要求高比如刚下单立刻查订单详情要么强制读主库要么在业务层做好容错。分库分表。当单表数据量到达千万级、单库撑不住写入压力的时候就要考虑水平拆分了。按什么字段分片用户 ID、订单 ID 之类要提前想清楚分片键选不好后面跨库查询、跨库事务会非常麻烦。这一步改造成本很高建议不要一上来就搞先把前面几步的优化空间榨干实在扛不住了再动这个手术。缓存的三个经典问题缓存穿透查询一个数据库里根本不存在的数据缓存和数据库都没有请求直接打穿到数据库。解决办法是缓存空值或者用布隆过滤器提前拦截明显不存在的请求。缓存击穿某个热点 key 突然过期大量请求同时打到数据库上。解决办法是热点数据不设过期时间或者用互斥锁保证只有一个请求去查库重建缓存。缓存雪崩大量 key 在同一时间集中过期或者缓存服务整体宕机。解决办法是给过期时间加上随机值避免同时失效同时缓存服务本身要做高可用集群。连接池调优也别忘了数据库连接是有限资源连接池配置不合理比如设置得过大导致数据库本身压力过载或者过小导致请求排队同样会成为并发瓶颈。四、如何实现系统的高可用性高可用的核心思路是假设任何一个组件都会挂系统整体依然要能扛住。去状态化 集群部署是基础。应用尽量做成无状态会话信息放到 Redis 之类的外部存储里这样任何一个节点挂了请求转到其他节点照样能处理配合负载均衡做健康检查自动把有问题的节点摘掉。限流、熔断、降级是三件套专门应对流量洪峰和依赖故障。限流是保护自己防止流量把系统打垮常见算法有令牌桶、漏桶熔断是保护调用链路当某个下游服务持续报错或者超时就暂时切断对它的调用避免故障扩散、拖垮整条链路类似电路的保险丝降级是保底方案核心功能优先保证可用非核心功能在系统压力大的时候可以暂时关闭或者返回简化结果。Sentinel、Hystrix 这类框架把这几件事都封装好了接入成本不高。多机房 / 异地多活是更高级别的容灾手段用于应对整个机房级别的故障断电、光缆被挖断这种极端情况。这个改造成本很大涉及跨机房的数据同步和流量调度一般是业务规模到了一定量级、单机房故障的代价已经无法接受时才会投入做。优雅上下线容易被忽视但很实际。发布新版本的时候如果直接把老实例杀掉正在处理的请求会被中断。正确做法是先让负载均衡把流量切走等实例上正在处理的请求都跑完了再关闭进程这就是常说的优雅停机。监控和告警是高可用的眼睛。没有监控故障发生了你都不知道更别提及时处理。链路追踪SkyWalking、Pinpoint能帮你快速定位问题出在哪个环节配合合理的告警阈值做到故障发生的第一时间有人响应,而不是等用户投诉了才知道出问题了。有条件的团队还会做混沌工程主动在生产或者预发环境模拟节点宕机、网络延迟这些故障场景验证系统的容错能力是不是真的靠谱而不是设计上应该没问题这种自我安慰。五、如何进行系统性能优化性能优化最容易踩的坑是凭感觉优化比如听说某个写法性能更好就直接改代码改完发现根本没用还引入了新的 bug。靠谱的做法是有一套方法论第一步压测找到真正的瓶颈在哪。用 JMeter、wrk 之类的工具模拟真实流量观察系统在什么并发量下开始出现响应时间飙升或者错误率上升别猜用数据说话。第二步自上而下排查。从接入层网关、负载均衡到应用层业务代码、线程池再到数据层数据库、缓存、消息队列最后是基础设施网络、磁盘 IO。大部分性能问题最后会定位到数据层但也不能一上来就默认是数据库的锅用 Arthas、JProfiler 这些工具对 JVM 里的方法耗时做火焰图分析能比较直观地看出时间到底花在哪一行代码上。第三步优化之后再压测验证确认改动是不是真的有效果避免优化了寂寞。还有一个心态上的建议不要过早优化。在业务量还没起来、瓶颈还没出现的时候就为了以后可能会有高并发去做复杂的架构设计往往会增加系统的复杂度和维护成本却没有实际收益。性能优化应该是按需驱动先把系统做对、做稳再根据实际的压测数据和线上表现有针对性地优化。六、常用的负载均衡算法负载均衡是把请求合理分发到多个后端节点的机制常见算法各有适用场景轮询Round Robin请求按顺序依次分配给每个节点实现简单适合各节点性能相近的场景。加权轮询Weighted Round Robin给性能强的节点分配更高的权重处理更多请求适合节点配置不一致的集群。随机 / 加权随机按概率随机选择节点效果和轮询类似在节点数量较多时分布会比较均匀。最小连接数Least Connections把请求分配给当前连接数最少的节点比轮询更能反映节点的实时负载情况适合请求处理时长差异较大的场景。一致性哈希Consistent Hashing根据请求的某个特征比如用户 ID计算哈希值映射到一个哈希环上固定的节点。它的好处是当某个节点增加或者减少时只有少部分请求的路由会发生变化非常适合有状态服务或者需要缓存亲和性的场景同一个用户的请求尽量落到同一台机器命中本地缓存。IP Hash按客户端 IP 计算哈希保证同一个客户端的请求始终落到同一个节点常用于需要会话保持的场景。实际项目中这几种算法往往分层使用比如 Nginx 层用加权轮询做初步分发微服务内部用 Ribbon/LoadBalancer 结合最小连接数或者一致性哈希做更细粒度的调度。选哪种算法归根结底还是看业务对负载均匀和路由稳定性哪个更看重。七、高并发下如何保证数据的一致性这是三高里最容易让人头疼的一块因为一致性和性能天然是矛盾的。先明确一个前提不是所有场景都需要强一致性大部分互联网业务用最终一致性就够了用户能接受稍微延迟一会儿数据才同步过来换来的是好得多的并发能力和可用性。真正需要强一致性的往往是涉及资金、库存这类不能出错的核心场景。分布式事务是保证一致性的核心手段几种主流方案各有取舍2PC / XA数据库层面原生支持的两阶段提交一致性强但所有参与方在提交前都要持有锁性能差、可用性也不好协调者挂了会导致资源一直被锁着现在生产环境用得已经不多。TCCTry-Confirm-Cancel把一个操作拆成预留资源Try、确认Confirm、取消Cancel三个阶段业务代码需要自己实现这三个接口改造成本高但性能和灵活性比 2PC 好很多适合对一致性要求高、又不想牺牲太多性能的核心链路比如支付扣款这类场景。Saga把一个长事务拆成一系列本地事务每一步都有对应的补偿操作出错时依次反向执行补偿。适合流程长、步骤多的业务比如下单-扣库存-发货这种链路但补偿逻辑设计起来比较考验业务建模能力。本地消息表 / 事务消息在本地事务里把要发送的消息先记录到一张消息表再通过定时任务或者 MQ 的事务消息机制保证消息一定会被投递出去下游消费者消费消息完成后续操作。这个方案实现相对简单是很多团队处理最终一致性问题时的首选。幂等设计是配合分布式事务必须要做的事情。网络重试、消息重复投递几乎是分布式系统的常态如果下游接口不是幂等的一次重复调用就可能导致重复扣款、库存扣两次这种事故。常见做法是给每个请求生成唯一的业务流水号处理前先检查这个流水号是否已经处理过配合数据库唯一索引兜底。分布式锁用来解决跨节点的资源竞争问题比如秒杀场景下防止超卖。Redis 的SETNX加上合理的过期时间是最常见的实现方式如果对可靠性要求更高可以用 Redisson 封装好的可重入锁或者用 ZooKeeper 的临时节点实现ZooKeeper 的方案在一致性上更有保障但性能不如 Redis。举个具体例子帮助理解一次转账操作涉及扣款方扣钱和收款方加钱两个动作分别落在不同的服务甚至不同的数据库里。用 TCC 的思路来做Try 阶段先冻结扣款方的余额、预占收款方的入账记录业务方确认双方状态都正常后进入 Confirm 阶段正式扣款加钱如果中途任何一步失败则执行 Cancel 阶段把冻结的余额解冻、撤销预占的记录。整个过程配合幂等校验和事务日志才能在保证性能的前提下,把资金类操作的一致性做扎实。写在最后三高架构没有放之四海皆准的标准答案,更多时候是在做取舍为了并发能力牺牲一部分一致性,为了可用性接受一定的性能开销,为了性能又要在一致性上打些折扣。真正做架构设计的时候,与其一上来就套用某个标准三高方案,不如先想清楚业务的核心诉求是什么——是绝对不能错账,还是绝对不能中断服务,还是响应速度必须够快——找准了业务的痛点在哪,再对症选技术方案,才不会做出华而不实的过度设计。以上是这些年做后端开发和一些分布式系统项目的一点心得,如果有理解不到位或者不同看法的地方,欢迎评论区一起探讨。