近些年,大家在项目中往往会遇到越来越多的秒杀活动:购物车、抢红包、抢金条等。形式多种多样,本质大差不离。这些活动都有类似的特点:时间短、并发高、有波峰波谷。那么他们对我们的工程有哪些要求呢?我们会不会经常性的问自己:
我们的系统能不能抗住这次压力?
系统运行参数设置多少为最优?
如果超过了系统的压力阈值系统会怎样?
系统运行的现状是什么样子的?
WTF,真是灵魂拷问四连击啊。不怕,我们马上会介绍一个神级工具解决你的问题,他能够实现:
集群流量控制
消息削峰填谷
流量熔断
实时监控
实时配置
有没有这么厉害啊,真有。下面我们来隆重推出我们的流量防卫兵Sentinel。
2018年下旬阿里巴巴开源了sentinel框架。
网址:https://github.com/alibaba/Sentinel
中文文档:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
他有丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
他有完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
他有广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
他有完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
这么厉害,我们快好好介绍一下怎么玩吧。
框架使用
Sentinel组成
Sentinel主要由两部分组成:
核心库(Java 客户端):Sentinel的核心库不依赖任何第三方框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
控制台(Dashboard):基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
首先我们要介绍的就是核心库在我们自己的代码中如何使用。
1 引用
国际惯例,安利Maven。如果应用使用pom工程,在 pom.xml 文件中加入以下代码即可:
<dependency> <groupId>com.alibaba.cspgroupId> <artifactId>sentinel-coreartifactId> <version>x.y.zversion>dependency>
注意: 从 Sentinel 1.5.0 开始仅支持 JDK 1.7 或者以上版本。Sentinel 1.5.0 之前的版本最低支持 JDK 1.6。所以各位小伙伴尽量跟上JDK版本。
2 基础示例
public static void main(String[] args) { initFlowRules(); // 定义流量规则 while (true) { Entry entry = null; //开启监控 try { entry = SphU.entry("HelloWorld");//选择流程规则唯一值 System.out.println("hello world"); } catch (BlockException e1) { System.out.println("block!"); } finally { if (entry != null) { entry.exit();//关闭监控 } } } }
以上就是Sentinel的helloworld了。这里面有几个关键的点在这里做一下详细说明。
initFlowRules:initFlowRules方法是定义流量规则的重要方法,一般位于启动类中定义,在main方法中调用。
Entry:Entry类是定义监控的抽象类,使用它的实例化来开启监控。
SphU.entry(“HelloWorld”):SphU类中的entry方法则是用过一个规则ID来使用实例化Entry类。SphU为啥叫这个奇怪的名字,查了半天也没查到,大概是SPH(Smoothed Particle Hydrodynamics)是光滑粒子流体动力学的意思。纯瞎猜。
entry.exit():在finally中一定要关闭监控。
另外举例说明一下,假如是springboot工程,initFlowRules方法可以在main方法中定义,也可以在controller里面定义,但是后面的Entry则应该在controller里定义。
OK,那么我们可以判断重点有两个
initFlowRules方法
SphU.entry(“HelloWorld”)的实例化
Sentinel的不同的限流规则也是通过这两个点的变化来实现的。下面我们重点来讲限流规则。
3 规则类型
Sentinel有以下几种规则类型。
A流量控制规则:限制方法的QPS 或线程数。
B熔断降级规则:根据规则对方法进行降级。
C系统保护规则:限制工程的响应时间、QPS、并发数等。
D来源访问控制规则:对访问资源进行黑名单白名单控制。
E热点参数规则:限制访问频次最高的 Top K 数据。
A流量控制示例代码如下:
private void initFlowQpsRule() { List rules = new ArrayList<>(); FlowRule rule = new FlowRule(resourceName); rule.setCount(20); //限制qps为20 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); rules.add(rule); FlowRuleManager.loadRules(rules); }
关键代码就两行
rule.setCount(20); //限制最大线程数为20
rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
当 QPS 超过某个阈值的时候,则采取措施进行流量控制。流量控制的手段包括以下几种:直接拒绝、慢启动模式、匀速排队。
直接拒绝:是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
慢启动模式:即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
匀速排队:会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
系统默认的直接拒绝模式:当QPS大于20时,新请求会被直接拒绝
private void initFlowQpsRule() { List rules = new ArrayList<>(); FlowRule rule = new FlowRule(resourceName); rule.setCount(20); //限制qps为20 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);//直接拒绝 rules.add(rule); FlowRuleManager.loadRules(rules); }
系统默认的慢启动模式:同样首先设置系统QPS上限为20,但当流量增加时,系统会在10秒内将QPS慢慢提升至20,而超过上限的请求则会被拒接。
private void initFlowQpsRule() { List rules = new ArrayList<>(); FlowRule rule = new FlowRule(resourceName); rule.setCount(20);//限制qps为20 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP); rule.setWarmUpPeriodSec(10);//预热时间为10s rules.add(rule); FlowRuleManager.loadRules(rules); }
系统默认的匀速排队模式:其他条件类似,但当QPS大于20后,系统不会直接拒绝,新来的请求会排队等候,当前面的请求完成后,新的请求会进入。但排队中的请求最长保持100ms。
private void initFlowQpsRule() { List rules = new ArrayList<>(); FlowRule rule = new FlowRule(resourceName); rule.setCount(20); //限制qps为20 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER); rule.setWarmUpPeriodSec(10);//预热时间10s rule.setMaxQueueingTimeMs(100);//最大的等待时长为100ms rules.add(rule); FlowRuleManager.loadRules(rules); }
B然后是熔断降级规则,有三种具体分类:
平均响应时间:当资源的平均响应时间超过阈值之后,资源进入准降级状态。接下来如果1s内持续进入5个请求,它们的 RT 都持续超过这个阈值,那么在接下的时间窗口之内,对这个方法的调用都会自动地熔断。
异常比例:当资源的每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态,即在接下的时间窗口之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
异常数:当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timeWindow小于60s,则结束熔断状态后仍可能再进入熔断状态。
平均响应时间降级的例子:当目前资源访问的响应时间大于10ms之后,该服务将会降级10秒,接下来10秒所有请求均无法访问。
private void initDegradeRule() { List rules = new ArrayList(); DegradeRule rule = new DegradeRule(); rule.setResource(KEY); rule.setCount(10);//响应时间10ms rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); rule.setTimeWindow(10);//降级10s rules.add(rule); DegradeRuleManager.loadRules(rules); }
异常比例降级:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex) 记录业务异常。
Entry entry = null;try { entry = SphU.entry(key, EntryType.IN, key);. } catch (Throwable t) { if (!BlockException.isBlockException(t)) { Tracer.trace(t); }} finally { if (entry != null) { entry.exit(); }}
具体的规则如下:异常比例超过60%后,服务将会降级10秒,接下来10秒所有请求均无法访问。
private void initDegradeRule() { List rules = new ArrayList(); DegradeRule rule = new DegradeRule(); rule.setResource(KEY); rule.setCount(0.6); //异常比例超过60% rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); rule.setTimeWindow(10); //降级10s rules.add(rule); DegradeRuleManager.loadRules(rules);}
异常数降级:和异常比例降级类似,当异常数超过10个后,服务将会降级10秒,接下来10秒所有请求均无法访问。
private void initDegradeRule() { List rules = new ArrayList(); DegradeRule rule = new DegradeRule(); rule.setResource(KEY); rule.setCount(10); //异常数超过10个 rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); rule.setTimeWindow(10); //降级10s rules.add(rule); DegradeRuleManager.loadRules(rules);}
C系统保护规则支持以下四种阈值类型:
Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
由于开发团队一般仅设置到应用层面,系统保护规则一般用的比较少。以下仅举一例说明:以下设置当出现系统并发数超过3.0或平均响应时间超过10ms,或qps大于20.或应用线程数超过10的情况,则相关服务无法访问。
private void initSystemRule() { List rules = new ArrayList(); SystemRule rule = new SystemRule(); rule.setHighestSystemLoad(3.0); //系统的并发数 rule.setAvgRt(10); //平均响应时间10ms rule.setQps(20); //qps20 rule.setMaxThread(10); //应用的线程数 rules.add(rule); SystemRuleManager.loadRules(Collections.singletonList(rule));}
D来源访问控制规则:根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的访问控制(黑白名单)的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
相关配置代码:
AuthorityRule rule = new AuthorityRule();rule.setResource("test");rule.setStrategy(RuleConstant.AUTHORITY_WHITE);rule.setLimitApp("appA,appB");AuthorityRuleManager.loadRules(Collections.singletonList(rule));
相关资源端代码:
ContextUtil.enter(resource, origin);Entry entry = null;try { entry = SphU.entry(resource);} catch (BlockException ex) {} finally { if (entry != null) { entry.exit(); } ContextUtil.exit();}
E热点参数规则:热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。目前只支持QPS限流。
热点参数规则较为特殊,首先要引入新的包:
<dependency> <groupId>com.alibaba.cspgroupId> <artifactId>sentinel-parameter-flow-controlartifactId> <version>x.y.zversion>dependency>
具体的规则如下:我们可以设置某个调用参数的参数名与参数类型进行限流,并设置相关参数的QPS,以下代码例子,参数名是PARAM_B,参数类型为int,QPS为10。
private void initHotParamFlowRules() { ParamFlowRule rule = new ParamFlowRule(RESOURCE_KEY) .setParamIdx(0) .setGrade(RuleConstant.FLOW_GRADE_QPS) .setCount(5); ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(PARAM_B)) .setClassType(int.class.getName()) .setCount(10); rule.setParamFlowItemList(Collections.singletonList(item)); ParamFlowRuleManager.loadRules(Collections.singletonList(rule));}
其他相关
框架使用基本介绍完毕,而Sentinel使用时还有另外一些特性,在这一节进行补充说明。
1 注解方式调用
处过普通的代码使用方式,Sentinel框架还支持注解方式的调用。注解方式需要使用新的第三方包,POM引入如下:
<dependency> <groupId>com.alibaba.cspgroupId> <artifactId>sentinel-annotation-aspectjartifactId> <version>x.y.zversion>dependency>
调用方式可以参照相关git代码:
https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
2 异步调用
Sentinel支持异步调用链路的统计。在异步调用中,需要通过SphU.asyncEntry(xxx)方法定义资源,并通常需要在异步的回调函数中调用 exit方法。示例如下:
try { AsyncEntry entry = SphU.asyncEntry(resourceName); doAsync(userId, result -> { try { System.out.println("helloworld"); } finally { entry.exit(); } });} catch (BlockException ex) {}
小结
Sentinel的基本核心调用的使用方式以上基本介绍完毕,我们回顾一下以上的内容,可以和Hystrix等类似框架做一个比较:
我们可以看到,Sentinel确实继承了阿里框架的一贯特点,实用,能打!
最后,我们回顾下一开始提到的Sentinel能够实现的特点:
集群流量控制
消息削峰填谷
流量熔断
实时监控
实时配置
前三点我们的文章已经做了详细说明,可最后两点好像并没提到??
实际上如果说前三点是Sentinel核心功能的话,后两点则是Sentinel 中Dashboard的特点。因此下半部我们集中来介绍Sentinel的控制台。
控制台介绍
现在几乎所有的好用的技术插件都会有一个控制台,Sentinel也不例外。如果说核心jar包是Sentinel的运行心脏,控制台就是他的脸面了。我们能使用控制台干什么呢?
查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
规则管理和推送:统一管理推送规则。
鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
那么下面就是使用了:
在哪下载控制台?
https://github.com/alibaba/Sentinel/releases
如何启动控制台?
java -jar sentinel-dashboard.jar
如何访问控制台?
http://localhost:8080
哈哈,是不是非常简单。
等等,这么简单,一定有坑吧。猜得没错,确实有坑。
注意:当我们搞定以上步骤,访问控制台的时候,打开界面什么内容都没有,只有以下截图:
原来,只有满足两个条件才能出来菜单。
(1)有客户端连接该服务器
(2)客户端流控相关接口有被调用
也就是说,当我们在应用中使用Sentinel后,只有真正的业务流量访问我们的系统后,控制台才能将相关的数据菜单展示出来,否则只能像上图一样,空空如也,什么都看不到了。
有访问后的控制台显示如下:
这样我们就可以清晰的看到Sentinel的控制台都有以下菜单:
下面我们仅仅对各个菜单做一个截图展示:
实时监控
簇点链路
流控规则
降级规则
热点规则
系统规则
授权规则
集群流控
机器列表
以上就是界面的全部截图,请大家根据截图中的表头自行猜测功能,想来作为冰雪聪明的程序员肯定是一看就懂了。
应用接入
应用接入Sentinel的控制台是需要单独再多加一些配置的。
<dependency> <groupId>com.alibaba.cspgroupId> <artifactId>sentinel-transport-simple-httpartifactId> <version>${sentinel.version}version>dependency><dependency> <groupId>com.alibaba.cspgroupId> <artifactId>sentinel-coreartifactId> <version>${sentinel.version}version>dependency><dependency> <groupId>com.alibaba.cspgroupId> <artifactId>sentinel-extensionartifactId> <version>${sentinel.version}version>dependency><dependency> <groupId>com.alibaba.cspgroupId> <artifactId>sentinel-annotation-aspectjartifactId> <version>${sentinel.version}version>dependency>
以上代码的第二段即为我们前文中的核心的POM,其他三部分则是为了接入控制台的单独配置。
将相关jar引入工程后,我们还需要在工程启动中增加一些参数。以下还是以springboot工程为例,被监控业务系统启动命令如下:
java -jar *****.jar -Dproject.name=DemoApplication -Dcsp.sentinel.dashboard.server=localhost:8080 -Dcsp.sentinel.api.port=8722
*****.jar 是我们要启动的应用。-Dproject.name参数指定了应用的资源名,这个参数值会显示在控制台大多数界面中。Dcsp.sentinel.dashboard.server参数则指定了被监控应用的url上下文。Dcsp.sentinel.api.port参数则是指定被监控应用向sentinel控制台回传数据的端口号。因此我们一定要保证在sentinel控制台服务器上,相关端口不被占用哦。准备就绪后启动,我们就能在控制台中看到相关监控结果了。
注意:如果业务系统里面没有用到sentinel,或者相关接口没有被调用,则在监控系统里面看不到该项目。
在应用成功接入控制台后,我们需要重新说明一下规则的加载方式。接入后我们会有两种规则的加载方式。
1 在程序启动时候加载到内存中,具体的做法我们已经在文章的上半部分详细说明。
2 控制台直接配置。这就是控制台的好处啦。我们可以直接在控制台的界面中配置相关流控规则。具体在上文中的流控规、降级规则、热点规则、系统规则、授权规则的界面中,均有新增的按钮。使用新增功能即可增加规则。非常简单易用了。
其他内容
最后我们补充一小部分关于控制台其他问题的说明。
1 集群问题说明。
默认的控制台配置是不支持集群的。如果我们的应用是集群应用,需要额外增加配置中心。以下有一个配置集群控制台的例子,感兴趣的同学可以继续深入研究。https://gitee.com/all_4_you/sentinel-tutorial/blob/master/sentinel-practice/sentinel-cluster-flow-control/sentinel-cluster-flow-environment-build.md
2 持久化问题。
我们默认的控制台所有流控访问的记录与设置其实是保存在控制台服务器的内存中的。根据官方wiki文档,sentinel控制台的实时监控数据,默认仅存储 5 分钟以内的数据。如需持久化,需要定制实现相关接口。这种情况对于我们生产来说当然是无法试用了。
下面这个文章说明了如何一步步实现将监控数据保存在mysql中,感兴趣的同学可以深入研究:
https://www.cnblogs.com/cdfive2018/p/9838577.html
具体数据会在数据库中储存为一张表,表结构如下,供大家参考。
3 商业化。
就算解决了以上两个问题,sentinel的控制台也是很单薄的,想要真正的商业化投产,依旧是不够的。实际上,我们这边已经有同事基于sentinel在做公司的商业化流控平台了,起码需要解决这几个问题:
(1)增加系统登录模块和权限划分
(2)增加配置持久化的功能
(3)增加拦截器,保证系统安全
(4)支持集群
有兴趣的同学可以私信获取进一步的信息。
最后依旧献上我们讲师小哥哥张杰的玉照。这个帅气可爱的小哥哥还是单身狗哦,单身的妹子们赶紧看过来吧。
以上我们的技术专题系列已经第三期啦,也收到了一些关注与留言,希望看的觉得有货的大家也帮助我们多多推广呀,有想法想要发声的同学也可以通过后台留言进行投稿哦。将前两期连接附上,没看过的朋友感兴趣也可以看一看。
第一期 编码有故事
第二期 计算精度与Java编译问题探究