在前面两篇文章给大家介绍了 Sentinel 的功能和基本使用。现在我们继续来学习 Sentinel 控制台的基本使用,以及一些规则配置的说明。让大家能够在工作中使用 Sentinel 得心应手 (大部分理论和描述来源于官方文档和网络)。
在正文开始之前,我先说一下我的基本环境信息
jdk 1.8
sentinel 1.8.0
spring-boot 2.3.5.RELEASE
spring-cloud Hoxton.SR8
spring-cloud-alibaba 2.2.5.RELEASE
控制台简介
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。这里,我们将会详细讲述如何通过简单的步骤就可以使用这些功能。
Sentinel 控制台包含如下功能:
- 查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
- 监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
- 规则管理和推送:统一管理推送规则。
- 鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
注意:Sentinel 控制台目前仅支持单机部署。Sentinel 控制台项目提供 Sentinel 功能全集示例,不作为开箱即用的生产环境控制台,若希望在生产环境使用需要自行定制和改造。
Alibaba 提供了企业版本的 Sentinel 我们可以在 aliyun.com
上面购买 AHAS Sentinel
查看机器列表以及健康情况
如果我们正确的接入 Sentinel 之后我们可以在 Sentinel 控制台的 机器列表
菜单中来查看我们服务节点的健康情况
如果 Sentinel 接入不成功,可以查阅 Sentinel 官方文档或者 FAQ 来对应排查
服务监控
1. 实时监控
同时,同一个服务下的所有机器的簇点信息会被汇总,并且秒级地展示在"实时监控"下。
注意: 实时监控仅存储 5 分钟以内的数据,如果需要持久化,需要通过调用实时监控接口来定制。
注意:请确保 Sentinel 控制台所在的机器时间与自己应用的机器时间保持一致,否则会导致拉不到实时的监控数据。
2. 簇点链路
簇点链路(单机调用链路)页面实时的去拉取指定客户端资源的运行情况。它一共提供两种展示模式:一种用树状结构展示资源的调用链路,另外一种则不区分调用链路展示资源的实时情况。
注意: 簇点链路监控是内存态的信息,它仅展示启动后调用过的资源。
注意:请确保 Sentinel 控制台所在的机器时间与自己应用的机器时间保持一致,否则会导致拉不到实时的监控数据。
3 流控规则
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
FlowSlot
会根据预设的规则,结合 NodeSelectorSlot
、ClusterBuilderSlot
、StatisticSlot
统计出来的实时信息进行流量控制。
限流的直接表现是在执行 Entry nodeA = SphU.entry(resourceName)
的时候抛出 FlowException
异常。FlowException
是 BlockException
的子类,您可以捕捉 BlockException
来自定义被限流之后的处理逻辑。
Sentinel 在触发规则保护时,返回的异常页面是一样的。不好区分是因为哪种规则导致的异常。所以需要自定义异常返回信息,明确是触发了哪种类型的规则。
@Component public class SentinelBlockHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception { CommonResult<Void> result = new CommonResult<>(); if (e instanceof FlowException) { result = CommonResult.error(101, "接口限流了"); } else if (e instanceof DegradeException) { result = CommonResult.error(102, "服务降级了"); } else if (e instanceof ParamFlowException) { result = CommonResult.error(103, "热点参数限流了"); } else if (e instanceof SystemBlockException) { result = CommonResult.error(104, "系统规则(负载/...不满足要求)"); } else if (e instanceof AuthorityException) { result = CommonResult.error(105, "授权规则不通过"); } // http状态码 httpServletResponse.setStatus(500); httpServletResponse.setCharacterEncoding("utf-8"); httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8"); httpServletResponse.setContentType("application/json;charset=utf-8"); // spring mvc自带的json操作工具,叫jackson new ObjectMapper().writeValue(httpServletResponse.getWriter(), result); } }
效果如下:
➜ curl http://127.0.0.1:8088/getStockDetail {"code":1,"message":"this is a success message","data":{"id":1,"code":"STOCK==>1000"}}% ➜ curl http://127.0.0.1:8088/getStockDetail {"code":1,"message":"this is a success message","data":{"id":1,"code":"STOCK==>1000"}}% ➜ curl http://127.0.0.1:8088/getStockDetail {"code":101,"message":"接口限流了","data":null}%
阈值类型
线程数
并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。
可以通过线程池模拟客户端调用, 也可以通过 Jmeter 模拟,触发流控的结果如下:
➜ ~ curl http://127.0.0.1:8088/getStockDetail {"code":101,"message":"接口限流了","data":null}%
流控模式
调用关系包括调用方、被调用方;一个方法又可能会调用其它方法,形成一个调用链路的层次关系。
直接
当资源触发流控规则过后直接,抛出异常信息
➜ ~ curl http://127.0.0.1:8088/getStockDetail {"code":101,"message":"接口限流了","data":null}%
关联
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db
和 write_db
这两个资源分别代表数据库读写,我们可以给 read_db
设置限流规则来达到写优先的目的:设置 strategy
为 RuleConstant.STRATEGY_RELATE
同时设置 refResource
为 write_db
。这样当写库操作过于频繁时,读数据的请求会被限流。
如果配置流控规则为关联模式,那么当 /hello
接口超过阈值过后,就会对 /getStockDetail
接口触发流控规则。
链路
NodeSelectorSlot
中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root
的虚拟节点,调用链的入口都是这个虚节点的子节点。
一棵典型的调用树如下图所示:
machine-root / \ / \ Entrance1 Entrance2 / \ / \ DefaultNode(nodeA) DefaultNode(nodeA)
上图中来自入口 Entrance1
和 Entrance2
的请求都调用到了资源 NodeA
,Sentinel 允许只根据某个入口的统计信息对资源限流。比如我们可以设置 strategy
为 RuleConstant.STRATEGY_CHAIN
,同时设置 refResource
为 Entrance1
来表示只有从入口 Entrance1
的调用才会记录到 NodeA
的限流统计当中,而不关心经 Entrance2
到来的调用。
调用链的入口(上下文)是通过 API 方法 ContextUtil.enter(contextName)
定义的,其中 contextName 即对应调用链路入口名称。详情可以参考 ContextUtil 文档。]
测试会发现 链路
不会生效
从1.6.3版本开始,Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效。1.7.0版本开始(对应SCA 2.1.1.RELEASE),我们在CommonFilter引入了WEB_CONTEXT_UNIFY这个init parameter,用于控制是否收敛context。将其配置为false即可根据不同的URL进行链路限流。 参考:github.com/alibaba/sen…
解决方案:
1.7.0 版本开始(对应Spring Cloud Alibaba的2.1.1.RELEASE) 需要新增依赖
@Configuration public class FilterContextConfig { @Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns("/*"); // 入口资源关闭聚合 registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false"); registration.setName("sentinelFilter"); registration.setOrder(1); return registration; } }
然后我们再尝试触发流控规则, 对 /getStockDetail
进行访问,这里返回了
FlowException
默认情况会返回
如果我们使用 OpenFeign 不添加 fallbackFactory
就会返回500 , 如果我们添加了就可以避免这个问题。
// Controller @Autowired private StockFeign stockFeign; @GetMapping("/getStockDetail") public CommonResult<StockModel> getStockDetail() { CommonResult<StockModel> result = stockFeign.getStockDetail(); if (result.getCode() != 1) { return CommonResult.error(null, result.getCode(), result.getMessage()); } return result; } // FeignClient @FeignClient(name = "stock-service") //, fallbackFactory = StockFeignFallbackFactory.class) public interface StockFeign { @GetMapping("/getStockDetail") CommonResult<StockModel> getStockDetail(); }
Sentinel 部分源码:
所以,我们在设置链路流控规则的时候一定要设置 fallbackFactory。 不然无法处理 FlowExecption
异常信息,造成系统出错。对于个人而言,Sentinel 的链路规则比不是特别的好用,无特殊要求,不建议使用,或者选择Sentinel 的收费版本 AHAS