微服务环境搭建全记录:Nacos+Gateway+MyBatis版本兼容与避坑指南

发布时间:2026/10/20 7:33:37
微服务环境搭建全记录:Nacos+Gateway+MyBatis版本兼容与避坑指南 简介面向正在学习微服务架构的Java开发者这份项目以SpringBoot为启动框架整合SpringCloud生态、Nacos注册与配置中心、Gateway网关以及MyBatis持久层演示了分布式系统从服务注册、配置管理、请求路由到数据库交互的完整链路可作为微服务入门或课程设计的参考骨架。\n\n资源共71个文件以Java源码、class编译文件、XML配置和YAML配置为主另含SQL脚本、properties配置等压缩包仅53KB轻量便于下载。目录按模块组织包含user-service、product-service、gateway、feign-api等结构与实际微服务项目对应。\n\n已有2419人学习下载。通过研读项目可理解服务间远程调用的RESTful与Feign两种方式掌握Nacos作为注册中心和配置中心的使用并参考Gateway路由规则与MyBatis数据访问的落地写法。尽管包体不大源码和配置覆盖了关键环节适合对照学习或在此基础上扩展业务模块。1. 微服务环境搭建前版本兼容性先谈一次微服务从零搭建看起来就是下载几个组件、写几个接口的事真正动手才发现版本不兼容能把人卡在原地一整天。SpringBoot、SpringCloud、Nacos、Gateway、MyBatis这套组合版本对应关系极其严格——SpringBoot 2.4之后必须用2020.0.x以上的SpringCloud而SpringCloud Alibaba和SpringCloud官方组件又各有各的维护节奏稍有不慎服务注册不上、路由转发404、配置中心拉不到配置一个接一个冒出来。这篇文章是我自己从空目录开始搭完这套环境的完整记录包括Nacos单机部署、父工程依赖锁定、Gateway路由与鉴权、MyBatis接入以及最容易翻车的几个点。适合准备动手搭微服务但不想在版本和配置上反复折腾的开发者照着走完至少不会卡在第一步。2. Nacos注册中心部署单机起步与配置持久化2.1 环境要求与安装方式选择Nacos是这套微服务架构里的注册中心和配置中心所有服务都要先注册到它这里Gateway才能通过服务名找到下游实例。先明确一点Nacos依赖Java环境JDK必须1.8以上我用的是JDK 1.8这点和SpringBoot 2.x正好对应不用上Java 17反而省事。下载Nacos建议直接去GitHub的Release页面拿编译好的压缩包别自己从源码编译耗时长且容易因为依赖拉取失败中断。单机演示或小规模项目用standalone模式就够了内存占用大约1G以内服务器配置不高的也能跑。官方提供cluster模式用于生产环境多节点部署需要配合MySQL存储配置信息但那是后话先把单机跑通再说。# 解压并进入目录 tar -xzf nacos-server-2.2.0.tar.gz cd nacos/conf cp application.properties.example application.properties这里把配置文件复制一份再改保留原始模板方便以后查默认值。Nacos 2.x的配置文件和1.x有明显差异注册中心默认端口都是8848但2.x新增了gRPC通信端口分别是9848和9849防火墙如果只放行8848会导致服务注册偶尔超时这个坑后面细说。2.2 数据源配置默认Derby还是MySQLNacos单机模式默认使用内嵌Derby数据库存储配置和服务实例数据好处是零依赖开箱即用坏处是数据不好做迁移备份。我一般会直接切到MySQL因为后面集群部署时反正也要用不如一开始就统一。修改conf/application.properties把默认数据源注释掉放开MySQL配置段spring.sql.init.platformmysql db.num1 db.url.0jdbc:mysql://127.0.0.1:3306/nacos?characterEncodingutf8connectTimeout1000socketTimeout3000autoReconnecttrueuseUnicodetrueuseSSLfalseserverTimezoneAsia/Shanghai db.user.0root db.password.0你的数据库密码对应的需要提前在MySQL里建好nacos库并把conf/mysql-schema.sql导入进去。这个SQL文件包含了Nacos运行所需的全部表结构不导入的话启动时会直接报表不存在的错误。切了MySQL之后Nacos配置中心发布的所有配置都会持久化到数据库后面服务重启、Nacos重启都不会丢配置这是它相比Derby最实用的地方。生产环境建议再加上nacos.core.auth.enabledtrue开启鉴权不然服务端口暴露到公网就等于把配置中心裸奔了。2.3 启动Nacos与常用校验命令单机启动用官方提供的启动脚本Linux环境直接执行cd nacos/bin sh startup.sh -m standalone-m standalone指定单机模式启动不加这个参数默认会尝试以集群模式启动本地只有一台机器的话会报错。启动完成后看日志确认没有异常tail -f /data/nacos/logs/start.out看到Nacos started successfully字样就说明起来了。然后打开浏览器访问http://服务器IP:8848/nacos默认账号密码都是nacos登录进去能看服务列表和配置列表。# 命令行验证端口监听 netstat -tlnp | grep -E 8848|9848这里的9848就是上面提到的gRPC端口检查时必须确认它也处于监听状态否则客户端SDK没法通过gRPC长连接推送服务变更服务列表会一直不刷新表现为新服务注册成功但旧服务下线感知不到——这个问题非常隐蔽排查半天最后发现是端口没放行。3. 父工程与公共模块构建一套不冲突的依赖骨架3.1 依赖管理为什么必须用dependencyManagement锁版本微服务项目通常采用Maven多模块结构父POM只做依赖管理不实际引入依赖。这里最关键的是把SpringBoot、SpringCloud、SpringCloud Alibaba三套BOMBill of Materials都塞进dependencyManagement并且顺序不能乱后声明的覆盖先声明的同坐标版本。dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version2.6.13/version typepom/type scopeimport/scope /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version2021.0.5/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顺序上必须先把SpringBoot的BOM放前面再放SpringCloud最后放SpringCloud Alibaba的BOM。因为SpringCloud Alibaba的BOM内部会传递依赖SpringCloud的版本如果写在最前面它引用的版本可能覆盖掉你后面声明的SpringCloud版本导致版本错乱。用import配合provided作用域意思是只在当前POM做版本管理子模块声明依赖时不写版本号自动套用这里的约束子模块之间不会互相干扰版本。3.2 bootstrap.ymlNacos配置中心接入的敲门砖SpringBoot 2.4版本之前bootstrap.yml默认生效之后需要引入spring-cloud-starter-bootstrap依赖才会加载。这个文件承载了Nacos配置中心的客户端参数必须在应用启动早期就被读取因此它独立于application.yml存在。spring: application: name: order-service cloud: nacos: server-addr: 127.0.0.1:8848 username: nacos password: nacos discovery: namespace: public group: DEFAULT_GROUP config: namespace: public group: DEFAULT_GROUP file-extension: yaml refresh-enabled: truespring.application.name不仅是应用名也是注册到Nacos上的服务名和配置中心Data ID的前缀三者必须严格一致。namespace和group是Nacos上隔离配置的两层维度默认public和DEFAULT_GROUP如果服务里指定了自定义的namespace但Nacos控制台上没建配置会加载失败服务还是会注册但配置全部缺失表现为启动时不报错、运行时提示数据库连接失败等诡异问题。refresh-enabled: true开启配置动态刷新改完Nacos上的配置文件应用不需要重启就能拿到最新值这个能力在后面统一调整超时时间和线程池参数时非常方便我习惯把所有环境相关的参数都放Nacos管理本地application.yml只留一些不随环境变化的静态配置。3.3 MyBatis数据源配置与Mapper扫描MyBatis在SpringBoot项目里的接入方式比较简单引入mybatis-spring-boot-starter并配置数据源即可。这里我倾向于直接使用spring-boot-starter-jdbc自带的数据源自动配置配合连接池参数调整。spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/order_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowMultiQueriestrue username: root password: 你的数据库密码 hikari: minimum-idle: 5 maximum-pool-size: 20 connection-timeout: 30000 idle-timeout: 600000 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.order.entity configuration: map-underscore-to-camel-case: trueallowMultiQueriestrue这个参数容易被忽略如果SQL里有批量更新、多条语句一次执行的需求不加这个参数MySQL驱动会直接报语法错误。map-underscore-to-camel-case开启后数据库user_name字段能自动映射到Java的userName属性少写很多resultMap。Mapper接口的扫描注解必须放在启动类上或者用一个独立的配置类统一管理SpringBootApplication MapperScan(com.example.order.mapper) public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }MapperScan声明了Mapper接口所在的包路径Spring会为这个包下所有接口生成代理实现注入容器。常见的坑是把MapperScan写到了实体类包或者Controller包上启动时扫描不到任何Mapper抛Invalid bound statement异常——所有的MapperScan路径和XML里的namespace必须对应同一个接口全限定名少一个冒号都不行。4. Gateway网关搭建路由规则与全局鉴权过滤器4.1 路由配置与服务发现Gateway是这个微服务体系的统一入口外部请求先进网关再由网关根据路由规则转发到具体服务。它基于WebFlux实现因此不能和传统的SpringMVC依赖共存引入spring-boot-starter-web会在启动时报Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway项目里凡是作为网关的模块都不要引入web起步依赖。server: port: 9000 spring: application: name: gateway-service cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true routes: - id: order-route uri: lb://order-service predicates: - Path/api/order/** filters: - StripPrefix1 - id: user-route uri: lb://user-service predicates: - Path/api/user/** filters: - StripPrefix1uri里的lb://前缀非常关键它告诉Gateway从注册中心按服务名负载均衡地去寻找目标实例而不是直接路由到固定IP服务多实例部署时不用改动路由配置实例上下线也能自动感知。StripPrefix1表示转发前去掉一段路径前缀客户端请求/api/order/list网关剥离/api后转发给order-service的/order/list服务端Controller的RequestMapping不需要关心前端路径前缀。如果服务之间的调用关系复杂Controller层的路径设计要避开网关的路径前缀比如服务内部自调用用的是/order/detail网关暴露给前端的路径是/api/order/detail两者职责分离不会互相污染。4.2 全局过滤器实现Token鉴权网关层面统一做鉴权是微服务架构的标准做法下游服务不需要各自解析Token减少重复代码。我用一个全局过滤器拦截所有请求放行登录接口其余请求校验Authorization请求头。Component public class AuthGlobalFilter implements GlobalFilter, Ordered { private static final ListString WHITE_LIST Arrays.asList(/api/user/login, /api/user/register); Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { String path exchange.getRequest().getURI().getPath(); if (WHITE_LIST.contains(path)) { return chain.filter(exchange); } String token exchange.getRequest().getHeaders().getFirst(Authorization); if (token null || token.isEmpty()) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } // 这里调用用户服务或JWT工具解析Token exchange.getRequest().mutate().header(userId, 10086); return chain.filter(exchange); } Override public int getOrder() { return -100; } }过滤器通过getOrder()方法控制执行顺序数字越小越先执行-100保证它在其他过滤器之前运行。ServerWebExchange是WebFlux环境下的请求上下文对象mutate()方法可以在请求头里注入新的字段把解析出来的用户ID透传给下游服务下游Controller可以直接RequestHeader(userId)取值省去再次解析Token的IO开销。有个细节必须注意直接在exchange.getRequest().mutate()上修改头信息是不够的因为原来的exchange对象仍然持有旧的请求必须用mutate()返回的新请求构造一个新的ServerWebExchange实例再传给chain.filter()否则下游收到的还是没有userId的请求头。最简单做法是给ServerWebExchange加上ServerWebExchangeUtils的装饰类重写getRequest()方法或者直接在filter方法内重新声明ServerWebExchange newExchange exchange.mutate() .request(exchange.getRequest().mutate() .header(userId, 10086).build()) .build(); return chain.filter(newExchange);4.3 跨域配置与超时时间前端分离架构下跨域是网关绕不开的问题。Gateway层面配置CORS比在每个下游服务都加一次跨域注解要高效得多。spring: cloud: gateway: globalcors: add-to-simple-url-handler-mapping: true cors-configurations: [/**]: allowedOriginPatterns: - http://localhost:8080 - http://127.0.0.1:8080 allowedMethods: - GET - POST - PUT - DELETE - OPTIONS allowedHeaders: * allowCredentials: true maxAge: 3600allowedOriginPatterns比allowedOrigins更好用前者支持正则和通配符后者不能和allowCredentials: true一起使用因为带凭证的跨域请求不允许指定通配符来源。add-to-simple-url-handler-mapping控制预检请求的处理不开启的话部分浏览器会在OPTIONS预检时直接返回405。超时配置同样放在Gateway的配置文件里路由转发到下游如果迟迟没有响应网关要有一个兜底超时时间避免连接一直挂着占用线程资源spring: cloud: gateway: httpclient: connect-timeout: 3000 response-timeout: 10sconnect-timeout是建立连接的等待时间单位是毫秒response-timeout是等待响应的时间单位可以是数字毫秒或带单位的字符串。这两个值要根据下游接口的最慢耗时来定不能为了保守把时间拉太长否则接口出问题时调用方会一直卡到超时才发现失败。我一般会把response-timeout设成下游接口P99耗时的3倍左右。5. 避坑排查这几个问题能卡住大半天5.1 服务注册成功但调用报Connection Refused现象Nacos控制台上能看到服务实例已经注册成功但通过Gateway或OpenFeign调用时频繁报Connection refused且不是每次都会失败像是随机性的。原因Nacos 2.x引入了gRPC长连接客户端端口范围是server-port 1000开始的几个端口。很多服务器只放行了8848和常用的服务端口gRPC通信端口9848没有放行导致注册中心推送给客户端的连接请求被防火墙丢弃或拒绝。解决在防火墙和安全组规则里同时放行8848和9848端口如果是阿里云或腾讯云的安全组策略也别忘了加对应规则。放行后重启服务再用netstat确认9848端口处于LISTEN状态调用恢复正常。这个问题在Nacos 1.x时代不存在升级到2.x之后才出现排查思路要跟着架构演进。5.2 配置中心参数修改后应用没生效现象在Nacos控制台修改了某个配置项点击发布成功但应用行为没有变化日志里也没看到配置刷新的痕迹。原因配置动态刷新不是默认全开的RefreshScope注解加在需要动态刷新的ConfigurationProperties类或Value所在的Bean上才会生效。另外bootstrap.yml里如果没设置refresh-enabled: true客户端即使收到了变更通知也不会主动重新拉取配置。解决给配置类加上RefreshScope注解同时确认bootstrap.yml中spring.cloud.nacos.config.refresh-enabledtrue。做完这两步还不行检查Nacos上配置的Data ID是否和应用名匹配比如应用名order-service对应的默认配置Data ID是order-service.yaml写成了order-service.yml注意后缀是拉不到的Nacos不会报错只会静默失败。Component RefreshScope ConfigurationProperties(prefix order) public class OrderConfig { private Integer timeout; // getter和setter省略 }5.3 Gateway启动直接报错退出现象Gateway模块一启动就抛异常日志提示Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway。原因工程中某个公共模块或者pom.xml里引入了spring-boot-starter-webGateway基于WebFlux的响应式模型SpringMVC的Servlet模型和它冲突两者不能共存。这种冲突通常在依赖传递中隐藏得很深不一定是Gateway模块自己显式引用了web依赖。解决在Gateway模块的pom中排除掉传递进来的web依赖同时排查公共依赖模块是否间接引入了spring-boot-starter-web。注意spring-boot-starter-webflux和spring-boot-starter-web也不能同时出现哪怕是不同的传递路径也不行。排除方式如下dependency groupIdcom.example/groupId artifactIdcommon-utils/artifactId version1.0.0/version exclusions exclusion groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /exclusion /exclusions /dependency排除后重新构建启动恢复正常。这也提醒一件事微服务工程里的公共模块依赖要克制一些看似通用的web工具类不要都往上堆最后所有服务都背上一个SpringMVC的包袱。5.4 MyBatis的Mapper接口和XML不匹配现象启动时顺利通过一调用数据访问层接口就抛Invalid bound statement (not found)异常XML里的SQL执行不了。原因mapper-locations配置的路径和实际XML存放位置不一致或者XML文件的namespace和Mapper接口全限定名对不上。还有一种情况是接口方法名和XML里的id不一致MyBatis默认按方法名去找对应的SQL语句找不到就抛这个异常。解决检查application.yml里mybatis.mapper-locations的路径我用的classpath:mapper/*.xml要求XML文件在resources/mapper目录下且打包后必须出现在classes里。如果调用了多次mvn package后XML被过滤掉可以在pom里加build-helper插件强制复制XML资源或者把resources配置里加上**/*.xml的include。最靠谱的排查方式是用IDE查看构建产物目录确认XML确实存在、路径正确、namespace和接口全限定名逐字一致。这类问题不要猜直接看编译产物最快。5.5 Nacos控制台访问慢或登录超时现象Nacos控制台首次打开要转很久甚至登录后页面上有些菜单刷新很慢操作日志里能看到大量超时重试。原因Nacos控制台页面和前后端资源是打包在一个应用里的打开控制台时会加载大量静态资源如果是远程服务器网络延迟会被放大。另外Nacos控制台默认从${nacos.home}下加载资源遇到权限问题或者磁盘IO慢的情况页面资源加载会更慢。更隐蔽的一个可能Nacos所在的机器内存不足GC频繁导致控制台响应变慢。解决单机部署注意给Nacos分配合理的内存修改bin/startup.sh里的JAVA_OPT参数把-Xms和-Xmx设置成512m或1g不要让它和业务服务抢内存。资源加载慢的部分可以通过Nginx反向代理加CDN缓存来优化本地调试则直接用IP访问即可。JAVA_OPT${JAVA_OPT} -Xms512m -Xmx512m -Xmn256m改完重启Nacos控制台响应速度会有明显改善。6. 验证一套微服务链路跑通从启动到调用的检查清单环境搭建完不能只看服务是否注册成功就认为万事大吉要模拟一次完整的用户请求链路来验证网关、鉴权、注册中心、下游服务、数据库这五层是否真正打通。按照下面的顺序逐层验证任何一层有问题都能精准定位。第一步先确认基础设施层的状态。Nacos的8848和9848端口都在监听MySQL的nacos库表结构完整。第二步按顺序启动三个服务先启动Nacos再启动order-service和user-service最后启动gateway-serviceGateway要等所有下游服务注册完成后再启动避免路由初始化时找不到后端实例。第三步调注册中心接口验证。访问http://IP:8848/nacos/v1/ns/service/list?pageNo1pageSize10serviceName看返回的JSON里是否包含order-service和user-service两个服务名每个服务名下有不同的实例数量对应运行中的节点数。第四步用curl模拟完整请求链路。先调登录接口拿Token再带Token调业务接口# 1. 登录获取token curl -X POST http://localhost:9000/api/user/login \ -H Content-Type: application/json \ -d {username:admin,password:123456} # 2. 带token访问订单接口 curl -X GET http://localhost:9000/api/order/list \ -H Authorization: 上一步返回的token字符串如果第一步返回了合法的Token第二步返回了订单数据说明网关路由、鉴权过滤器、服务发现、下游Controller、MyBatis数据访问这一整条链路都通顺。如果第二步返回401问题在鉴权过滤器返回404问题在网关路由或StripPrefix配置返回500问题一般在下游服务或数据库连接。第五步验证配置动态刷新。在Nacos控制台改一下order-service.yaml里的某个参数比如把order.timeout从3000改成5000观察应用日志是否出现Refresh keys changed之类的刷新日志再调一次接口确认新值生效。动态刷新是微服务架构里非常实用但又容易出问题的一环必须专门验证不然上线后改配置靠重启等于放弃了这个特性。第六步验证高可用性。手动杀掉一个order-service实例网关应该自动不再往这个实例转发流量调用仍然成功再重启这个实例等待约30秒注册完成调用自动恢复。这一步验证了服务注册发现的自动感知能力。从那以后我每次新部署一套微服务环境都会强制自己完整走一遍这六步检查哪怕只是改了个数据库密码也要把链路重新跑一次。之前吃过不少亏——有一次上线前只验证了单个服务能启动、以为没问题结果真正联调时发现网关的跨域配置被另一个模块的依赖覆盖掉前端全部请求失败只能紧急回滚。从那以后我把验证清单放在项目里任何人接手部署照着跑一遍就知道环境是否健康。希望这套从零搭建的记录能帮到你少走点弯路。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询