做后端开发这些年,我见过太多项目一开始是散装起步的。数据库裸奔、缓存靠本地 map、服务间直连、日志靠 print、没人看监控……等用户量和请求量上来,各种问题扎堆冒出来,最后只能周末加班重构。其实这些问题,几乎都能靠一套标准化的基础组件体系提前规避。所谓互联网后端的 10 个基础组件,就是我这些年总结下来的“后端标配”:MySQL、Redis、Elasticsearch、Nginx、注册中心、API 网关、消息队列、对象存储、定时任务调度、监控与日志系统。这篇内容适合刚转后端的同学建立全局视角,也适合正在做架构梳理的团队拿来当对照清单。我会把每个组件说清楚:它解决什么问题、核心原理长什么样、实际部署的时候有哪些关键参数,以及最容易踩的坑是什么。
1. 为什么后端绕不开这 10 个组件
1.1 从一次用户请求看组件的分工
要理解这 10 个组件为什么会成为互联网后端的“标准件”,最好的方式不是背概念,而是跟着一次真实请求走一遍。
假设用户在小程序里点了一下“提交订单”。请求最先到达的是 Nginx,它把流量转发到内网;接着 API 网关接住请求,做统一鉴权、限流,然后按路由规则把请求转发到订单服务的某个实例。订单服务启动的时候把自己注册到了注册中心,网关正是从注册中心拿到了一份可用的服务实例列表,才知道该把请求打给谁。进入业务代码后,订单服务先查 Redis 缓存里有没有商品信息,没有再去查 MySQL;下单成功后会往消息队列里发一条“订单已创建”的消息,通知积分服务、库存服务、短信服务各自去干活。用户上传的头像、图片验证码这些非结构化数据,则统一扔进对象存储。整个过程产生的接口日志、业务日志会被采集到日志系统;每个节点的 CPU、内存、QPS、延迟,则被监控系统盯得死死的;每天早上八点给用户推优惠券的活,则交给分布式定时任务调度平台。
你看,10 个组件各管一段,又互相配合。它们的共性是:不直接写业务逻辑,但业务系统离开任何一个都会很难受。
| 组件 | 核心职责 | 常见产品 |
|---|---|---|
| MySQL | 持久化存储业务数据 | MySQL 8.x、Percona、TDSQL |
| Redis | 缓存、计数器、分布式锁 | Redis 6.x、Redisson |
| Elasticsearch | 搜索、日志分析、聚合查询 | Elasticsearch |
| Nginx | 反向代理、负载均衡、静态资源 | Nginx、OpenResty |
| 注册中心 | 服务注册与发现、配置下发 | Nacos、etcd、Consul |
| API 网关 | 统一入口、鉴权、限流、灰度 | Spring Cloud Gateway、APISIX |
| 消息队列 | 异步解耦、削峰填谷 | Kafka、RocketMQ、RabbitMQ |
| 对象存储 | 海量文件与静态资源存储 | 云 OSS、MinIO |
| 定时任务 | 周期性任务调度与分片执行 | XXL-Job、PowerJob |
| 监控与日志 | 可观测性、告警、排查 | Prometheus、Grafana、ELK、Loki |
1.2 选型的底层逻辑:别追新,追稳
很多新人问,为什么必须选这些组件?有没有替代品?当然有,但互联网行业经过十几年沉淀,每个领域都已经跑出了几个“经过大规模验证”的默认选项。
选型时我通常只看三件事:团队能不能运维、成本扛不扛得住、生态熟不熟。自建 Kafka 和直接用云上的 MQ 各有取舍,自建要养维护人力,上云要花真金白银;开源组件最怕的是有 Bug 没人修,所以尽量选大厂开源或者社区活跃的项目。还有一点很容易被忽略:如果你团队的主力语言是 Java,那 Spring Cloud Gateway、RocketMQ、Nacos 这些 Java 生态的组件会更顺手;如果团队偏 Go,etcd、Kafka、Consul、APISIX 可能更自然。基础组件是给业务系统打底子的,最重要的属性是稳定,而不是看起来新潮。用业界默认方案,能搜到最多的踩坑文章,能招到最熟悉的人,这才是最大的成本优势。
2. 数据三件套:MySQL、Redis、Elasticsearch
2.1 MySQL:所有业务数据的最终归宿
先说数据库。互联网后端可以没有很多中间件,但绝大多数业务最终都长在 MySQL 上,或者至少把 MySQL 作为核心数据源之一。选它不是因为性能天花板高,而是因为关系模型、事务和 SQL 生态这三样东西太适合业务系统了。
MySQL 的底层存储引擎,我基本只看 InnoDB。它支持事务(ACID)、行级锁、崩溃恢复,B+ 树索引结构把随机读写变成了相对可控的磁盘访问。B+ 树的好处是所有叶子节点在同一层,数据有序存储,范围查询和排序都很友好;它还有双向链表,天然适合走索引扫区间。理解这一点,你就明白为什么主键那么关键:主键决定了数据在 B+ 树里的物理顺序,如果主键是无序的 UUID,数据插入会导致页分裂,写性能和空间利用率都会下降。
实际部署中有几个点比引擎更值得关注:
- 主键别用 UUID,自增 ID 或雪花 ID 都行。雪花 ID 还能避免分库分表后的全局唯一性问题。
- 索引不是越多越好,联合索引要遵循最左前缀原则。比如建了
(user_id, status),查询条件里只有status是走不上这个索引的。 - 连接池别乱调。很多人一看连接池默认 8 就往上加,加到几百,结果数据库线程被拖垮。连接数是给业务用的,不是越大越快。
- 慢查询日志必须开。
long_query_time设 1 秒,定期把慢 SQL 拿出来打磨,这是数据库运维里投入产出比最高的一件事。
我在真实项目里踩过最重的坑是什么?是无脑分库分表。单表数据量几十万其实连索引优化都还没做到位,就把订单表拆成 64 张,结果跨库查询、分布式事务、全局 ID、数据迁移一个个全来了。分库分表真的应该放到最后一步,先试索引优化、归档冷数据、升级硬件、读写分离,扛不住了再分。
2.2 Redis:把热数据扛在肩上
Redis 能火,核心原因是快。它把所有数据放在内存里,又用了单线程事件循环加 I/O 多路复用,避免锁竞争和线程切换开销。这个“单线程”常让新人误会,其实 Redis 6 之后某些子命令也有多线程,但主线程处理命令依然是单线程,所以它天生适合做缓存、计数器、排行榜、分布式锁这些对延迟极度敏感的场景。
部署上首先要搞懂持久化。RDB 是周期性快照,恢复快,但可能丢最近一段时间的数据;AOF 是追加写日志,最多丢几秒,但文件大、重写开销高。生产一般是 AOF + RDB 组合,业务允许少量丢失就把appendfsync设成everysec,既兼顾性能又不会丢太多数据。高可用方面,主从复制加哨兵是入门标配,哨兵负责故障自动切换;数据量大了要分片,再上 Redis Cluster。
使用 Redis 一定会遇到三个“缓存经典问题”:
- 缓存穿透:查询一个根本不存在的 key,每次都打到数据库。解决用布隆过滤器挡一层,或者缓存空值。
- 缓存击穿:某个热点 key 刚好过期,大量请求瞬间压到数据库。解决用互斥锁重建缓存,或者逻辑过期。
- 缓存雪崩:大量 key 在同一时间过期,或者 Redis 整体宕机。解决是过期时间加随机值、做多级缓存,关键业务接口加本地进程缓存兜底。
还有一个新人必踩的坑是 Big Key。一个 string 存了几 MB 的序列化对象,读它的时候主线程处理时间长,其他命令全部排队;删除大 key 也可能阻塞 Redis。线上要定期用redis-cli --bigkeys扫描,发现大 key 及时拆分。记住一个边界:Redis 是缓存/加速器,不是主存储。它的持久化机制决定了极端情况下会丢数据,拿它存核心订单状态,出事就是大事故。
2.3 Elasticsearch:把“找东西”变成秒级响应
业务量上来之后,你会遇到两类搜索问题。第一类是“电商需求:帮我按关键词搜商品、按价格排序”;第二类是“排查需求:帮我把最近一小时的错误日志过滤出来”。用 MySQL 的LIKE '%关键词%'去做,数据量一大就性能崩盘,这时候就得请出 Elasticsearch。
ES 的核心是倒排索引。普通数据库正排索引是“文档 → 关键词”,倒排索引反着来:“关键词 → 包含它的所有文档 ID”。搜索时先查词项,再拿到文档列表,速度跟命中文档数相关,而不是跟全表数据量强相关。所以它在全文搜索、聚合统计、日志分析上特别能打。它会把数据分到多个分片(shard),每个分片又是一个完整的 Lucene 索引,分布在集群节点上;副本分片则提供高可用和查询吞吐。
建索引的时候有个原则:mapping 一旦建好,字段类型就不能改了。我见过最典型的错误是把订单金额字段定义成了text,然后发现无法做范围查询,只能重建索引。所以设计 mapping 要谨慎:需要精确匹配和范围查询的字段用keyword或long,需要分词搜索的字段用text,中文搜索要配好 IK 分词器和自定义词典。写入数据时,ES 要走“主分片 → 副本分片”的复制流程,频繁的 update/delete 会产生大量段合并开销,所以它特别不适合做高频强一致更新。深分页也有坑:from + size超过一万就会性能断崖,正确姿势是用search_after或滚动查询。
更本质的认知是:ES 不是“另一个数据库”,它是搜索与分析引擎。你需要保证 MySQL 是事实源,ES 是同步出来的检索视图。数据同步可以用 Canal 监听 binlog,也可以直接用消息队列的异步事件来更新索引,别想着让业务在 ES 上跑事务。
3. 流量接入与服务治理:Nginx、注册中心、API 网关
3.1 Nginx:反向代理与负载均衡的第一道门
Nginx 是互联网后端的“迎宾”。它站在最前面,用户流量先进它,再由它转发给内部的网关或服务。为什么几乎所有团队都用它?因为性能好、配置灵活、稳定性极高,处理静态文件、做反向代理、做负载均衡,一件不落。
要理解 Nginx,先把正向代理和反向代理分清楚。正向代理代理的是客户端,比如你开着代理去访问外部网站,外面只看到代理服务器的 IP;反向代理代理的是服务端,客户端只知道 Nginx 的地址,它把请求分发到后面的多台业务服务器。Nginx 就是典型反向代理,它的 upstream 支持多种负载均衡算法:默认的 round-robin 轮询、按客户端 IP 散列的 ip_hash、按最少连接数的 least_conn。大部分场景轮询就够用了,如果需要同源请求进同一台实例处理,ip_hash 会方便些。
实际配置里最容易写错的是proxy_pass带不带斜杠:
# 不带斜杠:保留原始 URI 中的 /api/xxx proxy_pass http://backend; # 带斜杠:把 location 匹配部分替换掉 # location /api/ { proxy_pass http://backend/; } # 访问 /api/user 实际转发到 /user这一条我起码见过五六个人在这里踩过坑。线上改配置前一定要先跑nginx -t校验语法,再reload平滑重载,不要直接 restart,否则连接会瞬间中断。worker_processes一般设为 CPU 核数,worker_connections决定单进程能扛的连接数;如果服务端要同时处理大量长连接,还要调keepalive_timeout和 upstream 的 keepalive 连接池。Nginx 适合做 L4/L7 层的流量分发、TLS 卸载和静态资源缓存,但千万别在里面写复杂业务逻辑,那会让它变得又慢又难排查。
3.2 注册中心:服务之间的通讯录
服务多了以后,最麻烦的问题不是写代码,而是“互相怎么找到对方”。订单服务有三个实例,A 服务的 IP 变了,B 服务怎么知道?一个个写死 IP 不现实,这就是注册中心的用处:每个服务启动时把自己连同 IP、端口、元数据一起注册上去,同时定期发心跳续约;调用方不再记住具体实例地址,而是从注册中心动态拿一份“活着的实例列表”。
市面上主流选择有 Nacos、etcd、ZooKeeper、Consul。ZooKeeper 算老一辈,临时节点 + watcher 机制很经典,特殊文件系统和会话机制让很多团队觉得难维护;etcd 在 K8s 生态里最顺滑,强一致,公司如果全面云端原生化,etcd 是不错的选择;Consul 在功能上和 Nacos 很像,但国内社区热度相对低。我目前最常用的是 Nacos,因为它不只做服务发现,还自带配置中心,支持命名空间、分组、灰度发布,Java 生态的集成度也高。
用注册中心有两条铁律。第一,心跳参数别乱调。服务下线到被感知通常有几秒延迟,如果心跳超时时间设置过长,故障实例残留的时间就越久,调用方会不停把请求打到死节点上。第二,注册中心绝对不能成为单点,至少 3 节点集群起步。注册中心一旦抖动,线上服务批量摘除实例,整个系统的服务发现就崩了。这类事故我亲身经历过:一次网络抖动导致注册中心短暂不可用,客户端缓存全部过期,所有服务互相找不到,界面一片 5xx。所以客户端 SDK 的本地缓存必须打开,就算注册中心短暂失联,服务间的调用也要能靠缓存撑住。
3.3 API 网关:把横切能力统一收口
早期系统没有网关,登录鉴权写在每个服务里,限流各写各的,接口文档满天飞。后来大家发现,这些横切问题完全可以放在入口层统一处理,于是 API 网关成了标配。网关至少做这几件事:路由转发、统一鉴权、黑白名单、限流熔断、灰度发布、协议转换。
方案怎么选?Java 团队最常见是 Spring Cloud Gateway,内置了 Route、Predicate、Filter 三层抽象,开发体验很好,和 Spring Boot 无缝衔接。如果追求极致性能,或者需要 OpenResty 生态的动态脚本能力,APISIX 和 Kong 是更硬的选择:它们基于 Nginx/OpenResty,支持热更新路由,天然适合大规模流量入口。国内还有阿里开源的 Higress,云原生友好,也很适合新项目。
网关上的限流是面试和实战的高频点。令牌桶和固定窗口、滑动窗口这些算法虽然简单,但很实用。令牌桶用一个“桶”匀速放令牌,请求来了就取令牌,取不到就拒绝,允许一定程度的突发流量,比固定窗口那种“最后一秒打死所有请求”要平滑不少。实际落地时,限流阈值不要拍脑袋,拿压测数据说话:先压出单节点极限 QPS,再乘 0.6 到 0.8 做安全水位。网关本身也要多实例部署、前置负载均衡,因为网关一旦挂了,入口就没了。
有一条很重要的边界:网关里别塞业务逻辑。我在一些项目里看到有人把订单校验、商品价格填充写在网关过滤器里,结果业务每次改需求都要改网关,网关变成了第二个业务系统,性能还差。网关只做通用能力,和业务相关的交给下游服务,这样才能保持网关轻薄稳定。
4. 异步与海量存储:消息队列与对象存储
4.1 消息队列:削峰填谷与系统解耦
消息队列(MQ)解决两个经典问题:解耦和削峰。拿下单场景举例,用户下单后要通知积分服务、库存服务、短信服务、推荐统计服务,如果同步调用,任何一个下游卡顿都会拖慢下单接口;发到 MQ 里以后,订单服务只管投递消息,下游各自消费,互不影响。大促时瞬间流量冲到平时十倍,数据库扛不住,MQ 先把消息攒起来,下游按自己节奏消费,这就是削峰填谷。
选择 MQ 的核心思路看场景:
| 中间件 | 优势 | 适合场景 |
|---|---|---|
| Kafka | 吞吐量极高、生态最大 | 日志采集、大数据管道、实时计算 |
| RocketMQ | 事务消息、延迟消息、可靠性强 | 电商订单、金融交易、业务解耦 |
| RabbitMQ | 轻量灵活、路由模型丰富、社区资料多 | 中小团队通用业务、传统企业集成 |
Kafka 的核心模型是 Topic 加 Partition。一个 Topic 分成多个分区,分区内消息有序,分区间并行处理,所以它能扛住海量写入。消费的时候,每个消费者组里的多个消费者会瓜分分区,这带来一个非常实际的问题:消息至少被投递一次,重复消费是常态。所以消费端业务逻辑必须幂等:数据库插入用唯一索引去重,或者消费前先查状态,同一订单号的处理只执行一次。另一个高频事故是消息积压:往往是因为某个消费者宕机、出现异常消息反复重试、或者下游数据库抖动。排查思路是先看消费组 lag,再看消费者日志里的异常,最后看消费线程是不是被阻塞住了。积压期间不要轻率重启消费者,最好的办法是临时增加消费者实例来横向扩容,先把堆积清掉再定位根因。
顺序性也要聊一句。Kafka 能保证的是分区内有序,不是全局有序。如果你要保证同一订单的事件严格按顺序消费,那把业务主键(比如订单号)作为 key 投递,让同一订单的消息进同一个分区。跨分区的全局顺序,基本没有中间件能优雅保证,更好的方式是上游只发最终状态,下游直接对齐最终状态,而不是依赖一串顺序事件。
4.2 对象存储:海量非结构化数据的统一底座
业务跑起来以后,图片、视频、PDF、日志包这些文件会越来越多。这些东西最不适合放 MySQL 的 BLOB 字段里,也不适合塞服务器的本地磁盘:一是容量扩展难;二是单机磁盘坏了文件就全没了;三是静态内容如果走业务服务器,还会白白占用带宽和 CPU。正确做法是统一丢进对象存储。
对象存储和传统文件系统不一样,它没有目录层级,核心就是一个大桶(Bucket)加一个个对象(Object)。对象通过 HTTP API 访问,支持水平无限扩展,天然带多副本冗余,还能通过生命周期规则把冷数据自动迁移到低频存储甚至归档存储。生产环境我会区分几个桶:公开读的静态资源桶、私密的用户文件桶、日志归档桶,权限策略分开配。
自建还是上云?上云 OSS 最省心,CDN 加速、防盗链、数据处理功能都有,按量付费;但流量和 API 请求量大时账单非常吓人,我有一次误把备份任务写成高频 API 调用,月底账单直接翻倍。自建 MinIO 也不错:开源、S3 协议兼容、部署简单,适合数据不出私网、成本敏感的公司。MinIO 集群的纠删码配置要按磁盘数量仔细算,默认配置未必适合你的硬件。
接入方式上,比较推荐用签名 URL 直传。客户端拿到一个有有效期、限定路径和文件类型上限的预签名地址,直接往对象存储上传,服务端只下发 token 和存元数据,完全不经过应用服务器中转,带宽压力小很多。大文件上传要支持分片和断点续传,生命周期规则至少要设置过期清理,否则时间一长桶里的临时文件和备份会侵占大量成本。权限策略是另一个频发事故点:桶策略一旦写成public-read,就意味着任何人知道 URL 就能下载文件,包括用户的身份证照片。每次改桶策略,我都要反复确认“这个桶真的需要公开读吗”。
5. 稳定与可观测:定时任务、监控告警、日志
5.1 分布式定时任务:别再用服务器 Cron 硬扛
定时任务是后端最容易忽略的组件,但几乎所有业务都会用到:每天凌晨跑数据报表、定时关闭超时未支付订单、定期清理过期 token、给用户推送提醒。早期团队常用服务器 Cron,这在单机阶段没什么问题,一旦服务水平扩展到多实例,问题就来了:同一套代码在三个实例上跑,Cron 会在三个实例同时触发,同一个订单被关三次、同一批优惠券发三次。
解决思路是把调度和业务执行拆开。XXL-Job 是我用得比较早也最多的方案:调度中心负责到点触发,执行器(业务服务)负责干活,执行器启动时自动注册到调度中心。它支持失败重试、超时控制、告警通知,还支持分片广播——把所有执行器分成片,每片处理一部分数据,这在大批量任务里特别好用。比如跑一个 1000 万用户的数据刷新,可以分 5 片,每台执行器处理 200 万,并行度直接拉满。
用定时任务平台,最关键的是任务幂等。不管调度平台怎么做失败重试,任务执行时的网络抖动、重启、重复触发都可能导致同一条数据被处理两次。所以任务逻辑里一定要有防重手段:分布式锁、数据库状态字段判断、或者限制任务只能由负责人手动点一次。我在实践中专门给所有任务加了一个“执行批次号”,每次任务把批次号落库,消费消息和数据更新都带上它,配合唯一索引,重复执行也不会有副作用。还有一个经验:长耗时任务一定设超时和分片,一个任务跑超过半小时就会影响调度中心的心跳判定,容易被误判为执行失败然后重复调度。
5.2 监控告警:先于用户发现问题
没有监控的系统,就像没有仪表的飞机,飞着全靠感觉。基础组件堆得越多,越需要监控把所有节点的运行状态汇总到一个地方,否则每个组件都是信息孤岛。
监控体系我习惯分三层。第一层是基础设施监控:CPU、内存、磁盘、网络 IO,对象是每台机器和中间件节点;第二层是应用监控:接口 QPS、响应时间、错误率、JVM 内存、线程池状态;第三层是业务监控:订单量、注册量、支付成功率,这些指标直接反映用户体验。技术上最主流的搭配是 Prometheus 采集指标 + Grafana 可视化 + Alertmanager 告警。业务服务用 Prometheus 客户端暴露/metrics端点,Prometheus 定时来拉取;MySQL、Redis、Nginx 都有对应的 exporter 可以直接采集。
监控指标不用贪多,Google 的黄金信号四个维度就够了:
- 延迟:请求处理耗时,特别是 P95/P99。
- 流量:QPS、TPS、并发连接数,用来判断负载趋势。
- 错误:HTTP 5xx 比例、接口异常率、消息消费失败数。
- 饱和度:CPU 使用率、内存使用率、连接池占用、磁盘剩余空间。
告警规则的设计比很多人想的更难。最常见的失败是告警风暴:一点小抖动,几十条告警同时轰炸,真正的故障反而被淹没。我现在按优先级分三类:P0 严重(订单成功率下降、核心库主从切换失败、网关大面积 5xx),P1 警告(单节点 CPU 超过 85% 持续 5 分钟、消息积压超过阈值),P2 信息(磁盘使用率超过 70%,提前提醒扩容)。阈值不能拍脑袋,要基于一周到一个月的历史基线来定。还有一条反直觉的经验:告警消息里必须带上排查入口,比如 Grafana 面板链接、最近变更记录、相关服务负责人,不然告警发了也没人知道从哪查起。
5.3 日志系统:排障的第一手现场
最后看日志。日志系统和监控不一样:监控告诉你系统出问题了,日志告诉你问题到底出在哪儿。没有日志系统的项目,排查线上问题全靠登服务器一个个tail -f,效率极低。日志系统的价值就是把分散在几百台机器上的日志统一采集、集中检索、长期留存。
典型采集链路是 Filebeat 采集日志文件 → 发到 Kafka(缓冲削峰)→ Logstash 清洗解析 → 存入 Elasticsearch → Kibana 查询展示。链路有点重,但胜在稳定可靠,日志量大时还有缓冲兜底。小团队想轻量,可以试试 Loki:它不建全文索引,只按标签索引,Grafana 原生集成,部署成本低很多。
日志排障最关键的是链路追踪。如果只是单机日志,看不出一个请求经过了哪些服务。解决思路是每次请求进来时生成一个 traceId,用 MDC 塞进日志上下文,调用下游时通过 HTTP Header 传递,整个链路的所有服务日志都带上同一个 traceId。后续排查只要拿 traceId 去日志平台搜,一条请求从头到尾的全部日志就串起来了。日志平台里搜同一个 traceId,能把 Nginx、网关、订单服务、支付服务的日志按时间戳排成一条时间线,定位性能瓶颈特别快。
日志这边有三个我反复强调的雷区:一是不要打敏感信息,用户手机号、身份证、支付报文别透传进日志,真要用来排查就脱敏;二是日志不能同步写,用异步 appender 或日志管道,否则高并发下磁盘同步 IO 会阻塞业务线程;三是日志分级和保留策略要明确,debug 日志只在临时排查时开,默认 info,别把所有日志不分青红皂白存一年,磁盘再大也扛不住。
6. 一套最小可运行的后端架构怎么搭
6.1 简化拓扑:10 个组件如何连接起来
很多同学看完前面一大篇,脑子里的组件还都是孤立的。这里我把一套常见的小规模后端架构画成一个文本拓扑,帮你把关系理清楚:
客户端 ↓ Nginx(2 台,做接入和负载均衡) ↓ API 网关(2 台或更多,鉴权、限流、路由) ↓ 业务服务集群(订单、用户、商品,各自多实例) ↓ 读写 ↓ 注册/发现 ↓ 异步 MySQL(1主2从) Nacos(服务注册+配置) Kafka(主题消息) Redis(3节点哨兵) ↓ 消费 Elasticsearch(3节点搜索/日志) 下游服务、数据同步任务 对象存储 MinIO/OSS(存放图片、文件、备份) 监控 Prometheus + Grafana 覆盖所有节点 日志 Filebeat → Kafka → Logstash → ES → Kibana 定时任务 XXL-Job 调度中心 + 执行器(业务服务内置)这套架构里,所有组件分摊的角色一清二楚:Nginx 管入口流量,网关管统一治理,Nacos 管服务发现,MySQL 管事实数据,Redis 管热数据和速度,Kafka 管异步流转,ES 管搜索和日志,对象存储在管非结构化文件,定时任务管周期操作,监控日志管可观测性。一句话总结:入口、业务、数据、异步、存储、可观测,六层全齐了。
6.2 搭建顺序与资源估算:先跑通最小闭环
新项目落地时不要一上来就全量铺开,大概率会翻车。我的建议是先搭最小闭环,再加组件:
- 第一阶段:Nginx + 业务服务(Spring Boot/Go)+ MySQL + Redis。这套已经能支撑一个日活几万的单体应用,先把业务跑通。
- 第二阶段:业务继续拆分成多个微服务时,引入 Nacos 做服务注册发现和配置中心,再引入 API 网关收敛入口。
- 第三阶段:出现跨服务异步通知、削峰需求时,引入 Kafka 或 RocketMQ;搜索需求明确时,再上 Elasticsearch;有文件上传需求,就接对象存储。
- 第四阶段:服务实例多到排障困难时,补齐日志链路和 Prometheus 监控,再逐步把核心定时任务从服务器 Cron 迁到 XXL-Job。
资源估算是个很现实的成本问题。假设你是 1000 QPS 的读写混合业务,单台 4C8G 的业务服务实例乐观时可以扛 200 到 300 QPS,保守按每实例 200 算,业务服务至少准备 4 到 6 个实例。MySQL 用 1 主 2 从,主库扛写,从库扛读和离线任务;Redis 用 3 节点哨兵,或者直接 3 主 3 从的 Cluster;Kafka 3 节点起步,每个 Topic 先设 6 到 12 个分区,后面按消费压力和延迟数据再调;ES 至少 3 节点,避免脑裂,节点少时一定要配discovery.zen.minimum_master_nodes。这个规模大概是:3 台数据库、3 台缓存、3 台消息队列、3 台 ES、2 台 Nginx、2 台网关、4 台业务服务,共 20 台左右的虚机,对中小团队来说已经是覆盖所有关键路径的完整体系了。
7. 常见问题与排查技巧实录
7.1 十个组件最经典的坑:速查表
下面这张表是十年来被踩得最多的故障点,直接照着排查可以少走很多弯路。
| 组件 | 经典故障现象 | 排查方向 | 建议解法 |
|---|---|---|---|
| MySQL | 慢查询激增、锁等待 | 慢日志、SHOW ENGINE INNODB STATUS | 补索引、优化 SQL、避免大事务 |
| MySQL | 主从延迟过大 | SHOW SLAVE STATUS看 Seconds_Behind_Master | 优化主从同步、大事务拆分、考虑并行复制 |
| Redis | 缓存击穿/雪崩 | 监控 key 过期时间和热点访问 | 加随机过期、多级缓存、布隆过滤器 |
| Redis | 主线程阻塞、延迟抖动 | SLOWLOG、redis-cli --bigkeys | 拆分 bigkey、淘汰危险命令 |
| Nginx | 转发后路径丢失 | 检查proxy_pass是否带斜杠 | 按环境固定一套写法并加测试 |
| 注册中心 | 服务实例批量消失 | 看心跳超时、注册中心负载 | 调大心跳容忍、客户端加本地缓存 |
| 网关 | 限流误伤正常用户 | 看限流日志、令牌桶参数 | 按压测水位调整阈值、增加白名单 |
| MQ | 消息大量积压 | 查消费组 lag、消费者日志 | 扩容消费者、修异常消息、临时跳过死信 |
| 对象存储 | 文件访问报 403 | 检查桶策略、签名有效期 | 重新配置权限、检查 URL 签名 |
| 定时任务 | 同一任务被重复执行 | 看调度日志、执行记录 | 任务幂等设计、加执行批次号 |
| 监控 | 告警风暴 | 检查告警规则和抖动源 | 按优先级分级、基于基线设阈值 |
| 日志 | 全链路查不到 traceId | 检查 Header 传递和 MDC 配置 | 统一请求过滤器、日志框架异步化 |
7.2 几条亲身踩出来的独家心得
最后聊几个普通文档里不太会写的经验。第一,连接池不是越大越好。很多人觉得 Redis 连接池调到 1000 就更快,其实大量并发线程抢同一批连接只会增加等待和上下文切换。Redis 客户端连接池我一般保持在 16 到 64 之间,压测数据反而更好看。MySQL 连接池同理,设到 200 之前先看看数据库的max_connections能不能扛得住。
第二,基础组件升级别追太急。我有一次把 Redis 从 5 升到 7,结果部分客户端版本不兼容,线上频繁断连。中间件的升级要当作一次小型项目来管理:先在测试环境完全复现业务链路,再用金丝雀实例观察流量,最后才逐步切量。稳定运行的组件不要去赌它没风险。
第三,每个组件都要做容量预留和压测。Kafka 分区数不是越多越好,分区太多会增加文件句柄、延长 leader 切换时间;ES 分片太多会引起小分片风暴,分片太少又扛不住查询并发放。最靠谱的方法只有一条:拿接近真实的流量做压测,看延迟和资源使用率的拐点在哪,再定容量水位。
第四,排查问题时,永远先看基础设施再看业务代码。一个接口超时,先确认是不是 CPU 跑满、磁盘满了、网络抖动、连接池耗尽,再去看代码逻辑。我见过太多人一上来就查代码,最后发现是磁盘 IO 报废,浪费了几个小时。监控系统存在的意义,就是让你在陷入代码之前,先看到系统层到底发生了什么。
这个领域有个很朴素的道理:基础组件本身并不难,难的是对每个组件边界的尊重。数据库就安安稳稳存事实数据,缓存就负责扛热点,消息队列就处理异步,日志监控就是你的眼睛和耳朵。先跑通最小闭环,再按业务增长慢慢把组件补全,比一次性铺开十个中间件要稳妥得多。希望这篇梳理能给你的后端架构打下一个清晰的地基。