ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微服务架构6种模式:拆分、通信与数据管理的落地实践

微服务架构6种模式:拆分、通信与数据管理的落地实践 1. 微服务到底在解决什么问题先说一个我在实际项目里反复看到的场景一个团队维护着一套所谓的“单体巨石”代码量到了几十万行每次发版都是全公司联动改一个用户头像功能都要把订单、支付、优惠券模块一起回归测试。这还不算最难受的最难受的是数据库连接池被一个慢查询拖死整个系统跟着一起躺平最后运营同学拿着手机截图问你“为什么用户点啥都没反应”。微服务架构本质上就是把这套巨石拆开把原本在一个进程内完成的事情拆成多个独立进程通过接口协作完成。但拆完之后你会发现真正难的从来不是“拆”而是拆完之后怎么保证系统还能像以前一样稳、一样快、一样好维护。这也是为什么我特别想聊“微服务架构的6种模式”——这6种模式不是给你画大饼的而是我在多个项目里实实在在用过的、被验证过的套路能帮你少走很多弯路。在进入正题之前我想先把一个概念说清楚微服务模式和我们常说的设计模式不是一个层面的东西。设计模式是代码层面的比如单例、工厂、观察者而微服务模式是架构层面的它决定的是服务怎么划分、服务之间怎么通信、数据怎么管理、故障怎么隔离。打个比方设计模式是教你怎么盖好一面墙微服务模式是教你怎么规划整栋楼的户型和水电管道。这6种模式分别是服务拆分的聚合模式和代理模式、解决服务间通信的分支模式、管理海量调用的异步消息模式、以及处理共享数据的共享模式和数据分片模式。下面我一个个拆开讲每个模式我都会结合自己做过的项目来讲清楚它的适用场景、实现要点和坑。2. 服务拆分三大经典模式怎么切分才算合理服务拆分是整个微服务改造的第一步也是后面所有工作的基础。拆得太多服务数量爆炸运维成本直接拖垮团队拆得太粗服务之间耦合严重跟单体没啥区别。我在下面的三个模式里会把拆分的思路、边界判断方法、以及配套的注意事项讲透帮你在实际项目中找到那个“不多不少”的拆分粒度。2.1 聚合模式最稳妥的拆分起点聚合模式Aggregator Pattern的逻辑很直白把多个服务的输出聚合起来统一返回给调用方。你有一个聚合服务它负责调用用户服务、订单服务、商品服务然后把结果拼装成一个完整的响应给前端。我在一个电商中台项目里就用到了这个模式。当时前端的商品详情页需要展示商品基本信息、库存信息、优惠信息如果让前端分别调三个服务页面就要发三次请求网络开销大、串联超时概率高体验很差。于是我加了一个聚合服务前端只调一次聚合服务内部并行调三个服务等所有结果齐了再组装返回。这个模式最大的价值在于它把“编排逻辑”集中到了一处调用方不需要关心背后有多少服务。而且对于改造初期特别友好——单体系统拆出来的第一个微服务通常都需要一个聚合层来兜底让外部调用方感觉不到变化。但这里有个值得警惕的坑聚合服务容易变成新的“上帝服务”。如果什么聚合都往它身上堆它最后会变成一个堵塞点来源头就是所有调用都必须经过它它的性能直接决定了整个链路的性能。我在那个项目里就吃过这个亏后来做了两件事才缓解一是聚合服务不做事无巨细的字段拼装只做基础数据汇聚二是能并行调用的绝不串行用完了并发又不给聚合服务增加额外负担。还有一点聚合服务的接口设计要讲究。我见过不少团队把聚合服务做成了“传一个大JSON进去返回一大堆字段”的万能接口结果接口越用越重最后每个调用方都在传很多自己根本用不到的参数。最好按场景拆接口比如“商品列表聚合接口”和“商品详情聚合接口”分开宁可多几个接口也别让一个接口背负所有场景。2.2 代理模式给系统加一道网关代理模式Proxy Pattern和聚合模式长得很像但职责边界完全不同。代理模式是转发不做事后组装。你在客户端和服务端之间架一个代理客户端只跟代理说话代理根据请求的路径或者别的标识把请求转发给对应的微服务。在我们的实际项目里代理模式最常见的外在表现就是API网关。所有外部请求先进网关网关做路由、鉴权、限流、日志然后按规则分发到下游服务。有了这层代理之后服务实例的地址变了客户端也不需要知道网关去服务注册中心查一下最新的实例列表就行。用代理模式的时候我对团队的要求是网关里不要写业务逻辑。网关做的是“转发”和“横切”鉴权、限流、跨域这类一旦你在网关里写了业务判断后面维护就会很疼因为网关代码往往和老系统的治理体系绑在一起改动一次的回归影响面很难评估。还有一个在代理模式下常见的困惑聚合和代理到底选哪个我的判断标准很简单——如果客户端需要的响应恰好就是某个服务直接返回的样子就做转发用代理模式如果还需要把多个服务的结果拼装一下就做聚合用聚合模式。这两个模式不是对立的在复杂的微服务树形调用关系里经常同时出现。这里也提醒一句代理模式引入后服务的调用链路会变得“绕”排查问题的时候要多看一层。我们在实践里用链路追踪Trace ID贯穿整个请求来解决这个问题否则出了问题只能靠猜。2.3 分支模式处理并行调用的复杂装配分支模式Branch Pattern可以理解为聚合模式的“复杂版”。前面的聚合模式里聚合服务会把结果简单拼一起而分支模式是当系统要处理“带条件的分支调用”时使用的。举个例子我们在做订单详情的时候根据订单状态的不同需要拉取的数据完全不一样。待付款状态需要查询支付配置和优惠明细已发货状态需要查物流信息已完成状态需要拉评价信息。如果让聚合服务把所有这些都查一遍再拼装那对于每个请求都有大量无用功。分支模式的做法是聚合服务先看订单状态再决定调用哪些子服务每个分支的编排逻辑互不影响。在实际编码上分支模式一般就落在编排层。这块业务我用下来最大的心得是分支判断条件一定不能太复杂。我见过有团队把分支条件写成了几十行嵌套后来代码根本没人敢动。后来我们收敛了一下分支条件尽量收敛为“有限状态枚举”不做过多的范围判断和交叉判断可维护性立刻就好了。另外分支模式对调用的超时治理要求也很高。因为服务的分支数量多了以后部分服务的异常就可能被层层放大你需要在分支入口处就对超时和重试策略做统一管控。我通常会在编排层做“快速失败”某些非核心分支失败了直接降级不让它拖垮主流程。3. 服务间通信的两大核心模式消息和调用怎么选服务拆完之后最棘手的问题就是服务之间怎么通信。是同步调接口还是异步发消息两种方式在延时、耦合、可靠性、排障复杂度上的差异很大。下面的两个模式分别覆盖了这两条路线我会重点说明你在什么场景下该选哪条路以及选完之后有哪些要提前想清楚的代价。3.1 异步消息模式把同步阻塞从链路里拿掉异步消息模式Asynchronous Messaging Pattern应该是目前微服务架构里最“出圈”的一个词了大家经常听到的消息队列比如Kafka、RabbitMQ、RocketMQ就是干这个的。在这个模式里服务之间不直接同步等待而是通过消息中间件来传递事件生产者只管发消息消费者按自己的节奏处理。这个模式给了我一个特别深刻的体验。之前做过一个积分系统用户下单成功之后要加积分、发优惠券、给运营报表发事件。原来的做法是下单接口同步调这些服务每次大促的时候下单服务就特别容易因为下游的一个慢接口而整体变慢。后来改成异步消息模式下单服务只发一个“订单已支付”事件积分服务、优惠券服务、报表服务各自订阅、各自处理。下单链路的响应时间直接降了一个数量级大促期间也再没出现“下游抖动连带伤害主链路”的事故。但异步消息不是银弹。它有一个典型的副作用——调用链变得不直观。同步调用的时候A调B出问题可以顺着调用栈查异步消息模式下A发了消息B什么时候处理、处理到什么程度你都需要额外的追踪机制才能看到。我们在实践里引入了消息轨迹ID把业务流水号贯穿到消息头和日志上下文里排查问题才总算有迹可循。还有一点消息的“消费顺序”是个老大难。同一个订单的“创建”、“支付”、“取消”三个事件是有顺序的如果消费端乱序处理了数据就错了。解决思路一般是尽量让同一个业务的同一个Key比如订单号路由到同一个分区/队列保证局部有序同时业务层面做好幂等和状态机校验。异步消息模式的整个数据一致性也是个大话题这里先点一句能用本地消息表消息队列的就别先上分布式事务中间件。分布式事务中间件性能开销大维护复杂很多业务场景其实根本不到那个程度本地消息表配合对账任务足够稳。3.2 分支模式与异步组合的实战经验有一段时间我们做订单超时自动关闭的功能最初方案是客户端定时轮询订单状态效果很差。后来改用“延迟消息分支模式组合”订单创建后往消息中间件发一条延迟消息比如30分钟后触发消息到了就调订单服务检查状态如果还处于未支付就走关闭订单的分支逻辑如果已经支付就直接结束不需要再往下走分支。这算是异步消息模式和分支模式在一个真实业务里的交叉使用。这类组合里最值得注意的就是“消息延迟时间”的精度问题。中间件的延迟消息在很多实现里不是绝对精确的比如你发30分钟的延迟实际可能是31分钟甚至更晚才到。所以如果业务对时间精度要求极高还是要做兜底策略比如整点扫描任务去补偿。我常常开玩笑说系统的最终一致性不是靠某一个机制保证的而是靠“主流程补偿对账”两只脚一起走路。4. 数据管理的两大模式共享数据和数据分片怎么选微服务拆分之后数据层是最容易出乱子的地方。原来单体一个数据库所有的表都在一起服务拆了表还在一个库改动就又互相打架。下面两个模式分别讲了两种数据组织路线共享模式适合改造初期数据分片模式适合规模化之后的长期演进。我会把二者的适用边界和切换时机讲清楚。4.1 共享模式拆服务不拆库的过渡方案共享模式Shared Data Pattern指的是服务拆分后数据库还是共用的多个服务访问同一套数据表。很多团队从单体往微服务迁移的第一步都会走上这条路因为如果一上来就要求每个服务独享数据库意味着要对现有库做彻底的拆分工作量很大业务风险也高。我经历过一个“假微服务”阶段服务按照业务拆了但底层所有服务还在读写同一个订单库。这个阶段的好处是迁移平稳能让你先把服务的部署和发布模式跑起来把团队的交付节奏理顺同时阶段性地收集数据层面的依赖关系。但坏处也很明显一旦涉及订单表结构变更所有相关服务都要一起改又变成了变相的单体发布。共享模式只适合作为过渡不可能长期维持。我们的做法是在这个阶段就把“数据依赖清单”梳理出来每个服务到底读了哪些表、写了哪些表、哪些表是真正多个服务共享的。这份清单后来成了数据库拆分的重要输入。有了它才知道真正需要拆的表就那几张大部分表其实可以顺着服务边界一起划走。共享模式还有一个隐蔽的坑连接池资源竞争。多个服务连同一个库任何一个服务的慢查询都可能把连接池占满拖垮其他服务这相当于把故障复发点又连在了一起。所以这个阶段强烈建议给不同服务配置不同的数据库账号配合限流熔断至少要能快速定位出“是哪个服务把连接池打爆的”。4.2 数据分片模式每个服务独占数据领地数据分片模式Database Per Service / Data Sharding是微服务架构落到数据层后的最终形态每个服务拥有自己的数据库或至少自己的表空间服务与服务之间不直接访问对方的数据库数据交换只能通过服务接口或消息来完成。这里要特别说清楚“分片”在微服务语境下往往被翻译成两层意思一是按服务拆分数据库Database per Service二是数据量太大做的水平分库分表Sharding。在本文里我更强调前者因为这是微服务架构数据治理的第一性原理——服务自治数据边界必须和服务边界一致。从“共享模式”切到“数据分片模式”是有阵痛期的。最典型的问题是“跨服务查询”。原来所有表都在一个库里一条SQL就能join出来现在订单数据在订单库用户数据在用户库join不了了。我见过一些团队在这个阶段又走回了老路把服务间的数据同步成冗余表然后又陷入数据一致性的泥潭。正确的做法不是试图让数据库重新“假装成为一个库”而是接受分布式环境下的数据冗余和最终一致性用接口或事件来同步数据。跨服务查询的合理解法之一是CQRS读写分离把查询侧的模型单独抽取出来用事件流同步成适合查询的视图表。这个方案初期会多写不少代码但一旦数据量级上来、查询模式复杂起来就会觉得当初的投入太值了。5. 微服务架构模式落地的实操清单模式讲完了很多人会问那我在实际项目里到底该怎么开始这里我给一份我自己改造项目时反复使用到的实操清单不是泛泛的建议而是每一步都有明确动作和检查标准的操作指南。5.1 如何判断服务拆分边界是否合理判断拆分粒度是否合理我一般问三个问题第一这个服务是否拥有独立的数据领地如果两个服务频繁地共享同一组表那它们大概率不是两个独立业务而是同一个业务被误拆了。第二服务之间是否通过接口契约通信而不是共享数据库只要出现“我直接查你的表”的情况边界就算破裂了。第三更新一个业务功能时需要两个以上服务同时发布会边界一定有问题。理想状态下一个功能变更应该是大多数时候只改动一个服务。我在一个订单履约项目里就用这三个问题排查出了一个大坑原来的“订单服务”表面上是独立的但它的很多核心表都被“库存服务”在直连读导致库存服务一变订单服务就得跟着改。后来我们把这个直连读的接口全部改成了服务调用发布耦合才彻底解开。5.2 微服务改造的节奏与顺序建议除非是全新项目否则我不建议一次性把所有服务都拆完。我比较推荐的是“绞杀者模式”Strangler Pattern的思路在旧系统外围逐渐构建新服务让老系统一点点被替换掉而不是搞一刀切的“大爆炸重写”。实操上我们的顺序是先选定一个业务相对独立、改动频率高、性能瓶颈明显的模块比如用户认证、消息通知把它拆出来做成第一个微服务通过代理模式接入API网关让外部调用方式保持不变。第一个服务跑顺之后团队有了关于注册中心、配置中心、日志链路、监控告警等基础设施的完整认知再逐步把其他模块按同样套路迁出来。这里有个容易被低估的环节环境的搭建和交付流水线。微服务化之后服务数量变多如果没有一套成熟的CI/CD每次发版都是灾难。我们在改造同步就对交付流程做了标准化——每个服务都有独立的代码仓库、独立的构建流水线、独立的部署单元发布节奏互不影响。这个投入比你想的要重要得多。5.3 注册中心、配置中心与网关的协同配置一谈到微服务的实操就绕不开注册中心、配置中心、网关这“三件套”。这里我分享一个我在生产环境中最常用的协同配置方案给读者一个可以直接照抄的底座。注册中心我推荐用Nacos性能好、功能全或者Consul实现简单、社区成熟。服务的每个实例启动时都向注册中心上报自己的IP和端口同时注册中心提供健康检查机制把不健康的实例及时剔除。网关比如Spring Cloud Gateway会定时从注册中心拉取服务实例列表把请求路由到健康的实例上。配置中心的思路是把每个应用的环境配置数据库地址、开关策略、线程池大小等集中管理支持动态刷新避免改配置还要重新发布。我强烈建议把配置的变更也纳入代码审查流程否则配置中心可能成为新的故障源。一个实际配合的例子订单服务在压测时出现大量超时。我们当时做的动作是先通过配置中心的动态开关把下游调用的超时时间从300ms降到150ms再通过注册中心的权重配置把两台不稳定实例的流量调低让它们慢慢排空然后在网关层把某些非核心分支的调用降级为异步消息。整个过程中我们没有对任何一个服务做过一次停机发布。这种“动态调控能力”才是微服务架构在运维上最值的回报。6. 常见问题与避坑经验最后这部分我把这些年踩过的坑消化一下整理成几个最常见的问题解答。这些内容不一定写在官方文档里但你在实操中大概率会遇到。6.1 拆出来的服务越来越多测试怎么办很多团队在微服务初期最头疼的就是“测试链路太长”。原来单体测试只需要起一个应用微服务化之后要起一堆依赖本地开发跑不起来测试环境也经常因为某个下游服务没有部署而阻塞。我的经验是两个方向并行一是契约测试Contract Test每个服务对外暴露的接口都有一套契约下游服务按契约做Mock测试这样就不需要每改一次接口都把整个分布式系统拉起来跑一遍二是测试环境容器化把依赖的服务、中间件都做成Docker Compose或Kubernetes模板一键拉起一套完整环境开发自测不求人。契约测试的好处是它把“服务之间接口是否兼容”这件事从集成测试阶段提前到了每个服务的开发阶段发现问题早、修复成本低。我见过很多团队发版前必须做一次完整的环境联调一次大版本联调要用掉一两周时间上了契约测试后联调时间可以压缩80%以上。6.2 链路追踪和数据一致性怎么配合微服务化之后的“灵魂拷问”永远是数据一致性和链路追踪。我的建议是链路追踪必须第一时间就上不要等服务数量多了再补。每个请求进来都生成一个全局Trace ID在日志框架里自动携带所有服务的日志都能按Trace ID串起来。排查问题的时候一条命令就能拉出整条调用链的日志。数据一致性上我的原则是“能异步就异步能本地消息表就本地消息表分布式事务是最后的选择”。实际业务里很多“一致性问题”其实是可以在业务设计层面规避掉的比如利用状态机保证事件处理的顺序通过幂等键保证消息不重复执行通过对账任务兜底长期不一致这些手段组合起来已经能覆盖绝大多数业务场景。6.3 微服务化之后性能反而变差了怎么办这是最容易被吐槽的一点“微服务化之后每次调用都多了一层网络开销性能反而比不上单体。”这话有一定道理但通常不是微服务架构本身的问题而是实现方式的效率问题。先检查是不是有“接口胖调用”问题一个业务请求的背后为了拿一个字段跨服务调用了十几次。如果这样优先查一下是不是能通过聚合模式把多次调用合并一次或者通过数据冗余、缓存等手段减少跨服务查询。再检查是不是有“服务间同步调用过深”问题A调BB调CC调D一次请求串行四五跳每跳都有网络开销延迟自然爆炸。这种链路我会考虑把中间几跳改成异步化或者至少把最后端的数据访问改成批量查询和本地缓存把时长缩回去。还有一个经常被忽略的点序列化与传输效率。JSON在人类可读性上有优势但性能不如二进制序列化协议。在内部服务之间传输的数据其实没必要完全JSON化我曾经在一个高QPS场景下把服务间的传输协议从JSON切换到二进制化整体时延直接降了30%以上改动面并不大。6.4 微服务团队该怎么组织技术问题说完了最后说一个“人”的问题。微服务架构不仅改变技术实现方式也会倒逼团队结构变化。业内常说“康威定律”系统的架构会反映出组织的沟通结构。如果团队还是按照前端、后端、测试来组织微服务化的收益会被沟通成本吃掉大半。更合理的方式是围绕业务能力组建“全栈式小队”每个小队拥有一个或几个微服务的完整自治权——包括开发、测试、部署和运维。我在落地这个组织方式的时候最大的变化是以前跨模块的需求要在几个团队之间来回协调现在一个小队就能闭环交付。但在初期每个小队的能力参差不齐需要花时间培训和磨合这也是微服务改造里最容易被低估的管理成本。7. 一个小技巧从最简单的单体价值开始验证就算读到这你可能还是觉得微服务很重、很复杂。我的建议是如果你不是遇到真实的痛点比如发布困难、团队协作阻塞、性能扩展受限不建议为了“微服务而微服务”。我在现实中做过不少微服务改造项目最成功的那几个往往都是先解决了一个具体痛点比如“发布太频繁导致事故多”或者“某个模块要独立扩缩容”再顺势带着团队走上微服务化道路。真正判断一个项目要不要微服务化我会问自己如果继续用单体会死吗如果不会那微服务带来的是锦上添花而不是雪中送炭。如果会那哪怕改造再痛苦也得硬着头皮往前走。我个人在实际操作中的体会是微服务架构是一种高成本、高回报的架构选择它不适合所有团队和所有业务但当你确实需要它时这6种模式就是你手里最基础的武器。先用聚合模式解决调用复杂度用代理模式统一入口用分支模式处理条件化流程用异步消息模式解耦核心链路再根据数据情况从共享模式演进到数据分片模式这套组合拳打下来微服务化这条路就不会那么吓人了。
返回列表