从单体到云原生:我的后端架构演进之路
背景介绍:为什么我要写这篇文章?

去年我换了一家新公司,接手了一个已经运营了五年、代码量几十万行的Java项目。说实话,刚接手那会儿挺崩溃的——服务部署在一台老旧的物理机上,所有模块耦合在一起,一个功能上线可能要牵动整个系统重启,每次发布都像拆炸弹一样小心翼翼。
作为一个有5年经验的后端开发,我之前参与过几次中小型项目的架构设计,但这次面对的是一个真正意义上的“遗留系统”。团队人数不少,运维成本却越来越高,需求交付速度反而越来越慢。我们开始意识到,必须对系统进行一次彻底的架构升级。
于是,我们开启了一场从单体架构向云原生架构迁移的实践旅程。
遇到的问题和挑战

先来看看当时我们的系统长什么样:
- 单体架构,Spring Boot + MyBatis,所有功能都在一个工程里
- 没有服务拆分,数据库也是单实例MySQL
- 所有接口共用一个Redis缓存
- 部署方式是手动打jar包上传服务器运行,没有CI/CD流程
- 日志分散在各个服务器节点,排查问题非常麻烦
具体来说,这些问题逐渐暴露出了很多痛点:
1. 发布风险大,更新频率低
有一次上线一个小功能,改了一个定时任务的调度时间,结果不小心搞崩了另一个模块的数据同步逻辑,整整花了两个小时才发现问题并回滚。那次之后,我们老大明确说:“不能再这么干了!”
2. 系统性能瓶颈明显
随着用户增长,单台服务器的CPU经常飙到90%+,数据库连接池也经常满,响应时间变得越来越不稳定。我们尝试做过一些优化,比如增加Redis缓存层、调整SQL索引等,但始终是治标不治本。
3. 技术债务高,新人难以上手
新人入职后,光理解代码结构都要半个月,更别说参与开发了。因为模块之间依赖太多,改动一处可能影响全局,大家都不敢轻易下手。
4. 维护成本高,自动化缺失
部署、配置、日志查看全部靠人工,出问题只能靠grep查日志,效率极低。没有监控告警,也没有自动恢复机制,半夜报警电话响个不停,成了常态。
架构演进:从单体到微服务再到云原生

既然问题这么严重,那就得下定决心重构。但我们并没有一上来就搞微服务,而是按照“小步快跑”的节奏,逐步推进系统的演进过程。
第一步:代码分层与模块解耦(内部微服务化)
我们先做了代码层面的模块划分,把原来的大单体项目按照业务域进行了初步解耦:
- 用户中心
- 订单服务
- 商品中心
- 支付系统
- 公共基础组件
虽然还是在一个工程里,但通过接口抽象和模块划分,让每个部分可以独立开发、测试、打包。这一步其实是个过渡阶段,为后续真正的微服务做准备。
第二步:拆分成真正的微服务架构
接下来,我们开始使用Spring Cloud搭建微服务框架,引入Eureka做注册中心,Feign做服务间通信,Gateway做统一入口,Config做集中配置管理。
这是当时我们微服务架构图的一个简化版本:
┌──────────────┐
│ Gateway │
└────┬─▲┬─────┘
│ ││
┌─────────┘ │└──────────┐
▼ ▼ ▼
User Service Order Service Product Service
▼ ▼ ▼
MySQL DB Redis Cache RabbitMQ
每个服务都有独立的数据库,避免了之前的强耦合问题。同时我们也引入了链路追踪工具SkyWalking,来帮助定位服务间的调用问题。
第三步:拥抱云原生,容器化部署
再后来,我们开始把服务迁移到Kubernetes集群上,并且引入了以下技术栈:
- Docker作为容器化载体
- Kubernetes作为编排平台
- Istio作为服务网格实现流量治理
- Prometheus+Grafana进行指标采集与可视化
- ELK(Elasticsearch + Logstash + Kibana)做日志收集分析
- Jenkins作为CI/CD流水线核心
整个系统终于摆脱了过去那种“人肉运维”的模式,实现了自动化构建、部署、扩容与监控。
关键代码片段分享

这里贴几个关键环节的代码示例,都是我们在实际生产中用过的,方便大家参考。
1. Spring Boot 微服务启动类
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
这个是最基本的Spring Boot应用启动模板,加上@EnableDiscoveryClient后就可以注册到Eureka或者Nacos上了。
2. 使用OpenFeign进行服务调用
@FeignClient(name = "order-service")
public interface OrderServiceClient {
@GetMapping("/orders/{userId}")
List<Order> getOrdersByUserId(@PathVariable("userId") Long userId);
}
这是我们用来调用订单服务的一个Feign客户端,简单易用,而且支持熔断降级(Hystrix或Resilience4j)。
3. Kubernetes Deployment 示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 2
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: registry.example.com/user-service:latest
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: user-service-config
resources:
requests:
memory: "512Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1"

上面是一个典型的Kubernetes Deployment配置文件,设置了副本数、资源限制、环境变量注入等内容。
踩坑记录和心得体会
整个演进过程并不是一帆风顺,踩了不少坑,这里总结几个印象深刻的:
1. 微服务之间的网络延迟导致雪崩效应
刚开始的时候,为了追求快速迭代,我们没有给Feign Client设置超时时间和重试策略。结果线上某次订单服务卡顿,导致用户服务也被拖垮,最终形成了连锁故障。
解决方案很简单,在Feign客户端加了如下配置:
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return template -> {
template.header("X-Trace-ID", UUID.randomUUID().toString());
};
}
@Bean
public Retryer feignRetryer() {
return new Retryer.Default(1000, 1000 * 5, 3); // 重试3次
}
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
并且结合Hystrix做熔断处理,避免了服务之间的级联失败。
2. Kubernetes 上的服务发现不及时
刚开始用Kubernetes原生的kube-dns做服务发现,但有时候Pod重启后IP变了,服务还连不到新的地址。后来我们切换成了Istio + Envoy的方式,提升了服务发现的准确性和稳定性。
3. CI/CD流水线初期没做好权限控制
一开始Jenkins的脚本权限太开放,有人误操作把正式环境的数据覆盖了……痛定思痛,我们后面做了严格的角色权限管理,所有的部署动作都必须审批,并且加入了灰度发布机制。
迁移后的效果和收益
经历将近一年的重构和迁移,我们取得了以下几个方面的成果:
| 指标 | 迁移前 | 迁移后 |
|---|---|---|
| 平均部署耗时 | 40分钟 | <5分钟 |
| 故障恢复时间 | >60分钟 | <10分钟 |
| 接口平均响应时间 | 2s | 0.3s |
| 新人学习曲线 | ≥2周 | ≤3天 |
| CPU利用率峰值 | 90%+ | 最高70% |
| 日志查询效率 | grep全靠运气 | 分钟级定位问题 |
不仅如此,团队的协作方式也发生了变化:
- 开发可以随时拉起本地环境测试
- 每个服务都可以独立部署和扩缩容
- 出现问题可以通过链路追踪迅速定位
- 产品方对发布频率也有了更高的信心
我的几点建议和思考
如果你也在考虑要不要对自己的系统进行架构升级,我想分享几个亲测有用的建议:
1. 架构不是越复杂越好,要看业务发展阶段
不要一上来就学大厂直接上Istio、Service Mesh那些东西。先从小处着手,把单体拆成多个可独立部署的小服务,然后再逐步上容器、搞集群。
2. 技术债尽早清理,不然越积越多
我见过太多项目一开始图快,结果后期翻车。一定要在早期就把基础设施和规范建立起来,否则后期想改的成本是指数级上升。
3. 技术选型要权衡利弊,适合自己最重要
举个例子,我们可以选择Dubbo也可以选择Spring Cloud,两者各有千秋。关键是要看团队的技术栈积累、运维能力以及未来的发展方向。
4. 自动化是提升效率的关键
无论是CI/CD、监控报警还是日志分析,尽可能做到自动化。这样才能释放人力,去做更有价值的事情。
结语:架构演进是一场持久战
最后我想说,架构演进从来不是一蹴而就的事,它更像是一个不断打磨、持续优化的过程。从最初的单体结构,到微服务,再到如今的云原生架构,这条路我们走了整整一年,但也带来了实实在在的价值。
在这条路上,我们经历了迷茫、怀疑,也收获了成长和成就感。希望这篇经验分享能给你带来启发。如果你也有类似的经历,欢迎留言交流!
作者简介:一位有五年经验的Java后端工程师,经历过多个项目架构改造,对微服务、云原生、DevOps有深入实践经验。目前专注于打造高可用、高性能的企业级服务架构。

评论 0