电商平台做大了,单体架构的问题就慢慢暴露出来了:代码搅在一起不好改、改一点东西全系统都得测、服务器资源不够用的时候各模块抢着用…… 这些都成了业务增长的绊脚石。ZKmall 开源商城专门针对商家管理模块的复杂情况,搞了次架构升级,把原来堆在一起的商家管理 Server 拆成了好几个能独立运行又能互相配合的服务单元。这么一改,系统灵活多了,能扩展的空间也大了,商家数量从几百家涨到几千家都能扛得住。下面就来好好说说这次拆分是怎么想的、怎么做的,还有些实际经验,给其他类似系统的微服务改造当个参考。
为啥要拆商家管理 Server?想达到啥目标?
商家管理是电商平台的核心模块,商家入驻、店铺运营、权限管理、费用结算这些关键功能都在这儿。可随着平台上的商家越来越多,业务场景也越来越复杂,单体架构下的商家管理 Server 问题越来越多,不得不拆。
单体架构的老毛病直接拖了业务的后腿。在 ZKmall 早期版本里,商家管理模块和商品、订单这些模块共用一个数据库和业务逻辑层,麻烦事儿主要有三个:一是改东西太慢,商家管理模块的代码超 10 万行,哪怕改个小地方,全系统都得重新测试,改一次得两周,根本跟不上商家需求的变化;二是资源不够抢,商家后台导个月度结算报表什么的,就和前台处理订单抢服务器资源,忙的时候数据库连接池都被占满了,接口半天没反应,有次促销活动,商家查结算的接口平均要等 8 秒才能出来结果;三是一出问题就全完蛋,商家管理模块出个 BUG 可能让整个系统崩溃,有回因为商家等级计算逻辑错了,搞出个死循环,直接让全平台的订单处理停了 2 小时。
微服务拆分的核心目标就是解决这些问题,从架构上做优化。具体说有四个方面:一是业务拆开,把商家管理的不同功能分成独立服务,各模块别再搅在一起,这样每个服务都能单独改、单独更;二是资源分开用,给不同服务分独立的服务器和数据库,别再互相抢资源;三是能灵活扩缩容,根据商家访问量随时加服务器或者减服务器,应付流量高峰;四是出问题别连累别人,一个服务出毛病了,其他服务还能正常跑,让系统整体更稳。有个零售平台的例子,差不多规模的商家管理模块拆完之后,改东西的周期从两周缩到 3 天,资源利用率提高了 40%,出问题恢复的时间从几小时变成了几分钟。
商家管理业务的特点决定了拆起来既有可能,又挺复杂。商家管理的业务边界其实挺清楚的:商家入驻就是资质审核、签协议这些事儿;店铺管理就是维护店铺信息、搞装修这些;权限管理就是分配角色和权限;费用结算就是生成账单、扣钱这些财务事儿。这些相对独立的业务块,正好给微服务拆分提供了划分的依据。但麻烦的是,商家管理和商品、订单这些模块联系太紧密了,比如商家要发布商品得先审资质,订单结算得关联商家账户,拆完之后怎么让这些模块配合好,是必须解决的关键问题。
微服务拆分的原则和边界怎么定?
微服务拆分不是随便把功能切切开就行,得根据业务领域和技术特点系统地重新组织。ZKmall 在拆商家管理 Server 的时候,有明确的原则和方法,保证拆出来的服务既能自己管好自己,又能高效合作。
领域驱动设计(DDD) 是划分服务边界的核心办法。ZKmall 团队用事件风暴(Event Storming)把商家管理领域里的核心实体、值对象、聚合根和领域事件都理清楚,找出了四个清晰的限界上下文:商家入驻上下文(包括商家、资质、审核记录这些实体)、店铺管理上下文(包括店铺、店铺类目、装修模板这些实体)、权限管理上下文(包括角色、权限、操作日志这些实体)、费用结算上下文(包括账单、账户、交易记录这些实体)。每个限界上下文对应一个独立的微服务,服务内部的业务逻辑和数据模型要紧凑配合,服务之间通过明确定好的接口交流。比如,商家入驻服务就管从商家注册到审核通过这一套流程,店铺管理服务就管审核通过后的店铺运营,俩服务靠 “商家审核完成事件” 配合工作。
服务拆分的三个基本原则能保证拆分方案合理。一是单一职责,每个服务就管一个业务领域的事儿,比如费用结算服务就只管和商家相关的费用计算、生成账单、扣钱这些,别掺和店铺装修什么的;二是高内聚低耦合,服务内部的组件要紧密合作,服务之间通过定义好的接口打交道,少搞直接依赖,比如店铺管理服务要拿商家资质信息,就调用商家入驻服务的查询接口,别直接去人数据库里查;三是数据自己管,每个服务要有独立的数据库或者数据 Schema,别好几服务共用一个库,ZKmall 给四个商家管理相关的服务各建了个独立的 MySQL 数据库,数据交换全靠 API 调用,彻底把数据层面的牵连给断了。
服务粒度得把握好,直接影响拆分效果。服务不是拆得越细越好:太细了,服务数量太多,运维起来麻烦,服务之间调用成本也高;太粗了,又解决不了单体架构的老问题。ZKmall 实际操作的时候用了 “渐进式拆分” 的办法,一开始把商家管理拆成 4 个核心服务,没再往细了拆成 “资质审核服务”“店铺装修服务” 这种。这种中等粒度的拆分,既解决了业务耦合的问题,又控制了服务之间的合作成本。等业务发展了,再看情况把某个核心服务往细了拆,比如要是费用结算服务里的账单生成和扣钱功能都发展到能单独成服务了,再拆一次。
拆分实施的关键步骤和技术方案
把商家管理 Server 拆成微服务是个系统工程,涉及到梳理业务、迁移数据、重构接口、部署运维等好多环节。ZKmall 团队分阶段推进,还搞了些技术创新,才保证了拆分过程平稳过渡。
数据层拆分是整个过程的基础,也是块硬骨头。单体架构的时候,商家管理相关的 18 张表(像 merchant、shop、role、bill 这些)都存在一个数据库里,好多查询都得跨表关联。拆分的时候,先按服务边界把这些表分到对应服务的数据库里,比如 merchant 表归商家入驻服务,shop 表归店铺管理服务;然后处理跨表查询的问题,必须关联查的(比如查某个店铺的商家资质),就通过服务间的 API 调用来汇总数据,别再保留表关联了;最后建数据同步的机制,有些需要重复存的数据(比如费用结算服务里存一份店铺基本信息),用 Canal 监听数据库 binlog 的方式实时同步,保证数据一致。有个实操细节,数据迁移的时候得留 30 天的双写期(同时往旧库和新库写数据),用比对工具检查数据一不一样,别丢了数据。
业务逻辑重构要盯着服务内聚和接口设计。单体架构里,商家管理的业务逻辑散在好几个 Service 类里,跨功能调用特别多。拆分的时候,得按服务边界重新整理:商家入驻服务专心管资质审核流程,实现从 “商家注册→传资质→初审→终审→入驻完成” 的状态流转;店铺管理服务管店铺信息维护、类目配置、模板装修这些;权限管理服务基于 RBAC(角色 - 权限 - 用户)模型搞权限分配和校验;费用结算服务管店铺租金、平台佣金这些费用的计算和收取。服务间的接口设计要遵循 RESTful 规范,把请求参数、响应格式、错误码都定清楚,比如商家入驻服务提供 /api/v1/merchants/\{id\}/qualification 接口,供其他服务查资质信息,接口响应时间控制在 200ms 以内。
跨服务协作机制要保证业务流程完整。拆完之后,原来在单体架构里通过本地方法调用就能完成的流程(比如商家入驻成功后自动建店铺),得靠跨服务协作来实现。ZKmall 用 “事件驱动” 模式解决这个问题:核心服务在关键业务节点发事件(比如商家入驻服务发 MerchantAuditPassedEvent),其他服务订阅了这些事件就处理(比如店铺管理服务订阅后自动建店铺)。事件通过 RabbitMQ 消息队列传递,保证通信是异步的、可靠的;同时引入 Seata 分布式事务框架,用 TCC 模式保证跨服务操作的一致性(比如商家扣钱和订单状态更新得同时成功或者同时失败)。有个业务场景的实测显示,事件驱动模式下跨服务流程的成功率能到 99.98%,比传统同步调用的 98.5% 高多了。
部署与运维体系要能支撑微服务稳定运行。ZKmall 用 Docker 容器化技术部署各个服务,通过 Kubernetes 搞服务编排,支持服务实例自动扩缩容:商家后台访问量超过阈值了,Kubernetes 就自动加店铺管理服务的实例;访问量降下来了,就自动减实例省资源。用 Spring Cloud Gateway 当 API 网关,统一处理服务路由、负载均衡、认证授权这些通用的事儿,商家通过 /merchant/*、/shop/* 这些路径访问不同服务,不用管服务具体在哪儿。还引入了 Prometheus+Grafana 搞服务监控,实时采集各服务的 CPU 使用率、内存占用、接口响应时间这些指标,设好几级告警阈值,出问题了就通过钉钉、短信通知运维人员。
拆分后的服务协同和业务流程
拆完之后的商家管理服务不是各干各的,得靠设计好的协同机制,一起支撑完整的业务流程。ZKmall 在实际操作中形成了一套高效的服务协作模式,保证拆完之后业务体验不下降。
商家入驻全流程的服务协同最能体现拆分的价值。商家入驻是个典型的多服务协作场景,流程是这样的:商家在前端填注册信息、传资质材料,请求经 API 网关传到商家入驻服务;商家入驻服务检查信息没问题后,把资质材料存到对象存储服务(比如阿里云 OSS),生成待审核任务;审核人员通过后台系统处理任务,商家入驻服务更新审核状态,审核过了就发 “商家审核通过事件”;店铺管理服务订阅了这个事件,自动给商家建个默认店铺,初始化店铺信息;权限管理服务同步给商家建管理员角色,分配基础操作权限;费用结算服务给商家建结算账户,设默认费率。整个流程靠服务间的事件和 API 调用无缝衔接,商家从提交入驻申请到建好店铺,平均时间从原来的 48 小时缩到 2 小时,审核效率提升了 96%。
店铺日常运营的服务协作看重效率和体验。商家在店铺管理后台操作(比如更新店铺信息、配促销活动),直接由店铺管理服务处理;涉及权限校验的操作(比如改结算账户),店铺管理服务调用权限管理服务的接口验证商家有没有权限;需要算费用的操作(比如参加平台促销活动),就调用费用结算服务的接口确认费率和扣款方式。各服务通过缓存减少跨服务调用次数,比如店铺管理服务缓存常用的商家权限信息,有效期 10 分钟,大大减少了对权限管理服务的依赖。有数据显示,店铺管理后台的平均页面加载时间从拆分前的 3 秒降到 1.2 秒,操作流畅多了。
费用结算周期流程的服务协作要保证资金安全。每月初,费用结算服务自动启动上月费用核算流程:调用订单服务的接口拿该商家的所有订单数据,算该交的佣金;调用店铺管理服务的接口拿店铺类型和服务套餐,算基础服务费;汇总之后生成电子账单,调用消息服务给商家发账单通知;商家通过费用结算服务的支付接口付款,系统同步更新账单状态,发 “账单支付完成事件”;商家入驻服务订阅这个事件,更新商家的费用缴纳记录,作为以后服务续约的依据。整个流程全自动化,人工干预的环节从原来的 5 个减到 1 个(处理异常账单),结算效率提升 80%,账单争议率下降 60%。
拆分实践的成效和经验总结
ZKmall 商家管理 Server 的微服务拆分用了 6 个月完成,不管是技术指标还是业务指标都改善不少,还积累了些能复用的经验。
核心指标的改善证明了拆分是值得的。技术层面,商家管理相关接口的平均响应时间从 1.5 秒降到 300ms,99% 的请求响应时间从 5 秒降到 800ms;服务可用性从 99.5% 提到 99.95%,全年故障时间从 18 小时缩到 2 小时;服务器资源利用率从 60% 提到 85%,同样的负载下硬件成本降了 30%。业务层面,商家入驻通过率从 65% 提到 82%(因为流程简化了、效率提高了);商家月均活跃率从 58% 提到 75%(因为后台操作体验好了);拆分后 6 个月,平台商家数量从 800 家涨到 2000 家,业务扩张有了支撑。
成功的关键在于方法科学、执行谨慎。一是业务放前面,拆分前花 3 个月梳理商家管理业务、搞领域建模,保证拆分边界合理;二是数据迁移小心处理,通过双写、校验、灰度切换这些办法,保证数据不丢;三是灰度发布,先把 10% 的商家切到新服务,没问题了再慢慢扩大范围,最后全量迁移;四是监控和回滚机制要全,拆分过程中实时盯着服务状态,出问题能快速切回单体架构,保证业务不停。
避坑指南能给其他系统提个醒。拆分过程中遇到过三个典型问题:一是设计过头,一开始想给每个小功能都建个独立服务,结果服务数量飙到 15 个,运维成本扛不住了,后来合并成 4 个核心服务才好;二是跨服务调用链太长,查商家结算得调 5 个服务,响应时间反而变长了,后来用 API 网关聚合和本地缓存优化才恢复正常;三是权限管理有问题,服务拆完之后权限校验逻辑散了,出现安全问题,最后靠统一权限服务和 Token 验证机制解决了。这些经验说明,微服务拆分得平衡理想和现实,别为了拆分而拆分。
ZKmall 商家管理 Server 的微服务拆分实践表明,架构升级本质上是为业务发展服务的,不是追求技术完美。用领域驱动设计划服务边界,用事件驱动实现跨服务协作,靠容器化技术提高部署灵活性,最后让商家管理模块从 “拖业务后腿” 变成 “给业务赋能”。打算搞微服务改造的系统,关键是深入理解自己的业务特点,制定符合实际的拆分策略,一步一步完成架构升级 —— 这正是 ZKmall 拆分实践的核心启示。