从单体到云原生:一个后端开发者的架构演进之路

萧秀英
2025-06-12 09:00
阅读 610

开篇:为什么我会关注架构的演进?

开篇:为什么我会关注架构的演进?

我在一家互联网公司做后端开发,主要负责电商平台的订单系统。刚开始加入团队时,项目还只是一个简单的Java应用,部署在一台服务器上,代码结构也还算清晰。但随着业务的扩展,用户量暴涨、需求频繁上线,我们逐渐发现这套“老”系统越来越难支撑现有的发展节奏。

于是,我们开始尝试对系统架构进行一系列改造,从单体架构逐步过渡到微服务,最终迈向了云原生架构。这篇文章将记录下我亲身经历的这段架构演化过程,分享一些踩过的坑、学过的教训以及技术选型背后的思考。


问题描述:我们遇到了什么瓶颈?

问题描述:我们遇到了什么瓶颈?

最初的系统是一个典型的Spring Boot单体应用,运行在阿里云的一台ECS实例上,数据库用的是MySQL单节点,缓存用了Redis。整个系统跑得还不错,直到下面几个问题接连出现:

  1. 部署困难:每次上线都得停机重启,影响用户体验不说,还容易出错。
  2. 性能瓶颈:高峰期时QPS超过5000,数据库连接池被打满,接口响应时间飙升。
  3. 模块耦合高:订单模块和库存模块硬编码在一起,牵一发而动全身。
  4. 扩容成本高:想临时加机器?不好意思,你得自己配置环境、启动脚本、监控等等……
  5. 运维效率低:故障排查慢,日志分散,监控靠人工,报警机制不完善。

这些问题逼着我们不得不重新审视系统的架构设计。


解决方案:分阶段推进,一步一个脚印

解决方案:分阶段推进,一步一个脚印

我们没有一口吃成胖子,而是根据实际业务情况和团队能力,分成了以下几个阶段来演进架构。

阶段一:拆分核心模块,引入微服务

我们首先将原有系统中相对独立的子系统拆出来作为独立服务。比如:

  • 用户中心(用户信息+权限)
  • 商品中心(商品管理)
  • 订单中心(下单/支付/售后)
  • 库存中心(库存扣减)

这些模块通过RPC通信调用彼此接口,使用Dubbo + Zookeeper作为服务注册与发现组件。

微服务的优势初显

  • 模块解耦,迭代更灵活
  • 可以单独扩容某个模块,资源利用率更高
  • 不同服务可以采用不同语言实现,技术栈更开放

不过,也带来了新的挑战,比如:

  • 服务间调用链路变长,出问题定位更复杂
  • 分布式事务需要考虑(比如库存锁定和订单创建要保证一致性)
  • 配置管理变得麻烦,各服务配置分散

阶段二:引入Spring Cloud生态,打造统一服务框架

为了更好地治理服务,我们转向Spring Cloud生态,使用如下组件:

组件 作用
Eureka 服务注册与发现
Zuul 网关,统一路由
Feign & Ribbon 客户端负载均衡和服务调用
Config Server 集中式配置管理
Sleuth + Zipkin 分布式链路追踪

这套组合拳下来,服务治理的能力明显增强,特别是在多环境(dev/staging/prod)部署的时候,Config Server统一配置大大减少了手动维护的成本。

阶段三:向Kubernetes迁移,拥抱云原生

虽然Spring Cloud解决了很多问题,但我们在部署和运维层面依然遇到痛点:

  • 滚动更新复杂,依赖人工干预
  • 服务弹性伸缩不够及时
  • 日志和监控数据分散,难以集中分析

于是,我们决定迁移到Kubernetes平台,开启真正的云原生之旅。


代码实践:关键环节的代码示例

API接口文档-1

代码实践:关键环节的代码示例

这里以服务拆分和K8s部署为例,展示一下我们的一些关键代码片段。

1. 服务拆分:使用Feign进行远程调用

// 商品服务提供的接口
@FeignClient(name = "product-service")
public interface ProductServiceClient {
    @GetMapping("/products/{id}")
    Product getProductById(@PathVariable("id") Long id);
}

// 在订单服务中直接注入使用
@RestController
public class OrderController {
    
    @Autowired
    private ProductServiceClient productService;

    public ResponseEntity<Order> createOrder(Long productId) {
        Product product = productService.getProductById(productId); // 远程调用
        ...
    }
}

这个方式让我们轻松地实现了服务间的通信,并结合Ribbon完成了客户端负载均衡。

2. Kubernetes部署文件示例(Deployment + Service)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: registry.example.com/order-service:latest
        ports:
        - containerPort: 8080
        envFrom:
        - configMapRef:
            name: order-config

---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080

通过这个YAML文件,我们可以在K8s集群中快速部署服务,同时借助ConfigMap统一管理配置。


踩坑经验:那些让人哭笑不得的日子

任何一次大的架构演进都不是一帆风顺的,我们也踩了不少坑。

坑一:分布式事务处理不当导致数据不一致

最开始我们简单地认为只要订单创建失败就回滚即可,但在实际场景中,库存已经被锁了,但订单没有创建成功的情况时常发生。

后来我们采用TCC(Try-Confirm-Cancel)模式,即:

  • Try阶段:冻结库存
  • Confirm:创建订单并减库存
  • Cancel:释放库存

通过这种补偿机制,解决了大部分的数据一致性问题。

坑二:服务雪崩效应

有一次,由于商品服务崩溃,订单服务因为大量请求堆积导致线程阻塞,进而引发连锁反应,整个系统几乎瘫痪。

为了解决这个问题,我们引入了Hystrix熔断机制:

@HystrixCommand(fallbackMethod = "fallbackGetProduct")
@GetMapping("/products/{id}")
public Product getProductById(@PathVariable("id") Long id) {
    // 如果调用超时或异常,自动触发 fallback
}

public Product fallbackGetProduct(Long id) {
    return new Product(); // 默认返回兜底数据
}

虽然Hystrix现在官方已经不推荐了,但我们当时确实通过这种方式缓解了服务级联故障的问题。

坑三:K8s网络不通,服务怎么都连不上

刚上K8s的时候,服务之间经常无法通信,排查半天才发现是Service的Selector写错了或者Pod没正常Ready。

建议大家一定要养成习惯:

  • 使用kubectl get pods,svc,ep检查状态
  • 查看Pod事件:kubectl describe pod
  • 确保Service和Deployment的label标签完全一致

效果总结:改造之后的变化

经过这一轮的架构升级,我们得到了不少好处:

改造前 改造后
单个服务宕机影响全局 局部服务出错不影响整体
手动发布、灰度难 一键部署、滚动更新
扩容需手动加机器 K8s自动扩容,弹性伸缩
日志分散、难监控 ELK统一收集日志,Prometheus+Grafana可视化监控
故障排查困难 基于Zipkin的全链路追踪帮助快速定位问题

最关键的是,整个团队的协作效率提升了,我们可以更快地响应业务变化,同时也更容易评估风险。


经验分享:给你的几点建议

如果你正在面临类似的架构升级问题,以下是一些我从实践中总结出来的建议,希望能帮到你:

1. 架构设计不是越新越好,适合自己最重要

不要盲目追求新技术潮流。比如,K8s虽然强大,但如果团队里没人懂,前期可能会非常痛苦。

建议:先从小规模试点开始,慢慢培养运维和开发能力。

2. 服务拆分要“小步快跑”,避免一次性重构

我们一开始想一口气把所有模块都拆出去,结果出了很多协调上的问题,最后还是选择优先拆分那些最容易、风险最低的服务。

建议:按照“高频调用、边界明确”的原则优先拆分。

3. 监控和日志系统必须尽早搭建

在微服务时代,你根本不知道哪个服务会在凌晨挂掉。没有完善的监控体系,相当于裸奔。

建议:ELK+Prometheus+Grafana几乎是标配,可以考虑用Loki轻量级方案替代ELK。

4. 自动化是提升效率的关键

手动操作越多,出错概率越高。CI/CD流程必须自动化,哪怕只是写几个Shell脚本起步。

建议:可以从Jenkins做起,逐步向ArgoCD、Tekton等现代工具过渡。

5. 不要忽视文档和沟通

微服务意味着每个模块都有不同的负责人,如果不做好文档沉淀和跨组沟通,后续协作成本会非常高。

建议:使用Swagger自动生成API文档,建立统一的技术Wiki,定期同步架构变化。


写在最后:架构演进的本质是组织能力和工程文化的演进

回头来看,所谓“架构升级”,不仅仅是技术的堆叠,更多是对团队协作、研发流程、工程规范的全面提升。每一个架构的选择背后,其实都是对人和事的权衡。

我常常跟新人说一句话:“架构是为了解决人的问题,而不是纯粹的技术堆砌。

希望这篇文章能给你带来一些启发,无论是正在规划架构升级的朋友,还是刚入行的小伙伴。欢迎留言交流你的经验和想法,我们一起成长!


作者简介:我是某大型电商平台的后端工程师,从业7年,从码农到架构师,经历过从单体到微服务再到K8s的实际落地全过程。目前专注于云原生、高性能服务架构和DevOps流程优化方向。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝