
压测是优化的起点没有经过真实流量压测的系统优化就像蒙着眼睛过马路——迟早出事。我第一次接手高并发项目时团队拍着胸脯说系统扛得住十万QPS结果上线当天仅五千并发就把数据库打挂了。压测不是走形式而是暴露系统脆弱点的核心手段。我们需要在压测中同时观察CPU、内存、网络IO、磁盘IO、GC次数、数据库连接池水位、线程池队列长度等十余个指标。压测工具推荐用wrk或locust但更重要的是自己写脚本模拟真实业务场景中的随机读写、热点数据访问和突发流量。记住压测数据要丢到线上环境或者至少同配置的预发布环境去跑开发环境的小马拉大车毫无意义。压测过程中最容易发现的第一类问题就是线程模型。常见的Tomcat默认有200个线程但你想象不到的是当每个线程都在等待数据库慢查询返回时200个线程很快会被耗尽新的请求只能排队。高并发下的首要瓶颈往往是线程池耗尽而非CPU满负荷。这时候你需要重新审视业务接口的耗时分布——是磁盘写入慢是外部RPC调用了还是数据库连接池满了我见过太多人一上来就加机器其实把某个慢查询从500ms优化到50ms一个节点的吞吐量就能翻好几倍。从单线程到协程并发模型的进化Java领域长期依赖多线程但线程是操作系统的重资源。随着Go语言的流行和Java的虚拟线程Project Loom引入协程正在成为高并发场景下的最优解。原因很简单一个线程占用约1MB的栈空间而一个协程只需要几KB因此单机可以轻松开启数十万甚至百万个协程。在类似网关转发、批量请求聚合这类IO密集型场景协程的切换成本远低于操作系统线程。我曾经将某个基于Netty的网关从线程池模型改为协程模型同等硬件条件下QPS从3万提升到8万而且CPU占用率还下降了。但协程不是万能的。如果业务中有大量的CPU密集型计算比如加密解密、图像处理那么多线程的并行优势依然明显。正确的做法是区分IO密集型和CPU密集型IO密集型优先使用协程或者异步回调CPU密集型则保持固定数量的线程来匹配CPU核数。另外注意协程的调度器需要精心调参否则容易出现伪共享False Sharing或者协程饥饿。我建议在采用协程之前先用Profiling工具仔细分析业务中到底哪些操作拉低了并发上限——很多时候锁竞争才是真正的恶龙。数据库的终极解耦读写分离与分库分表关系型数据库往往是高并发系统最薄弱的环节因为它在设计之初就强调ACID而高并发却要求CAP中取舍。读写分离是入门级的解放方案主库负责写入从库负责查询配合数据库代理如ProxySQL、MaxScale自动路由。但要注意主从延迟问题——业务中如果刚写入立即查询可能查不到数据。解决方案是强制读主库或者让前端等待1秒再展示。我经历过一次事故一个活动页面刚开奖就三万人同时查询从库压力过大导致延迟飙升到10秒用户看到开奖数据为空的假象怒火中烧。后来我们在应用层加了“读主库标记”只有开奖逻辑后的第一次查询才强制走主。当读写分离也撑不住时就要考虑分库分表。合理的分片策略是高并发场景下数据库的定海神针。常见做法是按用户ID哈希分库按时间分区。但分片键选择至关重要——一旦选择错误后续跨库查询、全局ID生成、分页排序都成了噩梦。我用过一个失败的案例按业务类型分片结果某个爆款业务类型占据了90%的流量那个分片直接被压垮。后来改成按用户ID 时间双维度分片配合ShardingSphere的自动路由才把流量平均散开。另外注意分库分表后一定要建立“降级开关”当某些分片出现故障时可以快速切到备用分片或者直接降级为本地缓存而不是直接返回500。缓存不是银弹很多团队一碰到性能问题就疯狂加缓存但缓存带来的问题往往比解决的问题更多。缓存的核心是减少计算和IO但缓存击穿、穿透、雪崩三大坑会随时引爆。我曾见过一个系统所有商品详情都缓存30分钟突然某个爆款商品被大量请求缓存过期瞬间几十万请求打到数据库数据库连接池瞬间爆满导致整个站点瘫痪。解决方案是设置“热点数据永不过期后台异步刷新”或者使用互斥锁setnx让单一线程去重建缓存。布隆过滤器是防止缓存穿透的利器它可以在缓存层面就过滤掉不存在的key避免无效的数据库查询。另外缓存层本身也要做高可用。单节点Redis扛不住时需要Redis集群官方集群或Twemproxy分片。但集群扩容和缩容时数据的rehash可能导致大量缓存失效。我推荐使用一致性哈希Consistent Hashing或者Redis Cluster自带的slot分片机制并且在客户端实现智能路由避免请求打到错误的节点。同时本地缓存如Caffeine和分布式缓存要搭配使用本地缓存毫秒级响应但容量有限适合存储最热门的少量数据分布式缓存容量大适合存储大量但访问频率稍低的数据。热key问题一定不能忽视——某个热点Key的QPS超过单节点Redis的处理能力时可以用本地缓存分布式缓存双层架构同时在客户端做key的随机后缀拆分让请求散列到不同Redis节点。异步化与消息队列同步调用在高并发下会形成多米诺骨牌效应A服务调用BB调用C只要C变慢整条链路都被阻塞线程被挂起资源被占满。将同步调用改为异步化是提升系统吞吐量的经典做法。消息队列如Kafka、RocketMQ是异步化的主流选择。比如用户下单后扣库存、发优惠券、发短信等操作都可以通过MQ异步执行主流程只需要保证订单创建成功即可。这样下单接口的响应时间从几百毫秒降到几十毫秒系统能承载的并发量立马翻倍。但异步化也引入了新挑战消息丢失、重复消费、顺序问题。高并发场景下必须保证消息至少投递一次且消费端做好幂等。幂等实现方式可以是消费前查DB是否有唯一ID或者利用数据库的唯一索引去重。我曾经遇到一个订单重复发货的严重事故就是因为MQ重复投递且消费端没有做幂等。后来我们在每条消息里带上业务流水号如订单号随机数消费时先查流水表只要存在就放弃执行。另外异步消息的积压风险也要提前预防——监控队列长度设置告警当积压超过阈值时自动触发扩容消费者。限流与降级保护系统的最后防线无论优化得多好总有流量超出系统承载的那一刻。限流不是限制用户而是保护系统不会因过载而崩溃。常用的限流算法有令牌桶、漏桶、滑动窗口。我偏爱令牌桶因为它允许一定程度的突发流量。在业务网关层如Nginx、Spring Cloud Gateway统一做限流比在业务代码里手写更可控。例如配置每秒1000个请求的令牌桶突发容量200当流量超过时直接返回429Too Many Requests客户端可以配合重试策略。降级则是在系统压力过大时主动牺牲非核心功能。降级策略要有优先级先降级非必要的功能如优惠券计算、日志统计再降级阅读类功能如评论区只显示前100条最后才考虑降级交易核心。我在双十一大促中把所有非核心的MQ消费者线程数缩减到1同时关闭了商品推荐、用户画像等强依赖服务换取了下单主链路的稳定性。一定要用配置中心如Apollo、Nacos动态开关降级而不是改代码重启。另外熔断器如Hystrix、Sentinel也是降级的重要工具当某个依赖调用失败率达到阈值时自动熔断快速失败避免自己也被拖垮。从全链路监控到智能运维没有监控的优化等于盲人摸象。高并发场景下必须建立全链路追踪Trace从用户请求进入网关开始到调用下游各个服务、访问缓存、读写数据库每一步都要记录耗时和状态。推荐使用OpenTelemetry或SkyWalking它们能生成调用链拓扑图让你一眼看到整个请求的瓶颈在哪。我遇到过最奇葩的案例某个接口从10ms变成500ms全链路图显示是Redis GET操作耗时异常——进一步排查发现是因为那个Redis实例的AOF重写占用了大量CPU。除了调用链系统指标的实时监控同样重要。CPU、内存、网络、磁盘的基线数据要持续记录当出现异常时能快速比对。比如CPU突然飙升到100%如果同时GC日志显示Full GC频繁大概率是内存泄漏或对象创建过多。如果网络带宽打满则可能是某个热点数据被重复传输。我建议用Prometheus Grafana搭建监控大盘设置三个级别的告警阈值黄色预警关注、橙色告警通知oncall、红色报警触发自动扩容或降级。自动弹性伸缩Auto Scaling是终极武器结合Kubernetes的HPA可以根据CPU、内存、自定义指标如QPS自动扩缩pod但注意扩缩频率要设置冷却时间避免频繁震荡。最后一个容易被忽略的点是日志。高并发下每个业务接口都打info日志不仅浪费磁盘还会拖慢性能。日志必须分级业务关键节点用info调试信息用debug并且上线环境要关闭debug日志。同时日志采集不要同步刷盘而是异步写入或交给专门的日志收集器如Filebeat。我曾有一次上线后客户端反馈接口超时结果发现是log日志每天产生200GB磁盘IO打满导致的。所以日志库的异步模式是必须开启的同步写日志会让线程阻塞在IO上。总之高并发优化不是一蹴而就的事而是一个持续对抗、持续进化的过程。每一次压测、每一次监控报警、每一次线上故障都是系统成长的养料。你永远无法设计出一个完美无缺的系统但你一定能打造出一个既能承压又能快速恢复的弹性架构。