从单体到微服务再合并,我们找到了平衡点
有人说,程序员往往对好的技术滔滔不绝,却对潜在的问题避而不谈。2015年,微服务的概念开始风靡,大家纷纷讨论它的种种优势:弹性、可扩展性、易于部署以及清晰的模块边界。然而,微服务也存在一些缺点和潜在风险,许多企业最终选择从微服务回归单体架构。国外的一家中小企业 Managed By Q 也经历了从单体架构转向微服务的过程,但最终他们在二者之间找到了一个平衡点。
这种平衡可能意味着他们在某些业务模块上保留了微服务的优势,而在其他领域则保持了单体架构的稳定性。这表明,技术选型并非绝对,需要根据企业的具体情况权衡利弊,找到最适合的架构模式。
从单体到微服务的历程
我在 2017 年加入公司时,我们的团队大约有 20 名工程师,当时我们的应用程序是一个部署在 ECS 上的 Django 单体架构。在过去的两年中,我们开发了许多新服务,看到服务数量的增长让人感到非常振奋。毕竟,这意味着我们紧跟着现代化开发实践的步伐。此外,与单体架构相比,开发这些小型服务让我们有一种更好的开发体验。
收割微服务的好处
微服务确实有显而易见的优势。
- 清晰的边界
各个模块的职责划分非常明确,几乎没有留下任何模棱两可的空间。这种清晰性让团队能够专注于自己的服务,不会让问题蔓延至其他领域,确保了问题的局部化处理。 - 更小的测试集
我们的单体应用需要 5 到 10 分钟才能跑完所有测试,而微服务只需几秒钟就能完成。这大大缩短了测试时间,让开发流程更加高效。 - 更快地部署
较小的测试集直接带来了更快的部署速度。我们的单体应用有一些基于 Selenium 的测试,用来确保仪表盘的关键功能正常运行。但许多服务并不是面向用户的,完全可以跳过这些测试,将这些服务的部署时间缩短了一半。 - CI/CD 问题的影响范围更小
在单体架构中,有时候某个服务的 CI/CD 管道出问题,可能会影响到其他新代码的部署,甚至导致 Bug 被发布到生产环境,迫使我们暂停部署,直到问题被修复。但在微服务架构下,其他团队的 CI/CD 问题不会影响到你的服务部署。 - 更简单的依赖管理,降低依赖风险如果只是对某个服务做出变更,依赖管理的复杂度会大大降低,变更过程也变得不那么可怕了。因此,相比单体架构,我们的许多微服务都能够保持更新状态,并更灵活地响应变更。
微服务的缺点
随着微服务数量的增长,我们开始感到事情不像之前那么顺利了。
- 需要维护更多的基础设施
每增加一个新服务,都会带来额外的基础设施维护需求。依赖项也需要不断更新,而更新的范围越来越广。基础设施团队花了大量时间在项目上,重复为每个服务进行相似的繁琐工作。 - 无人照看的服务
小型服务运行之后,常常容易被忽视,久而久之就会过时。如果一个微服务半年没有进行更新,人们往往不会再去动它,其中 90% 的更新都是依赖或基础设施的升级。这样,额外的维护负担就无形中增加了。 - 更慢的功能开发
如果服务的边界不明确,功能开发的速度会大幅下降,这是微服务架构的一个主要风险。对于初创公司而言,发展方向不可预测,最初完全独立的产品部分,可能在一年后就变得紧密耦合。因此,精确定义服务边界是一个巨大的挑战。 - 依赖库更新的难题
为了让所有微服务使用相同版本的依赖,我们需要从单体架构中拉取依赖库,而更新这些库成为了一个痛点。重要的依赖库更新需要发布新版本,并且在许多地方同时进行升级。 - 技术大杂烩
虽然微服务提供了“技术多样性”的好处,但这很快会演变成问题。我们的单体使用 Django,而一些微服务使用 Flask。单体的异步任务由 Celery 处理,而某些微服务使用 RabbitMQ。有的服务用 DynamoDB,其他服务则使用 Postgres。我们后来花了大量时间重构微服务,让它们统一技术栈,因为某些库依赖特定技术,部分微服务无法兼容。此外,技术多样性增加了新开发人员的学习成本。 - 本地开发问题
随着微服务的增加,开发时需要本地运行更多服务。虽然应用程序的容器化在一定程度上帮助了我们,但相比之前,我们在本地开发时不可避免地遇到更多问题。
找到平衡点:合理大小的服务
在转向微服务两年之后,我们开始合并微服务。一些微服务被合到了单体中,其他的则合并成较大的服务。在一年中我们总共移除了 9 个微服务。大小合理的服务承担着相当大的责任,大多数功能开发都可以在单个服务中完成。它们足够大,不会给我们增加太多的基础设施维护负担。服务边界清晰,避免团队在开发过程中彼此影响。