Java 项目从开发到上线:单体与微服务的日常流程
之前带过小团队,单体和微服务都做过。新人最常问的不是「Spring 注解怎么用」,而是 需求从哪进、代码怎么合、上线谁点、线上挂了先看哪。这篇按真实协作顺序写:开发 → 提测 → 上线 → 排错 → 改表发版,单体和微服务各举一套例子,方便你对照自己公司流程裁剪。
题目
请描述你参与的 Java 项目从开发到上线的完整流程(单体和微服务均可)。线上出问题如何排查、查日志、定位?修复后如何发版?表结构变更和 SQL 脚本在生产环境怎么执行才安全?
参考答案(口述要点)
- 开发链路:需求 → feature 分支 → 自测/Review → 合并 → CI 打包 → 测试/预发 → 发布单(含回滚、SQL)→ 生产发布 → 监控观察
- 单体:一个 jar,滚动或停机替换;配置改 yml 重启
- 微服务:多服务按依赖顺序发(下游先),Nacos 看健康,日志用 traceId 串链路
- 排错:先现象和时间 → 监控缩范围 →
grep/tail日志或 SkyWalking → 复现 → hotfix 分支- 改表/SQL:测试先跑、Flyway 版本化、低峰执行、大表分批 UPDATE、DDL 与发版顺序对齐
详细解析:主流流程总览
不管单体还是微服务,主干差不多:
| 阶段 | 产出物 | 谁负责 |
|---|---|---|
| 开发 | 代码、单元测试、接口文档 | 开发 |
| 提测 | 测试环境包、变更说明 | 开发 + 测试 |
| 上线 | 发布单、回滚方案、SQL 脚本 | 开发 + 运维/负责人 |
| 值守 | 监控、日志、告警响应 | 值班 / 发布人 |
下面用两个虚构但贴近真实的例子贯穿全文。
例子 A:单体 Spring Boot 订单服务
背景:order-service 一个 jar,MySQL + Redis,Nginx 反代,两台应用机 + 一台数据库。
1. 接到需求
需求:订单列表支持按「手机号后四位」筛选。
- 产品提 Jira / 飞书任务,开发评估 接口改动 + 索引
- 和 DBA 或自己确认:
orders表是否加索引,数据量多大
2. 分支开发
git checkout develop
git pull
git checkout -b feature/order-phone-suffix-filter改动点:
OrderController增加查询参数phoneSuffixOrderMapper.xml改WHERE(禁止%suffix%全模糊扫全表,用phone LIKE '____1234'或冗余字段)- 补充单元测试 / 本地 Postman 自测
@GetMapping("/orders")
public PageResult<OrderVO> list(
@RequestParam(required = false) String phoneSuffix,
@RequestParam(defaultValue = "1") int page) {
return orderQueryService.listByPhoneSuffix(phoneSuffix, page);
}3. 提测与合并
git push origin feature/order-phone-suffix-filter
# 提 MR → develop,至少一人 ReviewCI(Jenkins / GitLab CI / Gitee Go)跑:
mvn test- 打包
order-service.jar - 可选:Sonar 扫描
测试环境部署:运维脚本或 docker compose up,改的是 测试库,不是生产。
4. 上线前准备(单体常见清单)
发布单里写清:
【变更】订单列表支持手机号后四位筛选
【应用】order-service.jar v1.12.0
【配置】无
【数据库】见 sql/V1.12.0__add_idx_phone.sql(测试已执行)
【回滚】上一版 jar:order-service-1.11.0.jar;索引可保留
【验证】GET /api/orders?phoneSuffix=1234 返回正常;监控无 5xx 飙升5. 生产发布(滚动/停机二选一)
停机发布(小流量常见):
# 1. 通知 / 挂维护页(可选)
# 2. 停服务
systemctl stop order-service
# 3. 备份现网 jar
cp /opt/app/order-service.jar /opt/backup/order-service-$(date +%F).jar
# 4. 替换新包
cp order-service-1.12.0.jar /opt/app/order-service.jar
# 5. 若有 SQL,先 DBA 或运维执行(见下文「表修改」)
mysql -h prod-db -u deploy -p order_db < sql/V1.12.0__add_idx_phone.sql
# 6. 启动
systemctl start order-service
tail -f /var/log/order-service/app.log双机滚动(一台一台换,前面 Nginx down 一台):
upstream order_backend {
server 10.0.1.11:8080;
server 10.0.1.12:8080 down; # 先下线 12
}换完 12 再换 11,最后去掉 down。
6. 上线后观察 15~30 分钟
- 看 错误率、RT、CPU(Prometheus / 云监控)
- 抽几条核心接口日志是否正常
- 有问题立刻按回滚方案切旧 jar
例子 B:微服务「用户中心 + 订单」
背景:user-service、order-service 独立部署,注册中心 Nacos,网关 Spring Cloud Gateway,链路追踪 SkyWalking。
和单体的差异
| 项 | 单体 | 微服务 |
|---|---|---|
| 部署单元 | 1 个 jar | 多个服务,可能不同节奏发版 |
| 配置 | application.yml | Nacos 配置中心,分 namespace |
| 调用链 | 进程内 | Feign / HTTP,要 traceId |
| 数据库 | 常一个库 | 一服务一库 更常见 |
| 发布顺序 | 一次 | 下游先、网关最后等依赖顺序 |
需求实例:订单展示用户昵称
订单服务要通过 Feign 调 user-service 拿昵称。
开发:
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/internal/users/{id}")
UserDTO getById(@PathVariable Long id);
}注意:user-service 先上线兼容接口,或两服务 同版本窗口 一起发,避免 order 调了新接口 user 还没上。
发布顺序示例:
1. user-service(提供 /internal/users/{id})
2. order-service(依赖新 Feign)
3. gateway(若路由/鉴权有变)Nacos 里确认实例健康:
# 控制台看 user-service 实例数 = 2,状态 UP问题排查:线上报错怎么查
第一步:确认现象
- 用户反馈 / 告警:5xx 飙高?某个接口超时?还是定时任务失败?
- 记下 时间范围、接口路径、用户 ID、traceId(前端或网关常带
X-Request-Id)
第二步:看监控缩小范围
| 信号 | 可能方向 |
|---|---|
| 单接口 RT 涨 | 慢 SQL、下游 Feign 超时、锁 |
| 整机 CPU 100% | 死循环、Full GC、流量突增 |
| 错误集中在某台 | 该机配置旧、发布不完整 |
| 全集群同时挂 | 依赖(MySQL、Redis、Nacos)故障 |
第三步:查日志(单体 & 微服务通用)
日志路径(按你们实际改):
/var/log/order-service/app.log
/var/log/order-service/error.log常用命令:
# 最近 500 行错误
tail -n 500 /var/log/order-service/error.log
# 按时间筛(当天 14:00 之后)
awk '$1" "$2 >= "2026-06-08 14:00:00"' /var/log/order-service/app.log
# 按 traceId 串一整次请求(微服务必备)
grep 'traceId=a1b2c3d4e5f6' /var/log/order-service/app.log
# 统计某异常出现次数
grep -c 'NullPointerException' /var/log/order-service/error.logSpring Boot 日志级别临时调高(预发/单机生产慎用):
# Nacos 或 application-prod.yml 动态改
logging.level.com.lewyon.order.mapper: DEBUG改完 观察完记得改回 INFO,否则磁盘爆。
微服务:用链路追踪
SkyWalking / Zipkin 搜 traceId:
gateway → order-service → user-service → MySQL
200ms 1800ms ← 瓶颈在这一眼看出是 user-service 慢 还是 order 里 SQL 慢。
定位到代码后
- 本地或预发 复现
- 修分支
hotfix/order-npe-20260608 - 走紧急发布(可跳过部分非核心测试,但要有回滚包)
- 发布说明写 根因 + 影响面 + 是否补数据
实例:一次真实的「接口超时」排查
现象:14:02 告警,GET /api/orders P99 从 200ms 到 8s。
操作记录:
# 1. 看网关日志,是否只有 order 路由慢
grep '14:0[2-5]' /var/log/gateway/access.log | grep '/api/orders'
# 2. order 服务错误日志
grep '14:0[2-5]' /var/log/order-service/error.log | grep -i timeout
# 3. 发现大量 Feign Read timed out calling user-service
# 4. 查 user-service 同一时段
grep '14:0[2-5]' /var/log/user-service/app.log | grep 'slow'
# 5. user 库慢查询日志命中:
# SELECT * FROM users WHERE nickname LIKE '%张%';结论:运营后台新上了模糊搜昵称,没走索引,拖垮 user库,连带 order 调 Feign 超时。
处理:
- 短期:运营功能降级关闭;Feign 超时 + 熔断返回默认昵称
- 长期:搜昵称走 ES 或限制前缀匹配 + 索引
复盘:预发压一遍关联接口;Feign 必须配 超时和 fallback,不能无限等。
表修改与 SQL 脚本:怎么安全执行
脚本管理规范
sql/
├── V1.11.0__baseline.sql
├── V1.12.0__add_idx_orders_phone.sql
└── V1.12.0__add_column_remark.sql推荐 Flyway / Liquibase 纳入应用启动或独立 job,避免「谁手工跑过记不清」。
手工发布时 checklist:
□ 测试环境执行成功,有执行记录截图
□ 脚本可重复/幂等(或明确只能跑一次)
□ 大表变更评估时间(加索引是否 Online DDL)
□ 有回滚说明(删索引、删字段的代价写清)
□ 生产执行窗口(低峰期)
□ 执行人 + 复核人实例:加字段 + 加索引
-- V1.12.0__add_idx_orders_phone.sql
-- 执行前:备份 / 确认行数
SELECT COUNT(*) FROM orders;
-- 加字段(允许 NULL,避免锁表太久)
ALTER TABLE orders
ADD COLUMN phone_suffix CHAR(4) NULL COMMENT '手机号后四位' AFTER phone;
-- 回填(分批,别一次 UPDATE 全表)
UPDATE orders SET phone_suffix = RIGHT(phone, 4) WHERE id BETWEEN 1 AND 10000;
-- ... 分批到完
-- 加索引(InnoDB 5.7+ 可考虑 ALGORITHM=INPLACE)
CREATE INDEX idx_orders_phone_suffix ON orders(phone_suffix);禁止在生产直接:
- 无
WHERE的UPDATE/DELETE - 不设条件的
ALTER大表高峰期执行 - 通过微信发 SQL 让运维复制粘贴(容易错环境)
执行方式
# 方式 1:mysql 客户端(记录输出)
mysql -h 10.0.0.10 -u deploy_user -p order_db \
< sql/V1.12.0__add_idx_orders_phone.sql \
2>&1 | tee /tmp/sql-run-20260608.log
# 方式 2:Flyway 随应用启动(更推荐)
# spring.flyway.enabled=true
# 新版本 jar 启动时自动 migrate先 DDL 再发代码,还是先代码再 DDL?
| 变更类型 | 顺序 |
|---|---|
| 仅加可空字段 | 可先 DDL,新旧代码都能跑 |
| 加非空字段无默认值 | 必须分批:加可空 → 回填 → 改非空 → 发代码 |
| 删字段 | 先发代码不再读写字段,下个版本再 DDL 删 |
| 改字段类型 | 尽量新字段迁移,别原地强改 |
修改上线:热修 vs 常规发版
| 方式 | 适用 | 注意 |
|---|---|---|
| 常规发版 | 计划内需求 | 走完整测试 + 发布单 |
| 热修复 | P0 故障 | 最小改动分支,只修一事,仍要回滚包 |
| 只改配置 | 开关、限流阈值 | Nacos 改完 发布配置,不必换 jar |
| 只执行 SQL | 数据修复 | 单独评审,最好事务 + 备份 |
热修示例:
git checkout -b hotfix/fix-order-npe release-1.11
# 修一行空指针
git commit -m "fix: order list NPE when phone is null"
# 打 tag 1.11.1,只部署 order-service,30 分钟观察单体 vs 微服务:日常对照表
| 场景 | 单体 | 微服务 |
|---|---|---|
| 本地启动 | 一个 SpringBootApplication | 起 Nacos + 网关 + 相关服务,或用 Docker Compose |
| 配置变更 | 改 yml 重启 | Nacos 改配置,部分支持 refresh |
| 日志 | 一个 app.log | 按服务名分文件,用 traceId 关联 |
| 发布 | 一个 jar | 多服务,注意顺序和兼容性 |
| 回滚 | 换旧 jar | 各服务版本矩阵要对齐(兼容性矩阵文档) |
| 数据库 | 常一个库多表 | 按服务拆库,禁止跨库 join 在代码里硬搞 |
新人容易踩的坑
- 测试库数据当生产:执行 SQL 前
SELECT @@hostname或看提示符环境标红。 - 日志只会在本地看:生产要学会
grep、tail、链路平台。 - 发版不通知:上下游、运营、客服要不要知会,写在发布单里。
- 没有回滚包:新版本 jar/SQL 备份路径写进 checklist。
- 微服务单点先发:order 依赖 user 新接口,user 没上就先别发 order。
写在最后
不用背一整套 ITIL,把 分支 → CI → 发布单 → 观察 → 日志/trace → SQL 规范 这条线走熟,单体和微服务只是「部署几个 jar」和「谁先谁后」的差别。建议你们团队把本公司 环境地址、日志路径、发布窗口、SQL 评审人 整理成一页 wiki,比任何通用文章都管用。
创作不易,转载请注明出处和作者。
