SpringCloud微服务搭建:Nacos+Gateway+MyBatis
简介面向Java后端开发者和微服务架构初学者提供一套基于Spring Boot、Spring Cloud、Nacos、Gateway与MyBatis的微服务项目完整示例用于解决分布式系统中服务注册、配置管理、网关路由和数据持久化的整合问题。资源包采用RAR压缩格式共包含71个文件其中有21个Java源文件、21个class编译产物、10个XML配置文件含MyBatis映射、8个YML配置文件、SQL初始化脚本、依赖JAR及properties项目文件整体体积仅53KB目录结构紧凑适合快速查阅和学习。项目模块划分清晰包含gateway网关模块、user-service、product-service、feign-api等业务模块并附上cloud_user.sql数据库初始化脚本完整演示了服务注册发现、Nacos配置中心、Spring Cloud Gateway路由过滤与限流、MyBatis数据持久化、Feign声明式服务调用、Hystrix熔断保护等多个微服务核心场景的综合落地。当前已有2417人浏览学习适合需要参考微服务分层设计、梳理Eureka/Config/OpenFeign等Spring Cloud组件协作方式或希望直接获取可运行Demo进行二次开发的读者。 最近一个老项目要从单体架构往微服务迁移技术选型最终定在 SpringBoot SpringCloud Nacos Gateway MyBatis 这套组合上。老实说这套技术栈在国内中小团队里已经算“标准答案”了。注册中心用 Nacos 而不是 Eureka网关用 Spring Cloud Gateway 而不是 Zuul持久层用 MyBatis 而不是 JPA每一环都是经过大量生产环境验证过的方案。不过“标准答案”并不等于“照着写就能跑通”从版本选型到服务注册从网关路由到数据库访问中间可以踩的坑用一个星期都踩不完。这篇文章就完整记录我搭建这套微服务框架的全过程包括组件选型思路、版本兼容关系、核心配置文件、限流和配置热更新这些进阶玩法以及我实际遇到的报错和排查思路。想搭一套能直接落地、参考价值比较高的微服务骨架的兄弟可以顺着这条路走。1. 项目概述与技术选型思路1.1 这套技术栈能解决什么问题单体应用拆微服务说白了要解决四个问题服务之间怎么互相发现、请求从哪个入口进、公共配置怎么管理、数据层怎么访问。这四个问题对应到技术栈里就是服务注册发现和配置管理交给 Nacos统一的流量入口和路由转发交给 Gateway服务内部实现和跨服务调用交给 SpringBoot SpringCloud数据访问层交给 MyBatis。这套组合非常适合“业务复杂度上来了、团队规模在 5 到 20 人、不想引入太重的基础设施”的场景。相比 Kubernetes 那套完整的微服务治理方案SpringCloud 全家桶的学习成本和运维成本都低很多。一台 4C8G 的服务器就能把注册中心、网关、三四个业务服务全部跑起来对大多数中小项目来说完全够用这也是它能在国内长盛不衰的核心原因。1.2 组件选型背后的考量很多初学者会问为什么是 Nacos 而不是 Eureka为什么是 Gateway 而不是 Zuul这里我把当时选型的真实想法说清楚。先看注册中心。Eureka 1.x 已经停止维护而且它只做服务发现配置中心还得另找通常配 Spring Cloud Config引入 Git 仓库和 Bus 消息总线后链路一下子变长。Nacos 把注册中心和配置中心合二为一控制台界面直观支持 AP 和 CP 模式切换还支持从 Eureka 直接迁移国内社区活跃度高出了问题搜索解决方案非常容易。再看网关。Zuul 1.x 基于 Servlet 阻塞式模型在 IO 密集场景下性能瓶颈明显。Gateway 基于 Spring WebFlux 和 Netty是典型的非阻塞模型虽然编码上不如 Servlet 直观但作为入口层的路由转发和过滤性能优势是实打实的。持久层选 MyBatis 而不是 JPA是因为国内大部分项目的 SQL 复杂度都比较高尤其报表类查询让框架自动生成 SQL 反而不可控。MyBatis 把 SQL 掌控在自己手里慢查询优化心里有数。这套组合下的整体调用链路大致是这样客户端请求先进 Gateway网关根据路由规则转发到对应微服务微服务之间通过 OpenFeign 走 Nacos 做服务发现最终由 MyBatis 完成数据库访问。2. 环境与版本选型先避开最大的坑2.1 版本对应关系决定成败搭这套框架遇到的第一个大坑就是版本兼容。SpringBoot、SpringCloud、SpringCloud Alibaba 这三个东西的版本号是独立的不能随便组合。SpringCloud 的版本名是伦敦地铁站名比如 Hoxton、2020.0.xSpringBoot 是数字版本SpringCloud Alibaba 又有一套自己的版本号。没有官方对照表新手基本是一装一个错。我当时整理了一张对应关系表实测下来比较稳妥的组合如下。SpringBootSpring CloudSpring Cloud AlibabaNacos Server2.4.x2020.0.x2021.11.4.x2.6.x2021.0.x2021.0.1.0 ~ 2021.0.4.02.0.x2.7.x2021.0.x2021.0.5.02.2.x3.0.x2022.0.x2022.0.0.02.2.x因为项目还在用 JDK8 加老业务代码我最终选择的是 SpringBoot 2.7.18 SpringCloud 2021.0.8 SpringCloud Alibaba 2021.0.5.0 Nacos Server 2.2.3 的组合。这套在线上跑了半年多目前没有因为版本引发过问题。热词里那条“springboot版本太高”说的就是盲目用最新版 SpringBoot 导致一堆依赖不兼容的情况。我的建议很直接生产项目不要追新选社区验证时间足够长的版本组合尤其不要轻易跨大版本。2.2 父工程依赖管理搭建的第一步是建一个 Maven 父工程用 dependencyManagement 统一管理所有依赖版本避免子模块各自维护版本号导致的冲突。这里有个细节要注意SpringCloud 和 SpringCloud Alibaba 的 BOM 都要引入而且 Alibaba 的 BOM 要在 SpringCloud 的 BOM 之后声明这样 Alibaba 内部的版本不会覆盖 SpringCloud 的默认版本。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父工程建好后按业务域划分模块比如 order-service、user-service、gateway-service每个模块只在自己的 pom 里声明需要的依赖版本统一由父工程控制。这种方式在后面多模块并行开发时非常省心升级依赖也只用改父工程一处。3. Nacos 注册中心与配置中心接入3.1 安装与鉴权配置Nacos 服务端的安装不算复杂去官方仓库下载对应版本的压缩包解压后直接以单机模式启动就行。Linux 和 Mac 下执行sh startup.sh -m standaloneWindows 下执行startup.cmd -m standalone默认端口 8848控制台地址是http://localhost:8848/nacos初始账号密码都是 nacos。这里说一个很多人忽略的点生产环境一定要开启 Nacos 鉴权否则任何能访问 8848 端口的人都可以直接操作你的注册中心和配置中心风险非常大。开启方式是在 Nacos 的 application.properties 里设置鉴权开关和密钥同时第一时间把默认的 nacos 账号密码改掉。我见过有团队把 Nacos 裸奔在公网上注册中心和配置中心全部暴露这属于上线前必须处理的安全红线千万不要忽视。nacos.core.auth.enabledtrue nacos.core.auth.plugin.nacos.token.secret.key你的自定义Base64密钥3.2 服务注册与发现业务服务接入 Nacos需要引入 discovery 依赖。这里有个版本相关的坑SpringBoot 2.4 之后的版本如果要使用 bootstrap.yml 来引导配置还需要额外引入spring-cloud-starter-bootstrap这个依赖不然 bootstrap 文件不会生效很多人搭完发现配置没加载第一个要查的就是这个。dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-bootstrap/artifactId /dependency然后在 application.yml 里配置服务名和注册中心地址spring: application: name: order-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: devnamespace 是环境隔离的重要手段。我习惯用 namespace 区分 dev、test、prod 环境避免不同环境的服务互相注册错乱。配置好后启动服务在 Nacos 控制台的服务列表里就能看到 order-service 了。注意 namespace 填的是 ID 而不是名称这个在控制台的命名空间页面里能看到直接复制过来即可。3.3 配置中心动态刷新别再写错注解Nacos 作为配置中心的用法也很直接把公共配置放到 Nacos 的配置列表里服务启动时会自动拉取。配置文件要按照 dataId 的命名规则来建规则是“服务名-环境名.扩展名”比如 order-service-dev.yaml。说到动态刷新这里有个非常容易搞混的点。如果用Value注入配置需要在类上加上RefreshScope配置修改后才会自动刷新如果使用ConfigurationProperties方式绑定配置类默认就支持刷新不需要加RefreshScope。很多新手在用Value时忘了加注解然后在 Nacos 控制台改了配置发现不生效就开始怀疑是不是配置中心坏了。热词里关注的“nacos配置中心动态刷新”和“配置热更新”核心就是上面这两条规则记清楚就能少走很多弯路。另外刷新机制是实时的但要注意刷新后新配置是否满足业务校验比如数据库密码如果改错了服务可能直接被刷新挂掉这种场景需要对配置变更做一定的审计和灰度。4. Gateway 网关搭建与限流实践4.1 路由配置的正确姿势网关是微服务的统一入口路由转发是它的核心职责。搭建 Gateway 时有一个非常关键的注意点Gateway 基于 WebFlux 运行所以网关服务本身不能引入spring-boot-starter-web否则启动时会直接报错退出。我第一次搭就踩了这个坑后来排查发现是 web 和 webflux 冲突了把 web 依赖去掉就好。路由配置最基础也最常用的形式如下spring: cloud: gateway: routes: - id: order-service uri: lb://order-service predicates: - Path/api/order/** filters: - StripPrefix1lb://是负载均衡协议Gateway 收到/api/order/**的请求后会通过这个协议去 Nacos 服务列表里找 order-service 的实际地址再按负载均衡策略进行转发。StripPrefix1表示转发前把第一段路径去掉比如/api/order/create会被转发成/order/create。这个配置本身不难但衍生问题很多比如跨域配置、过滤器顺序、上下文路径丢失等。建议在网关层统一处理 CORS 和鉴权逻辑不要在业务服务里反复配置否则每加一个服务都要处理一遍跨域非常痛苦。4.2 502 Bad Gateway 排查实录热词里反复出现的 “502 bad gateway: unknown error” 是 Gateway 场景下最常见的问题。根据我的实际排查经验502 的原因通常集中在几个方向。第一路由地址写错。uri 配成了http://localhost:8080这种硬编码服务一多迁移就出问题应该统一用lb://服务名的形式。第二目标服务没有注册到 Nacos或者服务已经下线但 Gateway 的路由缓存里还有老地址。第三服务本身启动失败或端口没监听用 curl 直连服务端口验证一下就能排除。第四Gateway 与业务服务之间的参数不匹配比如超时时间太短、请求体过大等。一个实用的排查顺序是先用curl 服务IP:端口/接口确认业务服务本身正常再检查 Nacos 控制台服务列表里有没有这个服务最后看 Gateway 日志里的路由匹配情况。按这个顺序来大多数 502 都能在几分钟内定位。我之前帮同事排查过一次查了半天发现是目标服务的 context-path 改了但他忘了同步网关转发路径对不上才一直 502这种细节不看日志很难发现。4.3 网关限流不用额外组件也能快速实现做网关的时候很多人会问怎么限流尤其是从 Eureka Gateway 组合迁移过来的兄弟。Spring Cloud Gateway 内置了 RequestRateLimiter 过滤器配合 Redis 就能实现基于令牌桶算法的接口限流不依赖额外的控制台组件。核心配置如下spring: cloud: gateway: routes: - id: order-service uri: lb://order-service predicates: - Path/api/order/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 key-resolver: #{userKeyResolver}replenishRate是每秒允许处理的请求数burstCapacity是令牌桶容量也即峰值能承受的突发流量。key-resolver用来定义按什么维度限流通常是按用户 ID 或 IP需要实现一个 KeyResolver 的 Bean。这套方案胜在轻量Redis 大部分项目本来就有加一个限流配置就能用。如果系统后面流量涨起来了可以考虑引入 Sentinel 做更精细化的熔断、降级和热点防护那是另一个话题了。5. MyBatis 集成与数据访问层优化5.1 基础配置与 SQL 日志打印MyBatis 接入 SpringBoot 的路径已经非常成熟。引入 mybatis-spring-boot-starter配置好数据源在启动类上加MapperScan指定 Mapper 接口所在包就可以直接使用。这里有一个高频需求是打印 SQL很多人用了 IDEA 的 MyBatis Log 插件还是看不到日志其实是配置没对。在 application.yml 里做如下配置即可mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case开启下划线转驼峰数据库的 create_time 就能自动映射到 Java 属性的 createTime。log-impl配置成 StdOutImpl 后控制台就能看到完整的 SQL 语句和参数排查问题效率会高很多。如果你是用了 MyBatis-Plus写法略有差异它的日志打印配置在 mybatis-plus.configuration 下面层级不一样很多热词里问“mybatis log free 不生效”的大概率就是层级搞错了。5.2 分页插件与 MyBatis-Plus 的选择业务系统里分页基本绕不开。我习惯用 MyBatis 的分页插件 PageHelper引入依赖后一行代码就能完成分页PageHelper.startPage(pageNum, pageSize); ListUser users userMapper.selectAll(); PageInfoUser pageInfo new PageInfo(users);这里要强调一个注意事项PageHelper.startPage 只对紧接着的第一个查询生效如果中间穿插了其他数据库操作分页就失效了。所以调用之后要立刻执行查询不要把业务逻辑写得太绕。如果你直接用 MyBatis-Plus它自带分页插件 PaginationInnerInterceptor配置更简洁二选一即可不要两个同时用否则会出现分页结果错乱这种很难排查的诡异问题。5.3 表不存在自动建表怎么做热词里有一条“springboot mybatis 当表不存在自动建表”这是个常见的需求场景。需要先说明的是MyBatis 本身不会自动建表它是数据访问框架不是表结构管理工具。实现建表一般有三个方案一是用 Flyway 这类数据库版本管理工具把建表 SQL 放到迁移脚本里自动执行二是在应用启动时读取 schema.sql 初始化三是自己写一个启动监听器查询表是否存在不存在就执行对应的建表 SQL。最省事的还是方案三写一个 ApplicationRunner 的实现在项目启动完成后执行表检查Component public class TableCheckRunner implements ApplicationRunner { Autowired private JdbcTemplate jdbcTemplate; Override public void run(ApplicationArguments args) { Integer count jdbcTemplate.queryForObject( select count(*) from information_schema.tables where table_name user, Integer.class); if (count 0) { jdbcTemplate.execute(create table user (...)); } } }这套方案的优点是简单直接适合表结构基本稳定的内部工具类项目。对于正式业务系统我还是建议用 Flyway 管理表结构变更它可以记录每次变更的版本号团队协作时不会出现“有人本地改了表结构、别人拉代码后跑不起来”的情况比启动时现查现有靠谱得多。6. 服务间调用与高频问题排查速查6.1 OpenFeign 做服务间调用微服务之间互相调用最常用的方式是 OpenFeign。引入依赖、启动类加EnableFeignClients然后写一个 Feign 接口即可FeignClient(name order-service, path /order) public interface OrderFeignClient { GetMapping(/detail/{id}) ResultOrder getOrderDetail(PathVariable(id) Long id); }Feign 客户端底层通过 Nacos 做服务发现通过负载均衡找到对应的实例发起 HTTP 调用。这里有两个经验第一Feign 接口的返回值尽量统一包装方便做全局异常处理第二调用超时配置要在 spring.cloud.openfeign.client.config 里单独设置Feign 默认连接超时非常短业务稍微慢一点就报 Read timed out。有一次我排查线上接口间歇性报错最后发现就是对方服务偶发慢查询超过了 Feign 默认的 5 秒读超时调整后问题立刻消失。6.2 高频问题速查表这篇文章写到这里把过程中遇到的高频问题整理成一张速查表覆盖前面提到的绝大部分场景。问题现象常见原因排查思路Gateway 启动失败引入了 spring-boot-starter-web与 WebFlux 冲突网关模块去掉 web 依赖请求返回 502 Bad Gateway路由 uri 写错、目标服务未注册、服务已下线按“服务本身-注册中心-网关日志”顺序排查Nacos 连接不上服务端未启动、namespace 不一致、网络不通先确认 8848 端口再看 namespace配置修改后不生效Value 缺 RefreshScope加 RefreshScope 或用 ConfigurationPropertiesSQL 日志不打印log-impl 未配置或配置在错误位置检查 yml 中 mybatis 配置层级Feign 调用超时默认超时时间太短在 openfeign.client.config 中调大超时分页数据不对PageHelper 调用位置错误startPage 后必须紧跟查询语句这张表不敢说覆盖所有场景但基本把新手搭建阶段 80% 的报错原因都列进去了。如果遇到没见过的错误我建议先去看服务日志的堆栈信息重点看 Caused by 后面的真正报错原因。很多时候框架包装出来的报错信息会误导排查方向比 502 这种一大串错误里真正有用的可能只有最后几行。6.3 个人实操体会最后再补充一点个人感受。整套微服务框架从上手到跑通最大的障碍其实不在编码而在“组件之间如何协作”的理解上。Nacos、Gateway、OpenFeign、MyBatis 每个组件单拎出来都不算难但它们组合在一起时版本兼容、配置加载顺序、组件间调用链路的每一个环节都可能出问题。搭这套环境我强烈建议分步验证先把注册中心跑通一个服务注册进去再搭网关确保路由能通最后加上数据库访问和 Feign 调用。每加一个环节就验证一次出问题时能快速定位到具体层比一次性把所有配置写完再启动调试要省力得多。还有一个小技巧本地开发时把 Nacos、Gateway、业务服务全部启动后直接用 IDEA 的 HTTP Client 或者 Postman 打一个完整链路请求这样能第一时间发现路由、注册、调用三层的问题不用等前端联调才发现能省下大量沟通成本。本文还有配套的精品资源点击获取