03.微服务架构拆分


微服务拆分时机

微服务不仅仅是技术的升级,更是开发方式、组织架构、开发观念的转变。

  • 业务规模:业务模式得到市场的验证,需要进一步加快脚步快速占领市场,这时业务的规模变得越来越大,按产品生命周期来划分(导入期、成长期、成熟期、衰退期)这时一般在成长期阶段。如果是导入期,尽量采用单体架构
  • 团队规模:一般是团队达到百人的时候,主要还是要结合业务复杂度
  • 技术储备:领域驱动设计、注册中心、配置中心、日志系统、持续交付、监控系统、分布式定时任务、CAP 理论、分布式调用链、API 网关等等。
  • 人才储备:精通微服务落地经验的架构师及相应开发人员。
  • 研发效率:研发效率大幅下降。

微服务拆分原则

  • 单一服务内部功能高内聚低耦合:每个服务只完成自己职责内的任务,对于不是自己职责的功能交给其它服务来完成
  • 闭包原则(CCP):微服务的闭包原则就是当我们需要改变一个微服务的时候,所有依赖都在这个微服务的组件内,不需要修改其他微服务
  • 服务自治、接口隔离原则:尽量消除对其他服务的强依赖,这样可以降低沟通成本,提升服务稳定性。服务通过标准的接口隔离,隐藏内部实现细节。这使得服务可以独立开发、测试、部署、运行,以服务为单位持续交付。
  • 持续演进原则:在服务拆分的初期,你其实很难确定服务究竟要拆成什么样。应逐步划分,持续演进,避免服务数量的爆炸性增长
  • 拆分的过程尽量避免影响产品的日常功能迭代:也就是说要一边做产品功能迭代,一边完成服务化拆分。比如优先剥离比较独立的边界服务(如短信服务等),从非核心的服务出发减少拆分对现有业务的影响,也给团队一个练习、试错的机会。同时当两个服务存在依赖关系时优先拆分被依赖的服务
  • 服务接口的定义要具备可扩展性:比如微服务的接口因为升级把之前的三个参数改成了四个,上线后导致调用方大量报错,推荐做法服务接口的参数类型最好是封装类,这样如果增加参数就不必变更接口的签名
  • 避免环形依赖与双向依赖:尽量不要有服务之间的环形依赖或双向依赖,原因是存在这种情况说明我们的功能边界没有化分清楚或者有通用的功能没有下沉下来。
  • 阶段性合并:随着你对业务领域理解的逐渐深入或者业务本身逻辑发生了比较大的变化,亦或者之前的拆分没有考虑的很清楚,导致拆分后的服务边界变得越来越混乱,这时就要重新梳理领域边界,不断纠正拆分的合理性
  • 自动化驱动:部署和运维的成本会随着服务的增多呈指数级增长,每个服务都需要部署、监控、日志分析等运维工作,成本会显著提升。因此,在服务划分之前,应该首先构建自动化的工具及环境。开发人员应该以自动化为驱动力,简化服务在创建、开发、测试、部署、运维上的重复性工作,通过工具实现更可靠的操作,避免微服务数量增多带来的开发、管理复杂度问题。

微服务拆分策略

功能维度拆分策略

大的原则是基于业务复杂度拆分服务:业务复杂度足够高,应该基于领域驱动拆分服务。业务复杂度较低,选择基于数据驱动拆分服务

  • 基于数据驱动拆分服务:自下而上的架构设计方法,通过分析需求,确定整体数据结构,根据表之间的关系拆分服务
    • 拆分步骤:需求分析,抽象数据结构,划分服务,确定调用关系和业务流程验证。
  • 基于领域驱动拆分服务:自上而下的架构设计方法,通过和领域专家建立统一的语言,不断交流,确定关键业务场景,逐步确定边界上下文。领域驱动更强调业务实现效果,认为自下而上的设计可能会导致技术人员不能更好地理解业务方向,进而偏离业务目标。
    • 拆分步骤:通过模型和领域专家建立统一语言,业务分析,寻找聚合,确定服务调用关系,业务流程验证和持续优化。
  • 从已有单体架构中逐步拆分服务
    • 拆分步骤:前后端分离,提取公共基础服务(如授权服务,分布式ID服务),不断从老系统抽取服务,垂直划分优先,适当水平切分

以上几种拆分方式不是多选一,而是可以根据实际情况自由排列组合
拆分不仅仅是架构上的调整,也意味着要在组织结构上做出相应的适应性优化,以确保拆分后的服务由相对独立的团队负责维护。

非功能维度拆分策略

主要考虑六点:扩展性、复用性、高性能、高可用、安全性、异构性

  • 扩展性区分系统中变与不变的部分,不变的部分一般是成熟的、通用的服务功能,变的部分一般是改动比较多、满足业务迭代扩展性需要的功能,我们可以将不变的部分拆分出来,作为共用的服务,将变的部分独立出来满足个性化扩展需要同时根据二八原则,系统中经常变动的部分大约只占 20%,而剩下的 80% 基本不变或极少变化,这样的拆分也解决了发布频率过多而影响成熟服务稳定性的问题。
  • 复用性:不同的业务里或服务里经常会出现重复的功能,比如每个服务都有鉴权、限流、安全及日志监控等功能,可以将这些通过的功能拆分出来形成独立的服务。
  • 高性能:将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其它服务
    • 我们也可以基于读写分离来拆分,比如电商的商品信息,在 App 端主要是商品详情有大量的读取操作,但是写入端商家中心访问量确很少。因此可以对流量较大或较为核心的服务做读写分离,拆分为两个服务发布,一个负责读,另外一个负责写。
    • 数据一致性是另一个基于性能维度拆分需要考虑的点,对于强一致的数据,属于强耦合,尽量放在同一个服务中(但是有时会因为各种原因需要进行拆分,那就需要有相应的机制进行保证),弱一致性通常可以拆分为不同的服务。
  • 高可用:将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。具体拆分的时候,核心服务可以是一个也可以是多个,只要最终的服务数量满足“三个火枪手”的原则就可以。
  • 安全性:不同的服务可能对信息安全有不同的要求,因此把需要高度安全的服务拆分出来,进行区别部署,比如设置特定的 DMZ 区域对服务进行分区部署,可以更有针对性地满足信息安全的要求,也可以降低对防火墙等安全设备吞吐量、并发性等方面的要求,降低成本,提高效率。
  • 异构性:对于对开发语言种类有要求的业务场景,可以用不同的语言将其功能独立出来实现一个独立服务。

拆分注意的风险

  • 不打无准备之仗:开发团队是否具备足够的经验,能否驾驭微服务的技术栈,可能是第一个需要考虑的点。
  • 不断纠正:我们需要承认我们的认知是有限的,只能基于目前的业务状态和有限的对未来的预测来制定出一个相对合适的拆分方案,而不是所谓的最优方案,任何方案都只能保证在当下提供了相对合适的粒度和划分原则,要时刻做好在未来的末一个时刻会变得不和时宜、需要再次调整的准备。
  • 要做行动派,而不是理论派:在具体怎么拆分上,也不要太纠结于是否合适,如果拆了之后发现真的不合适,在重新调整就好了。如果要灵活调整,可以针对服务化架构搭建起一套完整的能力体系,比如服务治理平台、数据迁移工具、数据双写等等
  • 服务只拆不合:
    • 拆相当于我们开发代码,合相当于重构代码。随着我们对应用程序领域的了解越来越深,它们需要随着时间的推移而变化。
    • 人员和服务数量的不匹配,导致的维护成本增加,也是导致服务合并的一个重要原因。
    • 如果微服务数量过多和资源不匹配,则可以考虑合并多个微服务到服务包,部署到一台服务器,这样可以节省服务运行时的基础资源消耗也降低了维护成本。需要注意的是,虽然服务包是运行在一个进程中,但是服务包内的服务依然要满足微服务定义,以便在未来某一天要重新拆开的时候可以很快就分离

总结梳理

微服务拆分时机

业务规模、团队规模、技术储备、人才储备、研发效率

微服务拆分原则

  • 单一服务内部功能高内聚低耦合

  • 闭包原则(CCP)

  • 服务自治、接口隔离原则

  • 持续演进原则

  • 避免影响产品的日常功能迭代

  • 服务接口的定义要具备可扩展性

  • 避免环形依赖与双向依赖

  • 阶段性合并

  • 自动化驱动

功能维度拆分策略:领域驱动、数据驱动、单体拆分

  • 业务复杂度足够高:基于领域驱动拆分服务

  • 业务复杂度较低:基于数据驱动拆分服务

非功能维度拆分策略:扩展性、复用性、高性能、高可用、安全性、异构性

拆分注意的风险

  • 能否驾驭微服务的技术栈

  • 不断纠正

  • 不要纠结怎么拆,先拆了再说,发现不合适再重新调整

  • 如果拆了之后发现真的不合适,在重新调整就好了:合并后的包内服务也要满足微服务定义(DDD),以便日后拆分

微服务架构注意事项

  • 使用 RequestInterceptor 接口设置请求头传递 memberId

  • Skywalking + ELK


文章作者: 钱不寒
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 钱不寒 !
  目录