上个月我们团队开始收口 QuickBlue 这个 AI 微服务应用底座的第一阶段开发,我以为最麻烦的会是模型选型或者接口设计,结果真正把人按在地上磨的是环境准备。QuickBlue 的定位很明确:一个面向 AI 应用的微服务底座,把用户、权限、网关、基础业务和 AI 能力编排统一管起来。但正因为它是“微服务 + AI”的组合体,本地环境比传统业务系统多出了模型服务、配置同步、跨服务调用链这几层复杂性。这篇文章我就完整记录一下从裸机到微服务骨架跑通的全过程,包括硬件怎么选、JDK 和 Spring 版本怎么配、Nacos 怎么起、Ollama 怎么接、以及第一次联调时踩过的五个坑。内容适合准备做 AI 微服务开发的工程师、正在搭底座的架构师,也适合那些已经写完代码但环境一直起不来的朋友对照排查。
1. 为什么 QuickBlue 要把环境准备当成一个独立交付物
很多团队对微服务有个误判:以为代码拆成多个模块,配上几个中间件,环境自然就能跑起来。实际上微服务底座的环境准备本身就是第一个交付物,而且是最容易返工的交付物。
1.1 微服务底座的环境准备,和单体时代到底差在哪
单体应用的环境准备很简单:装一个 JDK,装一个 MySQL,装一个 Redis,IDE 一开,项目一跑,完事。最多再处理一下 Tomcat 端口冲突。到了微服务阶段,环境里至少要有注册中心、配置中心、网关、链路追踪、消息队列;到了 QuickBlue 这种 AI 应用底座,还要再加模型服务、向量存储、AI 网关(有时候还需要模型 API 的代理层)。
这些东西并不是装完就能协同工作。Nacos 起来了,你的服务不一定注册得上去;配置中心有配置,你的服务不一定拉得到;网关起来了,路由不一定找得到背后的实例。微服务环境准备本质是在本地模拟一套分布式运行时的最小集,任何一环配置不对,后面的功能开发全部卡住。
在单体时代,环境问题顶多让你多花十分钟;在 QuickBlue 这种系统里,环境问题会直接决定你一周的联调效率。
1.2 QuickBlue 技术栈补全:从注册中心到 AI 模型网关
QuickBlue 选型时,我们没有追求新潮,而是以“本地好跑、上手资料多、排查成本低”为标准。最终落地的核心组件如下表:
| 组件 | 作用 | 本地环境中的角色 |
|---|---|---|
| JDK 17 | Java 服务运行基础 | 所有 Java 微服务的底座 |
| Spring Boot 3.2.x | 应用框架 | 各业务服务的基础容器 |
| Spring Cloud 2023.x | 微服务治理框架 | 提供注册发现、配置管理、网关等能力 |
| Nacos 2.3.x | 注册中心 + 配置中心 | 服务注册与配置下发,本地以单机模式运行 |
| Spring Cloud Gateway | API 网关 | 统一切入流量,也将 AI 模型调用路由到对应服务 |
| Spring AI | AI 应用接入层 | 统一封装对话模型、向量模型、结构化输出 |
| Ollama / 云端 API | 模型运行时 | 本地推理或云端调用的承载方 |
| MySQL + Redis | 业务数据与缓存 | 基础业务模块的存储依赖 |
为什么用 Nacos 而不是 Eureka 或者 Consul?两个原因:第一,QuickBlue 同时需要注册中心和配置中心,Nacos 一个组件就能兼任,减少本地环境的进程数;第二,Spring Cloud Alibaba 生态对 Nacos 的适配非常完整,配合spring.config.import之后,配置文件从 Nacos 拉取几乎零成本。Eureka 虽然更轻,但配置中心还得单独搭一套,本地环境多一个进程就多一个故障点。
1.3 一个可复现的环境,才是团队协作的前提
我在 QuickBlue 里最坚持的一件事:所有环境准备必须脚本化、可复现。团队里十来个开发,如果每个人凭记忆装环境,你根本说不清某次联调失败是因为代码还是因为某个人的本地环境差异。
最简单的做法是把中间件的启动统一收口到docker compose,把 JDK、Maven、IDEA 的版本写进 README,并提供一个check-env.sh脚本做环境自检。这样后来者可以照着文档从一个空白环境完整拉起底座,而不是靠“你帮我看看我的环境怎么跑不起来”这种低效方式。
这个思路直接影响了后面所有章节的内容,我不会只告诉你“要装什么”,而是把版本、命令、验证方式都写清楚。
2. 开发机硬件配置与工具链版本:先把底线算清楚
进入实操之前,先把开发机这件事说透。QuickBlue 这类系统对开发机的真实需求往往被低估:你以为只是多开几个服务,实际上你是同时跑着 Nacos、Redis、MySQL、网关、三四个业务服务,以及一个可能占掉好几个 GB 内存的本地模型。
2.1 本地跑模型的硬件底线计算
如果你打算在本机跑 7B 参数级别的开源模型,量级大概是这样:以 Qwen2.5 7B 的 Q4 量化版为例,模型权重约 4.7GB,推理时还需要额外的 KV Cache 和上下文窗口空间,再叠加 Spring Boot 服务自身的 JVM 内存,一台 16GB 内存的开发机基本会顶满。
我的建议分两种情况:
- 纯 API 开发(模型部署在云端或远端 GPU 机器):开发机 16GB 起步、32GB 舒适。
- 本地模型开发(用 Ollama 或 vLLM 跑模型):内存至少 32GB,强烈建议 64GB;GPU 最好有 8GB 以上显存。
磁盘也有底线。本地模型动辄几个 GB,加上多个中间件容器镜像,1TB NVMe SSD 是合理的起步配置,否则后续同时拉几个模型时,磁盘很快就爆了。
2.2 双路线选择:本地模型 Runtime 与云端 API
QuickBlue 的 AI 接入层我们设计成双路线:既支持本地 Ollama 推理,也支持云端 API。环境准备阶段必须同时验证两条路线的连通性,因为实际开发中经常出现“本地模型跑不动,切云端 API 继续联调”的情况。
如果你的开发机没有独立显卡,建议直接走云端 API 路线,日常开发和联调都够了,本地模型留给专门的测试机。如果你的开发机有 8GB 以上显存,强烈建议把 Ollama 装上,因为调试时完全离线、响应快、也没有调用消耗,对模型输出的迭代非常有帮助。
两条路线的详细对接方式我在第 5 节展开,这里先给你一个版本层面的判断。
2.3 工具链版本匹配明细表
版本匹配是环境准备里最容易出问题的环节,尤其是 Spring Boot、Spring Cloud、Spring AI 三个框架的版本必须互相兼容。我直接给出 QuickBlue 当前用的版本组合:
| 工具/框架 | 推荐版本 | 选型原因 |
|---|---|---|
| JDK | 17 LTS | Spring Boot 3.x 的最低兼容版本,也避免升级到 21 带来的本地工具链兼容问题 |
| Maven | 3.9.x | 稳定,IDEA 内置兼容好 |
| Spring Boot | 3.2.5 | Spring AI 1.0.0 正式版对其支持完善 |
| Spring Cloud | 2023.0.3 | 与 Spring Boot 3.2.x 版本对应 |
| Spring AI | 1.0.0 及以上 | 模块化清晰,支持 Ollama、OpenAI 兼容接口 |
| Nacos | 2.3.2 | 2.x 之后的 gRPC 端口机制稳定 |
| Docker / Podman | Docker Desktop 4.30+ / Podman 4.6+ | 本地中间件容器化运行 |
| Ollama | 0.3+ | 跨平台,拉取模型方便 |
这里有一个非常实际的建议:不要盲目升版本。Spring AI 的迭代速度很快,但每次大版本升级都可能改配置项的命名空间,比如spring.ai.ollama.chat在不同版本间就调整过。如果团队目标是先把业务跑通,锁版本比追求新版本更明智。
3. 用 IDEA 拉起 QuickBlue 微服务骨架:父工程、模块拆分与依赖管理
环境底子打好了,接下来是把 QuickBlue 的代码骨架立起来。我们团队日常用 IDEA,所以下面以 IDEA 的操作为例,但底层的 Maven 结构你用命令行或 VS Code 也能复现。
3.1 父工程与模块清单
QuickBlue 的模块划分遵循一个原则:基于业务和能力边界拆分,而不是按代码复用拆分。所有模块都挂在同一个父 Maven 工程下,公共代码统一收敛到quickblue-common。
模块清单:
quickblue-common:公共工具、统一返回体、异常处理、常量定义。quickblue-base:基础业务服务,包含用户、角色、权限、字典等基础数据能力。quickblue-business-ai:AI 能力编排服务,负责对接模型、管理会话、处理 Prompt。quickblue-gateway:网关服务,负责路由转发、鉴权、以及 AI 相关请求的聚合路由。quickblue-auth:认证服务,负责登录态、Token 签发与校验。
在 IDEA 里创建时,我不会用 Initializr 一次性把问题扔给它,而是先建一个空的 Maven 父工程,然后在父工程上右键新建 Module,依次选择对应的 Spring Boot 依赖。这样模块边界最清晰。
父工程 POM 的骨架大致如下:
<groupId>com.quickblue</groupId> <artifactId>quickblue-application</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>quickblue-common</module> <module>quickblue-gateway</module> <module>quickblue-auth</module> <module>quickblue-base</module> <module>quickblue-business-ai</module> </modules> <properties> <spring.boot.version>3.2.5</spring.boot.version> <spring.cloud.version>2023.0.3</spring.cloud.version> <spring.ai.version>1.0.0</spring.ai.version> </properties>3.2 依赖版本集中管理的两种方式
微服务工程最大的隐患是依赖各自为政。A 服务用这个版本,B 服务用那个版本,联调时不报错还好,一旦报错,你分不清是代码兼容问题还是依赖版本问题。
我推荐两种方式叠加使用:
第一种是父 POM 里的dependencyManagement。父工程统一声明所有关键依赖的版本,子模块只声明 groupId 和 artifactId,不写版本号。这是 Java 工程的老传统,也是最直观的方式。
第二种是外部化 BOM 导入。对于 Spring Boot、Spring Cloud 这类官方已经提供 BOM 的框架,用import作用域直接引入即可。Spring Cloud Alibaba 和 Spring AI 也都有对应的 BOM,简化版本管理:
<dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2023.0.3.0</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring.ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>这里有个容易被坑的地方:Spring Cloud Alibaba 的版本号与 Spring Cloud 的版本号不是一一对应的。比如 Spring Cloud 2023.0.3,对应的 Spring Cloud Alibaba 可能是 2023.0.3.0。如果你只盯着 Spring Cloud 版本,很容易配错。建议直接到 Spring Cloud Alibaba 官方文档的版本说明页面确认对应关系。
3.3 微服务拆分的最小集原则
很多人在拆分微服务时会过度设计,QuickBlue 的经验是:本地环境能跑通的最小集才是微服务拆分的第一版参考标准。如果你拆出十几个服务,本地联调一次要启动十几个进程,单机内存直接见底,这种项目连日常开发效率都保障不了,更不用谈交付。
QuickBlue 第一阶段只拆了五个模块,加上网关正好六个,这套组合在 32GB 的开发机上能流畅跑起来。等业务复杂度真正上来之后,再按领域边界拆分新的服务,比一开始就拆得稀碎要科学得多。环境准备本质上也会反推你的架构是否合理:如果一套本地环境起一个服务就卡半天,那架构大概率有问题。
4. 中间件与注册中心本地化:Nacos、MySQL、Redis 的容器化启动
骨架工程有了,接下来是整个环境准备的重头戏:中间件。QuickBlue 本地联调依赖 Nacos、MySQL、Redis,如果做 AI 相关业务,还需要一个可用的模型服务或 API key。
4.1 Nacos 2.x 的单机启动与端口玄机
Nacos 在 QuickBlue 里承担两个角色:注册中心和配置中心。本地开发用单机模式就行,不需要搞集群。很多团队被 Nacos 坑过一次,基本都是同一个原因:只映射了 8848 端口。
Nacos 2.x 和 1.x 最大的区别是引入了 gRPC 通信。服务注册、配置监听、服务发现的长连接都走 gRPC,主端口是 8848,但 gRPC 端口默认是主端口加 1000,也就是 9848。如果本地只映射了 8848,服务端会显示正常,但客户端服务注册会反复失败,日志里出现Client not connected, current status: STARTING。
正确的启动命令是这样:
docker run -d --name nacos-quickblue \ -e MODE=standalone \ -e JVM_XMS=256m \ -e JVM_XMX=512m \ -p 8848:8848 \ -p 9848:9848 \ nacos/nacos-server:v2.3.2启动后先不要急着接服务,先确认 Nacos 控制台能打开,再确认 9848 端口处于监听状态。可以用docker logs看启动日志,看到startup相关的成功日志再继续。
4.2 用 docker compose 一键拉起底座中间件
如果每次手动敲docker run,那你迟早会漏掉某个环境变量。QuickBlue 把中间件统一收口到一个docker-compose.yml,一条命令拉起所有基础依赖:
services: mysql: image: mysql:8.0 container_name: quickblue-mysql environment: MYSQL_ROOT_PASSWORD: root TZ: Asia/Shanghai ports: - "3306:3306" volumes: - mysql-data:/var/lib/mysql redis: image: redis:7.2 container_name: quickblue-redis ports: - "6379:6379" nacos: image: nacos/nacos-server:v2.3.2 container_name: quickblue-nacos environment: - MODE=standalone - JVM_XMS=256m - JVM_XMX=512m ports: - "8848:8848" - "9848:9848" volumes: mysql-data:MySQL 有两个细节值得单独提醒。第一是字符集:如果数据库默认字符集不是 utf8mb4,AI 会话内容里的 emoji 或特殊字符入库时会报错。第二是时区:容器默认时区是 UTC,和本机时间对不上会影响日志排查,所以上面的配置里加了TZ=Asia/Shanghai。
4.3 配置中心的首份配置如何写入
中间件启动后,下一步是把共享配置写入 Nacos。QuickBlue 的做法是把公共配置(数据源、Redis、公共开关)放到 Data ID 为quickblue-common.yaml的配置里,各服务自己的配置文件保留在本地,只把关键内容通过spring.config.import拉取。
在 Spring Boot 3.2 和 Spring Cloud 2023 的组合里,从 Nacos 拉配置的标准写法是:
spring: config: import: - nacos:quickblue-common.yaml?group=DEFAULT_GROUP这条配置拉取机制经常被忽略,尤其是从旧版本升级上来的团队。以前用bootstrap.yml,现在默认需要用spring.config.import,否则你在 Nacos 里改了配置,服务端完全感知不到。
5. AI 引擎接入底座的两种路径:Ollama 本地推理与云端 API
QuickBlue 既然叫 AI 微服务应用底座,AI 引擎的环境准备自然避不开。这里我把两条路线的配置都完整贴出来,你按自己的硬件条件二选一。
5.1 Ollama 本地模型的部署与验证
安装 Ollama 这一步没什么悬念,去官网下载对应系统的安装包就好。关键是选模型。QuickBlue 默认使用 Qwen2.5 7B 做日常验证,因为它在中文场景表现稳定、资源占用又相对友好。
拉取并启动模型:
ollama pull qwen2.5:7b ollama run qwen2.5:7b看到输入框并能正常对话,说明本地模型服务已经通了。Ollama 默认监听 11434 端口,可以用下面的命令验证 API 是否可访问:
curl http://127.0.0.1:11434/api/tags返回模型列表就说明模型运行时正常。这一步验证非常关键,因为后面如果 Spring AI 连不上模型,问题大概率出在 Ollama 没启动或者模型没拉全,而不是代码本身的问题。
5.2 Spring AI 对接模型服务的基础配置
QuickBlue 的quickblue-business-ai模块中,接入本地 Ollama 的配置是这样的:
spring: ai: ollama: base-url: http://127.0.0.1:11434 chat: options: model: qwen2.5:7b temperature: 0.7如果是走云端 API,配置改成 OpenAPI 兼容的方式即可。现在很多云端模型服务都提供 OpenAI 兼容的接口,Spring AI 对这类接口的支持也最成熟:
spring: ai: openai: base-url: https://api.example.com/v1 api-key: ${AI_API_KEY} chat: options: model: gpt-4o-mini注意api-key不要硬编码在配置文件里,用环境变量注入。这个约定不是矫情,而是环境准备阶段就养成的好习惯,不然配置一不小心提交到代码仓库,密钥就泄露了。
5.3 两条路线的取舍建议
| 对比维度 | 本地 Ollama | 云端 API |
|---|---|---|
| 硬件要求 | 32GB 内存 + 8GB 显存起步 | 几乎无要求 |
| 响应速度 | 取决本地显卡,一般较快 | 受网络影响 |
| 离线能力 | 完全离线 | 不可离线 |
| 成本 | 一次性硬件成本 | 按量计费 |
| 隐私 | 数据不出本机 | 数据上传云端 |
| 调试便利性 | 可随时换模型、改参数 | 需要联网,API 波动影响排查 |
我的建议很直接:开发阶段优先本地 Ollama,因为你可以随便调参数、随便重启,不产生任何 API 费用;只有当你需要验证云端模型特有能力和真实生产环境表现时,再切到云端 API。把两套配置都放到环境变量开关后面,切换成本几乎为零。
6. 首次联调复盘:服务启动顺序与五个常见环境坑
最后这部分是 QuickBlue 联调时的真实复盘。代码层面其实大家写起来都差不多,真正拉低效率的是环境层面的问题。
6.1 启动顺序背后的依赖逻辑
微服务启动不是随手点的,QuickBlue 本地联调建议按这个顺序启动:
- 基础设施:MySQL、Redis、Nacos。
- 注册中心就绪:打开 Nacos 控制台,确认命名空间和分组正确。
- 基础能力服务:
quickblue-common是被依赖的 jar 包,不用单独启动;先启动quickblue-base。 - 网关服务:
quickblue-gateway。 - AI 业务服务:
quickblue-business-ai。 - 若走本地模型路线,提前把 Ollama 拉起来。
为什么网关不能先启动?因为网关启动后会注册到 Nacos,路由配置依赖服务发现。如果后面服务还没注册上,网关会因为找不到实例而报 503。虽然后续服务注册后网关会自动恢复,但日志里多一堆无意义的告警,干扰排查。
6.2 五个常见坑的完整排查链路
坑一:服务注册不上 Nacos,但控制台能打开。
这个症状主要会翻译成:“服务一直显示不健康”。排查链路是:
- 先确认容器是否映射了 9848 端口,用
netstat或lsof检查本机端口监听状态。 - 再确认服务配置里的
spring.cloud.nacos.discovery.server-addr是否写成了127.0.0.1:8848,这个写法本身没问题,但少了 gRPC 端口依赖。 - 最后看服务日志里有没有 gRPC 连接失败的异常,有的话基本就是端口问题。
坑二:配置中心的配置改了,服务不生效。
这种时候先看服务启动日志里有没有加载 Nacos 配置的记录。Spring Cloud 2023 之后不推荐直接用bootstrap.yml,如果你没配spring.config.import,服务根本不会去拉远程配置。这个问题很隐蔽,因为本地配置文件都在,服务能正常启动,但远程配置永远不生效。
坑三:网关路由 503,但目标服务在 Nacos 里明明是健康的。
这个坑的典型场景是网关没有使用负载均衡地址。网关配置路由时如果直接写了http://127.0.0.1:8081,服务实例一变地址就失效。正确做法是使用 lb 前缀,让网关从 Nacos 动态发现实例:
spring: cloud: gateway: routes: - id: route-business-ai uri: lb://quickblue-business-ai predicates: - Path=/ai/**坑四:Spring AI 调用模型一直超时。
先别急着调代码。用 curl 直接打 Ollama 或云端 API,确认模型服务的连通性。如果是本地 Ollama,确认模型是否已下载完成,第一次调用时可能还在加载模型;如果是云端 API,确认网络是否通、API key 是否有效、是否限流。把模型层的问题和代码层的问题分开,排查效率会高很多。
坑五:Lombok 编译失败,注解生成的方法找不到。
这类问题通常表现为新 clone 的代码一编译就报符号找不到。多数原因是 JDK 版本和 Lombok 版本不匹配,或者工程里同时存在多个 Lombok 版本。QuickBlue 的处理是在父 POM 统一声明 Lombok 版本,并启用annotationProcessorPaths显式指明注解处理器路径,避免 IDE 默认行为差异。
6.3 一分钟快速验证清单
联调开始前,按这个清单快速检查一轮:
- Nacos 控制台可达,服务列表为空或仅包含预期服务。
- Redis
ping返回PONG。 - MySQL 能用配置的用户名密码登录,字符集为 utf8mb4。
- 本地模型用
curl能请求通。 - 网关
/actuator/health返回UP。 - 任意调用一个经过网关的业务接口,确认路由和鉴权链路正常。
这些检查全部通过,环境准备才算合格;只要有一项不通过,后面的联调大概率会被同样的问题卡住。
在环境准备这块我最大的体会是:不要以为环境准备是“辅助工作”,它在微服务底座项目里是最该先被工程化的内容。把环境脚本化、版本化、验证化之后,团队新成员从拉代码到跑通底座只需要半天,而不是一周。这个投入相当值得。