问卷系统一夜扩容 30 倍:Spring Boot 3.5 扛住百万并发,Spring AI 2.0 让大模型不再拖后腿
摘要:很多团队第一次做问卷系统时,都会把它理解成“表单设计 + 提交 + 统计”的大号 CRUD。可一旦业务叠加了营销活动、租户隔离、实时统计、批量导出、AI 自动生成问卷、AI 智能分析答卷,系统复杂度会瞬间跃迁。本文以一套企业智能问卷平台的扩容实战为主线,完整拆解如何基于 Spring Boot 3.5 + Java 21 + Spring AI 2.0 + Redis + PostgreSQL + Kafka + Kubernetes,把一个只能扛日常流量的单体系统,升级成可支撑 百万级并发提交、秒级统计回显、AI 能力异步解耦 的生产级架构。
文章重点不只在“怎么写代码”,而在“为什么这么设计”:从写路径削峰、读路径分层缓存、数据库热点规避、虚拟线程与连接池协同,到 Spring AI 的 Advisor 链、Tool Calling、RAG 检索增强、模型降级与成本治理,尽量把原理、架构和工程落地讲透。
一、事故起点:为什么一个问卷活动会拖垮整条业务链
问题发生在一次大促活动上线后的第 4 分钟。
- 问卷提交接口 P99 从 180ms 飙升到 6.8s
- 统计页接口大量超时,Redis 命中率从 92% 掉到 37%
- AI 生成问卷与 AI 分析答卷全部堆积,第三方模型接口持续返回
429 - 数据库连接池被打满,连同库的其他业务查询也被连带拖慢
复盘后发现,真正的问题不是“访问量变大了”,而是系统在架构层面把四种完全不同的压力混在了一起:
- 用户提交答卷,是高并发、短事务、强一致写请求。
- 统计分析,是高频读、多维聚合、可接受最终一致。
- AI 问卷生成,是高延迟、昂贵、失败率不可忽视的外部调用。
- 报表导出,是重 CPU、重 I/O、重内存的后台任务。
如果它们共用一套 Web 线程池、共用一个数据库连接池、共用一个 Redis Key 空间、共用一个超时策略,那么任何一类流量异常都会触发级联雪崩。
所以这次“扩容 30 倍”的核心,不是简单加机器,而是先把架构里的耦合点拆掉。
二、先定边界:智能问卷系统到底是个什么系统
一个生产级问卷平台,通常至少包含以下能力域:
| 领域 | 核心职责 | 性能特征 |
|---|---|---|
| 问卷设计域 | 问卷创建、题目编排、模板管理、发布下线 | 低频写、中频读 |
| 答卷采集域 | 链接访问、匿名/实名提交、防刷、幂等校验 | 高频写、突发流量 |
| 统计分析域 | 实时计数、维度聚合、人群切片、排行榜 | 高频读、计算密集 |
| AI 能力域 | AI 生成问卷、AI 改写题目、AI 总结结论 | 高延迟、强依赖外部服务 |
| 运维治理域 | 限流、熔断、观测、审计、扩缩容 | 全局横切 |
如果从架构视角定义,这不是一个“问卷页面应用”,而是一个典型的 高并发写入系统 + 异步分析系统 + AI 编排系统。
因此,合理的目标不是“所有请求都实时完成”,而是:
- 提交链路必须极短,优先保证“收得住”
- 统计链路可缓存、可预聚合,优先保证“查得快”
- AI 链路必须异步解耦,优先保证“拖不垮”
- 核心链路必须隔离,优先保证“局部失败不扩散”
三、总体架构:从单体 CRUD 升级为分层解耦架构![]()
3.1 为什么拆成这几层
这套架构的关键不是服务数量,而是职责边界:
survey-api只负责核心交易链路:创建问卷、发布问卷、提交答卷、查询本人结果。stats-service专注统计读模型与聚合查询,不承担提交写压力。ai-service独立承接 Spring AI 调用、Prompt 编排、RAG 检索、Tool Calling、模型路由与降级。export-service用消息驱动处理重型导出任务,避免拖慢在线接口。
换句话说,让高并发写、复杂聚合读、慢速 AI 调用、重型后台任务各跑各的资源池,才是扩容的第一步。
3.2 单体还能不能做
能,但前提是逻辑上先分层,再决定是否物理拆分。
对于中小规模场景,完全可以先在单体内按照以下包结构组织:
com.example.survey ├── api # Controller / DTO ├── application # 用例编排、事务边界 ├── domain # 聚合、领域服务、领域事件 ├── infrastructure # DB / Redis / MQ / AI / 外部接口 └── interfaces # MCP Tool / Scheduler / Consumer先做逻辑解耦,后做物理拆分,能有效避免“服务拆了,耦合没降”的伪微服务。
四、核心建模:真正扛住高并发,先从数据模型开始
很多问卷系统慢,不是慢在 Java,而是慢在表设计。
4.1 聚合设计
建议以领域对象拆成四个主聚合:
Survey:问卷定义、题目结构、版本号、发布状态SurveyResponse:一次完整答卷提交SurveyAnswer:答卷中的题目答案明细SurveyStatSnapshot:统计快照或聚合结果
核心原则:
- 问卷定义和答卷数据必须分离,避免读写互相污染。
- 题目结构建议 JSONB 存储,但统计字段不要全靠 JSON 现算。
- 提交主表和答案明细表分离,写扩展性更好。
- 统计尽量走异步聚合表,不要在高峰期直接扫明细。
4.2 推荐表结构
create table survey ( id bigint primary key, tenant_id bigint not null, title varchar(256) not null, status varchar(32) not null, version integer not null, question_schema jsonb not null, publish_time timestamptz, created_at timestamptz not null, updated_at timestamptz not null ); create table survey_response ( id bigint primary key, tenant_id bigint not null, survey_id bigint not null, user_id bigint, submit_token varchar(64) not null, submit_time timestamptz not null, client_ip_hash varchar(64), total_score numeric(10,2), ext jsonb, unique (survey_id, submit_token) ); create table survey_answer ( id bigint primary key, response_id bigint not null, survey_id bigint not null, question_id varchar(64) not null, answer_type varchar(32) not null, option_values jsonb, text_value text, numeric_value numeric(10,2), created_at timestamptz not null ); create table survey_stat_snapshot ( survey_id bigint not null, stat_date date not null,