为什么需要微服务?这个问题没有标准答案,但几乎所有经历过单体应用后期维护的项目组,都会给出类似的理由:业务代码堆在一个大仓库里,改一个接口就要全量发布,数据库连接被打满后整个系统一起不可用,各个团队的代码相互纠缠,想要扩容只能整体复制一套应用。
这篇文章不把微服务当作银弹来吹,只讲清楚三件事:微服务到底解决什么问题、进入微服务架构要付出哪些代价、以及落地前你至少应该准备好哪些基础组件。整体偏架构认知和工程实践,会给出可运行的 Spring Cloud Alibaba 示例骨架,方便你直接放到本地环境里验证服务注册、服务发现、网关路由和接口调用这条主链路。
如果你正在准备微服务面试题,或者正在犹豫新项目要不要直接上微服务,这篇文章可以直接收藏。
1. 核心能力速览
微服务不是一个具体软件,而是一套架构风格。它的核心单元是“服务”,每个服务围绕一个业务域独立开发、独立部署、独立扩展。落地微服务通常需要组合注册中心、配置中心、API 网关、调用链监控、日志平台等一系列组件。
| 能力项 | 说明 |
|---|---|
| 服务拆分 | 按业务域拆分为独立单元,例如用户、订单、支付、库存 |
| 独立部署 | 每个服务单独发布、单独回滚,不阻塞其他团队 |
| 故障隔离 | 单个服务异常时,通过熔断、降级限制影响范围 |
| 弹性伸缩 | 只对瓶颈服务扩容,不必整包复制整个应用 |
| 技术异构 | 不同服务允许使用不同语言和框架,只要接口兼容 |
| 治理能力 | 通过注册中心、配置中心、网关、链路追踪统一治理 |
| 接口能力 | 外部请求统一走网关,内部服务通过 RPC 或 HTTP 调用 |
| 批量任务 | 独立任务服务或消息队列异步处理,不占用在线接口资源 |
| 主要代价 | 网络调用增多、运维组件变多、分布式一致性变复杂 |
从架构演进的角度看,微服务是单体架构在业务复杂度和团队规模增长后的自然产物。它并不适合所有项目,也不存在“用了微服务就一定稳定”的说法。真正决定系统上限的,是对服务边界、数据拆分和故障治理的理解。
2. 单体架构为什么不够用了
单体应用并不是一无是处。对于早期业务,单体应用开发简单、调试方便、部署成本低。代码都在一个工程里,方法之间直接调用,没有网络开销,也没有分布式事务问题。很多成功的产品在很长一段时间内都是单体架构。
问题出在业务规模扩大之后。
第一个痛点是代码耦合。模块之间在代码层面相互依赖,改订单逻辑时发现支付模块也引用了同一套内部类,改完以后整个工程要重新编译、重新测试、全量发布。一个 1MB 的改动,可能要等 30 分钟构建,再走一遍完整回归。这种状态下,团队发布节奏会明显变慢。
第二个痛点是故障影响范围。单体应用的所有功能打包在同一个进程里,一个接口出现内存泄漏,可能拖垮整个应用。用户量上来之后,数据库连接池被慢查询占满,影响的不只是出问题的那个模块,而是所有业务。定位问题时,日志都混在一堆线程信息里,排查成本非常高。
第三个痛点是扩展粒度太粗。系统压力大的时候,通常只能复制整个应用,在前端加负载均衡。即使只有订单接口存在性能瓶颈,也得把所有无关模块一起扩容。这种做法浪费资源,也增加了部署实例的维护成本。
第四个痛点是团队协作效率下降。一个仓库里有几十个人同时提交代码,合并冲突频繁,版本管理混乱。每个功能分支都需要跑全量测试,发布窗口越来越长。团队规模越大,单体架构对协作的拖累越明显。
当这些痛点集中出现,微服务就成了一种值得考虑的架构方案。核心思路是:把一个大单体按照业务边界拆成多个小服务,每个服务自身依然可以是“单体”,但服务之间通过接口通信,数据和部署完全隔离。
3. 微服务解决什么,以及不解决什么
微服务解决的问题可以归纳为四类。
第一类是故障隔离。订单服务异常时,如果配置了熔断和降级,支付服务和用户服务仍然可以对外提供基础能力。即使某个服务彻底不可用,影响范围也能被限制在局部。
第二类是独立发布。每个服务由对应团队独立控制版本,不需要等整个系统统一发布。发布失败时,服务本身可以单独回滚,不必回滚整个平台。
第三类是独立伸缩。哪个服务压力大,就只对这个服务扩容。比如大促期间支付流量高,可以给支付服务多起几个实例,用户服务保持原样。
第四类是技术异构。新服务可以用更适合业务的框架。老服务用 Java,新服务用 Go,通过 HTTP 接口互通,这在微服务架构里是允许的。单体应用很难做到这一点,因为所有模块最终都在一个进程里运行。
但微服务不会天然解决所有问题。以下这些代价是必须接受的:
- 网络调用代替方法调用,延迟显著上升。一次用户请求如果要经过五六个服务,每一跳都有网络开销。
- 运维复杂度成倍增加。注册中心、配置中心、网关、日志、监控、链路追踪,这些组件都需要专门维护。
- 数据一致性变难。订单创建后要扣库存,单体里是本地事务,微服务里就变成了跨服务的数据一致性问题。
- 调试和排障成本上升。一个分布式请求有多个服务日志,需要结合 traceId 串联链路。
因此,微服务并不适合所有项目。如果业务规模不大、团队人数不多、也没有专门的运维人员,直接上微服务很可能只是把业务问题改造成架构问题。比较稳妥的做法是先评估业务复杂度,再决定是否拆分。这也是微服务面试题里高频出现的一个切入点:微服务适合什么场景,不适合什么场景。
4. 微服务落地环境准备与前置条件
在写代码之前,先确认基础环境是否就绪。微服务落地不是一个 Spring Boot 工程就能搞定的,它依赖一批基础设施。
4.1 基础设施清单
| 项目 | 说明 | 优先级 |
|---|---|---|
| CI/CD 流水线 | 每个服务独立编译、构建、发布、回滚 | 必须 |
| 容器化平台 | Docker 打包服务镜像,Kubernetes 编排实例 | 推荐 |
| 注册中心 | 服务上线后自动注册,消费者动态发现服务地址 | 必须 |
| 配置中心 | 配置统一管理,支持动态刷新 | 推荐 |
| API 网关 | 统一入口,处理路由、鉴权、限流、日志 | 推荐 |
| 日志平台 | 集中收集多服务日志,方便排障 | 必须 |
| 链路追踪 | 通过 traceId 查看一次请求经过的所有服务 | 推荐 |
| 监控告警 | 服务 CPU、内存、QPS、错误率监控 | 必须 |
4.2 技术选型参考
最常用的是 Spring Cloud 生态,配合 Spring Cloud Alibaba 的 Nacos 组件,既包含注册中心又包含配置中心,使用成本相对较低。网关可以用 Spring Cloud Gateway,服务间调用用 OpenFeign 或 RestTemplate。如果对性能和可用性要求更高,可以引入 Service Mesh,比如 Istio,但学习和运维成本也更高。
语言选型不必全部统一。团队以 Java 为主,就先用 Spring Cloud 把主链路跑通。业务稳定后再考虑让部分服务用更适合的语言实现,通过网关统一对外暴露。
4.3 磁盘与开发环境准备
本地验证微服务,至少准备 8GB 内存的机器。Nacos、服务提供者、服务消费者、网关这些进程全部起在本机时,内存占用会明显高于单体应用。端口方面,常见的使用包括 Nacos 8848、服务端口 8080、网关端口 9000 或 8081,具体以项目配置为准。
数据库拆分要谨慎。从单体转向微服务,最容易出问题的就是数据库。一开始不必把数据库拆得很散,可以先从“一个服务对应一个独立 Schema”开始,共享同一个数据库实例。等业务和团队都稳定了,再考虑拆分物理库。
5. 微服务骨架搭建:一个可运行的示例
这一节用一个简单的 Spring Cloud Alibaba 示例,演示服务注册、服务发现、网关路由和 Feign 调用。目标不是生产级,而是让还没有微服务经验的同学能够快速看到一条完整链路。
5.1 项目结构
microservice-demo ├── gateway-service ├── order-service └── user-service- order-service:服务提供方,对外提供订单查询接口。
- user-service:服务提供方,对外提供用户查询接口。
- gateway-service:网关,统一接收外部请求并路由到下游服务。
5.2 引入依赖
每个业务服务的 pom.xml 至少需要包含 Web、Nacos Discovery、Nacos Config 和 OpenFeign 依赖。不同版本之间的兼容关系需要结合 Spring Boot 与 Spring Cloud 的版本对应表,这里不写死版本号。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 服务注册与发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- 配置中心 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!-- 服务间调用 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies>5.3 配置文件
order-service 的 bootstrap.yml 配置如下:
spring: application: name: order-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 config: server-addr: 127.0.0.1:8848 file-extension: yml这里有一个容易忽略的点:bootstrap.yml 里配置的是 Nacos 地址。服务启动时,会先连接配置中心加载配置,然后注册到注册中心。如果 Nacos 没启动,服务会启动失败。所以本地验证的第一步,应该是先把 Nacos 运行起来。
5.4 服务提供者示例
order-service 内的控制器:
@RestController @RequestMapping("/api/order") public class OrderController { @GetMapping("/info/{orderId}") public OrderInfo getOrderInfo(@PathVariable String orderId) { return new OrderInfo(orderId, "demo-order"); } }5.5 服务消费者示例
user-service 内声明 Feign 客户端:
@FeignClient(name = "order-service", path = "/api/order") public interface OrderClient { @GetMapping("/info/{orderId}") OrderInfo getOrderInfo(@PathVariable("orderId") String orderId); }启动类开通 Feign:
@SpringBootApplication @EnableFeignClients public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }5.6 网关路由示例
gateway-service 的 application.yml 示例:
spring: application: name: gateway-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 gateway: routes: - id: order-service-route uri: lb://order-service predicates: - Path=/api/order/**这样外部请求访问网关的/api/order/**路径时,会通过注册中心找到 order-service 的实例,再转发过去。
5.7 启动验证流程
- 启动 Nacos,访问控制台确认服务地址正常。
- 分别启动 order-service、user-service、gateway-service。
- 在 Nacos 服务列表确认三个服务都已经注册。
- 直接调用 order-service 验证接口正常。
- 通过网关调用接口,验证路由生效。
- 在 user-service 中通过 Feign 调用 order-service,验证服务间调用链路。
如果这六步全部通过,一条最基础的微服务调用链路就完整跑通了。
这里要提醒一点:如果某个服务没有出现在 Nacos 服务列表里,先确认这个服务的启动日志有没有报错,再确认注册中心地址是否能在本机访问,不要急着修改代码。
6. 服务发现、配置中心和网关协同
微服务架构里,服务实例的地址是动态变化的,不能像单体应用一样写死 IP。服务提供者启动后把自己注册到注册中心,服务消费者调用前从注册中心获取可用实例列表,这就是服务发现的基本流程。
Nacos 同时承担注册中心和配置中心的角色。配置中心解决了微服务环境下配置文件难以管理的问题。比如订单服务的数据库连接、超时时间、开关配置,都可以放到 Nacos 配置中心,修改后动态刷新,不必重启服务。这个能力在发布和调优时很有用,但也需要注意配置变更的权限控制,避免误操作影响所有实例。
网关是外部请求进入微服务系统的唯一入口。业务上常用网关做三件事:
- 路由转发:根据 URL 前缀找到对应服务。
- 鉴权:在入口统一校验 Token,避免每个服务都实现一遍认证逻辑。
- 限流:对高 QPS 接口做流量控制,保护下游服务。
网关不只是转发请求,还承担了横切关注点的收敛。让服务层只关注业务逻辑,是微服务治理的一个重要方向。
7. 接口 API 设计与批量任务
微服务对外提供的接口,建议统一从网关暴露,内部服务地址不要直接对外。这样做的好处是:外部只看到网关节点的地址,内部服务实例扩容或迁移时,外部无需感知。
接口设计上,有几个容易被忽略的点:
接口版本 /v1/api/order /v2/api/order服务拆分后,不同团队迭代速度不同,接口只增不改。需要破坏性变更时,用版本号区分,避免线上调用方被错误升级。内部接口和外部接口最好分开建模,内部接口可以包含更多上下文信息,外部接口只暴露必要字段,减少泄露风险。
批量任务在微服务架构里一般单独处理,不推荐把批量任务直接压在在线服务上。常见的做法有三种:
- 独立任务服务:单独部署一个任务应用,处理定时任务和数据批处理。
- 消息队列异步化:发消息到 MQ,消费方异步处理,适合削峰场景。
- 分布式任务调度:使用 XXL-Job 这类调度平台统一管理任务,处理分片执行、失败重试。
无论采用哪种方案,批量任务都要考虑幂等性。任务重复执行不应该产生错误结果。例如订单状态机更新,要保证同一笔订单在并发或重试时只流转到目标状态一次。实现幂等常用唯一键约束、状态前置判断、分布式锁或消息去重表。
8. 资源占用与性能观察
微服务架构的资源占用普遍比单体高。同一个业务拆成五个服务后,每个服务都要独立占用于 JVM 堆内存、独立维护日志、独立和注册中心通信。五六个 Java 服务跑在一台开发机上,内存占用很快就会超过单体应用。
生产环境观察资源,重点看几个方向:
- 每个服务的 CPU 使用率和内存占用,确认是否需要扩容或调优。
- 接口响应时间,重点关注 P95、P99 分位数,而不是平均值。
- 服务间调用延迟,一次请求经过多少层服务,是否有慢调用。
- 数据库连接池使用率,连接数是否达到上限。
- 注册中心、网关、消息队列等中间件自身的资源占用。
在本地开发机进行微服务验证时,通常用jstat或jmap观察 JVM 状态,用top观察进程 CPU 和内存,用 Nacos 控制台观察服务实例状态。如果集群规模大,建议直接上 APM 工具,比如 SkyWalking 或 Zipkin,通过 traceId 串联一次请求经过的所有服务,定位慢节点会比看单机日志高效得多。
显存之类的硬件指标在微服务架构里通常不是主要问题,微服务对 CPU 和内存更敏感。容器化部署时,要为每个服务设置明确的资源 limit,防止某个服务内存泄漏时拖垮整台机器。
9. 常见问题与排查方法
微服务排障比单体复杂,主要是因为故障范围从“一个应用”扩大到了“多个进程 + 多个中间件”。下面这张表覆盖了最常遇到的几类问题。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 服务启动失败 | Nacos 未启动或地址错误 | 查看启动日志、检查 Nacos 控制台 | 先启动 Nacos,确认 server-addr 可访问 |
| 服务注册不上 | 服务名冲突或注册地址不可达 | 检查配置文件,确认应用名唯一 | 修改服务名,确认网络连通 |
| 调用时报服务不存在 | 服务名拼写错误或 Feign 注解有误 | 检查 @FeignClient 的 name 与实际注册名 | 保持服务名完全一致 |
| 接口超时 | 下游服务慢、连接池满、网络延迟 | 查看链路追踪,确认超时节点 | 增加超时时间、优化慢查询、扩容 |
| 配置修改不生效 | 配置中心未刷新或 bootstrap 配置缺失 | 查看 Nacos 配置变更记录 | 启用动态刷新,重启服务验证 |
| 网关转发失败 | 路由规则错误或服务实例异常 | 检查网关日志和注册中心服务列表 | 修正路由配置,恢复下游实例 |
| 重复扣款或数据不一致 | 缺少幂等处理或分布式事务方案 | 查看消息消费日志和数据库流水 | 加入幂等校验,改用事务消息或本地消息表 |
| 日志分散难以追踪 | 缺少 traceId 串联机制 | 检查日志中是否有统一链路 ID | 引入链路追踪组件,日志接入统一平台 |
| 某个服务突然不可用 | 内存泄漏、连接数打满、依赖故障 | 检查监控、堆转储、慢日志 | 设置资源 limit,配置熔断降级 |
遇到这些问题,先看日志,再看链路。不要凭感觉猜测。微服务环境里,同一个现象可能来自完全不同的原因,比如接口超时可能是代码慢,也可能是网络抖动,也可能是数据库锁等待,需要结合 traceId 和监控数据定位。
10. 最佳实践与使用建议
很多团队从单体迁移到微服务,最大的问题不是技术,而是拆分策略。下面这些建议,基本来自常见的工程实践踩坑总结。
10.1 拆分先按业务域,不按技术层
不要把“controller 层、service 层、dao 层”拆成三个服务。微服务拆分应该按照业务能力划分,比如用户服务、订单服务、支付服务。否则一个业务请求要经过多个技术层服务,链路长且没有业务边界意义。
10.2 数据库拆分不能太激进
一开始可以共享数据库实例,但每个服务只访问自己的 Schema。直接在第一天就把每个服务拆成独立物理库,分布式事务会立刻拖垮开发效率。优先保障业务闭环,再逐步演进。
10.3 接口要做好版本管理和兼容
服务间接口升级时,老消费者可能还在使用旧参数。破坏式变更建议用新版本接口,并保留旧接口运行一段时间。内部接口变化频繁时,可以用契约测试保证接口兼容性。
10.4 优先使用最终一致性
刚启动微服务项目时,不要尝试设计复杂的分布式事务框架。尽量把跨服务写操作改成“本地事务 + 消息表”或“消息队列 + 状态机”,用最终一致性替代强一致。只有真正需要强一致的场景,再考虑 Seata 或 Saga 方案。
10.5 全链路日志和监控必须提前建设
微服务没有全局日志就等于瞎子。每一次调用都要有 traceId,日志里带上服务名、方法名、耗时和业务主键。监控不一定要买商业产品,Prometheus + Grafana 加 SkyWalking 已经能满足大部分中小团队的需求。
10.6 安全和访问控制
网关层统一做身份认证和权限校验,服务内部调用默认信任内网,但这不等于不鉴权。面向公网的接口必须验证 Token,内部管理接口要限制网段访问。涉及用户敏感数据时,接口返回内容要做最小化处理,避免数据泄露。
10.7 不要为了微服务而微服务
如果项目只有三五个人,业务还没跑通,单体应用依然是更稳妥的选择。先把业务做出来,总结出真正的性能瓶颈和团队协作瓶颈,再决定是否拆分。很多大型系统初始阶段都是单体,业务复杂度上升后才逐步演进到微服务。
11. 总结
微服务解决的核心问题,是单体应用在业务和团队规模增长后的硬约束:发布互相阻塞、故障范围大、扩展粒度粗、技术栈耦合。它能带来的价值是独立部署、故障隔离、独立伸缩和技术异构,但对应的成本是网络调用、中间件维护、分布式数据一致性和运维复杂度成倍上升。
最值得先验证的是一条完整的调用链路:服务注册中心正常运行,服务提供者注册成功,网关能路由到服务,服务间通过 Feign 调用成功。这条路跑通,后面再扩展配置中心、链路追踪、熔断降级就有了基础。
最容易踩的坑集中在两个地方:一是数据库和事务处理,一上来就想做强一致分布式事务;二是组件部署杂乱,注册中心、配置中心、网关都没有独立维护,出了问题很难定位。先把基础设施规范好,再写业务代码,比边做边补要省力得多。
后续可以继续扩展的方向包括:容器化和 Kubernetes 编排、Service Mesh、事件驱动架构、多环境流水线和自动化灰度发布。每一步都需要根据团队实际情况推进。看完这篇文章,建议先在本机把示例骨架跑一遍,感受一下微服务开发、启动、调用和排障的完整过程,再决定是否在正式项目中引入微服务架构。