后端架构演进:从单体到云原生,我的亲身经历

变量命名困难户
2025-06-27 12:03
阅读 281

背景介绍:为什么我们需要重构?

背景介绍:为什么我们需要重构?

2018年我加入了一家做本地生活服务的创业公司。当时系统是典型的PHP单体架构,代码部署在几台物理服务器上,前端和后端混在一起。业务初期发展很快,但随着用户量增加、需求迭代频繁,这套架构开始变得越来越不堪重负。

最直观的问题是部署效率低,每次上线都要停机维护;其次是性能问题频出,高峰期经常出现接口响应慢甚至超时,数据库压力大得像随时要爆炸一样。更头疼的是开发协作困难,多个团队在同一个代码库上改来改去,合并冲突频发,上线事故层出不穷。

那一年,我们决定从头开始设计一个全新的后端架构,目标很明确:解耦、高可用、可扩展、易运维。这篇文章就来分享这段从单体架构一路走到云原生的“进化之路”,以及过程中的点点滴滴。


问题描述:痛点一个接一个

问题描述:痛点一个接一个

部署流程卡顿严重

最初的时候,我们还在用传统的 FTP 上传+手动重启 Apache 的方式部署。后来虽然上了 Ansible 自动化部署,但由于服务之间没有隔离,每次更新都必须整个后端重新部署一次。这导致上线成了个需要全员守夜的大事,尤其在业务高峰期,一不小心就会把线上的老功能搞坏。

性能瓶颈显现

随着用户增长,API 请求量呈指数级上升。最典型的就是订单中心接口,在促销活动期间,数据库负载直接飙到了 90% 以上。尽管我们尝试过各种缓存策略,但因为数据模型耦合太深,很多地方还得直接访问原始表,缓存命中率并不理想。

服务不可控

因为所有模块都在一个工程里,调用关系异常复杂。有时候一个小小的改动,会牵动十几个类的连锁反应。而测试环境也难以完全模拟生产场景,导致线上问题频繁出现,比如某个服务依赖的第三方接口突然变慢,整个系统都会被拖垮。


技术方案选型:走向分布式与云原生

基于这些痛点,我们启动了第一轮架构升级,目标是:从单体走向微服务 + 容器化部署

第一步:微服务拆分

我们选择了 Java Spring Boot 作为主力技术栈(兼顾团队技能栈和未来扩展),并逐步将核心模块拆分为独立的服务:

  • 用户中心
  • 商品服务
  • 订单中心
  • 支付网关
  • 活动中心
  • CMS 内容管理后台

每个服务都有自己的数据库,通过 REST 接口或 gRPC 通信。一开始我们也试过 Eureka 做注册中心,但后来觉得组件太多,学习成本太高,于是转投 Kubernetes 原生的服务发现机制。

第二步:引入容器化部署

为了方便部署,我们在阿里云 ECS 上搭建了 Docker + Kubernetes 环境,并逐步迁移旧服务到容器中运行。使用 Helm 进行配置管理,CI/CD 流水线则基于 GitLab CI 实现。

第三步:接入云原生基础设施

我们不再自己维护 Redis 或 MySQL 高可用集群,而是全面采用云厂商提供的托管数据库服务(如 RDS)。日志收集使用的是 Fluentd + Elasticsearch + Kibana 方案,监控部分使用 Prometheus + Grafana。


一些关键实现细节

微服务拆分实践

以订单服务为例,我们将其从主应用中分离出来之后做的主要改动包括:

// OrderController.java
@RestController
@RequestMapping("/orders")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping("/{id}")
    public ResponseEntity<Order> getOrder(@PathVariable Long id) {
        return ResponseEntity.ok(orderService.getOrderById(id));
    }

    @PostMapping
    public ResponseEntity<Long> createOrder(@RequestBody CreateOrderRequest request) {
        Long orderId = orderService.createOrder(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(orderId);
    }
}

订单服务本身封装了对数据库的访问逻辑,还集成了 RabbitMQ 异步消息队列用于发送通知事件。

// OrderEventPublisher.java
@Component
public class OrderEventPublisher {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void publishOrderCreatedEvent(Order order) {
        String routingKey = "order.created";
        rabbitTemplate.convertAndSend(routingKey, order);
    }
}

这样的结构让订单服务可以作为一个独立单元进行部署和扩展。

服务注册与发现(Kubernetes)

在 Kubernetes 中我们使用 Service 对象进行服务发现,例如订单服务的 YAML 文件如下:

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

这样其他服务只需要通过 DNS 解析 order-service.default.svc.cluster.local 即可找到服务实例,省去了注册中心的麻烦。

数据库拆分策略

在做数据库拆分时,我们遵循以下原则:

  • 每个微服务拥有独立数据库,避免跨服务查询
  • 使用最终一致性保证数据同步(如通过消息队列异步更新)
  • 所有对外接口只暴露领域对象 ID,不泄露内部数据结构
  • 对于强一致性的场景(如交易流水),采用事务补偿机制处理失败情况

举个例子,下单流程涉及库存变更、积分调整等多个服务,我们通过 Saga 模式进行事务控制,一旦某一步失败,则通过反向操作回滚前面已完成的动作。


踩坑经验分享

网络延迟带来的隐性问题

刚开始使用 Kubernetes 的时候,我们忽略了网络层面的性能问题。特别是服务间频繁调用,如果不做好链路追踪和超时设置,很容易造成雪崩效应。

我们后来在所有服务中统一集成 Zipkin 进行分布式追踪,同时设置了熔断器(Hystrix),防止级联故障。

日志聚合的“陷阱”

早期我们只是简单地把日志写入容器标准输出,结果在排查线上问题时发现大量日志缺失。后来改为使用 Fluentd Agent Sidecar 模式,每个 Pod 中带一个日志采集 Sidecar,统一打到 Elasticsearch,这才解决了日志丢失和时间错乱的问题。

云原生不是万能的,别盲目迁移到 Kubernetes

我们曾试图将所有的 legacy 服务全部容器化,结果发现有些老旧服务由于依赖多、结构混乱,根本不适合轻易拆包。最后决定保留一部分基础服务继续跑在 VM 上,逐步过渡。


成效总结:从痛苦挣扎到从容应对

数据库设计模型-1

经过两年的持续迭代和优化,我们成功完成了从传统架构到云原生体系的全面转型。

  • 部署效率提升:CI/CD 全流程可以在5分钟内完成,支持灰度发布和滚动更新。
  • 系统弹性增强:可以根据流量自动扩缩容,应对高峰请求毫无压力。
  • 故障隔离能力显著提高:一个服务挂掉不会影响整体系统可用性。
  • 团队协作顺畅:每个服务由不同小组负责,代码质量明显提升,线上问题大幅减少。

最关键的是,我们的技术栈变得更加现代化,具备了良好的扩展性和开放性,为后续引入 AI、边缘计算等新技术打下了坚实基础。


给读者的建议

如果你正处在架构演进的过程中,这里有一些我走过的弯路,希望能帮到你:

1. 架构设计要“适度设计”

不要追求过度设计,特别是在创业阶段。我见过太多项目一开始就规划微服务、Kubernetes、Service Mesh,结果连最基本的功能都没跑通。先跑起来比什么都重要

2. 技术债要及时偿还

当系统开始暴露出性能瓶颈或者结构混乱的迹象时,一定要趁早重构。拖延只会让问题变得更复杂。

3. 团队协同和技术规范要同步推进

我们前期忽视了这一点,导致多个服务风格迥异、命名混乱、调用方式五花八门。后来专门组织了一个架构组,制定了统一的接口设计规范和错误码体系,才逐渐步入正轨。

4. 尽量拥抱云原生,但要有选择

K8s 是好东西,但它不是灵丹妙药。根据自身业务特点合理选择是否使用 Service Mesh、Serverless 等新兴技术,避免陷入“技术炫技”的误区。


写在最后:架构是不断演进的过程

这几年我参与了很多次架构升级,也经历了无数个深夜调试、反复推翻的痛苦时刻。但正是这些经历让我意识到:好的架构从来都不是一次性设计出来的,而是在实践中不断打磨出来的

无论你是刚起步的开发者,还是已经有一定经验的架构师,永远记住一句话:“架构服务于业务”。只有真正理解业务的需求,才能做出贴合实际的设计决策。

希望这篇真实的架构演进笔记,能够为你带来一点启发。如果有任何问题或者想交流更多经验,欢迎留言或私信联系,我很乐意一起探讨。


本文作者是一位拥有多年互联网架构经验的全栈工程师,热爱技术和写作,热衷于分享真实项目落地中的经验与教训。

评论 0

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