HOOOS

微服务拆分:业务领域与技术能力,我该如何选择?

0 6 架构小匠 微服务服务拆分数据一致性
Apple

在微服务架构的实践中,如何合理地划分服务边界,无疑是让许多开发者和架构师“纠结”的焦点问题。我们经常会陷入这样的两难境地:究竟是应该更侧重于按业务领域(Business Domain)来拆分,还是根据技术能力(Technical Capability)来组织服务?而这种拆分方式,又将如何影响我们最关心的数据一致性问题呢?

今天,我们就来深入探讨微服务拆分背后的原理、权衡,并提供一些实用的建议和案例。

业务领域划分 vs. 技术能力划分:核心考量

这两种划分方式各有其合理性,但适用的场景和带来的影响大相径庭。

1. 按业务领域划分(Business Domain-driven Decomposition)

这是微服务架构推崇的主流方式,其核心思想源于**领域驱动设计(DDD)**中的“限界上下文”(Bounded Context)。

  • 优点:

    • 高内聚、低耦合: 每个服务对应一个独立的业务功能模块,服务内部业务逻辑紧密相关,服务之间通过清晰的接口进行协作,降低了相互依赖。
    • 团队自治: 团队可以围绕一个或少数几个业务领域进行开发、测试、部署和运维,提高效率和责任感。
    • 独立演进: 当某个业务领域的需求发生变化时,只需要修改对应的服务,对其他服务的影响最小。
    • 易于理解: 服务结构与业务结构高度吻合,方便非技术人员理解系统功能。
  • 缺点:

    • 可能存在共享数据: 不同业务领域可能需要访问相同的数据,需要额外的机制(如事件驱动、API组合)来维护数据视图或保证最终一致性。
    • 通用功能分散: 一些看似通用的技术功能(如用户认证、通知)可能需要在多个业务服务中实现或集成,造成一定重复或复杂性。
  • 典型案例: 一个电商平台,可以按业务领域拆分为:用户服务(User Service)、商品服务(Product Service)、订单服务(Order Service)、支付服务(Payment Service)、库存服务(Inventory Service)等。

2. 按技术能力划分(Technical Capability-driven Decomposition)

这种方式是将具有相似技术职责或提供通用技术能力的部分抽离成独立服务。

  • 优点:

    • 技术复用: 某些通用的技术能力可以被多个业务服务复用,减少重复开发。
    • 专业化管理: 专门的服务可以由专注于该技术的团队负责,提高技术深度和维护效率(例如,专注于认证授权的团队)。
    • 简化业务服务: 业务服务可以更专注于业务逻辑本身,将底层技术细节委托给技术能力服务。
  • 缺点:

    • 业务逻辑分散: 一个完整的业务流程可能需要跨越多个技术能力服务,导致业务逻辑被“肢解”,增加理解和维护的难度。
    • 强耦合风险: 业务服务对技术能力服务的依赖可能很强,技术能力服务的变更可能影响大量业务服务。
    • 团队协作复杂: 一个业务功能的实现可能需要多个技术团队协作,增加沟通成本。
  • 典型案例: 日志服务(Logging Service)、文件存储服务(File Storage Service)、认证授权服务(Authentication Service)、通知服务(Notification Service)等。

微服务拆分的核心原则

在实践中,我们通常不会严格地只选择一种方式,而是根据具体情况进行权衡和融合。以下是一些关键的拆分原则:

  1. 高内聚,低耦合: 这是任何良好软件设计的基石。服务内部的功能应该紧密相关,而服务之间的依赖应尽可能少且松散。
  2. 职责单一原则(Single Responsibility Principle): 一个服务只应该做好一件事。这个“一件事”通常指的是一个内聚的业务功能或一组强相关的业务功能。
  3. 自治性(Autonomy): 每个服务应该拥有自己的数据存储,并独立部署、独立演进。尽量避免多个服务共享同一个数据库,这是微服务化的大忌。
  4. 领域边界优先: 尽可能以业务领域为核心进行拆分。当发现某个“技术能力”实际上承载了强烈的业务含义时,它就应该被视为一个业务领域服务(例如,权限管理本质上是一个业务领域)。
  5. 关注变化频率和原因: 如果两个功能总是因为相同的原因而一起变化,那么它们应该在一起;如果因为不同的原因而变化,那么它们应该分开。
  6. 团队边界匹配: 理想情况下,服务的边界应该与开发团队的边界相匹配,每个团队负责一个或少数几个服务,这就是所谓的“康威定律”(Conway's Law)。
  7. 逐步演进: 拆分并非一蹴而就,可以从一个单体应用开始,逐步识别热点模块、复杂模块,逐步将其拆分为微服务。

数据一致性保障:微服务架构下的挑战与方案

微服务架构下,由于服务自治和数据独立,传统的分布式事务(两阶段提交等)几乎不可行。因此,我们通常采用**最终一致性(Eventual Consistency)**模型来处理跨服务的数据操作。

1. 理解最终一致性

最终一致性意味着数据在某个时间点可能不一致,但在系统“静止”一段时间后,最终会达到一致状态。这需要我们重新思考业务流程,接受数据短暂不一致的情况。

2. 常见的数据一致性保障方案:

  • 事件驱动架构(Event-Driven Architecture):

    • 一个服务完成自己的业务操作并更新本地数据库后,会发布一个业务事件(例如,“订单已创建”)。
    • 其他感兴趣的服务订阅这些事件,接收到事件后执行自己的相应操作,并更新自己的本地数据库。
    • 案例: 订单服务创建订单成功后,发布“OrderCreated”事件;库存服务订阅该事件,接收到后扣减商品库存;积分服务订阅该事件,为用户增加积分。
  • Saga 模式:

    • 当一个业务流程需要跨越多个服务时,Saga 模式通过一系列本地事务和补偿事务来保证最终一致性。
    • 编排式 Saga (Orchestration-based Saga): 引入一个中心协调器(Orchestrator)来管理整个Saga流程。协调器发送命令给参与服务,并根据服务的响应决定下一步操作或触发补偿。
    • 协同式 Saga (Choreography-based Saga): 服务之间通过事件直接通信,没有中心协调器。每个服务在完成本地事务后发布事件,并监听其他服务的事件来决定自己的行为或触发补偿。
    • 案例: 支付流程:订单服务 → 发布支付请求事件 → 支付服务处理 → 发布支付成功事件 → 库存服务扣减库存 → 发布扣减成功事件。如果支付失败,会发布支付失败事件,订单服务监听此事件并回滚订单状态。
  • 事务性发件箱模式(Transactional Outbox Pattern):

    • 为了确保本地数据库操作和事件发布的原子性,可以将要发布的事件先写入本地数据库的一个“发件箱”表(outbox table),作为本地事务的一部分。
    • 一个独立的“事件发布器”进程(或Scheduler Job)会周期性地扫描发件箱表,将事件发布到消息队列,并标记为已发送。
    • 这保证了即使发布事件失败,只要本地事务提交,事件也不会丢失,会在稍后重试。

实用建议与案例:

  • 优先按业务领域拆分: 这是最符合微服务理念的拆分方式,能最大化服务的独立性和团队的自治性。

    • 案例: 一个内容管理系统 (CMS),可以拆分为:文章管理服务、用户管理服务、评论服务、标签服务。它们各自管理自己的数据,通过事件或API调用协作。
  • 将通用技术能力抽象为“库”或“SDK”而非独立服务(初期): 对于一些非常通用且无状态的技术能力(如时间工具、加密算法),可以考虑将其封装成共享库,而不是一开始就拆成微服务。避免过度拆分导致服务管理复杂性过高。

  • 识别跨领域但强相关的通用“平台”能力: 对于一些跨多个业务领域,且有状态、有独立生命周期,需要独立演进的通用能力,可以将其拆分为独立的服务。

    • 案例: 文件上传服务: 如果图片、视频文件在不同业务(用户头像、商品图片、文章附件)中都需要上传和管理,且文件存储、压缩、CDN等逻辑复杂,就可以将其作为一个独立的技术能力服务。但它承载的业务含义是“文件管理”,并非纯粹的技术。
    • 通知服务: 负责邮件、短信、站内信等各种通知的发送,并管理通知模板、发送记录等。它也是跨业务的,可以独立。
  • 警惕“上帝服务”: 避免将所有与某个实体相关的操作都塞到一个服务中。例如,一个“产品服务”如果既管理产品基本信息,又处理产品定价、库存、评论、推荐等所有相关逻辑,它会变得臃肿。可以考虑将定价、库存等拆分到独立的领域服务。

  • 服务边界是演进的: 第一次拆分不一定完美。随着业务发展和团队认识的深入,服务边界可能需要调整。微服务架构的灵活性也体现在这里,允许我们进行迭代和重构。

总结

微服务架构的服务边界划分没有银弹,以业务领域为核心,结合领域驱动设计的思想,是当前最被推崇的实践。同时,也需要适度考虑技术能力的复用,但要警惕将业务逻辑碎片化。对于数据一致性,拥抱最终一致性,并利用事件驱动架构Saga模式等手段来保障跨服务操作的可靠性。记住,服务拆分是一个持续演进的过程,重要的是理解原则,并根据团队和业务的实际情况灵活调整。

点评评价

captcha
健康