从单体到云原生:一个后端架构演进的实战手记

开源贡献者
2025-06-15 19:02
阅读 295

开篇:为什么我要写这篇分享?

开篇:为什么我要写这篇分享?

说实话,我到现在还记得第一次接手一个几十万行代码、部署在一台物理服务器上的 Java 单体应用时的那种窒息感。那是一个电商平台的后台系统,业务功能已经覆盖了商品管理、订单系统、支付、用户中心等多个模块。

刚开始的时候,我们还能靠“加人”、“加机器”来撑一阵子,但随着业务量增长和团队扩张,这个庞然大物开始暴露出越来越多的问题:开发效率低下、部署困难、线上问题排查慢、扩展性差……于是,一场架构改造之路就这样开始了。

这篇文章我会结合自己在过去几年里参与或主导的几个项目,详细聊聊我们是如何一步步从单体走向微服务,最终迈向云原生架构的。这不是一篇纯技术讲解的文章,而是实实在在踩过坑、掉过头发后的经验总结。


问题描述:痛点是推动架构升级的动力

问题描述:痛点是推动架构升级的动力

API接口文档-2

我们最初遇到的几个典型问题:

1. 发布成本高

每次上线都要打包整个项目,稍有不慎就会影响所有模块。尤其是凌晨灰度上线的时候,一不小心就把用户中心改崩了,导致登录系统全线瘫痪。

2. 部署耦合严重

前端请求通过 Nginx 直接打到 Tomcat,数据库也是单节点 MySQL。某个定时任务把 CPU 跑满了,整站都变慢;数据库连接池配置不合理,直接导致线程阻塞。

3. 维护难度大

一个新来的工程师想改个优惠券逻辑,得先花一周时间搞懂整个项目的模块依赖关系。更别提查找接口调用链路,简直是灾难现场。

4. 横向扩展能力弱

高峰期并发上不去,只能不停堆机器,但资源利用率极低。有些模块根本不需要那么多资源,但因为打包在一起,也只能跟着一块跑。

这些痛点逼着我们不得不重新审视系统的架构设计,并逐步走上拆分和服务化的道路。


解决方案:从模块拆分到服务化再到云原生

第一步:内部模块拆分(伪微服务)

我们首先尝试的是在单体应用中划分清楚各模块职责,比如 user-serviceorder-serviceproduct-service 等目录结构,通过接口抽象来隔离。

虽然没有真正做成独立服务,但为后续的拆分打下了基础。这一步最大的收获是规范了代码组织方式和接口定义,也让我们意识到哪些模块之间可以解耦,哪些还存在强依赖。

示例:统一接口层定义

// 用户服务接口定义
public interface UserService {
    User getUserById(Long id);
    void registerUser(String phone, String password);
}

第二步:正式拆分成微服务

我们在 Spring Boot + Dubbo 的基础上搭建了一套微服务框架,将核心业务模块拆分为独立的服务,并引入注册中心(Zookeeper)进行服务发现。

同时,我们也做了几个关键调整:

  • 使用 Feign 实现远程调用
  • 配置中心(Apollo)用于统一管理配置
  • 数据库按业务分库
  • 接口间通信使用统一的 DTO 对象

拆分后的架构图示意(简化版):

[网关] → [用户服务]
         ↑
         ↓
        [订单服务] ↔ [库存服务]
         ↑
         ↓
       [支付服务]   [消息中心]

每个服务都有自己独立的部署和生命周期管理,大大提升了灵活性。

第三步:容器化 & Kubernetes 上云

后来我们意识到,仅仅服务化还不够,运维成本依然很高,尤其是在频繁发布的场景下。所以我们决定拥抱容器化和 Kubernetes,将服务部署在阿里云 ACK(阿里云 Kubernetes 服务)上。

  • Docker 化镜像构建流程标准化
  • 基于 GitLab CI 实现自动化发布流水线
  • 引入 Helm 进行版本管理
  • Ingress 控制外网流量转发
  • 使用 Service Mesh(Istio)实现更细粒度的流量控制

这一步让我们的部署效率提升了一个数量级,从原来的几小时缩短到几分钟内完成灰度发布。


代码实践:以订单服务为例

下面是我们订单服务的一个简单接口示例,展示了如何定义一个 RESTful API,并通过 OpenFeign 调用其他服务。

OrderService.java

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

    @Autowired
    private OrderService orderService;

    @GetMapping("/{id}")
    public Response<OrderVO> getOrder(@PathVariable Long id) {
        return Response.success(orderService.getOrderDetail(id));
    }

    @PostMapping
    public Response<Boolean> createOrder(@RequestBody CreateOrderDTO dto) {
        return Response.success(orderService.create(dto));
    }
}

OrderService.java(与用户服务交互)

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private UserService userService; // Feign 客户端注入

    public OrderVO getOrderDetail(Long id) {
        Order order = orderDAO.findById(id);
        User user = userService.getUserById(order.getUserId());
        return buildOrderVO(order, user);
    }
}

Feign 客户端定义

@FeignClient(name = "user-service")
public interface UserServiceClient {

    @GetMapping("/users/{id}")
    Response<User> getUserById(@PathVariable("id") Long id);

}

这些是我们早期微服务拆分时的基础组件结构,看似简单,实则承载了很多架构层面的考量。


踩坑经验:那些让我深夜失眠的坑

1. 服务依赖混乱导致级联故障

在最开始拆分阶段,我们没有做太多的服务降级和容错机制。某次促销期间,库存服务响应超时,导致订单服务也被拖垮,最后整个系统宕机。

解决方案:

  • 引入 Hystrix 实现服务熔断
  • 使用 Sentinel 设置限流规则
  • 加强监控告警体系(Prometheus + Grafana)

2. 数据库分片不均匀导致性能瓶颈

我们将订单数据做了水平分片,但由于初期策略选择失误(用了 hash 分片),结果热点数据集中在某一片区。

改进方案:

  • 改成 range 分片 + 冷热分离
  • 引入分库中间件(MyCAT/ShardingSphere)
  • 做定期归档,保持单表大小可控

3. Kubernetes 服务发现不稳定

在刚上 Kubernetes 阶段,经常出现服务调用失败,追踪下来是 DNS 解析延迟问题。

解决办法:

  • 使用 CoreDNS 替代默认的 kube-dns
  • 启用本地缓存和负载均衡配置(如配置 feign 的负载均衡策略为 RoundRobin)
  • 结合 Istio 做精细化流量调度

4. 日志聚合难,定位问题费劲

原来每台机器都有自己的日志路径,出现问题查起来很麻烦。

优化点:

  • 统一接入 ELK 日志系统
  • 所有日志带上 traceId、spanId 便于链路追踪
  • 引入 SkyWalking 实现完整的 APM 观察链路

效果总结:我们获得了什么?

经过这一轮架构升级,我们主要实现了以下几个方面的收益:

维度 升级前 升级后
发布速度 小时级 分钟级
线上故障恢复时间 几十分钟 数分钟
模块独立性 互相耦合 松耦合
开发协作效率 多人修改易冲突 明确边界,互不影响
技术栈灵活性 固定 Java 多语言混用可能
成本控制 高冗余 动态弹性伸缩

数据库设计模型-1

更重要的是,架构的变化带来了团队协作方式的改变,每个人都能专注于自己的领域,系统也更有可拓展性。


经验分享:给正在转型的朋友几点建议

  1. 不要为了拆而拆

    • 拆服务一定要基于业务边界清晰、技术债可控的前提。
    • 微服务不是银弹,它只会放大你的优势和暴露你的短板。
  2. 做好治理配套

    • 没有完善的监控、日志、追踪系统,迟早会哭。
    • 提前规划好服务治理策略,别等到出了问题再补课。
  3. 数据库要提前规划

    • 服务拆了不代表数据库就能随意拆,数据一致性、查询复杂度都会增加。
    • 数据库也要考虑冷热分离、读写分离、主从同步等常见优化策略。
  4. 关注 DevOps 流程

    • 自动化程度决定交付效率,CI/CD 是必须项。
    • 不要用原始的 scp+rsync 或者手动重启了,那是对生产力的最大浪费。
  5. 不要低估运维成本

    • 云原生的确能带来弹性,但也带来了复杂性。
    • 要有专业的 SRE 或 DevOps 工程师,否则你早晚会被 Kubernetes 的状态搞晕。
  6. 保持学习与迭代的心态

    • 架构永远在路上,没有一劳永逸的设计。
    • 有时候退回来重新审视,比强行往前冲更聪明。

写在最后:架构是一门平衡的艺术

回头看看,我们一路走来也不是完全正确的,有些服务拆得早了些,有些又太晚。但我们始终坚持一点:架构服务于业务,不是炫技。

我始终认为,一个好的架构,应该是让团队更容易做事,而不是带来更多约束。如果你发现架构让你每天都在加班debug、填坑,那就是时候反思一下了。

希望这篇分享对你有所启发。如有任何问题或交流,欢迎随时联系我。

一起加油吧,我们终将在云上找到属于自己的节奏。


作者:老张 · 一线后端技术负责人 · 痛并快乐地写着代码的人
联系方式:zhang.xiangyu@example.com

评论 0

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