后端架构演进:从单体到云原生 —— 一个35岁老码农的血泪史
坐标上海,住在公司步行10分钟的出租屋里,Vim配置比女朋友还熟。上周五凌晨三点还在修一个线上OOM,突然想起自己去年面试时被问“你们系统怎么拆微服务”的尴尬场面——今天就来聊聊,我是怎么从一个单体应用里爬出来,滚进云原生这个大坑的。
起因:简历上的“高并发”差点让我背锅
去年年底想跳槽,更新简历时手一抖写上了“主导高并发系统架构设计”。结果面某大厂时,面试官眯着眼问:“你这系统QPS多少?怎么做的弹性伸缩?有做混沌测试吗?”我支支吾吾说“我们……用Nginx做了负载均衡”,对方笑了笑,没再追问。
那一刻我意识到:光会写业务逻辑,在35岁的年纪已经不够看了。
更打脸的是回公司后,产品老大在周会上拍桌子:“双11流量预估翻三倍,现在系统撑不住怎么办?”运维兄弟幽幽补刀:“上次扩容加了两台机器,结果数据库连接池爆了,半夜报警吵醒我三次。”
得,躲不掉了。要么重构,要么等着被流量冲垮。领导一句话:“你不是简历写得挺牛?这次你牵头搞云原生改造。”
第一阶段:单体架构的“甜蜜期”早已过去
我们系统最初是个典型的Spring Boot单体应用:前端Vue + 后端Java + MySQL + Redis,部署在两台阿里云ECS上,Nginx反向代理。早期日活几千人,跑得飞快,改个Bug五分钟上线,日子美滋滋。
但随着用户量涨到百万级,问题全来了:
- 发布一次全站停服:哪怕只改了个文案,也得停机部署。
- 资源浪费严重:订单模块忙死,用户中心闲得发霉,但CPU内存一起吃。
- 故障扩散:一个慢SQL拖垮整个JVM,全站502。
- 数据库成瓶颈:所有业务共用一个MySQL实例,连表查询越来越慢。
最惨的是去年618,促销活动刚上线,数据库主从同步延迟飙到300秒,用户下单成功但库存没扣,超卖了两千单。老板脸色铁青,我躲在厕所刷Stack Overflow,差点哭出来。
拆!但别瞎拆
很多人以为微服务就是“把单体切成一堆小服务”,天真了。拆分不当,等于把一个大雷换成十个地雷。
我们先做了领域划分(感谢DDD救我狗命),按业务边界拆出:
user-service(用户认证、资料)order-service(下单、支付回调)product-service(商品信息、库存)notification-service(短信、站内信)
每个服务独立数据库,通过Feign + Ribbon调用。初期确实爽:订单模块挂了不影响用户登录,发布粒度细了,测试也隔离了。
但新问题立马冒头:
- 分布式事务:下单要扣库存+创建订单+发通知,三个服务怎么保证原子性?
- 链路追踪缺失:一个请求跨五个服务,日志散落在不同机器,查Bug像破案。
- 配置爆炸:每个服务都要维护自己的
application.yml,改个超时参数要改八处。
那会儿每天早上第一件事不是喝咖啡,是看SkyWalking的调用链有没有红色警报。运维同事看我的眼神都带着怜悯。
容器化:Docker救不了世界,但能救我下班时间
拆完微服务,部署又成了噩梦。每台机器要装JDK、Maven、Node.js(因为前端也要打包),环境不一致导致“在我机器上能跑”。
被逼无奈,开始搞Docker。第一次写Dockerfile时,把整个target/目录COPY进去,镜像1.2G,CI流水线跑一次十分钟。测试小哥吐槽:“你这镜像比我的游戏安装包还大。”
后来学乖了,用多阶段构建:
# 构建阶段
FROM maven:3.8-openjdk-17 AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
# 运行阶段
FROM openjdk:17-jre-slim
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
镜像压到180MB,CI时间从10分钟降到90秒。运维终于不用半夜被叫起来“手动部署”。
但真正解放生产力的是Kubernetes。虽然一开始被YAML劝退(谁还记得kind: Deployment和kind: Service的区别?),但一旦跑通,自动扩缩容、滚动更新、健康探针这些功能真香。
比如订单服务高峰期自动从3副本扩到10副本:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
双11当天看着Pod数自动涨上去,我在工位啃着泡面笑出声——这钱花得值。
云原生不是终点,而是新坑的起点
上了K8s就万事大吉?Too young.
第一个坑是服务网格。早期用Spring Cloud Gateway做API网关,但熔断、限流规则写死在代码里,改个阈值得重新打包。后来引入Istio,用VirtualService动态配置:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
- route:
- destination:
host: order-service
subset: v1
retries:
attempts: 3
perTryTimeout: 2s
但Istio的学习曲线陡得像珠穆朗玛峰。有次配置错了mTLS,整个集群服务间通信中断,线上静默了20分钟。产品经理在群里@我:“系统是不是挂了?” 我盯着Prometheus监控图,手抖得不敢回。
第二个坑是可观测性。以前单体时代,ELK看日志就行。现在微服务+容器,日志分散、指标杂乱、链路断裂。我们最终搭了一套组合拳:
- Logging:Fluentd收集容器日志 → Kafka → ES
- Metrics:Prometheus抓取Micrometer指标 + Grafana看板
- Tracing:Jaeger采样全链路,配合OpenTelemetry SDK
现在查问题流程变成:Grafana发现CPU飙升 → Jaeger定位到具体Span → Kibana搜该Pod日志。效率提升十倍,再也不用求运维导日志了。
数据库怎么办?别只顾着拆服务
很多人只关注服务拆分,忘了数据才是最难拆的。
我们早期犯了个致命错误:把用户表和订单表物理分离后,还保留外键关联。结果跨库JOIN直接报错,ORM框架崩了。
解决方案分三步走:
- 去外键:所有关联靠ID,业务层校验
- 读写分离:主库写,从库读(用ShardingSphere中间件)
- 分库分表:订单表按user_id哈希,分16库16表
但分库分表带来新问题:全局ID怎么生成?雪花算法?UUID太长?最后选了Leaf-segment方案,用DB批量缓存ID段,性能扛住了。
接口设计也得改。以前单体时一个/order/detail接口返回订单+商品+用户信息,现在得聚合多个服务:
// 聚合服务(BFF层)
public OrderDetailVO getOrderDetail(Long orderId) {
Order order = orderClient.getOrder(orderId); // 调order-service
Product product = productClient.getProduct(order.getProductId()); // 调product-service
User user = userClient.getUser(order.getUserId()); // 调user-service
return OrderDetailVO.builder()
.order(order)
.product(product)
.user(user)
.build();
}
虽然增加了网络开销,但通过并行调用 + 缓存优化,P99延迟控制在200ms内。前端同学终于不用自己拼三个接口了。
面试题?我现在出题都带血泪经验
最近帮公司面试后端,必问一道题:
“如果让你把现在的单体系统改造成云原生架构,你会考虑哪些关键点?”
以前我答“拆微服务、上容器、加监控”,现在我会说:
| 维度 | 单体架构 | 云原生架构 | 踩过的坑 |
|---|---|---|---|
| 部署 | 手动SCP jar包 | GitOps + ArgoCD | 镜像tag没对齐,发布错版本 |
| 通信 | 内部方法调用 | gRPC/HTTP + 服务网格 | 超时设置不合理,雪崩 |
| 数据 | 共享DB | 分库分表 + CDC | 事务补偿逻辑漏了状态机 |
| 可观测 | 查单机日志 | Metrics+Logs+Traces | 采样率设太高,ES炸了 |
真正的云原生不是技术堆砌,而是思维方式的转变:从“保证单点稳定”到“容忍局部失败”,从“追求零故障”到“快速自愈”。
写在最后:35岁还在写代码,值不值?
有人问我,年纪不小了还折腾这些新东西干嘛?不如转管理。
但每次看到K8s自动拉起崩溃的Pod,看到Grafana上平稳的曲线,看到用户反馈“系统变快了”——那种亲手解决问题的快感,是开会画PPT给不了的。
而且说实话,懂云原生的35岁程序员,比只会CRUD的25岁新人更抢手。上周猎头开价比我现薪资高40%,就因为我简历写了“Istio + K8s + Prometheus全链路落地”。
所以别信什么“35岁危机”。只要保持折腾,键盘敲得动,Vim配得顺,咱们老码农照样能打。
对了,如果你也在搞架构升级,推荐几个救命教程:
- 《Cloud Native Patterns》by Cornelia Davis(别被厚吓到,前五章就够用)
- B站“云原生实验室”系列(中文讲Istio最清楚)
- GitHub搜“k8s-the-hard-way”(理解原理必看)
至于面试题?记住:面试官不关心你用什么技术,只关心你怎么解决问题。把我上面踩的坑讲清楚,offer基本稳了。
(完)
作者:沪漂老码农,Vim党,目前沉迷用kubectl get pods -w看Pod重生。个人博客不定期更新,内容包含但不限于:如何用Vim写K8s YAML、35岁如何不被优化、以及为什么前端动画比后端架构简单一万倍。

评论 0