从0开始学微服务:Nacos注册中心+网关+Feign最小闭环搭建
简介面向微服务入门者的系统化学习包围绕从0到1掌握微服务架构展开涵盖服务划分、容器化部署、Kubernetes编排、API网关、服务间通信、数据管理、CI/CD与监控治理等核心主题适合正在转型分布式开发、希望建立微服务全局认知的工程师按序推进学习。包内共127个文件由42个PDF教程、42个MP3音频讲解、42个HTML课件构成PDF适合精读原理、MP3适合通勤听学、HTML便于交互浏览另有辅助说明文件整体约305.1MB。内容预览显示收录了“如何监控微服务调用”“如何搭建微服务治理平台”“微博容器运维平台DCP”等专题从基础追踪到治理实践均有覆盖。已有210人学习下载可作为初学者从理论到实践的入门参考。1. 从0开始学微服务先跑通一个最小闭环再谈那些“听过的概念”接手一个微服务项目时最难受的不是看不懂代码而是代码能跑但你不知道为什么能跑。十几个服务注册中心、网关、配置中心、链路追踪每个名词都听过但连起来就成了一团乱麻。这份从0开始学微服务的资源就是拿来治这个病的它不堆概念直接给出一条从创建父工程、注册服务、互相调用、挂网关到最后本地一键启动的完整路径。适合有基础 Java 和 Spring 经验、想尽快把微服务跑起来的新手也适合被公司“微服务改造”逼着上手的后端开发者。照着敲一遍至少能让你接手项目时不再发怵。2. 微服务三件套先立住注册中心、配置中心、网关各自的职责边界很多人一上来就学 Spring Cloud Alibaba 全家桶结果越学越糊涂。我的习惯是先搞清楚三个核心组件的职责再动手敲代码注册中心管“服务在哪”配置中心管“参数在哪”网关管“请求从哪进”。这三件事想明白了后面所有的代码都只是在实现这层职责。2.1 注册中心选型为什么第一阶段基本上都选 Nacos注册中心要解决的核心问题是服务实例地址的动态发现。在早期单体时代服务地址写死在配置文件里但微服务实例会扩缩容、会迁移硬编码必然翻车。注册中心本质上就是一个“服务地址通讯录”服务启动时上报自己的 IP 和端口服务下线时自动剔除调用方只需要按服务名去查找不用关心具体实例在哪台机器。市面上的注册中心很多Eureka、Consul、Zookeeper、Nacos 都有。从0开始学的角度我一般建议先上手 Nacos理由很实际它同时覆盖了注册中心和配置中心两个角色控制台自带中文界面能看到服务列表、健康状态配合 Spring Cloud Alibaba 的 Starter 几乎零配置接入。对新手来说少搭一套组件就少踩一堆坑。启动 Nacos 单机版最省事的方式是直接用容器跑docker run -d --name nacos-standalone \ -p 8848:8848 \ -p 9848:9848 \ -e MODEstandalone \ nacos/nacos-server:v2.3.0这里解释一下参数-d让容器在后台运行-p 8848:8848暴露 HTTP 端口控制台和客户端请求都走它-p 9848:9848是 Nacos 2.x 的 gRPC 端口服务注册发现的长连接走这个端口漏掉会看到一堆奇怪的超时日志MODEstandalone表示以单机模式启动不需要额外配置数据库。启动完成后浏览器访问http://localhost:8848/nacos默认账号密码都是nacos能看到控制台页面就说明注册中心活了。这一步值得花时间确认因为后面的服务注册都是建立在它正常的基础上。端口、账号、启动模式这三样是体检项目先记下来。2.2 配置中心把“环境差异”从代码里剥离出来配置中心的价值要等你在测试环境和生产环境之间来回切换时才体会得到。最原始的做法是在配置文件里写死数据库地址、Redis 地址换环境就改配置重新打包改漏一个字段就要线上排查半天。配置中心的做法是把这些环境相关的参数外置服务启动时去配置中心拉取同一个 Jar 包在不同环境加载不同的配置。Nacos 作为配置中心时常见做法是在控制台里新建配置。Data ID 的命名推荐用“服务名-环境.yaml”这种格式比如user-service-dev.yamlGroup 保持默认的DEFAULT_GROUP配置格式选 YAML。这样后续想给某个服务单独调参就不用重新发包改完配置点发布服务端能自动感知。要让项目接上配置中心依赖和引导配置是两步关键。第一引入对应 Starter第二在bootstrap.yml或application.yml里指定 Nacos 地址。这里有个很重要的版本坑Spring Cloud 2020.0 之后bootstrap默认不再开启如果强行用旧写法配置死活拉不回来。解决办法是用spring.config.import这个新机制spring: application: name: user-service config: import: nacos:user-service-dev.yaml cloud: nacos: server-addr: localhost:8848这段配置的关键在spring.config.import这一行它的格式是nacos:Data ID。Spring Boot 启动时会先去 Nacos 拉取这个配置再启动服务。注意spring.cloud.nacos.server-addr要写完整地址带端口不能只写 IP。Nacos 默认从DEFAULT_GROUP里找对应 Data ID如果你把配置放到了别的分组需要额外声明group。2.3 网关让所有请求统一走一个口子网关这一层我见过不少人一开始会忽略觉得服务之间直接通过 Feign 调用就行呗为什么要多一跳。但等你要做统一鉴权、统一日志、统一限流的时候没有网关就只能去每个服务里各写一套改个需求全链路发版非常痛苦。Spring Cloud Gateway 是目前的主流选择底层基于 WebFlux非阻塞模型性能比 Zuul 1.x 好很多。核心概念就两个路由和过滤器。路由决定什么样的请求转发到哪个服务过滤器负责在转发前后做额外处理。最小可用配置长这样spring: cloud: gateway: routes: - id: user-service uri: lb://user-service predicates: - Path/api/user/** filters: - StripPrefix1这里的lb://user-service是关键它告诉网关走负载均衡方式去注册中心找到user-service这个服务的实际实例地址。Path/api/user/**是路由断言匹配以/api/user/开头的请求。StripPrefix1表示转发前去掉第一段路径也就是说请求/api/user/list最终到用户服务的实际路径是/list这样服务端的 Controller 就不用关心网关前缀。网关的配置是一层一层试出来的。第一次搭的时候不要想着把所有路由都配好先配一个最简单的服务通过网关访问通再往外扩。3. 从零搭起可运行骨架父工程拆分、服务注册与调试闭环到这一章资源里真正能抄的东西开始发力了。微服务工程大多采用 Maven 多模块结构一个父工程管依赖版本下面挂多个子服务。这一步没做好后面会反复被依赖冲突折磨。3.1 用父工程统一锁定依赖版本父工程的pom.xml不写业务代码只干两件事声明公共依赖、锁定各组件版本。这样做的好处是子模块不需要重复写版本号未来升级组件版本只改父工程一处。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencyManagement dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version2021.0.8/version typepom/type scopeimport/scope /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-dependencies/artifactId version2021.0.5.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement这里用了dependencyManagement而不是直接在 dependencies 里声明目的就是让子模块继承版本管理。特别注意 Spring Cloud 与 Spring Boot 的版本对应关系2021.0.x 对应 Boot 2.6/2.7配错版本启动时会出现NoSuchMethodError这类诡异异常。如果你确实拿不准建议直接照抄这套组合这是经过大量线上项目验证过的稳定搭配。子模块只需要在pom.xml里声明自己需要的依赖版本号由父工程统一兜底。比如一个注册到 Nacos 的服务最小依赖集是这样dependencies dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies3.2 写一个能注册到 Nacos 的最小服务有了父工程后创建一个user-service子模块代码量很小核心就是一个启动类、一段配置、一个 Controller。但每一步都有讲究。首先看启动类SpringBootApplication EnableDiscoveryClient public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }从 Spring Cloud 2020 之后的版本EnableDiscoveryClient其实可以省略因为只要 classpath 存在 Nacos Discovery 依赖自动配置就会生效。写不写都不影响功能我习惯保留主要为了显式表达“这个服务要注册到注册中心”。但这个注解本身不是必须先有。配置文件是重头戏server: port: 8081 spring: application: name: user-service cloud: nacos: discovery: server-addr: localhost:8848 ephemeral: truespring.application.name决定了服务在注册中心显示的名字后续 Feign 调用和网关路由都会用到所以名字一定要有辨识度、不要乱改。ephemeral: true表示临时实例服务进程挂掉后注册中心会自动删掉这条记录如果设成false服务需要主动注销否则会一直留在实例列表里。再写一个最简单的接口用于验证路由和调用链路RestController RequestMapping(/user) public class UserController { GetMapping(/list) public String list() { return user-service: user list; } }这里/user路径要和网关配置里StripPrefix搭配着看形成闭环。3.3 运行后如何确认注册成功服务启动后不要急着写下一个服务先花一分钟确认注册状态。两个最直接的手段curl http://localhost:8848/nacos/v1/ns/instance/list?serviceNameuser-service这条命令会返回 JSONhosts数组里有实例 IP 和端口就说明注册成功了。另外打开 Nacos 控制台的“服务管理-服务列表”也能看到user-service处于上线状态。如果服务列表里没有它优先检查server-addr是否写对、端口是否被占用再看启动日志里有没有连接注册中心失败的异常。这一步是后续所有联调的地基地基不稳后面每调一次接口都要怀疑一遍注册中心。4. 服务间调用与网关路由OpenFeign、负载均衡和一组能抄的参数服务注册好了接下来就是微服务最典型的场景服务 A 调服务 B。这一章解决两件事服务之间怎么优雅地互相调用以及网关怎么把这些服务统一暴露出去。4.1 OpenFeign声明式调用比写 HTTP 客户端靠谱服务间调用最常见的方式是用 OpenFeign。它的好处是面向接口编程开发者不需要关心 HTTP 请求怎么拼、响应怎么反序列化只需要声明一个接口框架自动生成代理实现。假设我要在gateway-service或某个调用方服务里获取用户信息先引入依赖dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency在启动类上加EnableFeignClients然后定义一个接口FeignClient(name user-service, path /user) public interface UserClient { GetMapping(/info/{userId}) String getUserInfo(PathVariable(userId) Long userId); }FeignClient的name是注册中心里的服务名只要服务注册成功Feign 会通过注册中心拿到实际地址发起调用。path /user是公共前缀对应那个服务 Controller 类上的RequestMapping(/user)。方法上的GetMapping与普通 Controller 写法一致。调用方只需要注入这个接口RestController RequestMapping(/order) public class OrderController { private final UserClient userClient; public OrderController(UserClient userClient) { this.userClient userClient; } GetMapping(/user-info/{userId}) public String getRemoteUser(PathVariable Long userId) { return userClient.getUserInfo(userId); } }这里要注意一个很容易忽略的点Feign 默认集成了 Ribbon 负载均衡同一个服务部署多个实例时请求会在实例间轮询转发。你想验证负载均衡效果可以在响应里加上实例端口号或者用环境变量标识多开几个服务实例就能看到返回结果来自不同的实例。但别用GetMapping(/info/{userId})里的{userId}去拼服务名服务名走的是注册中心解析不是路径变量。4.2 网关路由与过滤器统一入口加鉴权的最小实现网关这一层路由之外最重要的就是过滤器。最常见场景是验证请求里有没有 token。先写一个全局过滤器Component public class TokenCheckFilter implements GlobalFilter, Ordered { Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token exchange.getRequest().getQueryParams().getFirst(token); if (token null || token.isEmpty()) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } }过滤器拦截所有进入网关的请求getQueryParams().getFirst(token)从 URL 参数里取 token取不到就直接返回 401 并终止链路取到了就放行。Ordered接口用于控制多个过滤器的执行顺序数值越小优先级越高。实际项目中 token 一般放在 Header 里这里用参数只是为了演示最简链路。有了网关和过滤器前端只需要知道网关一个入口服务内部结构完全被屏蔽。比如前端请求http://localhost:8888/api/user/list网关收到后按规则转发到user-serviceStripPrefix1把/api剥掉最终到服务端的路径是/user/list和上面的 Controller 对上了。4.3 超时、重试与线程池参数稳定调用的底裤服务调不通十次里有八次是超时或重试配置不到位。Feign 的默认超时时间是 60 秒这在本地调试看起来没问题但一旦放到生产环境接口慢一点就会拖垮调用线程。建议给 Feign 配置如下参数feign: client: config: default: connectTimeout: 3000 readTimeout: 5000 httpclient: enabled: trueconnectTimeout是建立连接的最大等待时间readTimeout是等待响应数据的最大时间。这两个值要根据业务接口的真实耗时来定不是越小越好。如果接口本身要执行大量计算或调用外部系统readTimeout可以放宽到 10 秒但要配合熔断策略避免长时间占用线程。如果希望 Feign 在失败时自动重试需要额外引入spring-retry依赖并在配置里开启重试。我的建议是重试次数不要超过 2 次且只对 GET 请求重试写接口重试可能造成重复数据。这种参数组合只有踩过线上事故的人才会重视新手阶段先按上面的值照抄等遇到真实瓶颈再调整。5. 微服务避坑笔记注册掉线、路由404与配置不生效的完整复盘这一章是血泪经验的汇总。下面的每个坑都在模拟项目X里真实出现过也都在这个资源包的笔记里标注了原因。每一条按“现象、原因、解决”三层写建议你直接把这个问题清单当成调试手册。5.1 服务启动成功但注册不到 Nacos现象服务启动日志没有报错但 Nacos 控制台服务列表里看不到这个服务。原因大多数情况是spring.cloud.nacos.discovery.server-addr没写对最常见的是漏了端口号或者把localhost写成了127.0.0.1而 Nacos 实际监听的是别的绑定地址。另一种可能是依赖没引全只引入了spring-boot-starter-web而忘了加 Nacos Discovery Starter。解决第一步检查依赖确认有spring-cloud-starter-alibaba-nacos-discovery第二步检查配置里的server-addr是否带端口第三步看启动日志中是否有nacos registry register相关的成功记录。如果都没有问题手动访问 Nacos 地址确认注册中心本身是活的。这套排查顺序五分钟内走完比盲目改配置高效得多。5.2 实例注册成功但很快被剔除现象服务在列表里能看到但状态一会儿显示 UP一会儿变下线或者启动后几分钟之内就被注册中心踢掉。原因最典型的是健康检查机制匹配不上。Nacos 临时实例依赖客户端心跳续约如果服务所在机器网络不稳定或者 Nacos 的ephemeral参数和客户端配置不一致就可能出现“注册上了但续约失败”的情况。还有一个坑是服务最近频繁手动重启旧实例没有优雅下线的机会。解决先把ephemeral在客户端和服务端统一成同一个值绝大多数场景用默认的临时实例即可。然后检查网络确认服务到 Nacos 的 8848 和 9848 端口都通畅。9848 是 gRPC 端口很多人防火墙只放行了 8848结果心跳走 gRPC 连不上就会出现这种“藕断丝连”的状态。5.3 通过网关访问服务返回 404现象直接访问服务端口一切正常但通过网关访问同一个接口返回 404 或者 Cannot forward to remote。原因路由断言里的服务名拼写错误或者StripPrefix参数没写对。请求路径经过网关裁剪后到达服务端时和 Controller 里声明的路径对不上。举个例子网关配置StripPrefix1请求进来是/api/user/list转出去变成/user/list如果服务端 Controller 没有/user/list这个映射自然就 404 了。解决在网关日志里打开可以追踪转发路径的日志级别或者直接临时把StripPrefix去掉测试。我比较常用的办法是先在网关配置里加一条/user/**的断言直连测试通后再加前缀逻辑。还要确认uri里的服务名和注册中心里的名字一字不差这个字段区分大小写并且对空格敏感复制粘贴最容易出问题。5.4 配置中心的配置始终不生效现象Nacos 控制台里改配置、发不了布服务里怎么都不生效甚至启动时直接报找不到配置。原因最常见的坑是 Spring Cloud 版本升级后bootstrap机制关闭了。旧项目里配置写在bootstrap.yml中新版本默认不走引导上下文自然拉不到配置。还有一种情况是 Data ID 后面的group对不上Nacos 默认去取DEFAULT_GROUP你把配置放进了自定义分组又没在配置里指定。解决确认项目用的 Spring Cloud 版本2020.0 以后优先用spring.config.import: nacos:xxx.yaml的写法。在配置里显式加上group: DEFAULT_GROUP或者把控制台上的配置移到默认分组。最后提醒一个细节改了配置后默认情况下只有标了RefreshScope的 Bean 才会动态刷新普通 Bean 需要等重启。5.5 Feign 调用报序列化或连接超时现象Feign 偶尔调用成功、偶尔报feign.RetryableException: connect timed out或者返回的 JSON 反序列化失败提示Cannot deserialize。原因第一类是网络层问题服务实例之间的依赖超时时间太短一次慢查询就把连接拖断了。第二类是接口返回结构没对齐调用方声明的返回类型和服务端实际返回的数据结构不一致。第三类容易被忽略多个服务实例中有一个老实例还在它返回的数据格式和新的不一致正好被负载均衡轮询到就会出现“时好时坏”的诡异现场。解决超时类问题先调大readTimeout同时检查服务端有没有长时间占用线程的慢 SQL。结构类问题把 Feign 接口的返回类型改成和实际 JSON 完全对应的 DTO不要在接口里直接用String接收复杂对象。老实例问题最让人头疼建议部署的时候用滚动发布而不是原地重启确保同一时刻只有一种版本在服务。6. 收尾实战用 docker-compose 一键拉起本地微服务演示环境资源包里的工程骨架最后给了一套 Docker Compose 编排我强烈建议你把它跑起来因为“本地一键启动”是验证整套配置是否正确的唯一标准。这里给出一个最小的三服务编排Nacos、一个用户服务、一个网关。version: 3.8 services: nacos: image: nacos/nacos-server:v2.3.0 container_name: nacos-standalone environment: MODE: standalone ports: - 8848:8848 - 9848:9848 user-service: build: ./user-service depends_on: - nacos environment: SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR: nacos:8848 SERVER_PORT: 8081 ports: - 8081:8081 gateway-service: build: ./gateway-service depends_on: - nacos - user-service environment: SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR: nacos:8848 SPRING_CLOUD_GATEWAY_ROUTES_0_URI: lb://user-service SPRING_CLOUD_GATEWAY_ROUTES_0_PREDICATES_0: Path/api/user/** ports: - 8888:8888要点在环境变量这一段容器内部访问宿主机时不能再用localhost要写成服务名nacosCompose 网络会把nacos解析到对应的容器 IP。SERVER_PORT要显式传给服务否则容器内默认端口和 Dockerfile 里暴露的端口可能不一致。网关的路由配置也通过环境变量注入格式里数组下标0不能省。整套环境起来后按下面顺序做一次端到端验证访问网关的/api/user/list能返回用户服务的响应直接访问user-service:8081也能通Nacos 控制台两个服务都在线。三步全过微服务最小闭环就算真正跑通了。从那以后我每次接手新的微服务项目都强制走一遍这套流程注册中心手动启动、一个最小服务注册、一个调用方 Feign 调用、一个网关路由透传。四步都走完才去碰复杂业务代码这让我避免了很多次“以为自己懂了、一跑全是坑”的尴尬。希望这份从0开始学微服务的资源也能帮你把那些抽象概念变成手底下跑得通的代码希望帮到你。本文还有配套的精品资源点击获取