很多实时产品在后端阶段会同时撞上四件事:HTTP API、长连接、缓存/消息、关系型权威状态。若边界划不清,常见结局是:缓存与数据库双写不一致、WebSocket 连接散落在各实例上无法扇出、故障时只会重启进程而说不清哪一层坏了。

下文按「可部署单元内的模块边界 → 数据与一致性 → 实时通道 → 运维与演进」组织,讨论一套在中小规模生产环境仍站得住的组合:Spring Boot 承载业务与接入,MySQL 作真相源,Redis 作低延迟协调层。不鼓吹微服务拆分,但会为后续拆分留下接缝。

1. 先画清边界:什么进单体,什么必须外置

1.1 推荐的分层(逻辑模块,而非文件夹堆砌)

| 层 | 职责 | 禁止做的事 | |----|------|------------| | 接入层 | REST/gRPC、WebSocket 握手、鉴权、限流、DTO 校验 | 直接写复杂 SQL 或操作 Redis 细节 | | 应用层 | 用例编排、事务边界、领域规则 | 感知 HTTP 状态码或 WS 帧格式 | | 领域层 | 实体、聚合、领域事件(纯 Java) | 依赖 Spring、JPA、Redis 客户端 | | 基础设施层 | Repository 实现、消息发布、外部 API 适配 | 承载业务规则 |

Spring Boot 的价值在于:用同一进程把接入与应用跑通,同时用包结构与接口把「以后可拆成独立服务」的接缝留住。过早拆成五个微服务,往往只是把网络调用的不确定性提前引入。

1.2 何时仍用「模块化单体」

满足以下多数条件时,单体 + 清晰模块通常优于微服务:

  • 团队规模小,跨服务联调成本高于代码耦合成本;
  • 峰值 QPS 在单机垂直扩展 + 读写分离可覆盖范围;
  • 领域边界尚未稳定,每周仍在改核心用例。

拆分信号往往是:某条链路需要独立扩缩容(例如 WS 网关)、或某子域发布节奏与主 API 完全脱节——而不是「代码行数超过 X 万行」。

2. MySQL:权威状态与一致性接缝

2.1 什么必须落库

凡涉及 钱、权限、审计、不可丢失的业务事实,应以 MySQL(或同等 ACID 存储)为 单一真相源(SSOT)。Redis 中的会话、在线状态、限流计数都是 派生状态,必须能由库内数据重建或容忍短期过期。

2.2 事务边界要「短而硬」

典型反模式:在 @Transactional 方法里调用外部 HTTP、发 Kafka、或 Thread.sleep。正确做法:

  1. 事务内只做 DB 读写;
  2. 提交后通过 领域事件 / Outbox 表 触发副作用。

Transactional Outbox 简述:业务表与 outbox 表同事务写入;独立轮询进程读取 outbox 并投递到 MQ 或调用下游。这样避免「库已提交、消息没发出」或相反的幽灵状态。

2.3 连接池与慢查询

  • HikariCP:maximumPoolSize 不是越大越好,公式需结合 CPU 核数与查询类型(OLTP 常取 cores * 2 量级作起点,再压测);
  • 慢查询日志 + EXPLAIN 常态化;对热路径建立覆盖索引,避免 ORM 隐式 N+1(@EntityGraph / fetch join 要有意识使用);
  • 读多写少场景:读写分离 或只读副本承接报表;写路径仍走主库,避免应用在副本上写。

3. Redis:不是缓存插件,而是协调层

3.1 按数据结构选型

| 场景 | 结构 | 注意点 | |------|------|--------| | 会话 / Token 元数据 | String + TTL | 密钥命名空间 session:{userId};轮换密钥时双写窗口 | | 限流 | String INCR + EXPIRE 或 Redis Cell | 区分用户级、IP 级、全局限流;超限返回 429 与 Retry-After | | 在线状态 / Presence | Hash 或 ZSET | 必须 TTL + 心跳续期;断线清理依赖过期而非只靠 disconnect 事件 | | 跨实例 WS 扇出 | Pub/Sub 或 Streams | Pub/Sub 无持久化,订阅者离线即丢;Streams 可消费组,适合「至少一次」投递 | | 分布式锁 | Redisson / SET NX PX | 锁必须带超时;临界区尽量短;能不用锁就不用(用 DB 乐观锁) |

3.2 缓存策略写进设计文档

  • Cache-Aside:读 miss 回源 DB 再写入;写路径先更新 DB 再删缓存(或延迟双删防脏读);
  • 禁止「默认缓存一切对象」:明确 哪些键、TTL、失效条件、是否允许击穿(布隆 / 单飞 singleflight);
  • 热 key:本地 Caffeine 一级缓存 + Redis 二级;或大促前预热。

Presence 过期在 UI 上表现为「好友明明在线却显示离线」——这是产品缺陷,应归类为数据模型问题,而不是「缓存命中率低」。

4. WebSocket:接入模式与水平扩展

4.1 不要在业务线程里阻塞 IO

Spring 场景下常见组合:

  • Spring WebFlux + Reactor Netty(全链路非阻塞),或
  • Servlet 容器 + 独立 Netty/Socket.IO 网关进程,与 Spring MVC API 解耦。

若在 Tomcat 线程模型里塞满长连接,线程池会被占满,HTTP API 与 WS 互相拖死。

4.2 推荐网关职责

Client ──WS──► Gateway 实例 ──pub/sub──► Redis ◄──pub/sub── 其他 Gateway
                      │                              ▲
                      └── 鉴权 / 游标 / 心跳 ──────────┘
Business API (Spring) ── 写 DB + 发布领域事件 ──► 推送到 Redis channel
  1. 连接建立:校验 JWT(建议短效 access + 刷新分离);绑定 userId、deviceId、协议版本;
  2. 订阅模型:按 room:{id} 或 user:{id} 频道订阅 Redis;业务服务不持有连接句柄;
  3. 游标 / 增量:每条下行消息带 seq 或 revision;客户端 ACK 或上报 lastSeq,重连时走 补拉 HTTP API 对齐缺口,而非让客户端猜;
  4. 背压:每连接发送队列设上限;队列满则丢弃低优先级(正在输入 > 已读回执 > 消息体),并下发 degraded 状态。

4.3 水平扩展与粘性

多实例部署时:

  • 有状态连接 必须 sticky(负载均衡基于 cookie/ip hash),或
  • 彻底 无状态网关(连接在任意实例,扇出只依赖 Redis),推荐后者。

滚动发布时:graceful shutdown——停止新连接、等待 DRAIN 周期、再杀进程,避免大量同时断线重连打穿 API。

5. 异步与集成:别把慢依赖绑在请求线程上

  • @Async 仅适合非关键路径;关键链路用 MQ(RabbitMQ / Kafka)+ 消费幂等(业务键去重表);
  • 调用 LLM / 第三方 HTTP:独立线程池 + 超时 + 熔断(Resilience4j);主 API 返回 202 + 任务 ID,结果通过 WS 或轮询推送;
  • 定时任务(@Scheduled)考虑 分布式调度(ShedLock / Quartz 集群),避免多实例重复执行。

6. 可观测性:要能回答「慢在哪一段」

6.1 三大信号

  • Metrics:QPS、P99 延迟、池使用率(Hikari active/idle)、Redis 命中率、WS 连接数、每频道推送延迟;
  • Logs:结构化 JSON,字段至少含 trace_id、span_id、userId(脱敏)、route;
  • Traces:OpenTelemetry 贯穿 HTTP → DB → Redis → 下游 HTTP;WS 网关单独 instrumentation。

HTTP 与 WS 必须共用 trace_id(握手时由客户端带 X-Trace-Id 或网关生成并下发),否则排障断链。

6.2 健康检查分层

| 探针 | 检查项 | |------|--------| | Liveness | 进程存活 | | Readiness | DB ping、Redis ping、下游依赖抽样 | | 深度检查(可选) | 只读查询、Redis 读写测试键 |

Readiness 失败应从负载均衡摘除,而不是反复重启 Pod。

7. 安全与配置

  • 密钥 只走环境变量 / Secret 管理(K8s Secret、Vault),禁止入库;
  • Redis、MySQL 启用 TLS(内网亦建议);最小权限账号(业务账号无 DDL);
  • WebSocket 同样走 WSS;防范 跨站 WebSocket 劫持(校验 Origin);
  • 限流 + 鉴权失败次数告警,防撞库与滥用。

8. 演进路径(仍保持务实)

  1. 阶段 A:模块化单体 + 单 Redis + 主从 MySQL;WS 网关可与 API 同进程或旁路进程;
  2. 阶段 B:WS 网关独立扩缩容;引入 Outbox + MQ;读副本承接查询;
  3. 阶段 C:按域拆服务(仅当指标与组织真的需要);引入服务网格处理 mTLS 与重试策略。

每一阶段都应有 回滚方案 与 容量压测报告(HTTP + WS 混合场景),而不是凭直觉加机器。

小结

现代服务端基础设施设计的核心不是「选了 Spring Boot 和 Redis」,而是:

  • 真相源唯一,派生状态可重建或可控过期;
  • 实时通道与业务解耦,扇出走协调层,连接状态不进业务库;
  • 事务短、副作用异步、观测可串联;
  • 先模块化单体,后为真实瓶颈拆分。

把上述接缝在设计阶段写进 ADR(Architecture Decision Record),比堆更多框架更能降低生产事故概率。