[TOC] # 自定义sentinel-spring-boot-starter sentinel-spring-boot-starter 封装完善了sentinel的功能,通过了平台通用的熔断限流能力。 ## 什么是 Sentinel > 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 ## Sentinel 的特征 * **丰富的应用场景**:**Sentinel承接了阿里巴巴近 10 年的**双十一大促流量\*\*的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。 * **完备的实时监控**:\*\*Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。 * **广泛的开源生态**:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。 * **完善的 SPI 扩展点**:Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。 ## 应用访问次数控制 * 核心类 ![](https://img.kancloud.cn/de/d7/ded71e9c57a1d69aa2649f646655464b_2090x1003.png) * 核心redis数据结构 ![](https://img.kancloud.cn/58/ad/58ad08c241e664b0a3096bcc7623907a_1905x826.png) * 核心表结构 oauth_client_details 增加 if_limit 是否需要控制访问次数 ,limit_count 访问阀值 ![](https://img.kancloud.cn/69/9d/699daf854eb4344ea508bd85a075536b_1908x332.png) ## 滑动窗口流量助手 ### 代码一览 ![](https://img.kancloud.cn/1c/08/1c088549f7b915dddc8634ed00392d42_2100x1027.png) ### 滑动窗口 如果我们希望能够知道某个接口的每秒处理成功请求数(成功 QPS)、每秒处理失败请求数(失败 QPS),以及处理每个成功请求的平均耗时(avg RT),我们只需要控制 Bucket 统计一秒钟的指标数据即可。我们只需要控制 Bucket 统计一秒钟内的指标数据即可。但如何才能确保 Bucket 存储的就是精确到 1 秒内的数据呢? 最 low 的做法就是启一个定时任务每秒创建一个 Bucket,但统计出来的数据误差绝对很大。Sentinel 是这样实现的,它定义一个 Bucket 数组,根据时间戳来定位到数组的下标。假设我们需要统计每 1 秒处理的请求数等数据,且只需要保存最近一分钟的数据。那么 Bucket 数组的大小就可以设置为 60,每个 Bucket 的 windowLengthInMs(窗口时间)大小就是 1000 毫秒(1 秒),如下图所示。 ![](https://img.kancloud.cn/a9/70/a97043ef8b78c6db94a9321a69a99b87_848x245.png) 由于每个 Bucket 存储的是 1 秒的数据,假设 Bucket 数组的大小是无限大的,那么我们只需要将当前时间戳去掉毫秒部分就能得到当前的秒数,将得到的秒数作为索引就能从 Bucket 数组中获取当前时间窗口的 Bucket。 一切资源均有限,所以我们不可能无限的存储 Bucket,我们也不需要存储那么多历史数据在内存中。当我们只需要保留一分钟的数据时,Bucket 数组的大小就可以设置为 60,我们希望这个数组可以循环使用,并且永远只保存最近 1 分钟的数据,这样不仅可以避免频繁的创建 Bucket,也减少内存资源的占用。 这种情况下如何定位 Bucket 呢?我们只需要将当前时间戳去掉毫秒部分得到当前的秒数,再将得到的秒数与数组长度取余数,就能得到当前时间窗口的 Bucket 在数组中的位置(索引),如下图所示: ![](https://img.kancloud.cn/e8/c6/e8c657eb5760fa04ea012934f3383f75_965x274.png) 根据当前时间戳计算出当前时间窗口的 Bucket 在数组中的索引,算法实现如下: ![](https://img.kancloud.cn/17/db/17db3965a9eed9476b23cf092dd5ae0d_2442x973.png) calculateTimeIdx 方法中,取余数就是实现循环利用数组。如果想要获取连续的一分钟的 Bucket 数据,就不能简单的从头开始遍历数组,而是指定一个开始时间和结束时间,从开始时间戳开始计算 Bucket 存放在数组中的下标,然后循环每次将开始时间戳加上 1 秒,直到开始时间等于结束时间。 由于循环使用的问题,当前时间戳与一分钟之前的时间戳和一分钟之后的时间戳都会映射到数组中的同一个 Bucket,因此,必须要能够判断取得的 Bucket 是否是统计当前时间窗口内的指标数据,这便要数组每个元素都存储 Bucket 时间窗口的开始时间戳。 比如当前时间戳是 1577017699235,Bucket 统计一秒的数据,将时间戳的毫秒部分全部替换为 0,就能得到 Bucket 时间窗口的开始时间戳为 1577017699000。 计算 Bucket 时间窗口的开始时间戳代码实现如下: ``` /** * 获取bucket开始时间戳 * * @param timeMillis * @return */ protected long calculateWindowStart(long timeMillis) { /** * 假设窗口大小为1000毫秒,即数组每个元素存储1秒钟的统计数据 * timeMillis % windowLengthInMs 就是取得毫秒部分 * timeMillis - 毫秒数 = 秒部分 * 这就得到每秒的开始时间戳 */ return timeMillis - timeMillis % windowLengthInMs; } ``` ### 使用案例 统计每秒钟 ``` public class Main{ private FlowHelper flowHelper = new FlowHelper(FlowType.Second); } ``` 输出最近一秒钟统计 ``` public class Main { public static void print() { Flower flower = flowHelper.getFlow(FlowType.Second); System.out.println("总请求数:" + flower.total()); System.out.println("成功请求数:" + flower.totalSuccess()); System.out.println("异常请求数:" + flower.totalException()); System.out.println("平均请求耗时:" + flower.avgRt()); System.out.println("最大请求耗时:" + flower.maxRt()); System.out.println("最小请求耗时:" + flower.minRt()); System.out.println("平均请求成功数(每毫秒):" + flower.successAvg()); System.out.println("平均请求异常数(每毫秒):" + flower.exceptionAvg()); System.out.println(); } } ``` 统计每分钟 ``` public class Main{ private FlowHelper flowHelper = new FlowHelper(FlowType.Minute); } ``` 输出最近一分钟统计 ``` public class Main { public static void print() { Flower flower = flowHelper.getFlow(FlowType.Minute); System.out.println("总请求数:" + flower.total()); System.out.println("成功请求数:" + flower.totalSuccess()); System.out.println("异常请求数:" + flower.totalException()); System.out.println("平均请求耗时:" + flower.avgRt()); System.out.println("最大请求耗时:" + flower.maxRt()); System.out.println("最小请求耗时:" + flower.minRt()); System.out.println("平均请求成功数(每秒钟):" + flower.successAvg()); System.out.println("平均请求异常数(每秒钟):" + flower.exceptionAvg()); System.out.println(); } } ``` 统计每小时 ``` public class Main{ private FlowHelper flowHelper = new FlowHelper(FlowType.HOUR); } ``` 输出最近一小时统计 ``` public class Main { public static void print() { Flower flower = flowHelper.getFlow(FlowType.HOUR); System.out.println("总请求数:" + flower.total()); System.out.println("成功请求数:" + flower.totalSuccess()); System.out.println("异常请求数:" + flower.totalException()); System.out.println("平均请求耗时:" + flower.avgRt()); System.out.println("最大请求耗时:" + flower.maxRt()); System.out.println("最小请求耗时:" + flower.minRt()); System.out.println("平均请求成功数(每分钟):" + flower.successAvg()); System.out.println("平均请求异常数(每分钟):" + flower.exceptionAvg()); System.out.println(); } } ```