【微服务】服务拆分详解【目标、准则、策略、详细电商服务示例】
微服务拆分的核心目标
在深入准则之前,首先要明确拆分的核心目标:
- 高内聚,低耦合:这是软件设计的黄金法则,在微服务中尤为重要。每个服务内部的功能应该紧密相关,而服务与服务之间的依赖和调用应尽可能少且简单。
- 独立部署和扩展:每个服务应该可以独立于其他服务进行部署、升级和伸缩。这是微服务架构最大的优势之一。
- 团队自治:每个服务可以由一个小的、跨职能的团队(如“双比萨团队”)独立负责开发、测试、部署和运维。
微服务拆分的准则与策略
以下是进行服务拆分时可以参考的一系列详细准则和策略,它们通常需要结合使用。
1. 基于业务领域驱动设计(DDD)进行拆分(最核心的准则)
这是目前最受推崇的拆分方法。DDD 的核心是通过与业务专家沟通,建立“通用语言”,并识别出领域的“限界上下文”。
- 准则:根据业务的边界进行拆分,而不是根据技术层次(如Controller、Service、DAO层)进行拆分。
- 做法:
- 战略设计:识别出核心业务领域和子域。
- 限界上下文:每个子域都有一个明确的边界,边界内的概念和术语有特定的含义。这个限界上下文就是微服务的最佳候选者。
- 举例:一个电子商务系统,通过DDD可以识别出以下限界上下文,每个都可以成为一个服务:
- 用户服务:负责用户注册、登录、个人信息管理。
- 商品目录服务:负责商品的增、删、改、查、分类管理。
- 订单服务:负责下单、订单状态管理、订单历史。
- 库存服务:负责库存数量的管理和扣减。
- 支付服务:负责与第三方支付网关集成,处理支付流程。
- 物流服务:负责发货、物流跟踪。
2. 单一职责原则(SRP)
这是面向对象设计原则在微服务层面的应用。
- 准则:一个微服务应该只负责一个单一的业务功能或聚合了一组高度相关的功能。如果一个服务的修改理由超过一个,那么它可能承担了过多的职责。
- 举例:在电商系统中,“图片服务”应该只负责图片的上传、存储、裁剪和分发。而“商品服务”不应该自己处理图片,它应该通过调用“图片服务”的API来关联商品图片。
3. 高内聚,低耦合原则
这是衡量拆分是否合理的黄金标准。
- 高内聚:一个服务内部的模块和代码应该为了实现同一个业务目标而紧密合作。例如,订单服务内部的所有代码都只关心与订单相关的事情。
- 低耦合:服务与服务之间的依赖应该尽可能弱。修改一个服务不应该导致另一个服务必须修改。可以通过异步消息、API网关、防腐层等模式来降低耦合。
4. 基于“数据”的考量进行拆分
数据是服务的私有资产,这是实现低耦合的关键。
- 准则:每个微服务应该拥有并独享自己的数据库(或Schema/表集合)。其他服务不能直接访问该数据库,必须通过服务提供的API来操作数据。
- 好处:这确保了服务的封装性和技术异构性(不同服务可以使用不同类型的数据库,如订单用关系型,商品用文档型)。
- 举例:用户服务使用MySQL存储用户信息,商品服务使用MongoDB存储商品文档。订单服务需要用户信息时,不能直接连用户库,必须调用用户服务的API。
5. 基于“非功能性需求”进行拆分
有时,拆分是为了满足性能、安全性或可扩展性等需求。
- 准则:将具有不同伸缩性、安全性、可用性要求的业务功能分离成独立的服务。
- 举例:
- 读写分离:电商的“商品搜索”服务(读多写少)和“商品管理”服务(写多读少)对资源的需求完全不同。将它们拆分,可以独立对搜索服务进行大规模水平扩展,而管理服务只需少量实例。
- 安全性:一个需要非常高安全级别的“支付服务”应该与普通的“用户信息服务”隔离开,以便实施更严格的安全策略和网络隔离。
6. 团队结构驱动(康威定律)
康威定律指出:“设计系统的组织,其产生的设计等同于组织内的沟通结构。”
- 准则:服务的拆分应该与团队的职责边界相匹配。如果一个功能由两个不同的团队负责,那么它就应该被考虑拆分成两个服务。
- 举例:如果公司有一个专门的“风控团队”负责反欺诈,那么就应该有一个独立的“风控服务”,供下单、支付等业务流程调用。
服务拆分的具体方法与步骤
在实际操作中,通常采用“绞杀者模式”或“修缮模式”,而不是一次性重写整个单体应用。
- 识别并定义边界:使用DDD的方法,画出业务上下文映射图,清晰地定义出服务的边界和它们之间的交互关系。
- 先粗后细,持续演进:不要一开始就拆得太细。先从大的业务模块拆起(比如先拆出用户、商品、订单三个大服务)。随着业务发展,如果发现某个服务变得过于庞大或职责不清,再对其进行二次拆分。
- 确定拆分顺序:从耦合度最低、最稳定的功能模块开始拆分。这样风险最小。例如,先从单体应用中拆出“日志服务”或“配置服务”,再拆核心的“订单服务”。
- 实现数据解耦:这是拆分过程中最复杂的一步。需要:
- 停止在单体应用中对目标表进行写操作。
- 将数据迁移到新服务的数据库中。
- 修改代码,让新服务通过API提供数据访问,让单体应用通过API调用新服务。
- 部署和运维:为每个新服务建立独立的CI/CD流水线、监控和告警体系。
需要警惕的拆分陷阱(反面例子)
- 拆分过细(纳米服务):服务太小,导致系统由成百上千个服务组成,运维、调试、网络通信的复杂度呈指数级增长,团队疲于奔命。准则:除非有强烈需求,否则不要拆分。
- 分布式单体:服务虽然被拆开了,但它们必须同时部署、同时伸缩,彼此之间紧密耦合,就像一个被分布到多台机器上的单体应用。这通常是因为没有处理好数据库的共享或服务间的强依赖。
- 跨服务的事务:在多个服务中维护数据一致性非常困难。应尽量避免分布式事务,转而采用最终一致性模式,如 Saga 模式。
案例背景:一个名为“易购”的单体电商应用
这个应用最初是一个典型的单体架构,包含以下主要功能模块:
- 用户管理:注册、登录、个人信息维护、地址管理。
- 商品管理:商品发布、编辑、分类管理、库存管理。
- 订单管理:购物车、下单、支付处理、订单状态跟踪、订单历史查询。
- 支付处理:对接第三方支付网关(如支付宝、微信支付),处理支付请求、回调、退款。
- 物流跟踪:对接物流公司API,查询订单的物流状态。
- 商品搜索:提供基于关键词的商品搜索功能。
- 促销活动:管理优惠券、满减活动、秒杀活动。
- 评价系统:用户对已购商品进行评价和打分。
单体应用的问题:
- 代码库庞大,开发、测试、部署效率低下。
- 所有模块共享一个数据库,数据库成为性能瓶颈和单点故障源。
- 一个小功能的修改或上线需要部署整个应用,风险高。
- 不同功能模块的资源需求不同(如搜索需要大量内存和CPU,支付需要高安全隔离),但无法独立扩展。
- 团队协作困难,代码冲突频繁。
目标: 将“易购”拆分成一组微服务。
拆分过程详解(应用准则):
第一步:基于业务领域驱动设计(DDD)识别限界上下文(核心准则)
与业务专家沟通,分析核心业务流程和概念,识别出不同的业务子域及其边界:
- 用户域: 核心概念是
用户、用户资料、收货地址。目标是管理用户身份和基础信息。 - 商品域: 核心概念是
商品、商品分类、商品属性、库存。目标是管理商品信息和可用性。 - 订单域: 核心概念是
购物车、订单、订单项、订单状态。目标是管理用户的购买流程和订单生命周期。 - 支付域: 核心概念是
支付、支付记录、退款。目标是安全、可靠地处理资金交易。 - 物流域: 核心概念是
物流单号、物流状态。目标是跟踪商品的配送过程。 - 搜索域: 核心概念是
搜索索引、关键词、相关性排序。目标是提供高效的商品检索。 - 促销域: 核心概念是
优惠券、活动规则、折扣。目标是管理营销活动和用户优惠。 - 评价域: 核心概念是
评价、评分、评论。目标是收集和展示用户反馈。
- 准则应用: 每个限界上下文代表了业务的一个内聚单元,自然成为微服务的候选者。例如,“订单”上下文包含了创建订单、修改订单状态、查询订单历史等紧密相关的操作,与“商品”上下文的库存扣减逻辑(属于商品域)有明确交互边界。
第二步:应用单一职责原则(SRP)和进一步细化
审视每个初步识别的限界上下文,看其职责是否足够单一:
- 商品域分析: 商品管理(增删改查)和库存管理(扣减、回滚、查询)虽然相关,但变动原因不同。商品信息相对稳定,而库存变动非常频繁(尤其在促销时)。拆分决策: 将
库存管理从商品管理中分离出来,形成独立的库存服务。商品服务只负责商品基础信息,库存服务负责库存数量、锁定、扣减逻辑。- 服务1:商品服务 (Product Service): 管理商品信息(名称、描述、图片、分类、价格基础价)。
- 服务2:库存服务 (Inventory Service): 管理商品库存数量(总库存、可用库存)、处理库存锁定(下单时预占)、扣减(支付成功后)、回滚(订单取消/支付失败)。
- 订单域分析: 购物车是下单流程的起点,但其状态是临时的(用户会话级),而订单是持久化的交易记录。拆分决策: 将
购物车管理拆分成独立的购物车服务。订单服务专注于已提交订单的生命周期管理。- 服务3:购物车服务 (Cart Service): 管理用户的购物车项(添加、删除、修改数量、计算临时总价)。
- 服务4:订单服务 (Order Service): 创建订单(基于购物车)、管理订单状态(待支付、已支付、已发货、已完成、已取消)、查询订单历史。
- 支付域分析: 支付本身是核心,但退款流程虽然相关,逻辑相对独立且可能涉及与订单、客服的交互。决策(可选): 如果初期复杂度不高,可将退款放在支付服务内。如果退款规则复杂或调用量大,可拆分为退款服务。本例暂不拆分。
- 服务5:支付服务 (Payment Service): 发起支付请求、处理支付网关回调、记录支付状态、处理退款请求(初期)。
- 搜索域分析: 搜索需要构建和维护独立的索引(如Elasticsearch),与商品服务的写操作(商品上/下架、信息修改)需要解耦。决策: 保持独立。
- 服务6:搜索服务 (Search Service): 提供商品搜索API,负责索引构建(监听商品变更事件)和查询处理。
- 促销域分析: 优惠券和活动规则管理相对独立。决策: 保持独立。
- 服务7:促销服务 (Promotion Service): 管理优惠券(发放、核销)、活动规则(满减、折扣)、计算订单应享优惠。
- 评价域分析: 评价依赖于订单(只有购买后才能评价),但自身管理评价内容。决策: 保持独立。
- 服务8:评价服务 (Rating Service): 提交评价、查询商品评价、管理评价内容。
- 用户域、物流域: 职责相对清晰单一,暂不进一步拆分。
- 服务9:用户服务 (User Service): 用户注册、登录、信息管理、地址管理。
- 服务10:物流服务 (Shipping Service): 获取物流单号(发货时)、查询物流状态(通过第三方API)。
第三步:确保高内聚低耦合和数据私有(关键准则)
- 高内聚: 每个服务内部功能高度相关。例如,订单服务内部代码只处理订单状态流转、查询;库存服务只处理库存数量的变化逻辑。
- 低耦合:
- 服务间通信: 使用定义良好的API(通常是RESTful或gRPC)进行同步调用,或使用消息队列(如Kafka, RabbitMQ)进行异步事件驱动。
- 同步调用示例:
- 下单流程:
订单服务创建订单前,需要调用库存服务的API检查并锁定库存。支付成功后,订单服务调用库存服务API扣减库存。 - 获取商品详情:
前端/API网关调用商品服务获取基础信息,同时可能调用库存服务获取实时库存(或商品服务聚合库存信息)。
- 下单流程:
- 异步事件驱动示例:
- 商品信息变更:当
商品服务修改了商品信息(如价格、下架),发布一个ProductUpdated事件到消息队列。搜索服务监听此事件,更新搜索索引。购物车服务监听此事件,如果购物车中有该商品,可能需要更新价格提示或失效提示。 - 订单状态变更:
订单服务在订单支付成功时,发布OrderPaid事件。物流服务监听此事件,开始安排发货并获取物流单号。促销服务监听此事件,核销使用的优惠券。
- 商品信息变更:当
- 同步调用示例:
- API网关: 引入API网关作为所有客户端的单一入口点。网关负责路由请求到对应服务、聚合结果、认证、限流等。避免客户端直接耦合多个后端服务。
- 服务间通信: 使用定义良好的API(通常是RESTful或gRPC)进行同步调用,或使用消息队列(如Kafka, RabbitMQ)进行异步事件驱动。
- 数据私有: 这是实现低耦合的核心! 每个服务拥有自己的数据库(或Schema/表集合),其他服务只能通过其API访问数据。
- 订单服务数据库: 存储订单表、订单项表、订单状态日志表。不存储商品详情(只存商品ID和快照信息)、用户详情(只存用户ID和快照的收货地址)。
- 商品服务数据库: 存储商品表、分类表、属性表等。不存储库存数量(库存服务管)、订单信息。
- 库存服务数据库: 存储库存表(商品ID、总库存、锁定库存、可用库存)、库存变更流水表。
- 用户服务数据库: 存储用户表、地址表等。不直接暴露给其他服务。订单服务需要地址时,在创建订单时通过调用用户服务API获取并快照存储在自己的库中。
- 支付服务数据库: 存储支付记录表、退款记录表。与订单ID关联,但不存储订单详情。
第四步:考虑非功能性需求(扩展性、安全性等)
- 独立扩展:
搜索服务和促销服务(尤其在双11)会承受巨大读压力,可以独立部署大量实例进行水平扩展。库存服务的写操作(扣减)压力大,也可以独立扩展。支付服务需要高可用性和严格的安全隔离,可以部署在独立的、安全性更高的网络区域,并配置更严格的资源配额和监控。
- 技术异构性:
搜索服务使用Elasticsearch作为数据存储。商品服务可能使用MongoDB存储灵活的商品属性结构。订单服务、支付服务使用关系型数据库(如MySQL/PostgreSQL)保证事务一致性(在服务内)。- 其他服务根据需求选择合适的存储(如Redis用于购物车服务)。
第五步:团队结构(康威定律)
- 每个服务由一个小的、跨职能团队(如5-8人)负责,包括开发、测试、运维(或DevOps)。例如:
- “交易核心”团队: 负责购物车服务、订单服务、库存服务。
- “支付与风控”团队: 负责支付服务(未来可能拆分风控服务)。
- “商品与搜索”团队: 负责商品服务、搜索服务。
- “用户与增长”团队: 负责用户服务、促销服务。
- “履约”团队: 负责物流服务、评价服务(如果评价与物流反馈相关性强)。
拆分前的服务架构示意图

拆分后的微服务架构示意图(简化):

关键交互流程示例:用户下单
- 用户添加商品到购物车: 客户端 -> API网关 -> 购物车服务 (添加商品ID和数量)。
- 用户进入结算页:
- 购物车服务读取购物车内容。
- 购物车服务调用商品服务获取商品最新信息(名称、图片、基础价)和调用促销服务计算该购物车可用的优惠(优惠券、满减)。
- 购物车服务返回结算页所需数据(商品信息、数量、优惠后价格)。
- 用户提交订单:
- 客户端提交订单请求(含收货地址ID、支付方式、优惠券等)到API网关 -> 订单服务。
- 订单服务:
- 调用用户服务API,验证地址ID有效性并获取地址快照。
- 调用促销服务API,验证优惠券有效性并锁定优惠。
- 调用库存服务API,检查并锁定订单所需商品的库存。
- 生成订单(状态:待支付),保存商品快照、地址快照、优惠信息快照、锁定库存记录ID。
- 调用支付服务API,发起支付请求,获得支付链接/参数。
- 订单服务将支付信息返回给客户端。
- 用户支付:
- 客户端引导用户完成支付(跳转第三方支付)。
- 第三方支付回调支付服务通知支付结果。
- 支付服务:
- 验证回调真实性,更新支付状态(成功/失败)。
- 发布
PaymentCompleted事件(成功)或PaymentFailed事件(失败)到消息队列。
- 处理支付结果:
- 订单服务监听
PaymentCompleted事件:- 更新订单状态为“已支付”。
- 调用库存服务API,将之前锁定的库存正式扣减。
- 调用促销服务API,核销使用的优惠券。
- 发布
OrderPaid事件。
- 订单服务监听
PaymentFailed事件:- 更新订单状态为“支付失败”或“已取消”。
- 调用库存服务API,释放之前锁定的库存。
- 调用促销服务API,释放锁定的优惠券。
- 物流服务监听
OrderPaid事件:- 开始处理发货,获取物流单号。
- 更新订单物流信息(调用订单服务API或直接更新自己的数据并通知)。
- 发布
OrderShipped事件(可选)。
- 订单服务监听
- 用户评价(支付成功后):
- 用户可在订单完成后进行评价。评价服务提供提交接口。
- 评价服务在提交评价时,会验证该用户是否拥有此订单且订单已完成(可能需要调用订单服务API验证)。
总结:
通过这个“易购”电商的例子,我们可以看到微服务拆分是一个结合业务分析(DDD)、设计原则(SRP、高内聚低耦合)、技术考量(数据私有、独立部署扩展)和团队协作(康威定律)的综合过程。核心在于识别出业务边界清晰、职责单一、数据独立的服务单元,并通过定义良好的接口(API/事件)进行协作。拆分不是一蹴而就的,需要持续演进和优化。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120290

浙公网安备 33010602011771号