先说结论如果你正在为一个日请求量接近百亿的系统做架构改造那么“CDN Nginx 接入层 Java 微服务”这套组合是目前最稳、最经得起实战考验的落地路径之一。这个方案的核心思路并不复杂——把流量分层消化掉让每一层只处理自己该处理的事。我在之前的项目里亲历过从单体应用被流量打穿到逐步改造为这套架构的全过程踩过不少坑也沉淀了一些可以直接照搬的经验这篇文章全部整理出来。这篇内容适合谁正在做高并发系统设计或架构升级的后端工程师、负责流量接入和稳定性保障的运维/SRE同学以及想了解大规模系统如何做容量规划的团队负责人。我会从数字换算、CDN配置、Nginx调优、Java微服务改造讲到真实场景的问题排查尽量让每个环节都能落地而不是停留在概念层面。1. 先搞清楚“百亿级流量”到底意味着什么很多人听到“百亿级”第一反应是加机器但架构设计的第一步恰恰不是扩容而是把需求翻译成指标。不是拍脑袋说“我们要抗住100亿请求”而是要知道这100亿在时间轴上怎么分布、请求大小如何、读写比例怎样。只有把这些算清楚才知道每一层该承担什么职责。1.1 从QPS和带宽倒推架构容量做容量规划时有个基础换算一天是86400秒。日请求量100亿意味着全天平均QPS大概是 100亿 / 86400 ≈ 11.6万。但业务流量不可能24小时均匀分布一般白天16小时是主要流量窗口这样平均QPS就变成 100亿 / 16 × 3600 ≈ 17.4万。这还只是平均值。真正决定系统生死的是峰值。电商大促、秒杀、热点事件这类场景峰值往往是平均值的3到5倍甚至更高。按5倍估算峰值QPS就在85万到90万之间。所以标题里说“百亿级流量”实际设计目标要按百万级QPS来打余量必须留足。带宽也是同样的算法。假设单次响应体平均20KB图片、接口JSON、静态资源混在一起这个估算不算夸张100亿次请求大概产生 100亿 × 20KB 200TB 的出网流量。按16小时集中窗口折算每秒需要处理的出网带宽大约是 200TB / 16 × 3600秒 ≈ 3.5GB/s也就是接近30Gbps。再加上一定峰值系数源站出口带宽至少要预留到50Gbps以上。看到这些数字就会明白一个道理所有流量都压到源站让Java应用直接扛百万QPS从成本和稳定性角度都是不现实的。单台Java应用能平稳处理1000到2000QPS已经算不错百万QPS至少需要几百台应用实例再叠加数据库、缓存压力机架都塞不下。所以必须分层能卸载到边缘的流量坚决卸载能静态化处理的不走动态链路源站只处理真正需要计算和存储的请求。1.2 三层架构各自的职责划分这套架构里每一层都有明确分工不能混着用。CDN管“距离”把内容送到离用户最近的地方Nginx管“接入”把到达源站的流量做统一收口和控制Java微服务管“业务”只处理必须动态计算的请求。层级核心职责关键指标典型问题CDN边缘节点就近分发静态内容、缓存热点资源、拦截部分攻击流量缓存命中率、回源率回源策略配置不当导致命中率过低Nginx接入层TLS终结、路由转发、限流、黑白名单、基本防护worker连接数、超时比例连接数耗尽、TIME_WAIT堆积Java微服务层业务逻辑、数据读写、分布式治理QPS、RT、错误率线程池阻塞、依赖超时导致雪崩把这三层想象成三层过滤网CDN先挡住大部分重复请求Nginx再挡住非常规流量和不合法请求最后到达微服务的才是真正需要“动脑子”的业务流量。每一层的设计目标都是让下一层的压力尽可能小而不是让某一层“包打天下”。2. CDN接入先把高频流量打散到边缘节点CDN的原理说起来很简单——把内容缓存到离用户更近的节点用户请求不再直接打到源站。但实际接入时有非常多细节配置稍不注意就会出现“CDN开着源站却比之前还忙”的怪象。我见过不少团队把静态资源全量接入CDN后回源率依然居高不下最后排查发现是缓存规则根本没生效。2.1 缓存规则设计直接决定回源比例CDN有一个核心概念叫回源率回源率 源站实际接收到的请求数 / 总用户请求数。回源率越低说明CDN缓存效果越好源站压力越小。理想情况下纯静态资源场景回源率可以做到5%以下也就是说95%的请求在边缘节点就返回了。要做到这个效果第一步是区分资源类型制定差异化的缓存策略带版本号的静态资源JS、CSS、图片建议缓存1年以上因为URL变了说明内容变了不用担心更新问题。不带版本号的页面静态区块缓存时间设置在5到15分钟兼顾新鲜度和命中率。API接口默认不缓存如果要缓存必须显式声明且要保证业务上能接受数据延迟。HTML页面视业务情况可以短缓存加回源校验用协商缓存兜底。第二步是配置回源HOST和回源协议。CDN回源时会带一个Host头这个必须指向源站实际能识别的域名我们之前就出现过CDN回源时Host配错导致Nginx路由到错误server块返回一堆404。回源协议建议直接保持HTTP让CDN边缘完成HTTPS卸载源站不需要再承担TLS加解密的开销。源站本身可以做TLS但如果全部流量都从CDN进源站只面对CDN的回源IP内部链路用HTTP没有太大问题省下的CPU资源可以留给业务。这里再补充一个公式化的估算思路假设全站上行流量200TB如果CDN命中率是90%那么真正回源的流量只有20TB如果命中率降到70%回源流量变成60TB源站Nginx和带宽压力直接翻三倍。所以CDN配置完成后第一件事就是盯回源率指标特别是大促之前回源率必须压到合理区间。2.2 刷新与预热大促前必做的两项操作CDN不是配置完就万事大吉它有两项低频但非常重要的操作刷新和预热。刷新是主动让CDN节点上的缓存失效预热是在请求到来之前把内容主动推到CDN节点上。实际场景里大促前一天运营改了首页价格、换了一批活动图如果直接上架用户请求会全部回源第一次打过来时CDN节点没有缓存源站瞬间被流量灌满。这时候必须预热把新的首页HTML和核心图片在大促开始前通过CDN厂商的预热接口批量推送到全国主要节点。预热时机的选择很讲究太早预热可能被其他流量挤出缓存太晚则来不及覆盖我的经验是提前2到4小时执行并在大促前半小时做一次抽样验证确认热点地区节点的缓存已生效。刷新操作则要小心粒度。全目录刷新代价极高相当于把该目录下所有文件的缓存全部清掉正常情况下不要用。日常更新尽量用URL维度精确刷新只清理发生变更的具体文件。我们曾经在一次营销活动上线时因为文件名没变但内容变了没有做URL刷新导致用户看了两个小时旧的活动图这就是典型的“缓存坑”。2.3 HTTPS证书与回源链路的小细节证书管理也是一个容易被忽视的环节。CDN边缘层的证书统一由CDN厂商管理回源链路在机房内部走HTTP没问题但如果你有安全合规要求源站也必须启用HTTPS那么回源协议要配置为HTTPS此时CDN回源IP需要加白名单避免源站安全策略误伤。回源超时同样要设置我习惯把回源超时控制在5到10秒连接超时3秒。CDN节点向源站发起请求时如果源站响应慢CDN会重试重试次数过多反而会放大源站压力这个参数同样需要根据源站能力调整。注意CDN回源IP通常是一批固定地址段上线后第一时间把回源IP段加进源站Nginx的 access 阶段白名单只允许CDN回源IP访问源站80/443端口。否则一旦源站IP泄漏用户可以直接绕过CDN打到源站前面做的所有加速和防护都会失效。3. Nginx全局接入层流量进源站的第一道闸门如果说CDN是第一道防线Nginx就是源站的“城门”。所有从CDN回源的流量、以及没法走CDN的动态请求都会汇聚到这里。Nginx配置得好不好直接决定后端Java应用是“平稳运行”还是“被冲垮”。在百亿级流量场景下Nginx本身必须做到高吞吐、低延迟、稳定不宕机。3.1 核心参数调优的底层逻辑先看一组我在生产环境验证过的基础配置worker_processes auto; worker_rlimit_nofile 65535; events { use epoll; worker_connections 10240; multi_accept on; } http { keepalive_timeout 30s; keepalive_requests 1000; sendfile on; tcp_nopush on; server_tokens off; upstream backend { server 10.0.0.11:8080 weight5 max_fails2 fail_timeout10s; server 10.0.0.12:8080 weight5 max_fails2 fail_timeout10s; keepalive 512; } }这些参数为什么这样调逐个说worker_processes auto表示按CPU核心数启动worker进程。Nginx是事件驱动模型每个worker能处理的并发连接数由worker_connections决定。总并发连接数上限约等于 worker进程数 × worker_connections。假设机器是32核配置10240理论并发连接数就是 32 × 10240 327680。这个数字已经足够覆盖高并发场景但要注意它包含所有状态的连接不只是活跃请求。use epoll是Linux下最高效的事件驱动模型几乎是标配。multi_accept on让每个worker一次accept多个连接减少唤醒次数。keepalive_timeout 30s是给客户端的长连接保留时间太短会导致频繁新建连接太长会占用连接槽。keepalive_requests 1000控制单条长连接上最多处理的请求数防止个别客户端霸占连接。关键中的关键其实是upstream里的keepalive 512这个参数让Nginx与后端Java服务之间保持长连接。如果不配置这个每个请求都要新建TCP连接高并发下源站会出现大量TIME_WAIT连接端口和文件句柄很快耗尽。我见过最典型的故障就是Nginx配置了worker_rlimit_nofile 65535但upstream没开keepalive压测到几万QPS时后端Java服务的TIME_WAIT连接数直接飙到几十万所有新连接建不起来大量请求报错。加了keepalive 512之后TIME_WAIT数量下降了一个数量级。3.2 upstream负载均衡与故障剔除策略负载均衡策略的选择要和业务场景匹配。默认的round-robin轮流分发在大部分场景够用但如果做商品详情页这类热点集中的业务一致性哈希策略更合适。upstream detail_service { hash $request_uri consistent; server 10.0.0.21:8080; server 10.0.0.22:8080; }一致性哈希的好处是同一个商品的请求始终落在同一台后端机器上后端能充分利用本地缓存减少缓存穿透。代价是某台机器故障时哈希环会重新映射部分请求会迁移到其他机器导致那台机器流量突增。所以需要配合max_fails和fail_timeout做故障自动剔除连续max_fails次失败后节点在fail_timeout时间内被标记为不可用不再转发流量。Nginx层还应该做一些基本的请求控制。比如限制请求体大小防止有人上传超大数据包设置合理的代理超时时间避免后端慢请求长时间占用连接client_max_body_size 10m; proxy_connect_timeout 5s; proxy_read_timeout 30s; proxy_send_timeout 30s;proxy_read_timeout尤其要重视。一次压测中我们发现后端接口偶发耗时超过10秒因为proxy_read_timeout只配了10秒Nginx会提前断开连接客户端看到504但后端实际上还在正常处理。后来把超时调整到30秒同时推动后端优化慢接口问题才真正解决。3.3 限流与并发控制不能全指望微服务网关网关可以做限流但网关前面必须有一道粗粒度限流。原因很简单网关能处理的QPS也是有限的如果流量直接穿透到网关网关一旦被打满整个微服务集群都会进入雪崩状态。Nginx做粗限流性价比最高。limit_req_zone $binary_remote_addr zoneper_ip:10m rate50r/s; server { location /api/ { limit_req zoneper_ip burst100 nodelay; proxy_pass http://backend; } }这段配置的含义是每个IP每秒最多50个请求允许瞬时100个请求的突发流量多余的直接返回503。$binary_remote_addr以远程IP为key如果整套系统前面全是CDN回源这个限流维度会失效——因为所有请求的远端IP都变成了CDN节点IP。这种情况下限流key要改成能从请求头识别的用户标识例如$http_x_user_id或者在CDN层开启X-Forwarded-For透传后用变量提取真实用户IP作为限流维度。实操经验Nginx限流粒度要“粗而有效”不要配太激进。我们线上Nginx限流阈值通常是后端实际处理能力的80%留出20%余量给突发流量。配置过严会把正常用户误伤配置过松则起不到保护作用。限流生效后务必配上监控告警每分钟触发次数超过阈值就要介入排查而不是等用户投诉了再去看。4. Java微服务落地从单体到可水平扩展的业务层CDN和Nginx把静态流量、非法流量都挡在了外面真正到达Java应用层的流量已经“瘦身”过一轮。但Java微服务本身的改造才是决定业务能不能稳定扛住这种量级的关键。单体应用即使水平部署多副本也存在部署耦合、故障影响面大、无法按业务维度精细扩容等问题。4.1 服务拆分的边界与注册发现服务拆分不能完全按功能模块拍脑袋我的落地经验是先按业务域拆再按稳定性要求拆。举个例子用户服务、商品服务、订单服务这种天然的业务边界第一批就要拆出来数据访问量极大但逻辑相对独立的服务比如商品详情聚合要单独成服务低频但不可中断的基础服务比如配置服务、消息通知服务也要单独拆分。每次拆分的标准就两条这个服务是否可以独立部署、独立扩容这个服务故障时影响面是否能控制在自身范围内。拆分之后服务发现机制必须跟上。推荐使用注册中心来管理服务实例列表。原理并不复杂服务启动时向注册中心注册自己的IP和端口运行时定时发送心跳消费方从注册中心获取可用实例列表如果某个实例心跳超时会被自动摘除。这里有一个实操中的细节注册中心健康检查参数的设置不能太激进。比如心跳超时设置过短网络抖动会导致大量实例被误摘流量突然集中到剩余实例上设置过长故障实例又不能被及时摘除。我的建议是结合业务心跳间隔5秒超时15秒摘除这个配置在大部分业务场景下比较稳妥。4.2 网关、熔断降级与兜底策略微服务网关和Nginx是两类不同的东西职责不能混淆。Nginx负责“流量接入”网关负责“流量治理”——包括路由转发、身份认证、接口鉴权、精准限流、灰度发布标记等。我习惯用网关做细粒度限流按不同接口设置不同的QPS阈值与Nginx的粗限流形成两级防护。熔断降级是这套体系里最重要的保险丝。分布式系统中某个下游服务响应变慢是非常常见的事情如果不做保护调用方线程池会被慢请求占满连锁反应导致整个调用链崩溃。以Sentinel为例一个合理的熔断配置思路是接口级别的QPS阈值设为该接口压测得出的安全上限的80%依赖下游服务时设置慢调用比例熔断例如响应时间超过1000ms的比例达到50%时熔断10秒对非关键路径降级策略直接返回兜底数据兜底数据的设置值得多说几句。比如商品详情页的库存数量实时性要求很高但在大促流量下库存服务抖动此时可以降级为返回缓存中的“有货”状态让用户能正常浏览下单流程真实扣减库存时再做校验虽然会有超卖风险但可以通过后续的订单一致性处理来弥补。相比直接报错导致整个页面不可用这种降级方式是更好的选择。链路追踪同样是个不能省的基础设施。在百亿级流量下一次请求会经过CDN→Nginx→网关→多个微服务任何一个环节出问题没有链路追踪就像在大海里捞针。项目里把全链路ID透传到所有调用点排查问题时直接按traceId查整个调用链的耗时分布秒级定位是网关慢、服务慢还是数据库慢效率能提升好几个量级。4.3 压测与容量预估别等大促才发现扛不住微服务改造完成不代表就一定能抗住流量必须做压测验证。这里的压测不是简单的“发几个请求看看通不通”而是要对整个调用链做全链路压测摸清每个服务的真实容量。我的压测节奏是分四步走单机压测对每个服务单独压测出单实例的QPS和RT拐点找到性能瓶颈在代码里还是数据库。链路压测对核心调用链做端到端压测确认网关、服务、数据库、缓存各环节之间的水位是否匹配。容量预估按峰值QPS除以单实例支撑QPS乘以1.5的安全系数得出需要的实例数。故障演练随机杀掉一台服务实例观察流量漂移后是否还有余量验证注册中心摘除是否及时。举一个容量预估的简单示例假设商品服务单实例压测上限是1000 QPS线上业务峰值是10万QPS那么至少需要 10万/1000 × 1.5 150 个实例。但实际不会这么机械因为数据库、缓存、线程池都可能先成为瓶颈所以实例数仅仅是参考起点真实压测数据才作数。5. 大量压测与排错总结这些坑我替你踩过了这套系统运行到现在经历了多轮大促流量考验也积累了不少问题排查经验。我整理了一张高频问题速查表方便大家在网上搜索时快速定位。现象可能原因排查路径解决方向源站Nginx大量502/504后端Java服务线程池满/宕机看后端服务日志、线程池活跃数扩容、熔断降级、优化慢接口Nginx报“no live upstreams”upstream后端全部被摘除检查后端进程、注册中心状态服务重启、排查健康检查客户端大量499客户端提前断开服务端仍在处理看Nginx日志和access log优化接口RT、调大proxy超时源站TIME_WAIT连接过多upstream未开keepalive/短连接风暴ss -s看连接状态统计upstream开启keepaliveCDN命中率低于80%缓存规则配置错误/资源未带版本号查看CDN命中率报表、抽查缓存header修正Cache-Control、开启刷新预热后端服务CPU打满但压力不大连接数配置过高、空转线程过多查看线程dump、连接池参数调整线程池大小、连接数上限再说几个具体场景的排查思路。场景一某次大促前压测CDN回源流量突然猛增源站Nginx的connection被全部打满。第一反应是扩容Nginx但扩到4台依然扛不住。后来仔细分析发现CDN节点的回源长连接到了Nginx之后Nginx默认会对同一客户端IP的连接做聚合但因为keepalive配置缺失每秒钟有大量TCP连接反复建立和关闭一个请求一个连接。加上了upstream keepalive之后Nginx的连接复用率大幅提升源站压力立刻降下来。这个案例再次说明连接复用比单纯加机器更重要。场景二接口偶发超时但后端日志显示处理时间只有几十毫秒。压测时用traceId追了一下发现耗时集中在Nginx与后端之间的等待上。查了Nginx日志发现proxy_read_timeout配置为10秒而后端某服务偶发GC导致停顿超过10秒Nginx在这期间已经断开了连接。将超时调整到30秒后偶发超时消失。排查这类问题一定要链路追踪光看单端日志很难定位。场景三业务反馈动态接口数据经常是旧数据排查发现前端的接口也走了CDN缓存缓存时间设置了5分钟。这是典型的“缓存过度”问题。解方案很粗暴动态API路径一律在CDN上设置为“不缓存”并在响应头强制Cache-Control: no-cache。在CDN缓存规则里静态资源和动态接口一定要用路径前缀严格区分不能混在一起。6. 稳定运行背后的事演练、监控与变更管理技术架构只是底座真正让系统稳定的还有运维习惯和流程保障。这部分内容可能不那么“性感”但每次大促前我都会把这几件事做完少了任何一件心里都不踏实。6.1 回源逃生演练假设CDN全部失效有个问题值得每季度做一次验证如果CDN节点突然全部失效所有流量直接打到源站系统能撑多久我第一次做这个演练时心里没底模拟了CDN回源比例突然拉到100%的场景源站Nginx和Java服务撑了不到5分钟就出现大面积超时。原因不是Nginx扛不住而是Java服务的线程池全部被慢查询占满。后来做了两件事一是给核心接口配了快速失败策略拒绝请求的速度比排队等数据库响应要快得多二是Nginx层限流挡掉一部分非核心流量保证下单、登录这类核心接口的资源可用。演练发现的问题往往比压测更容易暴露真实风险。CDN厂商的容灾往往是“边缘节点出问题、自动切换其他节点”但如果源站自身的逃生通道没有验证过真正出事时就会手忙脚乱。这个演练建议找运维同学一起做模拟真实故障而不是走过场。6.2 监控告警的三个核心信号系统监控指标很多但如果只让我看三个我会看CDN回源率、Nginx的5xx比例、Java服务线程池活跃度。CDN回源率曲线能提前暴露出配置或程序问题。正常情况回源率平稳如果某天突然成倍上升要么是缓存被大量刷新要么是缓存规则被误改要么是源站响应头里的缓存标记发生变化。Nginx的5xx比例是“系统健康温度计”。我设置了两级告警5xx比例连续1分钟超过1%触发警告超过5%触发严重告警。这个比例比绝对值更有价值因为流量小时5xx绝对数量少但比例高同样说明有问题。Java服务线程池活跃度则是“压垮前的最后预警”。线程池活跃数逐渐爬升并且没有回落的趋势说明后端处理能力开始跟不上了。这时候不是等着它被打满而是要立刻扩容、降级非核心功能、查看是否有慢调用拖垮线程。6.3 变更管理永远不要一次推全量这套系统经历过多轮架构调整我最大的体会是变更要克制。每次调整配置、上线新服务、修改CDN规则都先在一个小范围路径上灰度观察一小段时间确认稳定再推广到全量。举一个实际例子调整CDN某个静态路径的缓存时间从10分钟改为1小时。我先只对这个路径下的一份资源做验证确认用户感知正常、命中率上升后再逐步扩大到整个目录。运行一周数据稳定后才对这个路径全量生效。整个过程可能比“直接改”要多花半天时间但避免了一次可能的线上事故。配置管理上建议所有Nginx配置用版本化方式管理每个变更留审计记录。曾经就有同事直接在生产机器上改配置改完发现worker_processes拼错了Nginx启动失败整条接入层直接不可用。如果能先改配置再reload并且reload前用nginx -t验证语法这类问题完全可避免。最后补一个日常心得如果你准备用这套架构我的建议是先别急着照搬参数而是先花两个星期把监控指标和历史流量数据摸清楚。没有真实数据支撑的架构设计都是在盲人摸象。搞清楚你的资源类型占比、API调用分布、峰值时间段再决定CDN缓存策略、Nginx参数和微服务拆分方案。这套架构不是万能药但它提供了一条经过大规模实战验证的路径你的任务是把路径上每个参数调整到适合自己业务的刻度上。