多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Sentinel集群流控实战:从单机限流到全局QPS治理

Sentinel集群流控实战:从单机限流到全局QPS治理 先交代一个背景我一直维护着一个电商中台系统峰值流量基本都集中在秒杀和大促。前两年用Sentinel做单机限流上游的防护确实做起来了但每次大促一过复盘就会发现一个老问题同样一套流控规则在节点A上明明限到了在节点B上却还在持续放量最终打到下游数据库的流量远远超过了你配置的阈值。最开始我以为是规则没同步后来排查下来发现问题出在“单机限流”本身——这不是规则加载的问题而是单机维度的天然局限。后来我把Sentinel升级到集群流控方案用Token Server做跨节点协调总算把“按单机维度限流”变成“按整个集群维度限流”。这篇文章就把我当时踩过的坑、方案选型过程、以及最终的实操配置完整梳理一遍希望能给正在被同样问题困扰的团队一个可复用的思路。1. 单机限流到底败在哪里规则不一致的根因分析1.1 单机规则看似一样实际限流效果却天差地别单机限流的基础逻辑很简单每个节点独立加载规则、独立计数、独立触发限流。在Sentinel控制台上配置一个“QPS100”的规则实际效果是每个实例各自允许100 QPS而不是整个集群只允许100 QPS。如果一个服务部署了5个节点理论上集群总流量可以到500 QPS远远超出你预设的100 QPS。这里面的问题还不只是总量超标。流量在节点间的分配往往是不均匀的——网关层做负载均衡但长连接、hash路由、缓存击穿导致的瞬时热点都会让某个节点被倾斜命中。我见过一台机器扛了集群70%流量、其他机器吃灰的情况此时单机规则在热点节点上提前触发限流而空闲节点却没有任何流控压力这就导致两个结果热点节点频繁 rejected交易链路出现大量报错影响真实用户体验。规则阈值在控制台看起来是“全局统一”的但不同节点的实际限流水位完全不同规则执行效果不一致。这种“配置相同、行为不同”的问题单靠监控和人工调整阈值很难彻底解决因为流量分布本身是动态的。1.2 规则漂移和误配置加剧了单机限流的不确定性除了流量倾斜规则本身的管理也是个隐患。Sentinel规则可以推送到每个节点但如果在实际运维中用控制台逐个修改、或者通过不同批次发布配置非常容易出现某几个节点规则没有更新、而另外几个节点已经生效的情况。我早期遇到过线上某集群3个节点长时间运行旧规则新规则只推送到了另外2个节点结果流量一上来旧规则节点全部放行等于限流直接失效。这个问题本质上是因为规则下发和节点执行之间缺少一个强一致的协调中心。每个节点只对自己看到的规则副本负责谁也不管别人是否一致。集群流控从设计上就要解决这个协调问题把规则装载和QPS统计统一到一个或少数几个权威节点上其他节点向权威节点请求配额规则的一致性由协调逻辑保证。2. 集群流控的工作原理与整体架构设计2.1 跨节点协调的核心Token Server 与 Token Client 的角色分工Sentinel集群流控的基本思想是把“限流统计”抽离出业务节点变成一个独立的分布式协调过程。引入两个角色Token Server是流控规则的权威节点负责全局QPS统计、令牌分配和流控判断。Token Client是嵌入业务应用中的组件每当有请求进来时Client会向Server请求一个tokenServer根据全局计数决定是否放行。放行后Client才继续处理业务逻辑。一旦Server返回拒绝Client立即触发限流异常逻辑抛出BlockException或走降级逻辑。这个模式下限流判断能力从“分布式”收拢到“集中式”而真正的流量请求还是分散在业务节点上处理的。从效果上看集群整体只会有一份准确的计数器节点之间的规则执行不会互相打架。有两个细节值得注意。第一Client与Server之间每来一个请求就同步一次肯定是不可接受的所以Sentinel引入了Token Bucket预取机制Client会周期性向Server拉取一批token本地消费完后再次申请这样既能保证全局统计相对准确又大幅降低了网络交互频率。第二当Client与Server之间的通信出现异常时Sentinel有fallback机制可以退化为本地限流避免一损俱损。2.2 两种集群流控模式独立部署与内嵌模式如何选型集群流控提供了两种部署方式选错会让你后续运维非常被动。独立模式Token Server作为独立进程适合多团队共用、流量规模较大、规则重要性高的场景。独立进程的好处是故障域隔离Server异常不会直接影响业务JVM内存和CPU坏处是额外引入一个运维节点需要单体部署和监控。内嵌模式Token Server内嵌在某个业务应用实例中实现成本低不需要额外部署进程适合集群规模小、测试阶段或对基础设施运维能力有限的团队。坏处是Server角色占用了业务应用的资源存在隐患——该实例GC停顿或重启时会影响整个集群的流控。我个人在生产环境更倾向于独立模式具体原因后面讲部署时会再展开。2.3 集群限流与单机限流的互补关系需要说明的是集群流控并不是要完全替代单机限流两者是配合关系。Sentinel提供了“集群阈值 单机兜底阈值”的配置组合集群限制的是整个集群的实时总QPS单机兜底限制的是任何单一实例不能承担的峰值QPS。这是因为集群流控的通信、计算和预取都存在细微延迟完全依赖集群协调时热点节点可能在Server响应返回前就已经积压了大量请求兜底阈值可以在本地快速拦截防止极端情况击垮JVM。从控制效果看这个组合非常实用全局维度由集群流控管单机维度由本地规则管云防火墙和网关层再兜一层整体链路就有多层防护了。3. 集群流控的实操配置与部署要点3.1 Token Server独立节点的初始化配置我最终采用独立模式部署Token Server。首先需要引入Sentinel集群流控依赖在pom中配置dependency groupIdcom.alibaba.csp/groupId artifactIdsentinel-cluster-server-envoy/artifactId version1.8.6/version /dependency dependency groupIdcom.alibaba.csp/groupId artifactIdsentinel-transport-simple-http/artifactId version1.8.6/version /dependency注意sentinel-cluster-server-envoy这个artifactId在不同版本下可能略有差异有些版本是sentinel-cluster-server-default。1.8.x较稳定建议锁定版本号。如果项目已经引入sentinel-core正常不会冲突但要注意transport包版本一致否则会出现SPI加载异常。初始化Token Server的Java代码大致如下ClusterServerConfigManager.loadGlobalConfig( new ServerGlobalConfig() .setPort(18730) // 服务端口 .setRequestTimeout(3000) // token请求超时 .setMaxAllowedQpsRatio(0.9) // 单机最大容忍QPS比例 ); // 加载命名空间下的流控规则 ClusterServerConfigManager.loadServerNamespaceSet( Collections.singletonList(default) );这里的setMaxAllowedQpsRatio参数容易被人忽略它决定了当集群流控判定的总容量在某段时间内超出阈值时单个节点最多可以承担集群阈值的比例。比如集群阈值是1000 QPSratio设为0.9则单节点最多可以承担900 QPS超过的部分直接拒绝。这个参数主要作用是防止某个节点流量倾斜后在Server还没来得及响应前打垮自身。3.2 业务应用内嵌Token Client配置业务应用作为Token Client配置要比Server简单一些ClusterClientConfig clientConfig new ClusterClientConfig(); clientConfig.setServerHost(token-server.internal.host); clientConfig.setServerPort(18730); clientConfig.setRequestTimeout(3000); ClusterTransportClientManager.getInstance().applyConfig(clientConfig);这里有一个关键点Client配置的serverHost不要用localhost一定用独立的DNS域名或VIP。因为生产环境Token Server实例重启后IP可能发生变化如果Client写死了IP重启后所有Client都会连不上。另外Client与Server之间建议走内网不要走公网或跨机房网络。请求超时时间建议设置在1000~3000ms之间太短会导致网络抖动时频繁触发fallback太长会影响请求RT。我开始设了500ms结果大促期间网络一抖动大量请求走了本地限流等于集群流控名存实亡。3.3 集群流控规则如何配置生效规则配置方式有两种控制台操作和代码加载。我推荐代码加载因为可版本化管理、上线可灰度。ListClusterFlowRule rules new ArrayList(); ClusterFlowRule rule new ClusterFlowRule(); rule.setResource(order_create); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setThresholdCount(500D); // 集群总分流阈值 rule.setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL); rule.setCluster(true); rule.setClusterMode(true); rules.add(rule); ClusterRuleManager.loadRules(rules);注意setThresholdType有两个取值FLOW_THRESHOLD_GLOBAL表示配置的是集群总阈值FLOW_THRESHOLD_OVERALL表示配置的是单机平均值阈值集群总阈值 / 节点数。绝大多数场景用GLOBAL因为你的预期就是“整个集群只放这么多量”。OVERALL模式适合你想让每个节点平均承担固定QPS的场景但这种场景下用单机限流不是更直接吗所以OVERALL实际用得不多。生产环境规则通过Nacos或Apollo配置中心下发比较合适。规则变更时ClusterRuleManager.loadRules会热更新不用重启应用。如果走控制台需要注意版本问题——控制台保存的是内存态规则进程重启后规则会丢失除非配置了数据源持久化。4. 集群流控的运维实践与常见问题排障4.1 从监控指标判断集群流控是否正常工作配置完成后最大的疑问是它真的在按全局阈值工作吗我一般看三个指标来判断。第一Client端会暴露Sentinel的metric重点关注被Block的QPS曲线。如果集群阈值是500而各节点Block总和大约等于实际总QPS减去500说明集群流控在起作用。第二看Token Server的Sentinel监控面板其中有一个“集群流控通过QPS”的指标这个值应该和业务侧统计的总QPS趋于一致。第三看Client端日志。启动后日志中会输出“Cluster client init success”之类的信息没有该日志基本可以断定Client没有连接到Server。我踩过一次坑某次上线后Block曲线完全不规律查了半天发现是因为Token Server在另外一套环境上业务配置连到了测试服务器的端口虽然网络通但不报错集群流控等于透明了。这种情况一定要在监控面板上核对Server地址和Client的namespace是否匹配。4.2 连接中断与fallback行为的调优Token Client与Server的连接不是永远的网络闪断、Server重启都会导致连接中断。Sentinel自动处理了重连但中断期间流量如何处理完全取决于你的fallback逻辑。默认情况下Client侧在请求token超时或通信异常后会降级为本地限流——这意味着集群维度保护暂时失效单机规则接管。这个行为逻辑是对的但我建议在降级时增加告警。因为你很可能半天之后才发现集群流控一直在走fallback而期间已经发生了多次流量击穿。调整fallback行为可以通过自定义ClusterClientLeaves回调或修改transport配置来实现。我用的是回调方式在客户端检测到server不可达时记录日志并发告警ClusterClientTransportClientConfig config new ClusterClientTransportClientConfig(); config.setLifecycleHandler(new LifecycleEventHandler() { Override public void handleEvent(ClientLifecycleEvent event) { if (event.getType() ClientLifecycleEventType.DISCONNECTED) { // 发送告警事件通知值班同学检查Token Server } } });当然这需要你继承官方API来做不同版本类名略有差异但思路是一致的把“Client掉线”当作一等故障来处理而不是容忍静默降级。4.3 集群流控规则与配置中心的一致性保障规则通过Nacos推送到Token Server后理论上不会出现“单机规则不一致”的问题因为现在规则只加载在一台独立Server上。但在实际运维中我发现另一个坑如果规则通过控制台修改过控制台的配置会覆盖Nacos推送配置吗答案是不会两者其实是两套规则存储。控制台修改的是“控制台内存态规则”Nacos推送的是“数据源规则”谁最后调用loadRules谁生效。如果不做规范管理很容易出现控制台改了一版、Nacos又推了另一版导致规则被互相覆盖的情况。我这边定的规矩是规则变更统一走Nacos控制台仅供查看和临时调整重要变更必须通过代码评审和配置发布会。同时Nacos配置增加版本号和变更说明出现问题可以快速回滚。4.4 分布式一致性误差与阈值校准集群流控虽然在架构上解决了跨节点协调但严格意义上它也不是100%精确的。因为Token Client有预取机制所以实际放行量和配置阈值存在一点点偏差——预取的token在本地消费但Server端统计的是一个周期内的总量极端情况下可能超过阈值几个百分点。线上阈值配置我一般会预留10%~15%的buffer。比如预期后端数据库最多扛400 QPS集群阈值设450让流控有热量缓冲但下游报警阈值设置在430中间留出空间避免频繁触发误告警。另外多个资源共享同一个Token Server时要注意命名空间隔离。我遇到过不同服务用了同一个namespace导致流控规则互相干扰的情况最后为每个业务线单独分配namespace并调整Server配置问题才解决。5. 哪些场景真正需要集群流控5.1 场景一网关层统一做总流量保护如果你用的是Spring Cloud Gateway或自研网关做流量入口整个网关集群对外接收的流量总和才是你关心的指标。单机限流在这里几乎不可用——你根本不知道哪台网关会被负载均衡器分到多少流量。这种场景上集群流控是最自然的解法。5.2 场景二公共基础服务被多个上游共用比如短信服务、订单生成服务、消息推送服务上游有多个调用方每个调用方都有独立的流量特征。如果不做集群维度控制任何一个上游的突发流量都可能打爆整个下游服务。集群流控可以把总流量控制在服务承载能力之内即使某些上游从不同节点同时灌入流量也无法突破整体防线。5.3 场景三有状态服务和缓存一致性要求高的核心链路我之前维护的库存扣减链路就是典型卖点。库存扣减操作依赖数据库行锁数据库层面有独立的最大QPS约束。如果只做单机限流节点N1和N2各放行200 QPS数据库端实际看到的可能是400 QPS并发这远远超过安全水位。集群流控直接按数据库能承受的总交易量来限流等于给数据库加了一道前置挡水坝。不过有一点要提醒如果你的服务实际部署节点不超过2个且流量入口基本平均单机限流够用的话就不必为了“更高级”而强行上集群流控。集群流控引入的分布式通信、独立运维节点和fallback复杂性是要付出额外成本的任何技术选型都应该以解决问题为前提而不是为了追新。6. 落地过程中的一些心得最后分享几条经验。第一集群流控不等于“规则统一推送”规则一致只是前置条件真正的核心是“统计口径统一”——全局只有一份计数器在裁决流量。第二Token Server建议独立部署不要内嵌在业务实例里不然每次业务发版重启都会连带流控能力波动。第三接入前先把fallback行为测试透连续kill Token Server进程观察客户端是否自动重连、是否触发告警、降级后的单机限流是否正常兜底。我现在这套集群流控方案已经在生产环境稳定运行了四个多月大促高峰期集群阈值生效准确率在可接受范围内下游数据库再没有出现过因限流维度不一致导致的尖刺流量。如果团队最近正好在流量治理上头疼从单机限流升级到集群流控值得投入精力试一试。
返回列表