后端架构演进:从单体到云原生——一个百度搜索算法工程师的“代码人生”
大家好,我是小陈,在百度干了两年算法,主要搞搜索相关。没错,就是那个你每天搜“今天杭州天气怎么样”背后默默搬砖的人(虽然其实不是我直接写前端展示逻辑 😅)。坐标杭州,常年在阿里网易的地盘上晃悠,偶尔投个简历感受下外面世界的温度。
上周五晚上10点,我正用VSCode写着Python脚本处理离线特征,突然手机一震——产品PM发来消息:“陈哥,咱这个新需求能不能下周上线?老板很看好!” 我内心OS:又来了……但嘴上只能回“好的,我们评估下”。
结果一评估,发现当前系统是典型的Java单体应用,跑在几台物理机上,数据库还是MySQL 5.6,部署靠手动 scp + restart。别说快速迭代了,改个配置都得提心吊胆,生怕把线上服务搞崩。去年双11期间就因为一个NPE(NullPointerException)导致搜索建议接口503,被值班同学疯狂@,那会儿真的想砸电脑。
于是,我和后端兄弟们一合计:是时候重构了!
从“能跑就行”到“不敢动”的单体时代
刚入职那会儿,我们的后端系统其实挺简单的:
- 一个Spring Boot项目
- 所有业务逻辑塞在一个 repo 里(包括用户管理、搜索日志、推荐策略、甚至定时任务)
- 数据库就一张大表
search_records,字段多到连Navicat都要卡 - 部署方式:开发打包成 jar,运维手动拷贝到服务器,
nohup java -jar app.jar &
听起来是不是有点熟悉?很多创业公司或早期项目都是这样起步的——快、糙、猛。产品经理要个新功能?改两行代码,测一下,上线!那时候我们甚至觉得“微服务”是大厂才玩得起的奢侈品。
但问题很快来了:
- 代码耦合严重:加个用户画像功能,不小心改到了日志模块,测试没覆盖到,线上炸了。
- 部署风险高:哪怕只改了个文案,也得全量发布,万一回滚慢,影响面巨大。
- 资源浪费:搜索高峰期CPU打满,但用户管理模块几乎空闲,没法弹性伸缩。
- 技术栈僵化:想用Python写个轻量级特征服务?不行,整个项目都是Java,团队也不敢轻易引入新语言。
最惨的是有一次,为了支持实时热搜词,我在单体里加了个WebSocket连接池,结果内存泄漏,GC频繁,整个服务响应时间从200ms飙到2s+。产品问我:“为啥热搜加载这么慢?” 我只能苦笑:“因为咱们的‘巨石’太重了。”
拆!拆!拆!微服务初体验
被逼无奈,我们决定走微服务化路线。目标很明确:按业务域拆分,让每个服务独立开发、部署、扩展。
我们先画了个粗糙的领域模型:
| 原单体模块 | 拆分后服务 | 技术栈 |
|---|---|---|
| 用户管理 | user-service | Java + Spring Cloud |
| 搜索日志 | log-ingestor | Java + Kafka Producer |
| 搜索核心 | search-core | Java (保留) |
| 热搜计算 | trend-calculator | Python + Flask + Redis |
| 特征工程 | feature-engine | Python + gRPC |
对,你没看错,我们引入了Python!毕竟搞算法的,用Python写数据处理脚本效率高太多了。而且像trend-calculator这种计算密集型服务,用Python + NumPy/Redis比纯Java更轻便。
关键改造点
- API网关统一入口
用Spring Cloud Gateway做路由,所有请求先经过它,再分发到各服务。配置如下:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
- Here comes the trend-calculator!
- id: trend-service
uri: http://trend-calculator:8000
predicates:
- Path=/api/trend/**
服务通信:REST + gRPC 双轨制
内部高频调用(比如search-core调feature-engine)用gRPC,低频或外部对接用REST。gRPC的IDL定义清晰,性能也更好。数据库拆分
每个服务有自己的DB实例(或schema),彻底解耦。比如trend-calculator只读Redis和ClickHouse,不碰主MySQL。配置中心 & 注册中心
用了Nacos,服务启动自动注册,配置热更新。再也不用手动改properties文件了!
但微服务不是银弹。拆完第一天,我们就遇到了分布式事务问题:用户注册成功,但画像服务没收到消息。后来上了RocketMQ事务消息才搞定。还有一次,因为Kafka topic没建,log-ingestor直接OOM,差点背锅。
容器化:Docker 让部署不再玄学
微服务拆完,部署成了新痛点。每个服务依赖不同(Java 11 vs Python 3.9),环境变量一堆,运维小哥天天抱怨:“你们开发能不能统一一下版本?”
这时候,Docker救场了。
我们给每个服务写了Dockerfile:
# trend-calculator/Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/
COPY . .
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
然后用docker-compose.yml本地调试:
version: '3'
services:
trend-calculator:
build: ./trend-calculator
ports:
- "8000:8000"
environment:
- REDIS_URL=redis://redis:6379
redis:
image: redis:6-alpine
终于实现了“在我机器上是好的” → “在任何地方都一样”。运维也松了口气:“现在你们只要给个镜像,我扔K8s里就行。”
上云原生:拥抱 Kubernetes + Service Mesh
今年初,公司决定全面上云,技术栈向云原生靠拢。领导说:“我们要具备分钟级弹性扩缩容能力,应对流量洪峰。”
于是,我们把Docker镜像扔进了Kubernetes集群。
K8s 配置片段(简化版)
# trend-calculator-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: trend-calculator
spec:
replicas: 3
template:
spec:
containers:
- name: app
image: registry/trend-calculator:v1.2
ports:
- containerPort: 8000
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: trend-calculator-svc
spec:
selector:
app: trend-calculator
ports:
- protocol: TCP
port: 80
targetPort: 8000
配合HPA(Horizontal Pod Autoscaler),CPU超过70%就自动扩容。双11预演时,流量突增3倍,Pod从3个秒扩到20个,稳如老狗。
但服务间调用链路变复杂了。怎么监控?怎么限流?怎么灰度?
我们引入了Istio作为Service Mesh。Sidecar代理自动注入,无需改业务代码就能实现:
- 超时重试
- 熔断降级
- 流量镜像(用于灰度测试)
- 分布式追踪(集成Jaeger)
比如,给search-core加个500ms超时:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
spec:
hosts:
- search-core
http:
- route:
- destination:
host: search-core
timeout: 0.5s
运维同学看到Istio的Kiali面板后直呼“这比Zabbix酷多了”,而我终于不用在代码里手写Hystrix了(谁用谁知道,注解堆成山)。
架构对比:数字不会骗人
经过半年折腾,我们做了次性能压测(JMeter模拟1000并发):
| 指标 | 单体架构 | 云原生架构 |
|---|---|---|
| 平均响应时间 | 850ms | 210ms |
| P99延迟 | 2.1s | 450ms |
| 部署耗时 | 15分钟 | 2分钟 |
| 故障恢复时间 | 10分钟+ | <1分钟(自动重启Pod) |
| 资源利用率 | CPU平均30% | CPU平均65%(弹性调度) |
最爽的是,现在产品经理再来提“下周上线”,我们可以淡定回复:“没问题,feature分支测完,CI/CD自动发布到预发环境,你验收就行。”
给新手的几点血泪建议
别为了微服务而微服务
如果你的系统QPS不到100,用户就几百个,单体完全够用。微服务带来的运维复杂度可能远超收益。接口设计要“契约先行”
用OpenAPI/Swagger定义好接口,前后端、跨团队协作效率翻倍。别等联调才发现字段名对不上。日志、监控、告警必须跟上
云原生环境下,服务动态启停,传统日志文件根本没法查。上ELK或Loki+Promtail,配合Prometheus+AlertManager。数据库拆分要谨慎
别一上来就分库分表。先垂直拆分(按业务),再考虑水平拆分。否则join查询会让你哭。学会说“不”
产品经理想要“实时个性化热搜+用户行为分析+AI预测”,但资源有限?优先保核心链路。非核心功能可以用异步队列削峰。
结语:代码人生,不止于写码
从单体到云原生,表面看是技术升级,本质是工程思维的进化——从“让代码跑起来”到“让系统活得好”。
现在的我,依然用VSCode写Python处理特征,但背后的服务已经跑在K8s集群上,自动扩缩容,故障自愈。上周产品又来提需求,我说:“这次我们用Serverless函数试试?” 他一脸懵,我笑而不语。
技术人的“代码人生”,不就是一边吐槽产品,一边用更好的架构让他们“为所欲为”吗?
对了,如果你也在杭州,搞搜索/推荐/大数据,欢迎交流!说不定哪天就在网易食堂偶遇了(他们家的糖醋排骨真香)。
P.S. 文中所有配置和代码均来自真实项目脱敏,如有雷同,纯属同行。
P.P.S. 别问为啥不用Go——因为团队Java/Python栈熟啊!技术选型,适合最重要。

评论 0