
1. Sentinel 的用武之地从“调用链下游故障”到“服务雪崩”1.1 一次高并发故障的完整放大过程在很多服务刚起步的阶段单机部署、直连数据库流量一上来顶多是数据库慢一点整体还能撑住。但微服务拆分之后故障的传导路径会变得非常长。我在一个模拟电商链路上复现过一个很典型的场景用户下单请求命中订单服务订单服务要通过 OpenFeign 调用库存服务扣减库存库存服务再去查自己的数据库。整个链路是同步阻塞式的一旦库存服务的数据库连接池被打满库存接口的响应时间会从正常的 50ms 飙到 2 秒以上。订单服务这边的线程数量是有限的。Tomcat 默认最大工作线程数是 200当库存接口迟迟不返回时这 200 个线程并不会释放而是全部卡在等待远程响应上。新的下单请求进入后没有空闲线程线程池开始排队随后订单服务的 CPU 迅速升高内存里堆积大量 pending 请求。紧接着网关的线程也被拖住因为网关同样依赖订单服务的响应。从库存服务单点变慢到全链路不可用整个扩散过程不到五分钟。这就是服务雪崩的典型路径下游故障通过同步调用向上游传导最终导致整个调用链资源耗尽。而业界解决这一类问题的手段核心就是三件事限流、熔断、降级。限流保护的是自己不被突发流量打垮熔断防止持续调用已经故障的下游降级则是当资源不够时返回一个默认可接受的兜底结果。这里就是 Sentinel 登场的地方。Sentinel 是面向分布式服务架构的流量治理组件可以从流量控制、熔断降级、系统负载保护等多个维度对请求链路做保护。它在 Spring Cloud Alibaba 里是一个独立 starter能配合 Nacos 做规则持久化也能配合 OpenFeign 做服务间调用的降级兜底。这篇文章我想把整套接入过程按实际操作顺序梳理一遍特别是和 Nacos、OpenFeign 组合使用时那些文档里不太会细写的坑。1.2 为什么我最终选择了 Sentinel 而不是老牌熔断组件做服务容错选型时很多人第一反应是 Hystrix。Hystrix 确实是最早普及熔断降级概念的组件但它在 2018 年底就进入了维护模式官方明确表示不再新增功能。很多项目还在用它本质上只是因为历史代码难以迁移。Sentinel 和 Hystrix 的设计思路差异非常明显。Hystrix 更偏向于“线程池隔离”和“信号量隔离”每个依赖一个线程池隔离效果好但线程池开销大默认的超时和熔断配置比较重。Sentinel 则是围绕“资源”来做控制你既可以用注解声明一个业务方法为资源也可以让 Web 请求自动被拦截成为资源然后对资源配置流控和熔断规则。下面是几个实际使用中很关键的差距点对比维度HystrixSentinel流控维度以线程池/信号量为主QPS、并发线程数、热点参数、系统负载均可控制规则推送通过配置中心广播改造成本高原生支持 Nacos、Apollo、ZooKeeper 等配置源实时监控Hystrix Dashboard 较简陋Sentinel 控制台自带秒级监控和曲线注解支持HystrixCommandSentinelResource标注方式更轻社区活跃度基本停更仍在持续迭代从 Spring Cloud 生态的兼容性看Spring Cloud Alibaba 已经把 Sentinel、Nacos、OpenFeign 这三者天然地串联起来。我用下来最大的感受是Sentinel 特别适合“先快速接入再逐步精细化”的演进路径。刚开始可以让所有 Web 请求自动成为资源控制台里手动配几条规则跑通之后再考虑把规则迁移到 Nacos而 Hystrix 要做到同样效果需要额外引入很多配置类和线程池参数调优上手门槛明显更高。2. 动手前的环境准备Nacos 注册中心与基础的 OpenFeign 调用链路2.1 版本与依赖先选一组稳定的组合避免兼容问题抢戏接入 Sentinel 之前我建议先把服务注册和 Feign 调用跑通。这样后面再观察限流效果时你能明确知道到底是“限流生效”还是“调用本身没通”。先说版本。Spring Cloud Alibaba 的版本和 Spring Boot、Spring Cloud 的对应关系非常严格不同大版本之间的依赖坐标差异很大。我用得比较多的一组稳定搭配是Spring Boot 2.3.12.RELEASESpring Cloud Hoxton.SR12Spring Cloud Alibaba 2.2.7.RELEASENacos Server 1.4.2Sentinel Dashboard 1.8.6这套组合虽然时间稍早但生态成熟、坑少网上能搜到的案例也基本集中在这个版本段。如果你用的是 Spring Boot 3.x 或更现代的 Spring Cloud 版本需要对应升级到 Spring Cloud Alibaba 2023.x配置项名称可能会从spring.cloud.sentinel.*变为新的命名空间改动量会大不少。在pom.xml中先导入依赖管理dependencyManagement dependencies dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-dependencies/artifactId version2.2.7.RELEASE/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement然后在各个服务模块里加入下面几个核心依赖。一个服务如果既要做注册发现、又要做外部调用、还要接入流控通常是这三件套dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-sentinel/artifactId /dependency很多新手会漏掉 Nacos Discovery 的 starter或者误以为 Sentinel 会自动包含服务发现能力。实际上这三者各管一段Nacos 负责服务注册和配置管理Feign 负责声明式的远程调用Sentinel 负责流量控制与熔断降级。少任何一个组合链路都跑不完整。2.2 构造最小服务对Provider 与 Consumer我习惯用两个最简单的服务来验证链路一个是库存服务stock-service作为被调用方另一个是订单服务order-service作为调用方。这样能最直观地体现 Feign 调用关系。stock-service的启动类和 Controller 非常简洁SpringBootApplication EnableDiscoveryClient public class StockServiceApplication { public static void main(String[] args) { SpringApplication.run(StockServiceApplication.class, args); } }RestController RequestMapping(/stock) public class StockController { GetMapping(/reduce) public String reduce() { return stock reduced; } }order-service这边需要一个 Feign ClientFeignClient(name stock-service) public interface StockFeignClient { GetMapping(/stock/reduce) String reduce(); }然后在订单 Controller 里注入StockFeignClient并暴露一个下单接口RestController RequestMapping(/order) public class OrderController { private final StockFeignClient stockFeignClient; public OrderController(StockFeignClient stockFeignClient) { this.stockFeignClient stockFeignClient; } GetMapping(/create) public String createOrder() { return order created, stock: stockFeignClient.reduce(); } }两个服务的application.yml都需要配置 Nacos 地址和注册中心信息订单服务还要打开 Feignspring: application: name: order-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 feign: sentinel: enabled: true这里的feign.sentinel.enabled: true是后续做 Feign 熔断降级的开关。Spring Cloud Alibaba 默认不会自动开启这个开关很多人配了 fallback 类却没效果多半就是漏了它。2.3 先跑通 Feign 调用再谈流量治理在引入 Sentinel 规则之前先把两个服务启动确认 Nacos 控制台里能看到两个服务实例然后请求order-service的/order/create接口。如果返回内容包含stock reduced说明整条链路是通的。这一步非常重要。很多人在接入 Sentinel 后遇到“接口每次都被 Blocked”的情况排查半天才发现其实是 Feign 调用本身失败导致异常和限流规则没有半毛钱关系。先把基础链路跑通后面对规则的验证才不会被干扰。3. Sentinel 接入与核心概念资源、规则、控制台3.1 三个核心抽象资源、规则、上下文Sentinel 的模型只有三个核心抽象理解了它们基本上整个框架的用法就通了。第一个是资源也就是你想保护的东西可以是一个 URL、一个方法、一段代码或者一个 Feign 调用。第二个是规则用来定义这个资源在什么条件下触发保护比如 QPS 超过 5 就直接拒绝或者异常比例超过 20% 就开始熔断。第三个是上下文描述一次调用链路的来源和链路关系。可以把它想成自来水系统。资源是水龙头规则是水龙头上的流量计和控制阀上下文则是从水源到水龙头的整条管道。限流时可以按单个水龙头限也可以按整条管道限还可以根据当前主管道的水压做全局保护。这也是 Sentinel 比单纯“每个接口一个熔断器”更灵活的地方。Sentinel 内置的规则按用途可以分成四类流控规则FlowRule按 QPS 或并发线程数限制流量。熔断降级规则DegradeRule按慢调用比例、异常比例或异常数触发熔断。系统保护规则SystemRule根据整体负载、CPU、内存等指标兜底保护。热点参数规则ParamFlowRule针对某个方法入参做更精细的限制。初学阶段最容易混淆的是流控和熔断。流控是自己接口的入口流量太高时主动挡一部分熔断是下游已经明显故障时不再继续把请求打过去。前者保护自己后者保护调用链。3.2 用注解和自动适配把接口变成资源把接口变成 Sentinel 资源有两种常见方式。第一种是依赖 Sentinel 的 Web 适配器Spring Cloud 环境下它会自动拦截所有进入应用的 HTTP 请求每个 URL 天然就是一个资源资源名通常就是请求路径比如/order/create。你甚至不需要写一行代码启动后就能在控制台看到每个接口的 QPS、响应时间等指标。第二种方式是通过SentinelResource注解把任意一个 Spring Bean 方法声明为资源。这对非 Web 入口的调用特别有用比如 MQ 消费者、定时任务、异步方法。用法如下GetMapping(/create) SentinelResource( value createOrder, blockHandler createOrderBlock, fallback createOrderFallback ) public String createOrder() { return order created, stock: stockFeignClient.reduce(); } public String createOrderBlock(BlockException ex) { return flow limited; } public String createOrderFallback(Throwable t) { return service exception; }这里有两个很多人一开始会搞混的点。blockHandler是当 Sentinel 检测到资源被流控或熔断时触发的兜底方法入参需要包含BlockException而fallback是当业务方法本身抛出异常时的兜底方法。两者触发逻辑不同可以都配也可以只配一个。如果业务方法里的异常没有被处理直接抛给调用方Sentinel 会把它视作异常指标从而影响熔断规则的统计。3.3 控制台让规则先可视化地跑起来Sentinel 控制台是一个独立的 Java 应用下载后直接启动java -Dserver.port8080 -jar sentinel-dashboard-1.8.6.jar客户端接入控制台需要配置两处。一是控制台地址二是客户端本身的监听端口spring: cloud: sentinel: transport: dashboard: 127.0.0.1:8080 port: 8719 eager: truetransport.port是 Sentinel 客户端暴露给控制台拉取数据用的端口默认 8719如果被占用会自动向后递增。eager: true的作用是让应用启动时就主动连接到控制台而不是等第一次请求进来才建立连接。第一次接入时我强烈建议加上这个配置否则你会在控制台的“机器列表”里一直看不到自己的服务。控制台界面里可以对每个资源手动添加流控规则。比如给createOrder配置一个 QPS 阈值为 5 的快速失败规则然后并发请求该接口超过阈值的部分会直接返回错误提示同时控制台的实时监控曲线能清楚看到拒绝数量。不过要注意通过控制台添加的规则默认只保存在客户端内存里应用重启后这些规则全部丢失。这也是很多人接入 Sentinel 后最头疼的一件事我会在第五部分专门讲如何用 Nacos 做规则持久化。4. 与 OpenFeign 的集成实战消费方熔断降级落地与注意事项4.1feign.sentinel.enabled: true背后发生了什么在订单服务里StockFeignClient.reduce()是远程调用库存服务的入口。如果库存服务持续变慢订单服务这边如果不做保护线程照样被拖死。开启feign.sentinel.enabled: true后Spring Cloud Alibaba 会把每个 Feign Client 的调用包装进 Sentinel 的链路中。Feign 调用会被映射成一个 Sentinel 资源资源名的格式通常是GET:http://stock-service/stock/reduce。你可以像保护普通接口一样在控制台给这个 Feign 资源配置流控规则和熔断规则。比如当调用失败率达到 20% 时触发熔断熔断期间不再实际发起 HTTP 请求而是直接走 fallback 逻辑。这里要提醒一个容易忽略的点Feign 内部的超时机制和熔断是两回事。Feign 的超时控制决定“等待多久算失败”Sentinel 的熔断规则决定“失败多少次后开始快速失败”。两者配合使用时超时时间不能设得太长否则熔断规则还没触发线程池已经被占满了。4.2 fallback 与 fallbackFactory两种降级写法的选择前面说了开启feign.sentinel.enabled: true真正落地还要在FeignClient注解里指定 fallback。最常用的方式是定义一个实现类Component public class StockFeignClientFallback implements StockFeignClient { Override public String reduce() { return fallback: stock service unavailable; } }然后在 Feign Client 上引用它FeignClient( name stock-service, fallback StockFeignClientFallback.class ) public interface StockFeignClient { GetMapping(/stock/reduce) String reduce(); }当库存服务异常、被熔断或者触发流控时reduce()会直接返回兜底字符串而不会把异常抛给订单接口的调用方。如果希望拿到异常根因可以用fallbackFactory替代fallbackComponent Slf4j public class StockFeignClientFallbackFactory implements FallbackFactoryStockFeignClient { Override public StockFeignClient create(Throwable cause) { log.error(stock service call failed, cause); return new StockFeignClient() { Override public String reduce() { return fallback with error: cause.getMessage(); } }; } }两种方式的取舍也很简单只需要静态兜底就选fallback需要记录异常、区分不同失败原因、或者做更复杂的容错逻辑就选fallbackFactory。注意fallbackFactory.create方法里面需要返回一个接口实现通常用匿名内部类即可。4.3 实测中容易踩到的几个坑第一个坑是 fallback 类忘了加Component。FeignClient里的 fallback 类会被 Spring 容器扫描并注入如果它没有被容器管理启动时会直接报错或者运行时降级不生效。第二个坑是 fallback 方法找不到。Feign 的 fallback 必须实现完整的 Feign 接口方法参数和返回值要和接口方法完全一致。接口后续新增方法时fallback 类如果没有同步实现启动阶段就会失败。第三个坑是降级后异常信息丢失。用静态 fallback 时业务上的失败细节会被吞掉。在排查线上问题时你会发现只知道服务被降级了但不清楚到底是超时、连接拒绝还是其他原因。所以我在线上环境通常用fallbackFactory至少要把异常打进日志方便事后追踪。另外一旦服务进入降级状态调用方拿到的就是兜底数据比如这里的fallback: stock service unavailable。对于订单创建这种写操作要非常小心返回兜底结果带来的业务副作用。降级适合读操作或非核心链路对核心写操作我更倾向于降级后抛出一个明确的可预期异常而不是返回一个误导调用方的假成功结果。5. 规则持久化到 Nacos告别“重启丢规则”5.1 默认规则到底存在哪里为什么会丢Sentinel 客户端启动后规则会加载到 JVM 内存里的一个RuleManager中。通过控制台手动添加的规则也是通过客户端 API 直接写进内存。这个过程没有任何落盘动作所以应用重启后规则自然就清零了。如果规则数量少、环境固定重启后再手动配一次也还能接受。但只要服务数量超过五个手动配置就会变成一件非常痛苦的事每个服务几十条规则一次发布要重配一遍还很容易遗漏。解决思路只有一个把规则放到配置中心由配置中心统一管理、自动推送。Nacos 是 Spring Cloud Alibaba 生态里最天然的配置源正好可以用上。5.2 在 Nacos 配置中心里定义流控规则 JSON以订单服务为例进入 Nacos 控制台的“配置管理”页面新建一条配置。Data ID 我习惯命名为${spring.application.name}-flow-rules也就是order-service-flow-rulesGroup 用SENTINEL_GROUP配置格式选 JSON。配置内容是一个规则数组例如给createOrder这个资源配置 QPS 阈值为 5 的流控规则[ { resource: createOrder, limitApp: default, grade: 1, count: 5, strategy: 0, controlBehavior: 0, clusterMode: false } ]字段含义有必要稍微展开一下。resource是资源名对应SentinelResource的 value 值或者 URL 路径grade是限流维度0 表示并发线程数1 表示 QPScount是阈值strategy是流控策略0 直接、1 关联、2 链路controlBehavior是流量控制效果0 直接拒绝、1 Warm Up 预热、2 匀速排队。如果同时想配置熔断降级规则需要在另一条配置里存降级规则数组或者通过配置多个 datasource 分别绑定。Spring 配置里允许定义多个数据源按ds1、ds2这样依次命名。不同类型的规则不能混在同一个数组里解析所以最好一个 Data ID 只存一种 rule-type 的规则。5.3 用 spring.cloud.sentinel.datasource 让规则热生效Spring Cloud Alibaba 提供了非常简洁的接入方式不需要手动写Bean创建数据源类直接在application.yml里声明即可spring: application: name: order-service cloud: sentinel: transport: dashboard: 127.0.0.1:8080 port: 8719 eager: true datasource: ds-flow: nacos: server-addr: 127.0.0.1:8848 >for i in $(seq 1 20); do curl -s http://localhost:8083/order/create done wait当并发超过阈值时一部分请求会返回流控提示另一部分会正常返回order created。然后在 Nacos 里把count改成 1不需要重启应用再执行同样的并发请求你会看到几乎全部请求都被拦截。这说明 Nacos 热推送已经生效。在走向生产环境时我根据自己的实操经验整理了几个建议。第一所有规则修改都通过 Nacos 配置管理来做把控制台当成监控工具而不是规则编辑工具这样每次变更都有 Nacos 配置历史的留痕。第二规则上线前先在测试环境把压测跑一遍尤其是熔断规则的阈值不同机器配置和业务耗时差异很大照抄别的服务的阈值容易出问题。第三配置变更时注意 Nacos 配置发布是否成功Sentinel 客户端日志中如果出现拉取失败或 JSON 解析失败通常是因为服务端和本地版本不兼容或者 JSON 里多了逗号、字段名拼写错误这类低级问题。我在实际运维中还发现一个有意思的细节当 Nacos 配置源和应用控制台同时存在时如果你在控制台手动修改了同一条规则之后 Nacos 里再推一次规则最终会以 Nacos 的值为准。所以生产环境尽量固定一种规则管理入口避免两边互相覆盖给排查增加不必要的复杂度。最后再分享一个小技巧。eager: true不只是让控制台更容易看到机器列表它同样会让数据源在服务启动阶段就完成首次拉取。如果一份规则配置有格式错误启动阶段就会报错而不是等到第一次流量进来才暴露。这对发布流程来说友好得多至少能把问题挡在立项之前。