Sentinel搭建流程

雪崩效应java

雪崩效应如上图所示,咱们在微服务中的调用链中,当一个基础微服务的API接口A不可用时,当B调用A的服务会堆积阻塞,由于咱们知道咱们每一次调用,不管是调用方仍是服务提供方,其实都是一个线程,而这些线程通常都是线程池中的线程。通常一个线程池中的线程数是有限的,一直到请求超时的时候,这个线程才会被释放(在正常状况下,任务执行完毕,线程释放,因此要求每一个调用的执行时间越短越好,便于线程池中的线程不断重复使用,不出现阻塞)。在高并发的状况下,B的线程池中的线程资源会被瞬间彻底占用,在短时间内再也没法建立线程来执行任务,因而B停摆,挂掉了。同理,C、D服务在后续调用中也被B搞挂了。咱们把基础服务故障,致使上层服务故障,而且这个故障不断放大的过程,称为雪崩效应。linux

常见容错方案git

  • 超时 咱们在每次请求中都设置一个较短的超时时间,好比1秒,这样,只要线程释放的速度够快,那么在上图中的B服务就不那么容易被A服务拖死。
  • 限流 咱们知道,一个请求会有一个最大的吞吐量qps,咱们假设C、D调用B服务的请求达到一个阈值,即使A服务挂了,那么B请求A会在不断正常超时的状况下,依然能够释放线程,那么咱们能够给B作这样的限流,不让C、D请求B的并发量过大,使得B能够正常运行。
  • 仓壁模式 给同一个服务不一样的Controller设立不一样的线程池,彼此不影响,而且每个Controller的独立线程池的线程数并不大。
  • 断路器模式 给具体的某一个Restful API进行监控,若是在设定的时间内,好比5秒内的错误率,错误次数达到一个阈值,断路器自动切断该API的访问,断路器处于打开状态。在一段时间后,断路器会处于半开状态,会进行一种尝试性的访问,若是此时访问成功,断路器会关闭,容许该接口从新被正常访问。在次模式下,好比上图中的A服务失败,B服务访问A若干次后,失败,断路器关闭B服务的访问,若A服务恢复使用,则B服务无需人工干预,则本身能够恢复访问。

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接口的流量。

热点注意点

  • 参数必须是基本类型或者String

系统规则

咱们进入系统规则标签,点新增系统规则按钮会看到如图所示的界面。

这里有几个选项

  • Load 负载

当系统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

  1. maxQps: 秒级统计出来的最大QPS
  2. minRt: 秒级统计出来的最小响应时间
  • RT 平均响应时间,全部入口流量的平均RT达到阈值触发。
  • 线程数 全部入口流量的并发线程数达到阈值触发。
  • 入口QPS 全部入口流量的QPS达到阈值触发。

受权规则

假如咱们要给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));
}
}
相关文章
相关标签/搜索