微服务

集群部署解决了什么,又带来了什么

集群是解决单点故障和容量瓶颈的标准手段,但它不是免费的——会话共享、分布式锁、定时任务重复执行、日志排查,这些都是从单机走向集群后必须补上的课。

2026-03-10 约 6 分钟 2572 字 微服务 高可用 集群 架构

一、什么时候该上集群

单机部署有个绕不开的问题:它是一台机器。

服务器宕机、进程崩溃、内存溢出、CPU 打满——任何一个环节出问题,整个系统就直接不可用。业务量小的时候这可以接受,一旦到了不能停的程度,集群就是必选项。

集群的定义很简单:多台服务器同时运行同一个项目,对外提供服务。

但它解决的问题不止一个。

二、集群解决的四个问题

1. 高可用(最重要)

单机故障 = 全站不可用。集群里一台挂掉,其他机器继续干活,故障对用户不可见。

这是集群最核心的价值。注意它和其他三个有本质区别:吞吐量可以靠升级硬件解决,但单点故障只能靠冗余解决。

2. 提升并发能力

单台机器的 CPU、内存、连接数都有上限。Nginx 把请求分发到多个实例,多台机器共同处理流量,系统吞吐量随实例数增加。

3. 不停机发布

集群可以一台一台地更新:停其中一台更新代码,其余机器继续对外服务;更新完再换下一台。

这就是滚动发布(rolling update)。用户全程无感知,不需要挑凌晨低峰期停机维护。

配合灰度发布还能更稳:先更新一台,观察监控指标,没问题再推全量。

4. 容灾与故障隔离

某台服务器硬件故障、网络异常、磁盘损坏,其他节点正常工作。这和第 1 点的区别在于:高可用强调的是"服务不中断",容灾强调的是"故障范围可控"。

三、代价:集群带来的四类新问题

集群不是免费的。它把"单机问题"换成了"分布式问题",而分布式问题的排查难度往往更高。

1. 会话共享

单机时 HttpSession 存在本机内存,集群后用户第一次请求打到 A 机、第二次打到 B 机,B 机没有这个 session,用户就被登出了。

解决方案:

方案 做法 评价
Session 复制 各实例互相同步 session ❌ 实例多了广播风暴,不推荐
Session 粘滞 Nginx ip_hash,同一 IP 固定打同一台 ⚠️ 简单但有副作用:某台挂了会话就丢了,负载也不均
集中存储 Session 存 Redis,所有实例共享 ✅ 主流方案
无状态 token 用 JWT,服务端不存状态 ✅ 微服务下的常见选择

集中存储是主流。代价是每个请求都要查一次 Redis——这也是为什么后来很多人转向 JWT:用"服务端不存状态"换掉这次查询(但如前所述,JWT 有自己的代价)。

2. 单机锁失效

synchronized、ReentrantLock 只在单个 JVM 内有效。集群后:

实例 A 的线程 1 拿到 JVM 内的锁
实例 B 的线程 1 同时拿到它自己 JVM 内的锁
→ 两把不同的锁,临界区被同时进入

必须换成分布式锁(Redis / ZooKeeper / etcd)。

这是集群化最容易埋雷的地方——代码一行没改,单机测试完全正常,上线集群后突然出现并发 bug。

3. 定时任务重复执行

@Scheduled 在每台机器上都会执行。一个"每天凌晨清理过期订单"的任务,集群后会被执行 N 次。

解决方案:

方案 做法
分布式锁 任务开始前抢锁,抢到的才执行
选主 用 ZooKeeper / etcd 选出一个 leader,只有 leader 执行
分片执行 用 XXL-Job 这类调度平台,把任务分片给不同实例
数据库唯一约束 让重复执行天然失败(如插入带唯一键的任务记录)

注意":任务本身幂等"也是一个解法——如果重复执行不产生副作用,那就没必要防。但清理订单这种任务通常不幂等(重复执行可能误删),所以还是要防。

4. 日志排查变复杂

单机时 tail -f 一个文件就完事。集群后请求可能落在任意一台机器上,出了问题要一台台翻日志。

必须引入链路追踪:

  • 每个请求生成唯一 traceId,通过 HTTP header / MQ 属性一路透传
  • 日志格式里带上 traceId
  • 用 ELK / Loki 之类的工具集中收集
  • 排查时按 traceId 一次捞出整条链路的日志

没有链路追踪的集群,排查效率比单机还低——因为你甚至不知道请求打到了哪台机器。

四、集群还会引出这些设计变化

单机做法 集群后必须改成
synchronized 加锁 分布式锁
本地缓存(Guava Cache) 本地缓存 + 分布式缓存,或改用 Redis
AtomicInteger 本地计数 Redis 计数 / 分片聚合
本地文件存储(上传的图片) 对象存储(MinIO / OSS)
@Scheduled 定时任务 分布式调度 + 锁 / 选主
本地 session Redis session / JWT
内存限流(令牌桶) Redis + Lua 分布式限流
单机 ID 自增 雪花算法 / 号段模式

规律很清楚:凡是"依赖单机内存状态"的东西,集群后都会失效。 上线前按这个清单过一遍,能避免大部分坑。

五、什么时候不该上集群

小型项目不建议微服务,同样地,也不是所有系统都需要集群:

场景 建议
日活几百、可接受短暂停机 单机 + 快速重启更简单
内部管理系统、非核心时段可停 单机 + 定时备份
有状态且难拆分的应用 优先纵向扩容(升配),再考虑集群
流量波动大、要求高可用 集群 + 负载均衡

集群的运维复杂度是真实成本:多台机器的部署、监控、日志、证书、配置同步,都要额外投入。如果业务量根本到不了单机上限,上集群只是徒增麻烦。

小结

  • 集群解决四件事:高可用、并发能力、不停机发布、容灾隔离
  • 高可用只能靠冗余解决,升级硬件解决不了单点故障
  • 集群把"单机问题"换成了"分布式问题",代价是:
    • 会话共享 → Redis session 或 JWT
    • 单机锁失效 → 分布式锁
    • 定时任务重复执行 → 分布式锁 / 选主 / 分片
    • 日志排查变难 → 链路追踪 + 集中日志
  • 规律:凡是依赖单机内存状态的东西,集群后都会失效——上线前照清单过一遍
  • 集群不是默认选项:业务量不到单机上限时,单机 + 快速重启更划算

从单机到集群,不只是多部署几份代码,而是一次架构范式的切换。想清楚哪些状态必须外置,比配好 Nginx 更重要。

留下点什么

评论基于 GitHub Discussions(giscus)。在项目根目录配置.env.local填入 NEXT_PUBLIC_GISCUS_* 四个变量后自动开启。