☰
微服务基座搭建实战:Nacos注册配置双中心、Sentinel限流熔断与Seata分布式事务落地
2026/10/18 16:19:11 网站建设 项目流程

简介:采用前后端分离模式,基于若依框架的微服务权限管理系统完整代码,面向熟悉Spring Boot但需要落地微服务治理的Java后端与前端开发者。项目以Nacos作为注册与配置中心,整合Redis认证、Sentinel限流熔断与Seata分布式事务,并提供Vue3 + Element Plus + Vite版本前端,适合学习企业级微服务架构与权限模型。资源包共677个文件,约132.24MB,主体为261个Java源码与89个Vue页面组件,另含75个JS脚本、46个XML配置、7个SQL脚本及Dockerfile、yml等部署文件,可支撑从后端逻辑到前端页面再到容器化部署的全链路阅读。辅助脚本如bat、sh可快速启动gateway、auth、system等模块,便于本地运行调试。目前已有3578人学习下载,对需要借鉴若依微服务版设计思路、改造权限与网关模块的开发者有直接参考价值。

1. 若依SpringCloud微服务脚手架:一个字,先把基座调通再谈业务

微服务改造这件事,绝大多数团队卡住的不是业务代码,而是基座没打通:注册中心、配置中心、限流熔断、分布式事务、前端权限,每一个单独拎出来都有教程,一旦凑到一起,版本冲突和隐性配置能把人折磨到怀疑人生。这套基于若依SpringCloud的脚手架,把Nacos(注册+配置)、Sentinel(限流+熔断)、Seata(分布式事务)和Vue3前端预先调通,业务开发只需要关注自己的模块。适合两类人:一是从单体转微服务、想找个能直接跑的框架起步的团队;二是想看清这几个组件在真实项目里怎么协作的开发者。本文不聊理论,只讲落地。

2. Nacos注册+配置双中心:服务发现与动态刷新怎么落地

2.1 为什么选 Nacos 而不是 Eureka + Config

微服务基座里,注册中心和配置中心通常是两个独立组件:Eureka负责服务发现,Spring Cloud Config负责配置管理。这套脚手架选Nacos,核心原因是它把两件事合并了,省掉一套独立配置中心的部署和运维成本。另一个现实因素是Nacos原生支持服务端的配置持久化、命名空间隔离和权限控制,这些在Eureka + Config组合里要么没有,要么得自己用数据库或Git仓库去补。

Nacos的服务发现走的是AP模型下的注册表机制,客户端定时拉取服务实例列表,同时通过心跳维持实例健康状态。配置中心走的是服务端推送加客户端长轮询的组合,配置变更后客户端能在几秒内感知到。这套机制在中小团队规模下非常够用,而且部署简单,standalone模式一条命令就能起来,不需要额外依赖数据库。

2.2 接入Nacos:bootstrap.yml 与 namespace 隔离

接入Nacos的第一步是引入依赖。每个微服务模块的pom里需要加上注册中心和配置中心的starter,这是这套脚手架的基础依赖,版本跟随SpringCloud Alibaba的BOM统一管理,不需要手写版本号。

<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>

依赖引入后,配置写在bootstrap.yml里。这里有个容易踩的坑:Nacos配置中心的地址必须放在bootstrap阶段加载,如果写在application.yml里,服务启动时配置中心还没初始化,动态刷新就不会生效。

spring: application: name: system-service cloud: nacos: server-addr: 127.0.0.1:8848 username: nacos password: nacos discovery: namespace: dev group: DEFAULT_GROUP config: namespace: dev group: DEFAULT_GROUP file-extension: yaml shared-configs: ->@RefreshScope @RestController @RequestMapping("/api/gateway") public class GatewayConfigController { @Value("${gateway.routes.timeout:3000}") private Integer timeout; @GetMapping("/timeout") public Integer getTimeout() { return timeout; } }

注意@RefreshScope只对加了它的Bean生效,不会自动刷新所有配置项。比如数据源连接池、线程池这类在启动时创建且不销毁重建的Bean,修改配置后不会自动重建,需要配合@ConfigurationProperties或者专门的刷新逻辑。这套脚手架里公共配置抽成了shared-configs,所有服务共享同一个common.yaml,改一处全部刷新,适合放日志级别、公共开关这类配置。每个服务的独有配置则放在自己的dataId里,dataId命名规则是${spring.application.name}.${file-extension},如果文件后缀配成yaml,dataId就是system-service.yaml。

3. Sentinel流量治理:限流规则与熔断兜底配置

3.1 资源定义与控制台接入

Sentinel的核心概念是「资源」,它可以是一个方法、一个接口、一段代码块。这套脚手架里网关层和服务层都接入了Sentinel,网关负责入口流量控制,服务层负责接口级别保护。接入Sentinel需要引入依赖并配置控制台地址,控制台用来查看实时流量和推送规则。

<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>

控制台地址配置在application.yml里。要注意Sentinel客户端默认是懒加载,服务启动后如果不访问任何接口,控制台是看不到服务节点的,先随意请求一次接口再刷新控制台就能看到。

spring: cloud: sentinel: transport: dashboard: 127.0.0.1:8858 port: 8719 eager: true

参数说明:dashboard是控制台地址,port是客户端与控制台通信的本地端口,如果被占用会自动+1,为避免端口漂移问题建议固定。eager设为true可以让客户端启动时立即注册到控制台,不用等第一次请求。

3.2 限流规则:QPS阈值与流控效果

限流规则定义在控制台上配置后默认存储在内存里,这一点后面避坑章节会细说。先看规则本身。Sentinel支持QPS限流和线程数限流两种模式,实际项目里QPS限流用得最多。

[ { "resource": "GET:/api/system/user/list", "limitApp": "default", "grade": 1, "count": 100, "strategy": 0, "controlBehavior": 0 } ]

参数说明:grade=1表示按QPS限流,count=100表示每秒最多100次请求;strategy=0表示直接按资源本身限流,不涉及调用关系;controlBehavior=0是直接拒绝,1是Warm Up预热模式,2是排队等待。接口类限流我一般默认用0,预热适合秒杀这类冷启动场景,排队等待适合容忍延迟的异步场景。

代码层面也可以硬编码规则,适合规则不容易变化的场景,但生产环境建议用规则持久化,后面避坑章会给出接Nacos数据源的具体配置方式。

3.3 熔断降级:慢调用比例与兜底逻辑

熔断和限流是两回事。限流管的是流量大小,熔断管的是下游服务是否健康。这套脚手架里用的是慢调用比例熔断:当一段时间内接口调用超过指定耗时的比例达到阈值,熔断器打开,后续请求快速失败,不再等待下游超时。

[ { "resource": "POST:/api/order/create", "grade": 0, "count": 500, "timeWindow": 10, "minRequestAmount": 5, "slowRatioThreshold": 0.5 } ]

参数说明:grade=0表示按慢调用比例熔断,count=500是最大RT阈值(毫秒),slowRatioThreshold=0.5表示超过500ms的请求占比达到50%就触发熔断,timeWindow=10表示熔断后10秒内快速失败,minRequestAmount=5是触发熔断的最小请求数,避免流量太小时误判。

熔断触发后需要有兜底逻辑,这套脚手架里的做法是在被保护的方法上写降级函数。兜底方法必须与原方法签名一致,返回值类型也要一致,否则会启动报错。

@SentinelResource(value = "createOrder", fallback = "createOrderFallback") public Result createOrder(OrderDTO dto) { // 正常业务逻辑 } public Result createOrderFallback(OrderDTO dto, Throwable ex) { return Result.error("服务繁忙,请稍后重试"); }

注意fallback方法可以接收Throwable参数,这样能拿到熔断或异常的具体原因。我遇到过一个坑:fallback方法返回值类型和原方法不一致时,异常被吞掉,接口直接返回500空指针,排查了很久才定位到是降级函数签名问题。

4. Seata分布式事务:AT模式的回滚机制与落地配置

4.1 三种模式怎么选:AT、TCC、SAGA

Seata支持AT、TCC、SAGA三种事务模式。这套脚手架默认用AT模式,原因是侵入性最小:业务代码里只需加一个@GlobalTransactional注解,不需要像TCC那样写confirm和cancel两套方法。AT模式的原理是代理数据源,在业务SQL执行前记录数据快照到undo_log表,事务提交时根据undo_log的内容反向补偿。

三模式选型参考:

模式侵入性适用场景主要成本
AT低单体拆微服务,业务代码不想大改每库需建undo_log表,性能有一定损耗
TCC高对一致性要求极高,需要精细控制业务需写try/confirm/cancel三接口
SAGA中长事务,链路长且允许最终一致需维护状态机,补偿逻辑复杂

中小团队从单体重构上来的,无脑选AT,先把分布式事务跑通,后续业务确实复杂到需要精细控制再考虑TCC。

4.2 AT模式落地:undo_log表与全局事务注解

AT模式落地前,每个参与事务的数据库都要建undo_log表。这张表是Seata回滚的数据源,没建这张表,事务执行时不会报错,但回滚时会静默失败,数据就出现部分提交的情况,这是数据不一致最常见的隐患。

CREATE TABLE `undo_log` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `branch_id` BIGINT NOT NULL, `xid` VARCHAR(100) NOT NULL, `context` VARCHAR(128) NOT NULL, `rollback_info` LONGTEXT NOT NULL, `log_status` INT NOT NULL, `log_created` DATETIME NOT NULL, `log_modified` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) ) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;

建好表后,在业务入口方法上声明全局事务。这个方法需要是被外部调用的,不能是同类内部调用,否则Spring AOP代理失效,事务完全不生效。

@Service public class OrderServiceImpl implements OrderService { @GlobalTransactional(rollbackFor = Exception.class) public void createOrder(OrderDTO dto) { orderMapper.insert(dto); accountClient.decrease(dto.getAccountId(), dto.getAmount()); inventoryClient.decrease(dto.getProductId(), dto.getCount()); } }

代码说明:@GlobalTransactional是Seata的全局事务注解,rollbackFor=Exception.class表示任何异常都触发回滚。createOrder方法内部调用远程的扣减账户和扣减库存服务,如果第二步或第三步抛异常,前面已执行的本地SQL也会通过undo_log反向补偿回滚。

4.3 全局事务超时与回滚验证

Seata默认全局事务超时时间是60秒,如果业务链路里有慢SQL或远程调用超时超过这个值,事务会被强制回滚,接口还会返回一个不太容易看懂的异常。我一般会在application.yml里显式调大超时时间,但也不建议调太大,太长会导致全局锁持有时间过久,并发性能下降。

seata: enabled: true application-id: system-service tx-service-group: my_tx_group registry: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: public config: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: public service: vgroup-mapping: my_tx_group: default client: tm: default-global-transaction-timeout: 120000

部署完成后一定要做一次回滚验证:在createOrder方法第二步后面故意抛一个RuntimeException,然后查数据库,确认订单、账户、库存三张表的数据都没有变化。这个验证动作我在每次环境迁移后都会强制做一遍。

5. 常见问题排查:路由、鉴权、跨域与规则失效

5.1 现象:服务之间Feign调用报错,Nacos控制台却能看见实例

原因有两个:一是namespace不一致,服务A在dev命名空间,服务B在public命名空间,Feign查询时从当前服务所在命名空间查不到对方实例列表;二是Nacos 2.x版本客户端向服务端上报实例走的是gRPC端口9848,防火墙只放通了8848没放通9848,注册表数据能上报但实例列表拉取失败。

解决:检查所有服务的spring.cloud.nacos.discovery.namespace配置,确保指向同一个命名空间ID;同时放通Nacos服务器的8848和9848端口。这个坑在换服务器环境时特别常见,因为本机部署通常不涉及防火墙问题。

5.2 现象:Sentinel控制台配置的限流规则,服务重启后全部丢失

原因:Sentinel默认规则存储在客户端内存中,控制台推给客户端的规则不会持久化到任何存储介质,服务重启即失效。

解决:引入Sentinel的Nacos数据源扩展,让规则先从Nacos配置中心拉取,控制台的修改通过Nacos作为中间桥梁同步。

spring: cloud: sentinel: datasource: flow: nacos: server-addr: 127.0.0.1:8848 >service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) service.interceptors.response.use( response => response.data, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') window.location.href = '/login' } return Promise.reject(error) } )

代码说明:请求拦截器从localStorage读取token并写入请求头,响应拦截器统一处理401,token失效时清空缓存并跳回登录页。注意这里的token键名要和登录接口存储时保持一致,不一致的话请求拦截器永远拿不到token,这个低级错误排查起来很浪费时间。

5.5 现象:前端调用后端接口报CORS跨域,网关和前端都配置了跨域

原因:网关层和下游服务都配置了CORS响应头,浏览器收到两套Access-Control-Allow-Origin,或者网关拦截了预检请求后下游的CORS头没有透传,导致浏览器判定跨域失败。

解决:跨域只保留网关层处理,下游服务关闭CORS配置。网关的CORS配置需要显式允许预检请求,否则OPTIONS请求会被拦截无法继续。

spring: cloud: gateway: globalcors: cors-configurations: '[/**]': allowed-origin-patterns: - "http://localhost:8080" allowed-methods: - GET - POST - PUT - DELETE allowed-headers: "*" allow-credentials: true

参数说明:allowed-origin-patterns可以用通配符匹配前端地址,注意allow-credentials和allowed-origins不能同时使用通配符,否则浏览器会拒绝响应,这是Spring Cloud Gateway一个比较隐蔽的约束。

6. 进阶:本地联调顺序与Sentinel阈值调优经验

本地联调时,启动顺序有讲究。正确顺序是先基础设施后应用服务,具体如下表:

顺序组件启动方式关键检查点
1Nacossh startup.sh -m standalone访问8848控制台正常
2Seata Serversh seata-server.sh -p 80918091端口监听正常
3Redis / MySQL依本机环境确认连接配置可访问
4网关服务java -jar gateway-service.jar路由表加载日志无报错
5业务微服务java -jar system-service.jar控制台能看到注册实例
6Vue3前端npm run dev浏览器能正常登录

这套顺序我每次拉新环境都强制走一遍,跳过任何一步都可能出现“服务注册了但调用404”这种难以归因的怪问题。有次我图省事先启动了业务服务,结果Seata还没起来,全局事务注解全部报错,吓得以为代码有问题,排查半天才发现是启动顺序反了。

Sentinel阈值调优方面,我的经验是从小到大逐步加压,不要一上来就拍脑袋定1000 QPS。先用一个较低的阈值比如100跑压测,观察执行耗时曲线和控制台的实时流量,如果系统CPU和内存都很健康,再逐步上调,每次调完观察半小时。限流阈值没有普适公式,不同业务接口的RT和依赖开销差异很大,控制台实时监控比理论估算值可靠得多。

最后说一个实际教训:别在本地改Nacos的namespace来玩隔离,本地就用public命名空间,环境隔离交给dev、test、prod的环境配置文件去区分。我有个同事把服务注册到自定义命名空间后,又改回public,结果配置中心里的shared-configs没跟着迁,所有服务启动报错找不到配置,花了半天才排查出来。从那以后我每次改完命名空间配置,都强制核对一遍discovery和config的namespace是否一致。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询