ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Sentinel Dashboard 集成 Nacos 实现网关限流规则持久化方案

Sentinel Dashboard 集成 Nacos 实现网关限流规则持久化方案 简介面向微服务治理与网关限流场景本压缩包提供Sentinel Dashboard 1.8.6与Nacos集成并对接Gateway限流的实现资源适合负责服务降级、流控规则持久化或网关流量治理的开发者参考。包内共568个文件包含Java源码与编译后的class文件、控制台前端所需的js/html/css静态资源以及xml/properties等配置整体约26.25MB目录结构便于定位。通过修改resources/application.properties中的Nacos地址和账号信息即可让Sentinel从Nacos拉取动态流控规则进而对Gateway进行统一限流这种集成方式可有效简化微服务架构下的规则管理与运维成本。压缩包中涉及GatewayFlowRuleController、ParamFlowRuleController等关键类有助于理解网关流控规则的动态配置与API维度控制。目前已有194人学习下载适合具有微服务基础、希望快速落地Sentinel与Nacos整合方案的工程师。 先说项目结论这套方案做完之后Sentinel Dashboard 1.8.6 不再依赖本地内存存规则所有限流规则统一落到 Nacos重启不丢Gateway 侧的流控规则也走同一套 Nacos 配置下发改规则不用再手工登录每台机器逐个改。整个过程踩了挺多坑尤其是网关规则持久化这一块网上资料普遍只讲“普通流控规则对接 Nacos”没几个人说清楚 gateway 网关流控规则也要单独改造。这篇就把完整方案和源码改动点梳理出来给后面要做的人省点时间。1. 先想清楚为什么要做这套集成方案1.1 Sentinel 默认行为与生产需求的矛盾Sentinel Dashboard 默认情况下所有规则都保存在 JVM 内存里。开发环境玩玩没问题但在生产环境就是灾难Dashboard 一重启配置的全没了客户端规则存储在本地内存Dashboard 推送一次也就生效一次客户端一重启又回到初始状态。更难受的是微服务网关作为流量的第一道入口限流规则往往是最高优先级的一旦规则丢失整个服务的保护瞬间失效。所以生产环境必须引入一个配置中心来承载规则持久化Nacos 是 Spring Cloud Alibaba 体系里最顺手的选型本身和服务发现共用一套基础设施不用额外维护 ZooKeeper 或 Apollo成本最低。1.2 集成方案的架构链路这套集成的完整链路是Sentinel Dashboard 作为规则管理入口配置规则后通过 Nacos Open API 把规则写入 Nacos 配置中心Gateway 网关通过 Sentinel 客户端监听 Nacos 配置变更动态更新本地规则缓存并在流量到达时执行流控判断。规则不再依赖 Dashboard 在线推送而是靠配置中心主动推送或客户端长轮询拉取解耦了控制台和客户端。普通微服务接入 Sentinel 限流官方文档写得很清楚真正麻烦的是 Gateway 部分。Gateway 基于 WebFlux 响应式模型不能直接使用注解方式的SentinelResource必须依赖专用的sentinel-spring-cloud-gateway-adapter适配器并且需要在 Sentinel Dashboard 的“网关流控规则”菜单里配置。这里有个容易忽略的点改完普通流控规则的持久化之后网关流控规则和 API 分组仍然还是纯内存模式两条链路要分开改造后面我会详细说明。1.3 版本选型与兼容性说明版本选择上我用了 Sentinel 1.8.6 搭配 Nacos 2.x2.2.3 实测没问题。1.8.6 是 1.8 系列的后期版本修复了不少网关适配和规则推送的 bug同时保持了和 Spring Cloud Alibaba 2021.x 的兼容性。Nacos 版本建议选 2.x不要用 1.x。原因很简单Sentinel Dashboard 通过 Open API 推送配置时Nacos 1.x 和 2.x 的接口路径虽然兼容但 2.x 的配置长轮询和推送性能更好且后续升级不折腾。Gateway 端如果本身用的 Hoxton.SR12 或 2021.0.x 版本Spring Cloud Alibaba 对应的适配版本是 2021.0.4.0 左右这个组合比较成熟网上资料和踩坑案例都多出了问题也好排查。2. 环境准备与 Nacos 接入2.1 基础环境清单开始动手前建议先确认环境避免后面折腾半天发现基础版本不对组件版本建议用途JDK1.8Dashboard 编译和运行Maven3.6编译 Sentinel Dashboard 源码Nacos Server2.2.3配置中心和服务发现Spring Cloud Alibaba2021.0.4.0Gateway 接入 Sentinel 的依赖管理Sentinel1.8.6客户端依赖及 DashboardNacos 安装没有什么特殊的下载解压后直接执行bin/startup.sh -m standaloneWindows 是startup.cmd -m standalone启动单机版。需要注意 Nacos 2.x 默认启动后除了 8848 端口还会占用 9848 和 9849 两个 gRPC 端口网关所在服务器的防火墙必须放通我之前就在这个上面栽过跟头客户端日志一直报连接超时排查半天才发现是 9848 被防火墙挡了。2.2 Nacos 命名空间与配置规划如果只是单机 Demo用默认 public 命名空间就行。但稍微有点规模的项目我建议给 Sentinel 单独建一个命名空间比如叫sentinel不要和业务配置混在一起。原因有两个规则配置变更频率高独立命名空间可以针对性地做权限控制后续要清理或者导数据命名空间隔离方便很多。在 Nacos 控制台创建命名空间后会生成一个命名空间 ID一个长字符串记下来后面 Dashboard 改造和客户端接入都要用到。配置规划的总体思路是每种规则类型对应一个独立的 Data IDGateway 端和 Dashboard 端约定一致例如sentinel-flow-rules普通流控规则sentinel-gateway-flow-rules网关流控规则sentinel-api-definitionsAPI 分组Group 统一用SENTINEL_GROUP。这套命名规范虽然可以自定义但建议保持这样的语义化命名减少后续维护成本。2.3 Dashboard 启动配置调整在改造源码之前先用官方 release 包把 Dashboard 跑起来确认基础环境没问题再动源码。直接下载sentinel-dashboard-1.8.6.jar启动命令加上端口和鉴权配置java -Dserver.port8080 \ -Dcsp.sentinel.dashboard.serverlocalhost:8080 \ -Dproject.namesentinel-dashboard \ -Dsentinel.dashboard.auth.usernamesentinel \ -Dsentinel.dashboard.auth.passwordsentinel \ -jar sentinel-dashboard-1.8.6.jar这一步确认 Dashboard 页面能正常登录、应用列表能正常展示后再开始源码改造。直接改源码经常导致用户分不清“环境问题”还是“代码问题”先用原版跑通一遍后面排错压力小很多。3. Dashboard 源码改造让规则落进 Nacos3.1 改造思路与关键类从 GitHub 拉取sentinel-1.8.6版本的源码只保留sentinel-dashboard模块导入 IDE。改造的核心逻辑不复杂把原先操作内存规则的 Service 实现替换为操作 Nacos 配置的实现。但实际涉及的类比较多关键是别改错位置。Dashboard 里与规则相关的核心 Controller 有三个注意类名前缀FlowControllerV1普通流控规则入口GatewayFlowRuleController网关流控规则入口ApiControllerAPI 分组入口这三个 Controller 目前都是直接调用FlowRuleService、GatewayFlowRuleService等内存实现。改造时不要动 Controller 的接口路径只替换它们依赖的 Service 实现。因为前端页面是写死调用/v1/flow/rule/、/v1/gateway/flow/这些路径的路径一变前端就废了。3.2 普通流控规则持久化改造在sentinel-dashboard源码的rule包下新建nacos子包添加三个核心类。首先是NacosConfigUtil统一管理 Data ID 和 Grouppublic final class NacosConfigUtil { public static final String GROUP_ID SENTINEL_GROUP; public static final String FLOW_DATA_ID_POSTFIX -flow-rules; public static final String GATEWAY_FLOW_DATA_ID_POSTFIX -gateway-flow-rules; public static final String API_DATA_ID_POSTFIX -api-definitions; private static String serverAddr; private static String namespace; public static String getServerAddr() { return serverAddr; } // 提供静态初始化方法在配置类中注入 public static void init(String serverAddr, String namespace) { NacosConfigUtil.serverAddr serverAddr; NacosConfigUtil.namespace namespace; } public static ConfigService getConfigService() throws Exception { Properties properties new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr); properties.put(PropertyKeyConst.NAMESPACE, namespace); return NacosFactory.createConfigService(properties); } }然后是FlowRuleNacosProvider负责从 Nacos 读取规则FlowRuleNacosPublisher负责把规则推送到 Nacos。发布方法的核心代码如下Component(flowRuleNacosPublisher) public class FlowRuleNacosPublisher implements DynamicRulePublisherListFlowRuleEntity { Override public void publish(String app, ListFlowRuleEntity rules) throws Exception { if (rules null) { return; } String dataId app NacosConfigUtil.FLOW_DATA_ID_POSTFIX; String content JSON.toJSONString(rules); ConfigService configService NacosConfigUtil.getConfigService(); boolean published configService.publishConfig(dataId, NacosConfigUtil.GROUP_ID, content); if (!published) { throw new RuntimeException(publish flow rule to nacos failed); } } }关键点在于Data ID 必须以应用名app为前缀。因为一个 Dashboard 管理多个应用每个应用的规则必须独立存储否则应用 A 的规则会被应用 B 覆盖。接着修改FlowControllerV1把Autowired private FlowRuleService ruleService改成注入DynamicRuleProvider和DynamicRulePublisher实现类分别指向FlowRuleNacosProvider和FlowRuleNacosPublisher。核心就是把publishRules方法里的逻辑替换成private void publishRules(String app, String ip, Integer port) throws Exception { ListFlowRuleEntity rules repository.findAllByApp(app); publisher.publish(app, rules); }3.3 网关流控与 API 分组持久化这里是最多人踩坑的地方。普通流控规则改完后返回 Dashboard 页面在“网关流控规则”里配置规则发现重启后还是丢。原因很简单GatewayFlowRuleController和ApiController走的是另一套 Service和FlowControllerV1互不相干需要单独改一遍。改造方式和普通流控规则一致只是 Data ID 后缀不同。网关流控规则发布到app -gateway-flow-rulesAPI 分组发布到app -api-definitions。这类规则的实体是GatewayFlowRuleEntity和ApiDefinitionEntity转换 JSON 即可没有额外的兼容问题。需要提醒一个细节网关流控规则的resource字段存储的值是路由 ID比如user-route或者 API 分组名称这个对应关系在配置网关规则时必须保持一致否则规则不生效。体现在代码上就是 Dashboard 端和 Gateway 端对网关规则的解析逻辑要一致。3.4 重新构建并验证配置推送在application.properties里追加 Nacos 配置sentinel.nacos.serverAddr127.0.0.1:8848 sentinel.nacos.namespaceyour-namespace-id然后在某个配置类里调用NacosConfigUtil.init(serverAddr, namespace)完成初始化。之后重新构建mvn clean package -DskipTests启动新的 Dashboard进入普通流控规则页面添加一条规则然后到 Nacos 控制台的配置列表里看一眼如果能看到对应的 Data ID 和 JSON 内容说明普通规则持久化改造完成。再切到网关流控规则页面添加一条路由维度规则同样去 Nacos 验证。两个都通了改造部分就算完成。4. Gateway 接入 Sentinel 并实现限流4.1 引入依赖与版本规避Gateway 端引入依赖是第一个坑点。Spring Cloud Alibaba 提供的spring-cloud-starter-alibaba-sentinel会自动引入sentinel-transport-simple-http和sentinel-core但不会自动引入网关适配器需要手动追加dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-sentinel/artifactId /dependency dependency groupIdcom.alibaba.csp/groupId artifactIdsentinel-spring-cloud-gateway-adapter/artifactId version1.8.6/version /dependency注意版本不能随意变。sentinel-spring-cloud-gateway-adapter虽然和sentinel-core在同一个仓库发布但版本号必须保持 1.8.6否则可能出现类结构不匹配的兼容问题。另外要排除掉spring-cloud-starter-alibaba-sentinel自带的旧版本 sentinel-transport避免和适配器里的 transport 冲突。4.2 网关拦截与统一兜底返回接入适配器后默认的限流返回格式是Blocked by Sentinel: FlowException这个返回格式对前端很不友好。生产环境通常需要自定义返回 JSON 结构。做法是注册一个BlockRequestHandlerConfiguration public class SentinelGatewayConfig { Bean public BlockRequestHandler blockRequestHandler() { return (exchange, throwable) - { MapString, Object result new HashMap(); result.put(code, 429); result.put(message, 请求过于频繁请稍后再试); result.put(success, false); return ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(result)); }; } PostConstruct public void init() { GatewayCallbackManager.setBlockHandler(blockRequestHandler()); } }然后还要给网关配置注册流控规则和 API 分组。启动时如果规则已经存在于 Nacos可以在ApplicationRunner里主动加载一次依赖 Nacos 的长轮询自动感知后续变更。这里如果配了spring.cloud.sentinel.transport.dashboard127.0.0.1:8080Gateway 启动后会自动注册到 Dashboard 的应用列表中然后在 Dashboard 上就能直接对网关实例下发规则。4.3 规则配置与阈值评估网关流控规则的配置维度有两类路由维度和 API 分组维度。路由维度直接对应 Route ID比如配置了一个user-route指向用户服务那么在“网关流控规则”页面新增规则时资源名填user-route。阈值的选择要结合压测数据。比如用户服务单机能稳定支撑 500 QPS网关层限流阈值可以设置为 400给下游服务留 20% 的余量。阈值类型建议直接用 QPS并发线程数在网关层意义不大因为网关节点的业务线程模型和普通服务不同统计出来的线程数容易被连接数干扰。另外一个实用技巧是在 Dashboard 上看到“网关流控规则”下发的值会推送到 Nacos但第一次配置后Gateway 可能要等几秒才能感知到。感知延迟来自 Nacos 客户端的长轮询时间默认是 30 秒可以通过spring.cloud.sentinel.nacos.polling-interval或 Nacos 客户端配置缩短我在压测环境通常配置 3 秒。5. 实测踩坑记录5.1 配置下发成功但规则不生效现象Dashboard 上配置了规则Nacos 里也能看到配置数据但 Gateway 就是不限流。这个问题的排查思路要按链路来首先确认 Gateway 的spring.cloud.sentinel.transport.dashboard配的对不对Dashboard 应用列表里有没有出现这个网关实例其次确认网关适配器是否被正确加载启动日志里找sentinel-spring-cloud-gateway-adapter相关的初始化记录最后确认规则配置的 resource 是不是路由 ID 而不是路径。我遇到最多的情况是资源名填错把/api/user/**的路径当成资源名填进去了网关规则根本没匹配上。5.2 网关返回阻塞但格式不对现象限流生效了但返回的是纯文本Blocked by Sentinel不是自定义 JSON。这个问题基本可以断定是GatewayCallbackManager.setBlockHandler没有生效。原因通常是某个自定义Filter或ExceptionHandler优先级更高或者初始化顺序问题。解决方法是在PostConstruct里设置而不要在别的组件构造方法里设置。另外注意 Spring Cloud Gateway 的兜底异常处理也可能吃掉ResponseStatusException需要检查全局异常处理器里是否对 429 状态做了特殊拦截。5.3 Dashboard 和客户端版本不一致问题现象Gateway 能注册到 Dashboard但规则一律推送失败日志里各种ClassNotFoundException、NoSuchMethodError。这个问题常见于多个服务同时存在有些服务用的是旧版本 Sentinel 客户端而 Dashboard 已经升级到 1.8.6。Sentinel 控制台和客户端的通信协议不是完全向前兼容的尤其是新版本控制台引入的一些接口字段旧客户端解析不了。建议整个微服务集群统一升级到同一个小版本至少保证网关服务、普通业务服务、控制台三者的大版本一致。如果实在无法统一至少要保证网关服务和控制台一致因为网关限流是核心链路。5.4 规则存储重复导致规则越积越多改造完成后跑了一段时间发现 Nacos 里的规则配置越积越多每次推送都是全量覆盖但有大量重复的旧数据。原因在于改造时发布的逻辑是publish全量规则但删除规则时没有同步处理。我的建议是所有规则的变更增删改都统一走publish全量覆盖不要图省事做增量合并。全量覆盖虽然看起来效率低但规则数量一般不超过千条Nacos 处理这点量完全没有压力换来的是数据一致性。6. 最后分享一点个人经验整套方案做完之后最值得的其实不是“限流本身”而是规则管理方式的变化。以前每次改限流阈值都要通过控制台点到具体机器上操作现在直接在 Nacos 的配置中心里改一行 JSON 就能热更新运维同学也不用再担心 Dashboard 重启弄丢规则了。如果你接下来要在生产环境落地我建议再补两件事一是给 Nacos 里的规则配置加一个版本号或变更备注方便追踪谁在什么时候改过限流阈值二是做一个简单的巡检脚本定时检查 Nacos 里每个应用的规则配置是否存在防止有人误删导致服务直接裸奔。另外一个比较实用的技巧是在 Gateway 的日志里主动输出限流触发的日志配合GatewayCallbackManager.setBlockHandler在返回响应前记录exchange.getRequest().getURI()和来源 IP这样一旦线上出现误限流可以通过日志快速定位是哪个路由、哪个来源 IP 被拦了。有了这套日志后续无论调阈值还是排查问题都心里有底。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进