下面我们来在 Web 应用中针对 Dubbo consumer 来配置慢调用熔断规则,并模拟慢调用来观察效果。我们在 web-api-demo 中针对 com.alibaba.csp.sentinel.demo.dubbo.FooService
服务调用配置熔断降级规则。控制台配置的统计时长默认为 1s。在上面的这条规则中,我们设定慢调用临界值为 50ms,响应时间超出 50ms 即记为慢调用。当统计时长内的请求数 >=5 且慢调用的比例超出我们配置的阈值(80%)就会触发熔断,熔断时长为 5s,经过熔断时长后会允许一个请求探测通过,若请求正常则恢复,否则继续熔断。
我们的实例中 /demo/time
API 可以通过 slow 请求参数模拟慢调用,当 slow=true 时该请求耗时会超过 100ms。我们可以用 ab 等压测工具或脚本,批量请求 localhost:8090/demo/time?slow=true
,可以观察到熔断的返回
如果我们一直模拟慢调用,我们可以观察到熔断后每 5s 会允许通过一个请求,但该请求仍然是慢调用,会重新打回熔断,无法恢复。我们可以在触发熔断后,等待一段时间后手动发一个不带 slow=true
的正常请求,然后再进行请求,可以观察到熔断恢复。
需要注意的是,即使服务调用方引入了熔断降级机制,我们还是需要在 HTTP 或 RPC 客户端配置请求超时时间,来做一个兜底的防护。
注解方式自定义埋点
刚才我们看到的埋点都是 Sentinel 适配模块提供的自动埋点。有的时候自动埋点可能没法满足我们的需求,我们希望在某个业务逻辑的位置进行限流,能不能做到呢?当然可以!Sentinel 提供两种方式进行自定义埋点:SphU
API 和 @SentinelResource
注解,前者最为通用但是代码比较繁杂,耦合度较高;注解方式侵入性较低,但有使用场景的限制。这里我们来动手在 Web 应用的 DemoService 上添加注解,来达到针对本地服务埋点的目标。
在 DemoService
中我们实现了一个简单的打招呼的服务:
@Service public class DemoService { public String bonjour(String name) { return "Bonjour, " + name; } }
下面我们给 bonjour 这个函数添加 @SentinelResource
注解,注解的 value 代表这个埋点的名称(resourceName),会显示在簇点链路/监控页面。
@SentinelResource(value = "DemoService#bonjour") public String bonjour(String name)
加上该注解后,再通过网关访问 /demo/bonjour/{name}
这个 API 的时候,我们就可以在簇点链路页面看到我们自定义的 DemoService#bonjour
埋点了。添加注解埋点只是第一步。一般在生产环境中,我们希望在这些自定义埋点发生限流的时候,有一些 fallback 逻辑,而不是直接对外抛出异常。这里我们可以写一个 fallback 函数:
public String bonjourFallback(Throwable t) { if (BlockException.isBlockException(t)) { return "Blocked by Sentinel: " + t.getClass().getSimpleName(); } return "Oops, failed: " + t.getClass().getCanonicalName(); }
我们的 fallback 函数接受一个 Throwable 参数,可以从中获取异常信息。Sentinel 注解的 fallback 会捕获业务异常和流控异常(即 BlockException 及其子类),我们可以在 fallback 逻辑里面进行相应的处理(如日志记录),并返回 fallback 的值。
注意:Sentinel 注解对 fallback 和 blockHandler 函数的方法签名有要求,具体请参考此处文档。
写好 fallback 函数的实现后,我们在 @SentinelResource
注解里面指定一下:
@SentinelResource(value = "DemoService#bonjour", defaultFallback = "bonjourFallback") public String bonjour(String name)
这样当我们自定义的 DemoService#bonjour
资源被限流或熔断的时候,请求会走到 fallback 的逻辑中,返回 fallback 结果,而不会直接抛出异常。我们可以配一个 QPS=1 的限流规则,然后快速请求后观察返回值:
? ~ curl http://localhost:8090/demo/bonjour/Sentinel Bonjour, Sentinel ? ~ curl http://localhost:8090/demo/bonjour/Sentinel Blocked by Sentinel: FlowException
注意:使用 @SentinelResource
注解要求对应的类必须由 Spring 托管(即为 Spring bean),并且不能是内部调用(没法走到代理),不能是 private 方法。Sentinel 注解生效依赖 Spring AOP 动态代理机制。
配置自定义的流控处理逻辑
Sentinel 的各种适配方式均支持自定义的流控处理逻辑。以 Spring Web 适配为例,我们只需要提供自定义的 BlockExceptionHandler
实现并注册为 bean 即可为 Web 埋点提供自定义处理逻辑。其中 BlockExceptionHandler 的定义如下:
public interface BlockExceptionHandler { // 在此处处理限流异常,可以跳转到指定页面或返回指定的内容 void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception; }
我们的 Web 应用中提供了 Web 埋点自定义流控处理逻辑的示例:
@Configuration public class SentinelWebConfig { @Bean public BlockExceptionHandler sentinelBlockExceptionHandler() { return (request, response, e) -> { // 429 Too Many Requests response.setStatus(429); PrintWriter out = response.getWriter(); out.print("Oops, blocked by Sentinel: " + e.getClass().getSimpleName()); out.flush(); out.close(); }; } }
该 handler 会获取流控类型并打印返回信息,返回状态码为 429。我们可以根据实际的业务需求,配置跳转或自定义的返回信息。
对于注解方式,我们上一节已经提到,可以指定 fallback 函数来处理流控异常和业务异常,这里不再展开讲解;对于 Dubbo 适配,我们可以通过 DubboAdapterGlobalConfig 注册 provider/consumer fallback 来提供自定义的流控处理逻辑;对于 Spring Cloud Gateway 适配,我们可以注册自定义的 BlockRequestHandler 实现类来为网关流控注册自定义的处理逻辑。
对 Spring Cloud 其他组件的支持
Spring Cloud Alibaba Sentinel 还提供对 Spring Cloud 其它常用组件的支持,包括 RestTemplate、Feign 等。篇幅所限,我们不展开实践。大家可以参考 Spring Cloud Alibaba 文档 来进行接入和配置。
如何选择流控降级组件
讲到这里,大家可能会有疑问:Sentinel 和其它同类产品(如 Hystrix)相比有什么优缺点?是否有必要迁移到 Sentinel?如何快速迁移?以下是 Sentinel 与其它 fault-tolerance 组件的对比:
Sentinel | Hystrix | resilience4j | |
隔离策略 | 信号量隔离(并发控制) | 线程池隔离/信号量隔离 | 信号量隔离 |
熔断降级策略 | 基于慢调用比例、异常比例、异常数 | 基于异常比例 | 基于异常比例、响应时间 |
实时统计实现 | 滑动窗口(LeapArray) | 滑动窗口(基于 RxJava) | Ring Bit Buffer |
动态规则配置 | 支持多种数据源 | 支持多种数据源 | 有限支持 |
扩展性 | 多个扩展点 | 插件的形式 | 接口的形式 |
基于注解的支持 | 支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 | Rate Limiter |
流量整形 | 支持预热模式与匀速排队控制效果 | 不支持 | 简单的 Rate Limiter 模式 |
系统自适应保护 | 支持 | 不支持 | 不支持 |
多语言支持 | Java/Go/C++ | Java | Java |
Service Mesh 支持 | 支持 Envoy/Istio | 不支持 | 不支持 |
控制台 | 提供开箱即用的控制台,可配置规则、实时监控、机器发现等 | 简单的监控查看 | 不提供控制台,可对接其它监控系统 |
总结
通过本教程,我们了解了流控降级作为高可用防护手段的重要性,了解了 Sentinel 的核心特性和原理,并通过动手实践学习了如何快速接入 SCA Sentinel 来为微服务进行流控降级。Sentinel 还有着非常多的高级特性等着大家去发掘,如热点防护、集群流控等,大家可以参考 Sentinel 官方文档来了解更多的特性和场景。
那么是不是服务的量级很小就不用进行限流防护了呢?是不是微服务的架构比较简单就不用引入熔断保护机制了呢?其实,这与请求的量级、架构的复杂程度无关。很多时候,可能正是一个非常边缘的服务出现故障而导致整体业务受影响,造成巨大损失。我们需要具有面向失败设计的意识,在平时就做好容量规划和强弱依赖的梳理,合理地配置流控降级规则,做好事前防护,而不是在线上出现问题以后再进行补救。