上一篇说了微服务上的限流,用的是guava的RateLimit做的简单的限流,本篇说用阿里的sentinel 做微服务的限流。
sentinel 是阿里2018年开源的一个开源项目,具体中文文档:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D 。
简单来说 Sentinel就是干三件事: 限流、熔断、降级,来保证服务高可用,不会崩掉(你本身不会崩掉,也不会因为依赖的其他服务崩掉把你带死)。
一、简单的限流使用场景
流控
实验项目
1,引依赖(微服务上)
找到最新版的maven依赖,加到nb-order-api服务
<!--阿里巴巴sentinel--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.7.1</version> </dependency>
2,定义资源
最常见的就是你要保护的方法,或者一段逻辑。
手动编码方式,下面代码中红色部分就是定义资源try-catch中间就是要保护的逻辑
* 创建订单 * @param info * @param * @return */ //注解生效需在启动类配置@EnableGlobalMethodSecurity(prePostEnabled = true) //@PreAuthorize("#oauth2.hasScope('write')") @PreAuthorize("hasRole('ROLE_ADMIN')") @PostMapping public OrderInfo create(@RequestBody OrderInfo info,@AuthenticationPrincipal String username){ try(Entry entry = SphU.entry("createOrder")){ //资源名称-createOrder // 被保护的逻辑 log.info("获取到username = {}",username); }catch (BlockException ex){ // 处理被流控的逻辑 log.info("blocked!"); } //查询价格 //PriceInfo price = restTemplate.getForObject("http://localhost:9080/prices/"+info.getProductId(),PriceInfo.class); //log.info("price is "+price.getPrice()); return info; }
3,定义规则
针对某个定义的资源(上面的createOrder),定义一个规则,规则有很多种:流控规则、熔断规则、降级规则,都可以对定义的资源定义规则,这里做一个流控规则的演示。
新建sentinel的配置类
package com.nb.security; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * sentinel规则,硬编码方式 */ @Component public class SentinelConfig implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { //系统启动好以后执行 List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); rule.setResource("createOrder");//资源名称 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(1);//设置QPS=1,每秒1个请求 rules.add(rule); FlowRuleManager.loadRules(rules); } }
实验:启动订单服务,用postman直接请求创建订单服务 http://localhost:9060/orders ,先跳过网关以方便做实验
查看打印日志可以看出,每秒只能有一个请求通过,其他的则走了catch限流逻辑,这里就可以自定义处理。
3,sentinel日志
实验运行之后,会生成一些日志文件,在windows上的目录是:C:Usersdevlogscsp
sentinel-record.log.2020-03-16.0 , 可以看出sentinel定义的规则
2020-03-16 09:02:21.729 INFO [FlowRuleManager] Flow rules loaded: {} 2020-03-16 09:02:21.732 INFO App name resolved: com.nb.security.NbOrderApiApplication 2020-03-16 09:02:21.734 INFO [SentinelConfig] Application type resolved: 0 2020-03-16 09:02:21.736 INFO [MetricWriter] Creating new MetricWriter, singleFileSize=52428800, totalFileCount=6 2020-03-16 09:02:21.738 INFO [DynamicSentinelProperty] Config will be updated to: [FlowRule{resource=createOrder, limitApp=default, grade=1, count=1.0, strategy=0, refResource=null, controlBehavior=0, warmUpPeriodSec=10, maxQueueingTimeMs=500, clusterMode=false, clusterConfig=null, controller=null}] 2020-03-16 09:02:21.741 INFO [FlowRuleManager] Flow rules received: {createOrder=[FlowRule{resource=createOrder, limitApp=default, grade=1, count=1.0, strategy=0, refResource=null, controlBehavior=0, warmUpPeriodSec=10, maxQueueingTimeMs=500, clusterMode=false, clusterConfig=null, controller=com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController@2e2b9f53}]} 2020-03-16 09:03:24.012 INFO [InitExecutor] Found init func: com.alibaba.csp.sentinel.metric.extension.MetricCallbackInit 2020-03-16 09:03:24.017 INFO [InitExecutor] Executing com.alibaba.csp.sentinel.metric.extension.MetricCallbackInit with order 2147483647 2020-03-16 09:03:24.019 INFO Add child <sentinel_default_context> to node <machine-root> 2020-03-16 09:03:24.023 INFO [SlotChainProvider] Global slot chain builder resolved: com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder 2020-03-16 09:03:24.030 INFO Add child <createOrder> to node <sentinel_default_context> 2020-03-16 09:03:24.032 INFO [AuthorityRuleManager] Load authority rules: {} 2020-03-16 09:03:24.037 INFO [SystemRuleManager] Current system check status: false, highestSystemLoad: 1.797693e+308, highestCpuUsage: 1.797693e+308, maxRt: 9223372036854775807, maxThread: 9223372036854775807, maxQps: 1.797693e+308 2020-03-16 09:03:24.039 INFO [DegradeRuleManager] Degrade rules loaded: {} 2020-03-16 09:03:24.042 INFO [MetricExtensionProvider] MetricExtension resolved, size=0 2020-03-16 09:03:25.743 INFO [MetricWriter] New metric file created: C:Usersdevlogscspcom-nb-security-NbOrderApiApplication-metrics.log.2020-03-16 2020-03-16 09:03:25.744 INFO [MetricWriter] New metric index file created: C:Usersdevlogscspcom-nb-security-NbOrderApiApplication-metrics.log.2020-03-16.idx
com-nb-security-NbOrderApiApplication-metrics.log.2020-03-16 可以看出流量变化情况
|--timestamp-|------date time----|--resource-|p |block|s |e|rt
1584320604000|2020-03-16 09:03:24|createOrder|1|0|1|0|14|0|0|0
1584320610000|2020-03-16 09:03:30|createOrder|1|0|1|0|0|0|0|0
1584320611000|2020-03-16 09:03:31|createOrder|1|0|1|0|0|0|0|0
1584320713000|2020-03-16 09:05:13|createOrder|1|0|1|0|1|0|0|0
1584320716000|2020-03-16 09:05:16|createOrder|1|1|1|0|1|0|0|0
1584320718000|2020-03-16 09:05:18|createOrder|1|0|1|0|0|0|0|0
1584320719000|2020-03-16 09:05:19|createOrder|1|0|1|0|1|0|0|0
1584320722000|2020-03-16 09:05:22|createOrder|1|1|1|0|0|0|0|0
1584320723000|2020-03-16 09:05:23|createOrder|1|0|1|0|0|0|0|0
1584320724000|2020-03-16 09:05:24|createOrder|1|0|1|0|0|0|0|0
其中 p
代表通过的请求, block
代表被阻止的请求, s
代表成功执行完成的请求个数, e
代表用户自定义的异常, rt
代表平均响应时长。
4,启动 Sentinel 控制台
下载 Dashboard jar包 :https://github.com/alibaba/Sentinel/releases ,是一个springboot项目。
启动jar包: 其中 -Dserver.port=8888
用于指定 Sentinel 控制台端口为 8888
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar
默认用户名 密码都是sentinel ,如需修改请参照官方文档
首页 ,默认是他自己
5,客户端接入控制台
有两种方式,一种是通过制定JVM参数的形式,这种springboot不友好这里就不说了。
如果是springboot项目就应该这么做,通过Spring Cloud Alibaba Sentinel配置:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
引依赖(不需要上面说的 sentinel-transport-simple-http的依赖了)
<!-- https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>0.9.0.RELEASE</version>
</dependency>
使用注解定义资源
配置控制台信息 application.yml
spring:
cloud:
sentinel:
transport:
port: 8719
dashboard: localhost:8888
这里的 spring.cloud.sentinel.transport.port
端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了一个限流规则,会把规则数据 push 给这个 Http Server 接收,Http Server 再将规则注册到 Sentinel 中
启动order-api,http://localhost:8888 就可以访问sentinel控制台了
二,熔断降级
为什么要有熔断降级?
服务雪崩举例:
比如A是订单服务,它会调用商品服务B,调用价格服务C,调用用户服务D。
服务一切正常的时候是没问题的,假设用户服务D因为网络抖动或者压力变大,导致响应时间变慢,原来是10毫秒,现在变成了100、1000毫秒。
这时候A服务调D服务的时候,当前线程就会卡住了,线程就会等待D服务的响应。等待的时候继续有请求进来调用A服务,A服务就会开新的线程去调D服务,D服务没响应,就会在这等着。这样请求A服务的继续进来,请求就一直的等着,到了一定阈值后,就没办法新建线程了,所有线程都占满了,此时A服务也失去响应了。此时有依赖A服务的服务也不行了,引起连锁反应。根本的问题是D服务不行了,这样导致跟D有依赖关系的所有的服务都出问题。这就是服务雪崩。
熔断:
这时候就需要用熔断的方式,保证某一个服务出问题,不会把直接或者间接依赖它的所有的服务都带死。
原理就是在所有的服务前面加一个熔断器,熔断器平常处于关闭的状态,熔断器一旦发现后面服务(服务D)有问题不可用了,就打开熔断器。此时服务A再调用服务D的时候,到熔断器这就直接返回了。
请求不会到达服务D,服务A这就不会因为服务D响应变慢而有线程堆积,等待,这样就解决了服务雪崩的问题。
熔断器机制示意
1,当一个请求过来
如果熔断器是关闭的,请求就放给后面的业务父服务。
如果业务服务出问题(抛异常、网络抖动、压力大响应时间变长、服务超时),在熔断器这都会做一个统计,比如从熔断器放过去10个请求,5个超时,3个报错,熔断器都会进行统计。
可以在自己配置熔断器什么情况从关闭状态变为打开状态。如果触发了熔断器打开状态,请求到熔断器这就直接返回回去了。
2,如果是因为网络抖动 或者 瞬时压力变大导致的熔断器打开,等网络恢复或者后面压力变小了,其实是又可以正常提供服务的,就需要有个机制让熔断器知道后面的服务恢复正常了,重新让熔断器回到关闭状态。
机制就是:熔断器这会有一个定时任务,在一定的时间周期内,到了时间周期,就将熔断器转为【半打开状态】,会放一个请求给后面的业务服务,如果业务服务可以成功处理,就从半打开状态变为关闭状态。
如果放过去的这一个请求仍然是失败状态,熔断器就回到打开状态。过一个时间周期,再做一次这么的循环,一旦有一个请求成功了,就转为关闭状态。
降级:
熔断器打开的时候,指定一个更简化的业务逻辑处理。
比如,后面的服务是一个推荐的服务,正常的推荐是根据用户的用户画像,算出推荐的商品,但是这个服务需要实时计算的,如果压力大变慢了,熔断器处于打开状态后,可以指定一个预先设计好的简化的逻辑,一个商品列表,静态的。当熔断器打开的时候就直接返回这个列表,用户还可以看到一个商品列表,但是就不是根据用户推荐给他的,服务降级了。
在sentinel指定熔断降级规则
1,通过编码方式
调用创建订单,跨省
熔断异常信息
降级
一般通过控制台指定各种规则,那就可以去掉 SentinelConfig配置了。
代码 :https://github.com/lhy1234/springcloud-security/tree/chapt-6-6-sentinel-ratelimit