ARTICLE DETAIL

资讯详情

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

为什么需要微服务?从单体架构到微服务拆分的核心权衡与落地指南

为什么需要微服务?从单体架构到微服务拆分的核心权衡与落地指南 微服务这三个字在 Java 后端面试里几乎是必考题在技术方案评审里也经常被翻来覆去地讨论。但很多同学对它的理解还停留在“大厂都在用所以我也要用”这个层面。如果被问到“为什么需要微服务”回答往往是“因为单体应用撑不住高并发”或者“因为微服务好扩展”。这两个答案对吗对了一半但离真正说清楚还差很远。这次我们不聊抽象概念直接拆开讲单体架构到底在什么条件下会变成瓶颈微服务解决的是哪几个具体问题引入微服务之后又要付出哪些成本以及你的团队到底该不该拆、从哪拆起。整篇文章围绕“判断”和“落地”两个关键词展开最后会附上一份微服务面试常见问题的回答思路。想搞懂微服务本质、或者正在准备微服务面试的同学这篇可以收藏。1. 微服务到底解决了什么问题先回到单体架构。一个典型的单体应用代码库只有一个打包部署也只有一个 Jar 包或 War 包。业务早期这种架构开发效率其实很高项目结构简单调试方便一台服务器就能跑起来。但随着业务规模增长几个问题会越来越明显。第一个问题是代码耦合。订单模块要调用用户模块用户模块要调用商品模块模块之间直接方法调用甚至共享数据表。表面上是分包的实际上依赖关系剪不断理还乱。改一个底层公共类可能影响几十个上层功能。代码越堆越多回归测试的工作量越来越大。第二个问题是团队协作阻塞。几十个人开发同一个代码库提交代码冲突频繁合并分支成本高。上线窗口要协调一个人改坏了一个模块整个应用都要回滚。这时候项目管理系统里经常出现“一件事要等三个团队排期”的情况本质不是项目管理的问题而是部署单元太大。第三个问题是扩展性受限。单体应用虽然可以横向扩容但扩容的最小单位是整个应用。如果只有某个模块有性能压力也必须把完整的应用复制一份。受限于应用内共享状态、定时任务冲突、数据库连接池上限等因素扩容效率很低。第四个问题是发布和稳定性互相影响。一个服务进程里同时跑着订单逻辑、支付回调、库存扣减、消息推送。任何一个模块出现内存泄漏或死循环都可能拖垮整个进程。线上出问题时先要靠日志人工排查是哪个模块引起的再考虑要不要回滚。微服务解决了这几个问题的核心思路是把一个“大而全”的系统拆成多个“小而独立”的服务。每个服务有独立的代码仓库、独立的数据库边界、独立的部署管道服务之间只通过接口通信。这样某个模块出问题不会直接拖垮全局某个模块有压力可以单独扩容某个团队可以独立迭代自己的服务而不需要全局协调。这也就是微服务最本质的价值控制复杂度而不是消灭复杂度。它把原本集中在代码层面的复杂度转化为分布式系统层面的复杂度让问题的边界更清晰让团队可以按服务边界去自治。下面用一张表对比单体架构和微服务架构的关键差异。对比维度单体架构微服务架构代码管理单一代码库统一版本每个服务独立代码库、独立版本部署粒度整个应用一起部署每个服务独立部署扩展方式整个应用复制扩容按服务维度独立扩容故障隔离一个模块异常可能拖垮整体单服务故障影响范围受限团队组织按技术层分工前端、后端、DBA按业务能力组织跨职能小团队技术栈通常统一不同服务可以选不同技术栈交付周期协作成本高发布节奏慢服务自治发布频率更高运维复杂度较低较高依赖基础设施与自动化能力2. 微服务的核心定义与架构特征要回答“为什么需要微服务”首先要准确理解微服务的定义。微服务的概念可以简单概括为一句话将单个业务应用拆分成一组小型服务每个服务围绕具体业务能力构建可以独立开发、独立部署、独立扩展服务之间通过轻量级通信机制协同工作。这一定义包含几个关键点。按业务能力拆分而不是按技术层拆分。单体应用常见的分包方式是 controller、service、mapper这是按技术层划分。微服务更强调按业务域划分比如用户服务、订单服务、商品服务、支付服务每个服务内部自己拥有 controller、service、mapper 的完整层次。拆分的依据是业务流程和业务边界的自然切分点而不是技术栈的分层。服务独立部署。这是微服务“微”的关键特性。用户服务可以一天发布三次库存服务可能一周才发布一次两者互不阻塞。发布窗口不再需要全局统一协调。每个服务有自己的 CI/CD 管道有独立的配置管理有独立的版本号。去中心化治理。微服务架构不强制所有服务使用同一种语言或框架。订单服务可以用 Java Spring Boot推荐服务可以用 Go数据分析服务可以用 Python。服务之间通过 REST 或 RPC 协议通信语言差异被协议隔离开。当然实际项目中为了降低维护成本多数团队还是会统一技术栈但架构上允许这种灵活性。数据按服务独立管理。这是微服务拆分中最容易忽略也最有争议的一点。理想情况下每个微服务应该有自己独立的数据库或独立的 schema服务之间不允许直接访问对方的表只能通过接口获取数据。这样才能保证服务之间的边界真正隔离。实际落地时很多团队会先做应用拆分但保留共享数据库这是一种过渡方案不是最终形态。基础设施自动化。服务数量增多之后手工运维已经不现实必须依赖自动化工具完成服务的注册、发现、负载均衡、配置管理、日志采集、监控告警和链路追踪。没有这套基础设施微服务跑不起来。故障隔离与降级。单个服务出现故障时应该通过熔断、降级、重试等机制把影响限制在局部而不是让整个系统级联崩溃。这是微服务架构在稳定性上的核心收益但前提是每个服务都要做好依赖治理和容错设计。下面给一个典型的微服务项目结构示例方便理解服务的组织方式。microservice-demo/ ├── gateway/ # API 网关统一入口 ├── auth-service/ # 认证鉴权服务 ├── user-service/ # 用户服务 │ ├── src/main/java/ │ │ ├── controller/ # 接口层 │ │ ├── service/ # 业务逻辑层 │ │ ├── mapper/ # 数据访问层 │ │ └── entity/ # 领域模型 │ └── pom.xml ├── order-service/ # 订单服务 ├── product-service/ # 商品服务 ├── inventory-service/ # 库存服务 ├── common/ # 公共工具模块 └── docs/ # 架构文档与接口文档这个目录结构的关键点在于每个业务服务都是一个独立的 Maven 模块或独立代码仓库都可以单独打包、单独部署。公共依赖需要谨慎设计只放真正与业务无关的工具类不要放跨服务共享的业务代码否则会在服务之间埋下隐式耦合。3. 微服务不是免费方案引入之后要付什么成本很多团队引入微服务之后发现原来单体时代没怎么关注的问题全都变成了必须面对的日常挑战。这部分内容是判断“为什么需要微服务”时最容易被忽略的。第一个成本是分布式事务。单体应用里一个订单创建过程可以同时操作订单表、库存表、账户表靠本地事务就能保证一致性。拆成微服务之后这三个表分别属于不同的服务一个操作链路要跨三个服务本地事务不再适用。要保证最终一致性通常需要引入可靠消息、事务消息、或者分布式事务框架如 Seata 的 AT 模式、TCC 模式设计方案和理解成本都明显上升。第二个成本是网络开销与性能损耗。原来的本地方法调用变成了远程调用一次业务操作可能涉及 3 到 5 次服务间调用每次调用都有网络开销、序列化开销、超时等待。链路越长整体响应时间的方差越大延迟毛刺出现的概率也越高。单体应用一条 SQL 能查完的数据在微服务架构里可能要多次调用再内存聚合。第三个成本是测试与联调复杂度。单体应用启动一个进程就可以自测全流程。微服务环境要想完整跑通一条业务链路需要把这十几个服务全部启动依赖中间件也要齐全。本地开发时经常要 mock 掉大量依赖服务环境准备成本很高。这也是为什么微服务项目普遍需要 Docker Compose 或 Kubernetes 来搭建开发环境。第四个成本是运维门槛。服务数量从 1 变成 20意味着有 20 个进程要监控、20 套日志要采集、20 个指标要看还有注册中心、网关、配置中心、链路追踪系统这些额外的组件需要维护。如果没有专职或半专职的运维/平台团队微服务落地会非常痛苦。第五个成本是团队能力要求。微服务架构要求团队成员理解分布式系统的常见问题网络分区、超时重试、幂等设计、服务雪崩、数据一致性。这些问题在单体时代不太会出现但微服务环境下是每一个接口设计都必须考虑的基础问题。下面用表格把单体问题与微服务新问题对应起来。单体时代的痛点引入微服务后要付出的新成本代码耦合模块边界模糊服务边界与数据边界需要重新设计团队协作互相阻塞跨服务联调、接口契约管理成本上升扩展粒度太粗分布式事务、网络调用性能损耗故障影响面大依赖治理、熔断降级、链路追踪复杂度上升发布流程笨重基础设施与自动化运维能力要求提高技术栈不灵活团队需要掌握分布式系统相关知识所以“为什么需要微服务”这个问题的完整答案应该先讲清楚它的收益再讲清楚它的成本。只有当收益远大于成本时微服务才是一个合理的选择。4. 什么时候才应该引入微服务微服务不是新技术也不属于“越早用越好”的架构。判断是否要引入微服务至少要同时满足下面几个条件的大部分。团队规模达到一定阈值。微服务拆分之后每个服务至少需要两个开发人员长期维护。如果团队只有 10 个人拆出 10 个微服务的结果就是每个人维护一个服务业务没有形成技术壁垒反而让协作成本飞涨。通常团队规模达到 30 到 50 人以上或者存在多个跨职能小组时微服务带来的独立部署和团队自治收益才真正体现出来。模块边界已经足够清晰。如果单体应用内部的模块划分本来就是一团乱麻直接拆微服务会把“乱”复制到服务层面变成几十个互相乱调的分布式怪兽。正确的顺序是先梳理业务域画清上下文边界做好模块化单体再考虑服务化拆分。部署频率与发布冲突已经成为瓶颈。如果团队经常因为“A 团队改了公共模块导致 B 团队功能无法上线”而争吵说明部署单元确实过大。这时微服务可以按服务维度独立上线解决发布节奏冲突的问题。性能瓶颈需要更精细的伸缩粒度。如果系统整体并发不高单体完全没有问题。只有当某个模块的资源消耗明显独立于其他模块需要通过独立扩容来降低整体成本时微服务的伸缩粒度优势才有实际价值。团队具备或愿意建设 DevOps 能力。微服务的前提是自动化包括自动化构建、自动化测试、自动化部署、自动化监控。如果团队还停留在手工打 Jar 包、手工上传服务器、手动 kill 进程的阶段上微服务只会把线上问题放大。这里要特别提醒一点不要为了技术 KPI 或简历需要而引入微服务。模块化单体Modular Monolith是一个经常被低估的中间态。它保持单进程部署的低运维成本但内部通过模块边界约束代码依赖。当业务复杂度继续上升再按模块边界逐个拆为独立服务平滑过渡。很多系统拆微服务失败不是因为微服务本身不行而是因为起点不该是微服务。5. 从单体到微服务的演进路径如果已经确定要拆不建议一次性大重构。业界比较认可的做法是“绞杀者模式”在现有单体外部逐步构建新的微服务让新功能走新服务老功能继续留在单体中通过网关或路由层逐步切换流量最终把单体一点一点替换掉。具体落地步骤可以参考下面的顺序。第一步先做模块化单体。在单体内明确模块边界禁止跨模块直接访问数据库表只允许通过模块提供的内部接口调用。把公共代码抽成独立模块避免循环依赖。这一步不做服务化改造只是先让代码边界清晰。很多团队跳过这一步直接拆服务结果拆出来的服务之间仍然共享数据库边界形同虚设。第二步识别业务边界与数据边界。用领域驱动的思想找出业务中相对独立、变化频率不同、团队归属清晰的业务域。优先选择与核心流程耦合较弱的模块作为首个拆分对象比如短信服务、通知服务、日志服务。第一个拆出来的服务应该足够独立风险足够小让团队积累经验而不是一上来就拆最核心的交易链路。第三步按服务边界独立部署。每个被拆分出来的服务拥有独立代码仓库、独立数据库通过注册中心如 Nacos注册通过远程接口对外提供服务。单体应用里原来直接调用该模块的地方改为调用远程接口。调用方要有超时设置和降级策略避免新服务抖动影响核心链路。第四步引入网关与统一认证。随着拆出来的服务越来越多每个服务都维护一份认证逻辑变得不可接受。此时由 API 网关统一负责路由转发、鉴权、限流下游服务只信任网关传来的用户身份信息。第五步建设可观测性基础设施。服务多了之后一次用户请求会经过多个服务必须通过链路追踪如 SkyWalking、Zipkin把整个调用链串起来。日志要统一采集指标要统一监控。没有可观测性在线排查问题的难度会指数级上升。第六步完善发布与治理能力。灰度发布、优雅停机、健康检查、熔断降级、限流这些能力在服务数量多的时候不是可选项而是必选项。每接入一个新服务都要按统一标准接入这些治理能力。这个演进过程不是一蹴而就的可能要持续几个季度甚至更长。拆分过程中要保证每一次变更都是可回滚的每一阶段结束都要有明确的服务边界。6. 微服务核心组件与技术选型参考微服务的落地依赖一系列基础设施组件。这里整理一套目前 Java 生态中比较常见的选型对应不同能力项。能力项常见组件作用注册中心Nacos、Consul、Eureka服务实例的注册与发现健康检查API 网关Spring Cloud Gateway、APISIX、Kong统一入口、路由转发、鉴权、限流配置中心Nacos Config、Apollo配置集中管理与动态刷新远程调用OpenFeign、Dubbo服务间声明式 HTTP 调用或 RPC 调用熔断限流降级Sentinel、Resilience4j保护下游服务防止雪崩链路追踪SkyWalking、Zipkin、Micrometer Tracing跨服务调用链分析分布式事务Seata解决跨服务数据一致性问题异步解耦RocketMQ、Kafka削峰填谷、事件驱动、最终一致性部署与编排Docker、Kubernetes容器化部署与编排管理技术选型上没有绝对的最优解核心要看团队的技术积累和运维能力。如果团队熟悉 Spring Cloud 体系优先选择 Spring Cloud Alibaba 这套组合它把 Nacos、Sentinel、Seata 与 Spring Boot/Spring Cloud 的整合做得比较完善。下面给一个典型的服务间调用示例使用 OpenFeign 完成用户服务调用订单服务接口。FeignClient(name order-service, path /api/order) public interface OrderFeignClient { GetMapping(/detail) OrderDTO getOrderDetail(RequestParam(orderId) String orderId); }调用方通过FeignClient声明接口指定要调用的服务名order-service和路径。实际调用时Feign 会结合注册中心的服务列表完成负载均衡。限流降级是微服务治理的重要部分。以 Sentinel 为例通过控制台或代码方式配置规则对指定接口做 QPS 限流。FlowRule rule new FlowRule(); rule.setResource(GET:/api/order/detail); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(100); FlowRuleManager.loadRules(Collections.singletonList(rule));这段配置的意义是当/api/order/detail接口的 QPS 超过 100 时多余的请求会被限流拦截保护下游服务不被突发流量打垮。这里也顺便提一下“若依微服务”这类开源脚手架。它把注册中心、网关、认证授权、代码生成等基础能力整合在一起适合作为学习微服务工程结构的参考或者用于内部项目快速起站。需要注意的是生产项目使用开源脚手架之前要对认证方案、权限模型、数据隔离、运维监控做完整评估不能直接套用。7. 微服务架构的常见问题与排查思路微服务数量多了之后线上问题定位和排查的难度会明显上升。下面整理几种常见问题现象、可能原因和排查方向。问题现象可能原因排查方式解决方向服务间调用超时网络抖动、下游处理慢、线程池耗尽链路追踪查看耗时、看下游服务日志调整超时参数、优化下游性能、限流保护新服务注册后调用不到注册中心未同步、服务名不一致、网络不通检查注册中心实例列表、检查服务名配置确认服务名与调用方一致检查网络连通性网关路由 404路由规则未配置、服务未注册、路径不匹配查看网关日志、查看注册中心配置正确的路由规则确认服务已注册配置修改不生效客户端未监听配置变更、需要手动 refresh查看客户端监听日志使用RefreshScope或消息通知机制刷新分布式事务数据不一致未使用可靠事务方案、消息丢失、重复执行比对订单与库存日志检查消息日志引入 Seata 或基于消息的最终一致性方案做好幂等单服务异常导致级联故障未配置熔断降级、重试策略不合理查看依赖服务健康指标、QPS 曲线配置熔断降级规则限制重试次数接口升级后旧调用方报错接口参数或返回结构不兼容查看调用方版本与接口版本接口版本化管理兼容旧消费者这里有一个值得强调的原则在微服务架构中每个下游调用都必须设置超时时间。没有超时的远程调用一旦下游服务挂起上游线程池会被慢慢占满最终引发级联故障。设置超时之后还要配合合理的重试策略。重试只适合幂等接口写操作要考虑重复提交的问题。另外一个常见误区是服务拆分之后仍然让多个服务直连同一个数据库。这样虽然省事但会把服务之间的数据耦合带回系统。更稳的做法是每个服务独占自己的库或 schema通过接口暴露数据处理能力必要时通过事件机制同步数据。8. 微服务面试常见问题与回答思路热词里出现了“微服务面试题”这里整理几个高频问题给出一套简洁的回答思路。面试官问“为什么需要微服务”时不仅是要听技术点还要看候选人是否清楚成本和适用边界。问题一微服务的拆分原则是什么回答思路按业务能力拆分而不是按技术层拆分。拆分的粒度取决于业务边界、团队组织、发布频率和数据耦合度。优先拆分边界清晰、变化独立、团队归属明确的模块。保持每个服务可以独立开发、独立部署、独立扩展。问题二微服务之间如何通信回答思路同步调用可以使用 RESTOpenFeign或 RPCDubbo异步解耦可以使用消息队列。同步调用适合实时性要求高的场景异步适合削峰填谷、事件通知、最终一致性场景。无论哪种方式都要考虑超时、重试、幂等和链路追踪。问题三服务发现与注册的原理是什么回答思路服务启动时向注册中心注册实例信息包括 IP、端口和元数据。注册中心维护服务名到实例列表的映射通过心跳机制剔除不健康的实例。调用方通过注册中心获取实例列表结合负载均衡策略选择一个实例发起调用。业内常用实现有 Nacos、Consul 和 Eureka。问题四分布式事务如何解决回答思路优先考虑是否能通过事件驱动和消息队列实现最终一致性。如果强一致要求高可以考虑 Seata 的 AT 模式或 TCC 模式。核心要点是保证事务参与方要么都成功要么都回滚配合重试、对账和幂等机制。问题五熔断、降级、限流有什么区别回答思路熔断是当依赖服务故障率达到阈值时直接短路后续请求快速失败保护自身降级是当系统资源不足时丢弃非核心功能保证核心链路可用限流是控制进入系统的请求速率防止系统过载。三者都是保护机制但作用时机和策略不同。问题六如何保证微服务的幂等性回答思路写操作接口要支持幂等常用方案有唯一请求号、数据库唯一索引、状态机校验、Redis 分布式锁。调用方重试时携带相同请求号服务端根据请求号判断是否已经处理过。问题七为什么要做链路追踪回答思路一个请求在微服务架构中会经过多个服务节点传统的日志排查无法快速定位瓶颈或错误。链路追踪通过为每个请求生成全局 TraceId将跨服务调用串联起来展示完整调用链和耗时数据。常用工具包括 SkyWalking 和 Zipkin。问题八如果让你把一个单体系统拆成微服务第一步做什么回答思路第一步不是写代码而是梳理业务域。画出核心业务流程图标记模块之间的依赖关系和数据共享情况。先保证单体内部的模块边界清晰再挑选最独立的模块作为第一个试点。优先做风险和收益比最高的拆分而不是最核心的模块。这些问题的回答思路核心是体现出“理解收益也清楚代价”的架构观。面试官更愿意看到候选人能结合业务场景判断是否该拆、拆哪里、怎么控制风险而不是机械地背概念。9. 微服务架构的落地建议与最佳实践最后给一组工程化建议。这些建议来自单体服务拆分的常见教训越到后面越容易被忽略。第一先有清晰的模块边界再谈微服务。边界不清晰时拆出去的“微服务”本质上还是一个分布式单体只是把方法调用换成了远程调用性能更差、排查更难。第二每个服务要有独立的数据库或至少独立的 schema。服务之间禁止直连对方的表。如果临时需要对方的数据通过接口获取或通过事件异步同步。第三禁止服务之间共享公共业务表。公共数据如用户基础信息可以由一个服务专门负责其他服务通过接口或缓存获取而不是各自建表维护一份。第四接口版本要治理。服务升级时不能强制要求所有调用方同时升级。接口路径或参数结构变化时应保留旧版本通过版本号过渡。否则一次不兼容发布可能带崩整条业务链路。第五统一日志、统一异常码、统一配置规范。二十个服务如果各自有一套日志格式线上排查问题时需要在不同格式之间反复切换效率极低。基础设施在一开始就要定好标准。第六每个服务都要有健康检查和优雅停机机制。健康检查让注册中心能及时摘除不健康实例优雅停机让服务在重启时完成正在处理的请求再退出。第七建设可观测性要趁早。日志、指标、链路追踪三件套在服务数量不多时就要接入。后面补做可观测性的成本远高于一开始就做好。第八灰度发布和回滚是刚需。微服务部署频率高不可能每次发布都全量上线。通过网关或注册中心权重调整先让少量流量走新版本观察指标稳定后再全量放开。第九组织架构和系统架构要匹配。微服务的边界最好与团队分工一致否则会出现“服务边界是一条线团队边界是另一条线”的局面沟通成本依旧很高。第十涉及核心资金链路和敏感数据时要设计更加保守的一致性方案。新功能的引入可以先从旁路验证开始不要直接改主流程。这些建议并不复杂但每一项在真正的微服务项目里都值得反复校准。技术选型、代码框架反而是相对次要的部分更难的是组织协作方式和数据所有权边界的确定。10. 总结回到标题这个问题“为什么需要微服务”微服务解决的是单体应用在代码规模、团队规模、流量规模增长之后出现的协作和扩展问题它的价值体现在更细的部署粒度、更独立的团队自治、更可控的故障影响范围。但它同时带来了分布式事务、网络开销、运维复杂度、团队能力门槛这些不可回避的成本。判断是否需要微服务不看技术热度看实际痛点。当单体代码已经明显阻碍发布效率当团队协作成本高于服务治理成本当模块边界已经足够清晰当自动化基础设施已经就位这时候才应该启动拆分。如果当前系统只有一个服务、两个团队、三天发布一次模块化单体会是更稳妥的选择。无论你最终是否引入微服务理解它的核心权衡点对自己都有帮助服务如何拆、数据如何隔离、依赖如何治理、故障如何隔离、团队如何组织。把这几个问题想清楚面试和实际项目里的很多疑问都会迎刃而解。建议收藏备用下一次做架构评审或面试前再拿出来翻一翻会有更具体的体会。
返回列表