ARTICLE DETAIL

资讯详情

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

从单体到微服务:业务增长下的架构演进与实战落地

从单体到微服务:业务增长下的架构演进与实战落地

这类主题最怕讲成纯概念,一上来就分布式、微服务、集群、RPC,听上去高大上,但看完还是不知道从一碗面到连锁帝国到底该怎么落地。我更建议你换个思路:别急着背定义,先搞清楚单体应用在什么情况下会“撑不住”,以及分布式和微服务到底是怎么帮你“撑住”并实现业务扩张的。

很多人一听到“分布式”就想到一堆服务器,听到“微服务”就想到拆成小项目,但真正的问题在于:你的业务增长瓶颈在哪里?是用户量上来了页面卡顿,还是促销时订单系统直接崩溃,又或是新功能上线总要把整个应用重启一遍?分布式和微服务本质上是一套应对业务复杂度和规模增长的技术组织方案,它的核心不是技术炫技,而是解决“单店忙不过来”时,如何安全、高效地“开分店”。

所以,这篇文章不会只讲理论。我会用一个从“单一面馆”到“连锁餐饮集团”的比喻贯穿始终,带你理解每个技术决策背后的业务驱动力。你会看到什么时候该引入集群,什么时候该拆微服务,RPC和消息队列在“分店”之间怎么传递“订单”和“物料”,以及最让人头疼的“分布式事务”怎么处理“跨店结算”。目标是让你读完能建立一个清晰的认知框架:我的业务到了哪个阶段?我该优先解决什么问题?有哪些现成的技术方案和常见的“坑”需要提前避开?

1. 从“单一面馆”到“后厨爆炸”:认识单体架构的瓶颈

想象一下,你开了一家面馆。一开始,所有事情都在一个屋子里完成:你(服务器)既要接待顾客(接收请求)、下单(业务逻辑)、煮面(数据处理),还要收银(数据持久化)。这就是单体架构(Monolithic Architecture):一个应用包揽所有功能,部署在一个进程里。

1.1 单体架构的“甜蜜期”与典型特征

在创业初期,单体架构优势明显:

  • 开发简单:所有代码在一个项目里,IDE打开就能写,调试方便,没有复杂的模块间调用。
  • 部署简单:打包成一个JAR/WAR包,扔到一台服务器上启动就行。
  • 测试简单:功能都在内部,本地跑一遍单元测试,再做个集成测试,基本就能上线。
  • 初期性能足够:用户量少,一台配置不错的服务器完全扛得住。

用技术栈举例,这可能就是一个标准的Spring Boot应用,连接一个MySQL数据库,所有功能模块(用户、订单、商品、支付)都在同一个工程目录下,通过不同的ControllerServiceMapper来组织。

1.2 当业务增长,“单店”开始力不从心

随着生意火爆,问题接踵而至,这些都是单体架构的典型瓶颈:

  1. 并发瓶颈(顾客排队太长):促销时,大量用户同时下单,你的应用(单进程)处理请求的能力达到上限。CPU跑满,内存吃紧,响应时间变慢,用户开始看到“系统繁忙”或白屏。技术表现:Tomcat线程池被打满,数据库连接池耗尽,接口响应时间(RT)从几十毫秒飙升到几秒甚至超时。
  2. 开发与部署瓶颈(后厨混乱)
    • 团队协作难:10个开发人员都在改同一个代码库,合并代码冲突不断,功能上线相互影响。
    • 技术栈固化:所有模块必须使用同一种技术(如Java)。如果想用更擅长实时计算的Go写一个风控模块,或者用Python写一个推荐算法,会非常别扭甚至无法集成。
    • 部署风险高:哪怕只修改了“加个香菜选项”这样的小功能,也需要将整个庞大的应用重新打包、测试、部署。部署期间,整个面馆(所有功能)都要暂停营业(重启)。
  3. 可靠性瓶颈(一处着火,全店停业):订单模块的一个内存泄漏,可能导致整个应用崩溃,连用户登录、浏览菜单这些无关的功能也一并不可用。
  4. 扩展性瓶颈(无法按需扩容):你发现主要是“下单支付”这个环节压力大,但单体架构下,你只能给整个服务器升级(纵向扩展),或者再部署一整套完整的应用(横向扩展)。后者不仅浪费资源(把不忙的“后厨”也复制了),还引入了会话(Session)共享、数据一致性等新问题。

关键判断点:当你开始频繁地为整个应用集群部署负载均衡,为数据库读写分离而头疼,开发团队因为代码合并冲突而效率低下,或者一个小功能上线需要排期等待大版本发布时,就意味着单体架构已经成了业务增长的绊脚石。这时,就该考虑“开分店”了——也就是引入分布式与微服务的思想。

2. “开第一家分店”:理解分布式与集群的核心

“开分店”的第一步,不是马上把后厨、收银、保洁都拆出去,而是先解决接待能力的问题。在技术层面,这对应着分布式(Distributed)集群(Cluster)

2.1 分布式与集群:多台机器如何协同工作

  • 集群:简单说,就是多个相同的“你”。你开了三家一模一样的面馆,每个店都能独立完成从接待到煮面的全过程。通过一个“叫号机”(负载均衡器,如Nginx、F5)把顾客均匀地分配到不同店面。目标:提高吞吐量和可用性。一个店挂了,顾客可以被引导到其他店。
  • 分布式:含义更广,指多个不同的“部分”协同完成一项任务。比如,你把煮面的任务专门分给一个中央厨房,收银交给专业的财务公司,物流交给第三方车队。这些部分可能分布在不同的地理位置,通过网络通信。目标:解耦专业能力,提升整体效率和系统容量。

在初期,我们常从应用集群数据库集群入手。

2.2 应用集群:用Nginx和网关分流“顾客”

面对高并发,最直接的方案是把单体应用多部署几份,前面加一个流量入口。

  1. 负载均衡(Load Balancing)

    • 轮询(Round Robin):叫号机按顺序把顾客分给各个店。简单,但可能某个店刚好接到一堆“大单”(复杂请求)而累垮。
    • 加权轮询:给配置高的服务器(“大店”)分配更多顾客。
    • 最少连接(Least Connections):把新顾客分配给当前最闲的店。更合理,是常用策略。
    • IP哈希:同一个来源的顾客总是去同一家店。这解决了会话(Session)保持问题,比如顾客的购物车信息。但失去了负载均衡的灵活性。

    实操命令(Nginx配置示例)

    http { upstream backend { least_conn; # 使用最少连接策略 server 192.168.1.101:8080 weight=3; # 权重为3 server 192.168.1.102:8080 weight=2; server 192.168.1.103:8080 weight=1; } server { listen 80; location / { proxy_pass http://backend; } } }

    为什么这么配?least_conn比默认的round-robin更能应对请求处理时间不均的情况。weight参数可以根据服务器硬件性能差异化分配流量。

  2. 会话(Session)共享问题:用户登录信息(Session)默认存在单台服务器的内存里。如果下次请求被分配到另一台服务器,就会提示重新登录。

    • 解决方案1:粘性会话(Sticky Session):如上文的IP哈希,强制用户访问同一台服务器。缺点是该服务器宕机则用户状态丢失,且负载可能不均衡。
    • 解决方案2:Session集中存储:将Session存到所有服务器都能访问的中间件里,如Redis。这是更主流、更可靠的方案。
      // Spring Boot配置示例:使用Redis存储Session // 1. 引入依赖 spring-session-data-redis // 2. application.yml配置 spring: session: store-type: redis redis: host: localhost port: 6379

    选择建议:除非有强一致性且无法改造的旧系统,否则在新项目中优先采用Redis共享Session。

2.3 数据库集群:读写分离与分库分表

应用层能水平扩展了,数据库很快会成为下一个瓶颈。一家店的记账本(数据库)不可能所有分店都来抢着写。

  1. 读写分离:这是第一步。设立一个“总账房”(主库,Master)专门负责写操作(下单、支付)。再设立几个“分账房”(从库,Slave)同步总账房的数据,专门负责读操作(查菜单、查订单)。常用中间件有MyCat、ShardingSphere-Proxy,或直接使用云数据库的读写分离功能。

    • 好处:显著提升查询性能,分担主库压力。
    • 挑战主从延迟。顾客刚付完款,马上查订单可能显示未支付,因为数据同步到从库需要时间(毫秒到秒级)。业务代码需要能容忍这种短暂的不一致,或对一致性要求高的查询强制走主库。
  2. 分库分表:当单表数据量达到千万甚至亿级,索引效率下降,备份恢复困难。

    • 垂直分库:按业务拆分。把用户数据、订单数据、商品数据放到不同的物理数据库。这对应着“把财务、仓储、人事的账本分开”。
    • 水平分表(分片):把一张大表的数据,按某种规则(如用户ID哈希、订单时间范围)拆分到多个结构相同的表中。比如,2023年的订单一张表,2024年的订单另一张表。
    • 技术选型ShardingSphere(Sharding-JDBC)是Java生态中应用广泛的客户端分片中间件。它通过在应用层对SQL进行解析、改写、路由,将操作分发到不同的数据库或表。
      # ShardingSphere 简易配置示例:按user_id分表 rules: - !SHARDING tables: t_order: actualDataNodes: ds${0..1}.t_order${0..1} tableStrategy: standard: shardingColumn: user_id shardingAlgorithmName: table_inline shardingAlgorithms: table_inline: type: INLINE props: algorithm-expression: t_order${user_id % 2}

    避坑点:分库分表是“大招”,会带来分布式事务、跨分片查询(需要合并结果)、全局唯一ID生成等复杂问题。不要过早优化,通常单表数据在500万到1000万以下,通过优化索引和硬件就能解决。

走到这一步,你的“连锁店”已经有了雏形:多个相同的应用实例通过负载均衡对外服务,数据库通过读写分离和分库分表支撑海量数据。但这还是“连锁快餐”模式,每家店依然是大而全。当你想引入“火锅”、“烧烤”等新业态时,问题又来了——这就是微服务要解决的。

3. “成立餐饮集团”:微服务架构的拆分与治理

当你的业务越来越复杂,“面馆”里开始卖火锅、烧烤、奶茶。如果还用一个单体应用来管理,代码会变成一团乱麻(“大泥球”)。这时,你需要把集团拆分成不同的子公司(微服务),各自独立运营,但又需要协同合作。

3.1 什么是微服务?拆分的依据是什么?

微服务架构是一种将单个应用程序拆分为一组小型、独立服务的方法,每个服务运行在自己的进程中,服务间通过轻量级机制(如HTTP/RPC)通信,可独立部署和扩展。

关键不是“微”,而是**“单一职责”“独立自治”。拆分的依据通常是业务边界(Bounded Context)**,这是领域驱动设计(DDD)中的核心概念。例如:

  • 用户服务:负责注册、登录、个人信息管理。
  • 商品服务:负责商品上下架、库存管理、类目管理。
  • 订单服务:负责下单、订单状态流转。
  • 支付服务:负责与第三方支付渠道对接。
  • 配送服务:负责计算运费、调用物流公司接口。

每个服务拥有自己的独立数据库,这意味着其他服务不能直接访问它的数据库,只能通过其提供的API。这彻底解决了数据库层面的耦合。

3.2 服务间通信:RPC vs RESTful HTTP

子公司之间怎么沟通?主要两种方式:

  1. RESTful HTTP API:使用HTTP协议,JSON格式。简单、通用、易调试(用Postman就能测),语言无关。Spring Cloud Feign就是基于HTTP的声明式客户端。

    // Feign客户端示例:订单服务调用用户服务 @FeignClient(name = "user-service") public interface UserServiceClient { @GetMapping("/users/{id}") UserDTO getUserById(@PathVariable("id") Long userId); } // 在订单服务中直接注入UserServiceClient,像调用本地方法一样使用。

    优点:技术栈灵活,服务可以用Java、Go、Python等任何语言编写,只要提供HTTP接口。缺点:性能开销相对RPC大(HTTP头、序列化/反序列化),通信不够高效。

  2. RPC(远程过程调用):像调用本地方法一样调用远程服务。底层使用更高效的二进制协议(如gRPC的HTTP/2、Dubbo的私有协议)。

    • gRPC:Google开源的高性能RPC框架,基于Protocol Buffers(protobuf)定义接口和消息格式,支持多语言。
      // user.proto 定义 service UserService { rpc GetUser (UserRequest) returns (UserReply) {} } message UserRequest { int64 user_id = 1; }
    • Apache Dubbo:阿里开源的Java RPC框架,在国内互联网公司有广泛应用,服务治理功能丰富。优点:性能高,传输体积小,支持双向流等高级特性。缺点:通常要求服务端和客户端使用相同或兼容的框架,跨语言支持不如HTTP灵活(虽然gRPC做得很好)。

选择建议:对性能要求极高的内部服务调用(如交易核心链路)可选RPC(gRPC/Dubbo)。对需要开放给多语言团队或外部系统、追求简单易用的场景,选RESTful HTTP。很多公司会混合使用。

3.3 服务治理的核心组件:注册中心、配置中心、网关

集团大了,需要一套“管理体系”。

  1. 服务注册与发现(Service Registry & Discovery)

    • 问题:订单服务怎么知道用户服务的地址和端口?硬编码IP?服务实例动态扩缩容怎么办?
    • 解决方案:引入注册中心。所有服务启动时向注册中心“报到”(注册),下线时“注销”。调用方需要时去注册中心“查电话簿”(发现)。NacosEureka是常用选择。
    • Nacos示例
      # 服务提供者配置 spring: application: name: user-service cloud: nacos: discovery: server-addr: localhost:8848
      订单服务通过服务名user-service即可发起调用,无需关心其具体IP。
  2. 统一配置管理(Configuration Center)

    • 问题:数据库连接串、Redis地址、开关配置等,散落在每个服务的配置文件中,修改起来需要逐个重启服务。
    • 解决方案:将配置集中存储在配置中心(如Nacos Config、Apollo),服务启动时或运行时动态拉取。实现配置的集中管理、实时推送和版本控制。
  3. API网关(API Gateway)

    • 问题:前端需要对接几十个微服务,每个服务的地址、协议可能不同;需要统一的认证、鉴权、限流、监控入口。
    • 解决方案API网关作为系统的唯一入口,所有外部请求先经过网关,由网关负责路由到具体的微服务,并集成跨切面功能。
    • Spring Cloud Gateway配置示例:
      spring: cloud: gateway: routes: - id: user_route uri: lb://user-service # lb代表从注册中心负载均衡 predicates: - Path=/api/user/** filters: - StripPrefix=1 # 去掉路径前缀/api

    网关的价值:它解耦了前端与后端微服务,是实施安全、流控、监控策略的最佳位置。

3.4 容错与限流:如何防止“一家着火,殃及全楼”?

微服务间通过网络调用,网络是不稳定的。一个服务慢或宕机,可能导致调用它的服务线程池被占满,进而引发雪崩效应

  1. 服务熔断(Circuit Breaker)

    • 模式:像电路保险丝。当失败调用达到一定阈值,熔断器“跳闸”,后续请求直接快速失败,不再调用问题服务。过一段时间进入“半开”状态,尝试放一个请求过去,如果成功则关闭熔断器,恢复调用。
    • 工具SentinelResilience4j。Spring Cloud以前用Hystrix,现已停更。
    // 使用Sentinel注解进行熔断降级 @SentinelResource(value = "getUser", blockHandler = "handleBlock", fallback = "handleFallback") public UserDTO getUserById(Long id) { // 调用远程服务 } // blockHandler处理流控/熔断的BlockException // fallback处理业务异常
  2. 服务降级(Fallback)

    • 策略:当服务不可用时,提供一种备选方案。比如,查询用户详情失败,返回一个带有默认信息的用户对象,而不是直接抛错导致页面空白。
  3. 限流(Rate Limiting)

    • 目的:保护服务不被突发流量打垮。常见的算法有计数器、滑动窗口、漏桶、令牌桶。
    • 实践:在网关层或具体服务的入口进行限流。Sentinel可以很方便地配置QPS(每秒查询率)限流规则。

经验之谈:熔断、降级、限流的规则需要根据实际业务监控指标(如RT、错误率)来动态调整,不能配置后就不管。通常需要在预发环境进行充分的压力测试来找到合理的阈值。

4. “跨店结算”难题:分布式事务与数据一致性

这是分布式和微服务架构下最复杂的问题之一。在“餐饮集团”里,用户下一个订单,涉及“订单服务”创建订单、“库存服务”扣减库存、“账户服务”扣款。这三个操作可能属于三个不同的微服务,对应三个独立的数据库。如何保证它们要么全部成功,要么全部失败?

4.1 分布式事务的挑战与常见方案

ACID事务在单数据库内很容易保证,但在跨服务、跨数据库的场景下,无法使用传统的两阶段提交(2PC,性能差、对资源锁定时间长,在微服务中不实用)。目前主流方案是最终一致性,牺牲强一致性,保证高可用和性能。

  1. 本地消息表(异步确保)

    • 思路:在发起事务的服务本地,利用数据库事务,在执行业务操作的同时,插入一条消息记录到本地消息表。然后有一个后台任务轮询消息表,将消息发送给下游服务。下游服务消费成功后,回调确认或由发送方主动查询状态。
    • 流程
      1. 订单服务在本地事务中:1) 创建订单(状态:待支付),2) 向本地消息表插入一条“扣减库存”消息(状态:待发送)。
      2. 本地事务提交。
      3. 消息服务读取“待发送”消息,发送给库存服务。
      4. 库存服务消费消息,扣减库存,成功后回调通知订单服务或订单服务定时查询。
      5. 订单服务更新消息状态为“已发送”或删除。
    • 优点:方案简单,与业务结合紧密。
    • 缺点:消息表耦合在业务数据库中,增加了业务库压力;需要自己实现消息重试、幂等、对账等逻辑。
  2. 事务消息(RocketMQ等MQ支持)

    • 思路:利用支持事务消息的消息队列(如RocketMQ)。生产者先发送一个“半消息”到MQ,MQ会持久化此消息但不会投递给消费者。生产者执行本地事务,根据执行结果向MQ提交Commit或Rollback。MQ收到Commit后才会将消息投递给消费者。
    • 流程
      1. 订单服务发送“半消息”(含订单ID)给RocketMQ。
      2. 执行本地事务:创建订单。
      3. 根据本地事务结果,向MQ发送Commit或Rollback。
      4. MQ收到Commit,将消息投递给库存服务。
      5. 库存服务消费消息,扣减库存。
    • 优点:由MQ保证消息的可靠投递,解耦更彻底。
    • 缺点:需要MQ支持事务消息;消费端同样需要实现幂等。
  3. Saga模式

    • 思路:将一个分布式事务拆分为一系列本地事务,每个本地事务都有对应的补偿操作(Compensating Transaction)。按顺序执行所有本地事务,如果其中某一个失败,则按相反顺序执行已成功事务的补偿操作,回滚整个事务。
    • 流程(以创建订单为例)
      1. 订单服务:创建订单(T1)。
      2. 库存服务:预扣库存(T2)。
      3. 账户服务:扣款(T3)。 如果第3步失败,则执行:
      4. 账户服务:补偿扣款(C3,即加回金额)。
      5. 库存服务:补偿库存(C2,即加回库存)。
      6. 订单服务:补偿订单(C1,即取消订单)。
    • 优点:避免了长事务锁资源,适合长流程业务。
    • 缺点:补偿逻辑需要业务方仔细设计,实现复杂;不保证隔离性(可能读到中间状态)。
  4. TCC模式(Try-Confirm-Cancel)

    • 思路:二阶段提交的一种柔性实现。每个服务需要实现三个接口:Try(预留资源)、Confirm(确认执行)、Cancel(取消释放)。
    • 流程
      1. Try阶段:订单服务冻结用户账户中的金额,库存服务冻结商品库存。此阶段只做预留,不真正扣减。
      2. Confirm/Cancel阶段:如果所有Try成功,则进入Confirm阶段,各服务执行真正的扣减操作。如果任一Try失败,则进入Cancel阶段,各服务释放预留的资源。
    • 优点:数据强一致性较好,由业务逻辑保证。
    • 缺点:业务侵入性强,需要为每个操作设计三个接口,开发成本高。

方案选择对比表

方案一致性性能复杂度业务侵入性适用场景
本地消息表最终一致业务逻辑清晰,对一致性要求可放宽
事务消息最终一致已有RocketMQ,希望与业务解耦
Saga最终一致长流程业务,如电商下单、旅行预订
TCC强一致(柔性)对资金、库存一致性要求极高的场景

个人建议:对于大多数业务场景,追求最终一致性,结合消息队列(RocketMQ事务消息或可靠消息投递)和幂等性设计,是复杂度和可靠性平衡得比较好的方案。不要为了绝对的一致性,过度设计,牺牲了系统的可用性和性能。

4.2 必须掌握的辅助技能:分布式锁与幂等性

在分布式环境下,一些在单机环境下不是问题的事情,会变得棘手。

  1. 分布式锁:防止多个服务实例同时操作同一资源(如秒杀扣库存)。

    • 核心需求:互斥性、高可用、防死锁、可重入(可选)。
    • 实现方案
      • 基于Redis:使用SET key value NX PX timeout命令。简单高效,但存在锁过期而业务未执行完的风险(可通过看门狗机制续期解决,Redisson客户端实现了此机制)。
        // Redisson 分布式锁示例 RLock lock = redissonClient.getLock("orderLock"); try { if (lock.tryLock(10, 60, TimeUnit.SECONDS)) { // 等待10秒,锁持有60秒 // 执行业务逻辑 } } finally { lock.unlock(); }
      • 基于ZooKeeper:利用临时顺序节点和Watch机制。可靠性高,但性能比Redis差,且需要维护ZK集群。
    • 避坑点:锁的粒度要细(如锁订单ID,而不是锁整个库存表);一定要设置超时时间,并在finally块中释放锁。
  2. 幂等性:同一操作执行一次或多次,对系统产生的影响是一样的。在消息重试、接口超时重试的场景下至关重要。

    • 实现方式
      • 唯一业务标识:如订单ID、支付流水号。在处理请求前,先检查该标识是否已处理过。
      • Token机制:提交前先向服务端申请一个Token,提交时携带,服务端校验后删除Token。
      • 数据库唯一约束:利用数据库主键或唯一索引防止重复插入。
    • 原则幂等性是消费端(服务提供方)的责任,不能依赖消息发送方(调用方)不重复发送。

5. 从设计到部署:构建你的微服务“集团”实战要点

理解了概念和组件,最后我们聊聊从零开始搭建一个微服务系统,需要注意哪些实战要点,避免踩坑。

5.1 技术选型与全家桶

Java生态目前最主流的微服务全家桶是Spring Cloud Alibaba,它整合了阿里开源的诸多经过大规模实战检验的组件:

  • 服务注册与发现/配置中心:Nacos(替代Eureka/Config)
  • 服务网关:Spring Cloud Gateway(替代Zuul)
  • 服务熔断与限流:Sentinel(替代Hystrix)
  • 分布式事务:Seata(提供AT、TCC、Saga等模式)
  • RPC:可选Dubbo或Spring Cloud OpenFeign (HTTP)

对于新项目,直接上Spring Cloud Alibaba是省心且靠谱的选择。若依微服务版这类开源项目就是基于此套件,可以作为学习或快速原型的参考。

5.2 环境搭建与集群部署

微服务意味着更多的进程和中间件。本地开发可以用Docker Compose一键启动所有依赖(Nacos、Redis、RabbitMQ、MySQL等)。生产环境则需要考虑集群化部署以保证高可用。

  1. 中间件集群

    • Nacos集群:至少3个节点,通过内嵌的Raft协议实现数据一致性。需要配置集群IP列表和数据库(推荐MySQL)。
    • Redis集群:哨兵模式(Sentinel)或集群模式(Cluster)。哨兵模式主从切换,集群模式数据分片。生产环境建议至少3主3从。
    • RabbitMQ集群:采用镜像队列模式,确保消息高可用。仲裁队列是RabbitMQ 3.8+的高可用队列实现。
    • MySQL集群:主从复制是基础,更高级的可用MGR(MySQL Group Replication)或使用云数据库。
  2. 应用部署容器化(Docker)是微服务的最佳伴侣。配合Kubernetes(K8s),可以实现服务的自动部署、扩缩容、自愈和负载均衡。学习曲线陡峭,但这是云原生时代的标配。

5.3 监控、日志与排错

系统拆散后,问题定位变得困难。必须建立完善的**可观测性(Observability)**体系。

  1. 链路追踪(Tracing):一个请求经过了网关、A服务、B服务、数据库,到底在哪一步慢了?SkyWalkingZipkin可以帮你可视化整个调用链路, pinpoint性能瓶颈。
  2. 集中日志(Logging):所有服务的日志需要收集到一个地方(如ELK:Elasticsearch, Logstash, Kibana 或EFK:用Fluentd替代Logstash),方便根据Trace ID或业务ID串联查询。
  3. 指标监控(Metrics):收集CPU、内存、JVM GC、接口QPS、RT、错误率等指标。Prometheus+Grafana是经典组合。Spring Boot Actuator可以轻松暴露应用指标给Prometheus。

排错心法:当出现问题时(如RPC failed,Connection reset,分布式事务锁超时),按照以下顺序排查:

  1. 看日志:从网关或入口服务的错误日志看起,找到错误信息和Trace ID。
  2. 查链路:用Trace ID在链路追踪系统里还原整个调用路径,找到出问题的服务节点。
  3. 检状态:在监控大盘上查看该服务及下游服务(数据库、Redis、MQ)的实时指标(CPU、负载、错误率)。
  4. 析原因:结合日志和链路信息,判断是代码Bug、配置错误、资源不足(线程池满、连接池满),还是网络、中间件故障。
  5. 做预案:对于已知的中间件不稳定(如网络闪断),要在客户端配置合理的超时、重试和熔断策略。

5.4 何时该用,何时不该用?

微服务不是银弹。它的引入带来了巨大的复杂度:

  • 运维复杂度:需要管理几十甚至上百个服务。
  • 测试复杂度:集成测试、端到端测试难度大增。
  • 部署复杂度:需要完善的CI/CD流水线。
  • 分布式事务和数据一致性:如前所述,是持久挑战。

我的建议是

  • 不要一开始就微服务:创业公司或项目初期,强烈建议从单体架构开始,快速迭代验证业务。
  • 当单体出现明确的、且无法通过优化代码和架构(如模块化)解决的瓶颈时,再考虑拆分。这些瓶颈通常是:团队规模扩大导致协作效率低下、不同模块需要独立的技术栈或伸缩策略、部分功能故障率过高影响全局稳定性。
  • 拆分的节奏:遵循“演进式架构”思想。先拆出最独立、最不稳定或最需要频繁变更的模块(如支付、短信服务)。边拆边学,积累经验和工具链。

从一碗面的单体小店,到拥有中央厨房、专业物流、独立核算子公司的餐饮集团,分布式与微服务的演进之路,本质是业务规模和技术复杂度驱动下的必然选择。理解每个技术决策背后的业务诉求,比单纯堆砌技术组件更重要。先让单体健康成长,当它真的“装不下”你的梦想时,再带着这里梳理的地图,有节奏、有策略地开启你的“连锁帝国”之旅。

返回列表