在 BAT 某大厂深耕架构设计的八年,我亲手将一个日活千万级的超级 APP,从一个庞大而臃肿的单体架构,逐步拆分、演进成如今支撑着上百个业务、上千个服务的微服务集群。这段旅程,远非“拆分”二字所能概括,它更像是一场在理想与现实、收益与成本之间不断权衡、取舍的修行。
今天,我想抛开那些生涩的技术术语和代码,聊聊这背后最真实的思考、踩过的坑以及那些艰难的抉择。
第一章:黄金时代与“甜蜜的负担”——单体架构的荣光与枷锁
一切都要从那个“黄金时代”说起。项目启动初期,我们和所有初创团队一样,选择了单体架构。它简单、直接、高效。一个小团队,一个代码库,一次编译,一次部署,所有功能浑然一体。
那时的优势,现在想来依旧耀眼:
开发效率极高: 新功能开发,只需要在同一个项目里增删改查,IDE 调试一气呵成。
部署运维简单: 一个 WAR 包或一个 JAR 包,扔到服务器上就完事了,运维成本极低。
事务一致性天然保证: 一个本地数据库事务就能搞定所有业务逻辑,强一致性唾手可得。
然而,随着用户量的指数级增长和业务线的疯狂扩张,这个“甜蜜的负担”开始变得沉重。我们的单体应用,像一个被不断喂食的巨兽,逐渐变得臃肿、迟缓、难以驾驭。
枷锁随之而来:
编译与部署“牵一发而动全身”: 哪怕只是修改一个无关紧要的文案,也需要整个应用重新编译、回归测试、全量发布。发布周期从一天延长到一周,再到两周。
技术栈“古墓派”: 整个应用被锁定在一个陈旧的技术栈上。想尝试新的语言或框架?对不起,为了保持统一,不可能。技术创新成了一句空话。
团队协作“寸步难行”: 几百号人在同一个代码库里“裸奔”,代码冲突成了家常便饭。一个核心模块的 Bug,可能导致整个 APP 不可用。团队边界模糊,权责不清。
稳定性“单点故障”: 所有功能都绑在一根绳上。一个非核心业务(如用户积分模块)的内存溢出,可能导致整个 APP 崩溃。
当“发布一次如临大敌,修复一个 Bug 如履薄冰”成为常态时,我们明白,是时候做出改变了。微服务,这个听起来无比美好的词,进入了我们的视野。
第二章:拆分之痛——理想丰满,现实骨感
微服务的理念很诱人:将庞大的单体应用,按照业务边界,拆分成一个个独立、自治的小服务。每个服务都有自己的数据库、自己的团队,可以独立开发、独立部署、独立扩展。
但理想丰满,现实骨感。拆分的第一步,就让我们踩了无数的坑。
踩坑一:“大泥球”式拆分,换汤不换药
坑位描述: 我们最初的尝试是“按层拆分”,比如拆出一个“用户服务”、“订单服务”、“支付服务”。但很快发现,这些服务之间存在着千丝万缕的数据库调用和代码依赖。所谓的“服务”,本质上只是把原来的模块调用换成了远程调用,形成了一个“分布式的大泥球”。
反思与爬坑: 我们意识到,微服务拆分的核心不是技术分层,而是业务领域的边界划分。 我们引入了领域驱动设计(DDD)的思想,组织业务、产品、技术同学一起,通过事件风暴等方式,梳理出真正的“限界上下文”。服务拆分,必须围绕着一个稳定的、内聚的业务能力进行,而不是一个模糊的技术概念。
踩坑二:分布式事务的“魔鬼”
坑位描述: 在单体里,一个 @Transactional 注解就能搞定的事务,在微服务世界里成了魔鬼。用户下单,需要扣减库存、创建订单、扣减优惠券……这些操作分布在不同的服务里,如何保证数据一致性?我们尝试过两阶段提交(2PC),但性能和可用性极差,很快就被废弃。
反思与爬坑: 我们最终接受了 “BASE 理论”,放弃了追求强一致的幻想。我们转向了最终一致性方案,比如基于消息队列的本地消息表、Saga 模式等。这带来了一个巨大的观念转变:我们不再试图用一个“大事务”锁定所有资源,而是通过一系列可靠的“小事务”和补偿机制,来保证整个业务流程的最终正确。 这需要业务流程设计上的深度配合。
踩坑三:运维复杂度的“指数级爆炸”
坑位描述: 1 个单体应用,变成了 100 个微服务。这意味着什么?100 个代码库,100 个独立的部署流水线,100 个需要监控的实例。服务之间如何发现?配置如何管理?日志如何关联?链路如何追踪?一夜之间,运维的复杂度呈指数级上升。
反思与爬坑: 我们被迫投入巨大的资源,建设起一整套强大的基础设施“底座”,包括:
服务注册与发现中心
统一的配置中心
API 网关
集中式日志系统(ELK/EFK)
分布式链路追踪系统
容器化与容器编排平台(Docker + Kubernetes)
我们才明白,微服务不是免费的午餐,它将复杂度从应用内部转移到了基础设施和运维层面。 没有强大的“基建”能力,贸然上马微服务就是一场灾难。
第三章:演进中的取舍——没有银弹,只有权衡
走过最痛苦的拆分期,我们进入了漫长的演进期。这个过程,充满了各种艰难的取舍。
取舍一:服务粒度的“度”
困境: 服务到底应该拆多小?拆得太细,服务数量会爆炸,管理成本和通信开销急剧增加。拆得太粗,又退回到了“小单体”的状态,失去了微服务的意义。
我们的原则: 我们遵循 “康威定律” 和 “两披萨团队” 原则。一个服务的边界,最好能和一个自治团队的职责边界相匹配。这个团队(大约 6-10 人)能够完全负责这个服务的整个生命周期,从开发、测试到部署、运维。我们不追求绝对的“小”,而是追求“内聚高、职责单一、自治性强”。
取舍二:服务通信的“契约”
困境: 服务间通信,用 RESTful API 还是 RPC?RESTful 通用、灵活,但性能相对较差,协议“啰嗦”。RPC(如 gRPC、Dubbo)性能高、协议紧凑,但耦合度稍高,有跨语言限制。
我们的原则: 内外有别,场景驱动。
对于外部面向用户的、需要暴露给第三方的 API,我们倾向于使用 RESTful API,因为它标准化、通用性好。
对于内部服务间的高频、低延迟调用,我们更多地采用 RPC 框架,因为它性能更优。
同时,我们引入了 API 网关 作为统一入口,对外屏蔽内部服务的复杂性,并强制推行 API 契约先行 的理念,通过 Swagger 等工具定义好接口,前后端、服务与服务之间基于契约进行并行开发和 Mock 测试。
取舍三:数据一致性的“代价”
困境: 最终一致性方案虽然解决了分布式事务的问题,但它是有代价的。它需要业务设计上能够容忍短暂数据不一致,并且需要复杂的补偿逻辑。
我们的原则: 核心业务强兜底,边缘业务最终一致。 对于金融、交易等绝对不能出错的核心环节,我们不惜代价,通过 TCC、可靠消息等更复杂的方案,来保证数据的强一致性。而对于用户点赞、收藏等边缘业务,我们则可以接受最终一致,通过异步任务和定期对账来保证最终数据的准确。
结语:架构是服务于业务的,而不是反过来
八年的架构演进之路,让我深刻体会到:架构设计没有银弹,只有永恒的权衡。
从单体到微服务,我们用运维的复杂度,换取了业务的敏捷性、技术的多样性和系统的弹性。我们用放弃强一致性的“执念”,换来了系统的高可用和水平扩展能力。
微服务不是目的,而是解决特定业务规模和复杂度问题的手段。对于一个初创团队,一个好的单体架构,远比一个糟糕的微服务架构要健康得多。
最重要的,是保持架构的演进能力。今天我们引以为傲的微服务架构,或许在未来的某一天,也会成为新的“枷锁”。到那时,我们或许会探索 Service Mesh(服务网格),甚至 Serverless(无服务器)等新的架构模式。
但无论技术如何变迁,其核心思想始终不变:让架构去适应业务,而不是让业务去迁就架构。 这,或许就是一个架构师在漫长职业生涯中,需要不断修炼和领悟的真谛。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。