雪崩效应java
雪崩效应如上图所示,咱们在微服务中的调用链中,当一个基础微服务的API接口A不可用时,当B调用A的服务会堆积阻塞,由于咱们知道咱们每一次调用,不管是调用方仍是服务提供方,其实都是一个线程,而这些线程通常都是线程池中的线程。通常一个线程池中的线程数是有限的,一直到请求超时的时候,这个线程才会被释放(在正常状况下,任务执行完毕,线程释放,因此要求每一个调用的执行时间越短越好,便于线程池中的线程不断重复使用,不出现阻塞)。在高并发的状况下,B的线程池中的线程资源会被瞬间彻底占用,在短时间内再也没法建立线程来执行任务,因而B停摆,挂掉了。同理,C、D服务在后续调用中也被B搞挂了。咱们把基础服务故障,致使上层服务故障,而且这个故障不断放大的过程,称为雪崩效应。linux
常见容错方案git
Sentinel的控制台安装github
控制台下载地址 https://github.com/alibaba/Sentinel/releasesweb
根据你本身的版本挑选Sentinel的版本号,我这里使用的是1.7.0spring
下载好后使用docker
java -jar sentinel-dashboard-1.7.0.jarjson
使用docker安装控制台浏览器
docker pull bladex/sentinel-dashboard:1.7.0服务器
docker run -d --name sentinel -p 8858:8858 bladex/sentinel-dashboard:1.7.0
这里须要注意的是用docker启动的该实例端口号为8858,而不是咱们正常启动jar包的8080
启动后,界面以下,访问端口8080(以jar包启动为例)
输入用户名sentinel,密码sentinel后登陆后,界面以下,目前控制台中,什么都没有。
项目整合控制台
pom
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
添加配置
spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 sentinel: transport: dashboard: 127.0.0.1:8080 application: name: nacos
启动该项目,访问该项目的一个依赖于user的接口
@RestController public class BalanceController { @Autowired private RestTemplate restTemplate; @GetMapping("/trace") public String trace() { return restTemplate.getForObject("http://user/find", String.class); } }
因为user未启动,访问结果以下
刷新Sentinel控制台(此处的nacos是项目名叫nacos)
咱们多刷新几回trace接口的访问,此时再看到实时监控已经有数据了
在簇点链路中,咱们能够看到咱们访问过的url
咱们点流控按钮,能够对该访问API进行流控设置
好比我此处设置QPS为1的时候,设置为访问直接失败,点保存,当咱们不断的点浏览器刷新,就会出现访问被限流点状况。
第二个关联,当关联的资源达到阈值,就限流本身。
比方说咱们有另一个接口/trace
如今我用代码一直去访问这个/trace
public class TestFind { public static void main(String[] args) throws InterruptedException { RestTemplate restTemplate = new RestTemplate(); for (int i = 0;i < 10000;i++) { restTemplate.getForObject("http://127.0.0.1:8081/trace", String.class); Thread.sleep(500); } } }
此时,这个/trace的QPS确定是大于1的。咱们用浏览器来访问/find,结果以下
此处能够考虑的业务场景为某一个资源的读写优先级,根据业务需求,来进行关联限流。
第三个链路 只记录指定链路上的流量(经测试好像无效,正常配置以下)
如今来写一个测试代码
@Slf4j @Service public class CommonService { @SentinelResource("common") public String common() { log.info("Common"); return "Common"; } }
@RestController public class CommonController { @Autowired private CommonService commonService; @GetMapping("test-a") public String testA() { return commonService.common(); } @GetMapping("test-b") public String testB() { return commonService.common(); } }
咱们访问test-a
咱们能够在簇点链路中找到此次访问的状况,咱们会发如今/test-a下面有一个common,点击common的流控
进行以下设置
因为该设置无效,就不提供测试结果了。可是对资源点common设置"直接"是有效的。
Warm Up(预热) 根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,通过预热时长,才达到设置的QPS阈值。
这里设置的意思是一开始的QPS阈值为100/3,在10秒内不断上升到100。这里主要使用于一开始有大量突增激发流量到状况下,设置为该模式,不让这种突发到并发访问撑破服务器。
排队等待 匀速排队,让请求以均匀到速度经过,阈值类型必须设置为QPS,不然无效。
这个图的意思是1秒内只有1个请求容许经过,若是这个请求达到超时时间,就丢弃该请求。
用代码来测试
@Slf4j public class OneByOneTest { public static void main(String[] args) { RestTemplate restTemplate = new RestTemplate(); for (int i = 0;i < 10000;i++) { long start = System.currentTimeMillis(); String object = restTemplate.getForObject("http://127.0.0.1:8081/test-a", String.class); log.info(object + ",间隔时间:" + (System.currentTimeMillis() - start)); } } }
运行结果(部分):
15:44:15.463 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:15.474 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:15.482 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:15.483 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:15.485 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,间隔时间:45
15:44:15.486 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:15.486 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:16.484 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:16.484 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:16.484 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,间隔时间:998
15:44:16.485 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:16.485 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:17.483 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:17.483 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:17.483 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,间隔时间:999
15:44:17.484 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:17.484 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:18.485 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:18.485 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:18.485 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,间隔时间:1002
15:44:18.486 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:18.486 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:19.481 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:19.481 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:19.481 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,间隔时间:996
15:44:19.482 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:19.482 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:20.485 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:20.485 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:20.485 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,间隔时间:1004
15:44:20.486 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:20.486 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:21.485 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:21.485 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:21.486 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,间隔时间:1001
15:44:21.486 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8081/test-a
15:44:21.486 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
15:44:22.485 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
15:44:22.485 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
15:44:22.485 [main] INFO com.cgc.cloud.nacos.OneByOneTest - test-a,间隔时间:999
咱们能够看到,基本上每次请求的间隔为1秒。没有一个请求被丢弃。而以前的两种——快速失败和Worm Up,都会丢弃请求。该方式适用于流量访问不均衡的状况,有激增时段,有空闲时段,当激增时段到来时,不断让流量缓缓经过,用空闲时段来慢慢处理。
降级规则设置
咱们继续使用/test-a来讲明
点击降级按钮
这里RT为平均响应时间,上面设置为1毫秒,时间窗口5,这里的整体意思表示以下
平均响应时间(秒级统计)超出阈值(此处为大于1毫秒)而且在5秒内经过的请求>=5次——>触发降级(熔断器打开)——>时间窗口5秒结束——>关闭降级
咱们访问一次该接口,能够看到单次的相应时间为3毫秒,这个3毫秒是确定大于1毫秒的。咱们不断的刷新该接口,当咱们连续刷5次,到第6次的时候
触发降级策略,
那么在5秒内,咱们的访问就会一直是限流状态,直到5秒后才能够正常访问。
降级注意点
RT默认最大4900ms
这里就是说这个RT设置的再大,好比100000,它依然仍是按4900ms来执行,若是必定要修改这个值,能够经过-Dcsp.sentinel.statistic.max.rt=xxx修改
降级-异常比例
QPS >= 5而且异常比例(秒级统计,0.0-1.0)超过阈值——>触发降级(熔断器打开)——>时间窗口5秒结束——>关闭降级
降级-异常数
异常数(分钟统计)超过阈值——>触发降级(熔断器打开)——>时间窗口65秒结束——>关闭降级。
此处不一样于RT和异常比例都是以秒级统计,而异常数是以分钟统计的。
注意点 时间窗口<60秒可能会出问题。好比作以下配置
这里的意思为当1分钟之内的异常数大于10,就会触发降级,直到10秒的时间窗口结束,关闭降级。但异常数的统计是在分钟级别的,可能10秒结束的时候依然在1分钟之内,异常数依然大于10次,那么就会再次进入降级。因此时间窗口建议设置大于等于60的值。
由此能够看出,相对于断路器三态状态,Sentinel的断路器没有半开状态。但可能会在将来增长。
热点规则
添加一个Controller方法以下
@GetMapping("/testhost") @SentinelResource("hot") public String testHost(@RequestParam(value = "a",required = false) String a,@RequestParam(value = "b",required = false) String b) { return a + " " + b; }
访问该方法
在Sentinel控制台的蔟点链路中,点击hot的热点按钮
这里的参数索引0指的是@RequestParam(value = "a",required = false) String a,参数索引从0开始,若是设为1则为@RequestParam(value = "b",required = false) String b。这里表示若是有参数a传输过来,则在1秒内只容许有1个请求能够访问。若是咱们不填参数a.
则不管咱们怎么刷新,都不会被限流。
咱们再打开高级选项,参数类型选择a的类型String,参数值abc(此处好像不支持中文),限流阈值1000.添加,保存后。
若是咱们将a值填为abc,此时不管咱们如何刷新都不会被限流
应用场景:若是咱们发现某一个热词的提交很是高,则咱们能够设置热点规则,进行对单个热词进行限流,而不限制整个API接口的流量。
热点注意点
系统规则
咱们进入系统规则标签,点新增系统规则按钮会看到如图所示的界面。
这里有几个选项
当系统load1(1分钟的load)超过阈值,且并发线程数超过系统容量时触发,建议设置为CPU核数*2.5。(仅对Linux/Unix-like机器生效)
这里须要知道什么是load1,能够由linux uptime命令来看
[root@localhost ~]# uptime
10:46:36 up 11 days, 19:57, 1 user, load average: 0.10, 0.11, 0.13
这里的0.10被称为load1,意思就是1分钟之内的系统平均负载;0.11被称为load5,5分钟之内的系统平均负载,0.13被称为load15,15分钟内的系统平均负载。
系统容量 = maxQps * minRt
受权规则
假如咱们要给find添加受权规则
这里指的是受权给哪个微服务添加黑白名单,白名单指的是只容许某一个微服务访问,黑名单是禁止某一个微服务访问。
代码配置规则
其实以上的控制台的设置均可以直接在项目中使用代码来配置
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的做用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS或线程数模式 | QPS模式 |
limitApp | 流控针对的调用来源 | default,表明不区分调用来源 |
strategy | 判断的根据是资源自身,仍是根据其余关联资源(refResource),仍是根据链路入口 | 根据资源自己 |
controlBehavior | 流控效果(直接拒绝/排队等待/慢启动模式) | 直接拒绝 |
配置代码以下
@Component public class FlowRuleConfig { @PostConstruct public void init() { initFlowQpsRule(); } private void initFlowQpsRule() { List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule("/find"); rule.setCount(1); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); rules.add(rule); FlowRuleManager.loadRules(rules); } }
这段代码跟以前的控制台的配置效果是同样的。
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即限流规则的做用对象 | |
count | 阈值 | |
grade | 降级模式,根据RT降级仍是根据异常比例降级 | RT |
timeWindow | 降级的时间,单位为s |
配置代码以下
@Component public class FlowRuleConfig { @PostConstruct public void init() { initDegradeRule(); } private void initDegradeRule() { List<DegradeRule> rules = new ArrayList<>(); DegradeRule rule = new DegradeRule(); rule.setResource("/find"); rule.setCount(1); rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); rule.setTimeWindow(5); rules.add(rule); DegradeRuleManager.loadRules(rules); } }
当咱们在5秒内访问/find到6次以后开始降级,并在5秒内不容许访问该接口,直到5秒后才能够正常访问。
属性 | 说明 | 默认值 |
---|---|---|
resource | 资源名,必填 | |
count | 限流阈值,必填 | |
grade | 限流模式 | QPS模式 |
durationInSec | 统计窗口时间长度(单位为秒),1.6.0版本开始支持 | 1s |
controlBehavior | 流控效果(支持快速失败和匀速排队模式),1.6.0版本开始支持 | 快速失败 |
maxQueueingTimeMs | 最大排队等待时长(仅在匀速排队模式生效),1.6.0版本开始支持 | 0ms |
paramIdx | 热点参数的索引,必填,对应SphU.entry(xxx,args)中的参数索引位置 | |
paramFlowItemList | 参数例外项,能够针对指定的参数值单独设置限流阈值,不受前面count阈值的限制。仅支持基本类型 | |
clusterMode | 是不是集群参数流控规则 | false |
clusterConfig | 集群流控相关配置 |
代码配置以下
@Component public class FlowRuleConfig { @PostConstruct public void init() { initParamFlowRule(); } private void initParamFlowRule() { ParamFlowRule rule = new ParamFlowRule("hot") .setParamIdx(0) .setCount(1); ParamFlowItem item = new ParamFlowItem().setObject("abc") .setClassType(String.class.getName()) .setCount(1000); rule.setParamFlowItemList(Collections.singletonList(item)); ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); } }
当咱们在1秒内访问/testhost接口超过1次的时候就会被限流报错
可是若是咱们a的值为abc的时候,则不断刷新都不受影响
Field | 说明 | 默认值 |
---|---|---|
highestSystemLoad | 最大的load1 | -1(不生效) |
avgRt | 全部入口流量的平均响应时间 | -1(不生效) |
maxThread | 入口流量的最大并发数 | -1(不生效) |
qps | 全部入口资源的QPS | -1(不生效) |
代码配置以下
@Component public class FlowRuleConfig { @PostConstruct public void init() { initSystemRule(); } private void initSystemRule() { List<SystemRule> rules = new ArrayList<>(); SystemRule rule = new SystemRule(); rule.setHighestSystemLoad(20); rules.add(rule); SystemRuleManager.loadRules(rules); } }
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即限流规则的做用对象 | |
limitApp | 对应的黑名单/白名单,不一样origin用','分割,如appA,appB | default,表明不区分调用来源 |
strategy | 限制模式,AUTHORITY_WHITE为白名单模式,AUTHORITY_BLACK为黑名单模式,默认为白名单模式 | AUTHORITY_WHITE |
代码配置以下
@Component public class FlowRuleConfig { @PostConstruct public void init() { initAuthorityRule(); } private void initAuthorityRule() { AuthorityRule rule = new AuthorityRule(); rule.setResource("nacos"); rule.setStrategy(RuleConstant.AUTHORITY_WHITE); rule.setLimitApp("appA,appB"); AuthorityRuleManager.loadRules(Collections.singletonList(rule)); } }