Sentinel: 分布式系统的流量防卫兵
下载sentinel-dashboard-1.7.2.jar
,地址:https://github.com/alibaba/Sentinel/tags
运行jar包,访问localhost:8080
,来到sentinel界面,账号密码都是sentinel
微服务整合sentinel
- 建模块
cloudalibaba-sentinel-service8401
- 导依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<!-- SpringCloud ailibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
- 写yml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21server: port: 8401 spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 #Nacos服务注册中心地址 sentinel: transport: dashboard: localhost:8080 #配置sentinel dashboard地址 port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口 #sentinel会启动一个http server与dashboard进行通信,这个http server要占用8719这个端口 management: endpoints : web: exposure: include: "*"
- 主启动类
1
2
3
4
5
6
7
8@EnableDiscoveryClient @SpringBootApplication public class MainApp8401 { public static void main(String[] args) { SpringApplication.run(MainApp8401.class, args); } }
- controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14@RestController public class FlowLimitController { @GetMapping("/testA") public String testA() { return "-----testA"; } @GetMapping("/testB") public String testB() { return "-----testB"; } }
- 启动8401
此时我们到sentinel中查,发现并8401的任何信息,是因为sentinel是懒加载,需要我们执行一次访问才会有信息,访问localhost:8401/testA,再刷新sentinel的dashboard页面,可以看到已经开始监听
sentinel的流控规则和流控模式
- 资源名:唯一名称,默认请求路径
- 针对来源:Sentine可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
- 阈值类型/单机阈值:
- QPS(每秒钟的请求数量):当调用该api的SPs达到阈值值的时候,进行限流
- 线程数:当调用该api的线程数达到阈值的时候,进行限流
- 是否集群:不需要集群
- 流控模式:
- 直接:api达到限流条件时,直接限流
- 关联:设置关联资源B,当关联的资源B达到阈值时,就限流A自己
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】
- 流控效果:
- 快速失败:直接失败,抛异常
- Warm Up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
- 排队等待:让请求以均匀的速度通过,阈值类型必须设成QPS,否则无效。
sentinel的降级规则
Sentinel 降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)。
- RT(平均响应时间,秒级)
在时间窗口内通过的请求>=5
且平均响应时间超出阈值
,两个条件同时满足后触发降级,在实践窗口内开启断路器,窗口期过后关闭断路器
RT最大4900ms(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效) - 异常比列(秒级)
QPS>=5
且异常比例(秒级统计)超过阈值
时,触发降级;时间窗口结束后,关闭降级 - 异常数(分钟级)
异常数(分钟统计)超过阈值
时,触发降级;时间窗口结束后,关闭降级
sentinel的热点规则
如何自定义降级方法,而不是默认的抛出异常?
使用@SentinelResource直接实现降级方法,它等同Hystrix的@HystrixCommand
定义一个测试方法
1
2
3
4
5
6
7
8
9
10
11
12
13@GetMapping("/testHotKey") // value = "testHotKey"定义一个资源名,blockHandler指定降级方法 @SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey") public String testHotKey(@RequestParam(value = "p1", required = false) String p1, @RequestParam(value = "p2", required = false) String p2) { return "------testHotKey"; } public String deal_testHotKey(String p1, String p2, BlockException exception) { //sentinel系统默认的提示:BLocked by Sentin return "------deal_testHotKey,o(T-T)o"; }
测试:localhost:8401/testHotKey?p1=1
一秒钟内多次访问会发生降级,走的是我们自己定义的降级方法
有一点需要注意,如果是带第二个请求参数的请求,热点规则不会限制,因为我们配置的是第一个参数
sentinel的热点规则的高级选项
参数例外项,可以进行一些高级设置,比如p1为某个特殊值的时候,有单独的限流规则
当参数值为5的时候,有特殊的热点规则
注意
上面的配置中如果程序出现异常,是不会走blockHander的降级方法的
,因为这个@SentinelResource注解的方法只配置了热点规则,这里配置的降级方法是sentinel针对热点规则配置的,只有触发热点规则才会降级
那如何配置异常的降级方法呢,也很简单,在@SentinelResource
注解的注解后加上fallback参数进行定义
sentinel的系统规则
Sentinel系统自适应限流从整体维度对应用入口流量进行控制,结合应用的Load、CPU使用率、总体平
均RT、入口QPS和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和
系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统规则支持以下的模式:
- Load自适应(仅对Linux/Unix-like机器生效):系统的load1作为启发指标,进行自适应系统保护。当系统load1超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR阶段)。系统容量由系统的
maxQps*minRt
估算得出。设定参考值一般是CPU cores*2.5
。 - CPU usage(1.5.d版本):当系统CPU使用率超过阈值即触发系统保护(取值范围0.0-1.0),比较灵敏。
- 平均RT:当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口QPS:当单台机器上所有入口流量的QPS达到阈值即触发系统保护。
@SentinelResource注解
- 在模块
cloudalibaba-sentinel-service8401
中引入自定义的common依赖
1
2
3
4
5
6<dependency><!-- 引用自己定义的api通用包,可以使用Payment支付Entity --> <groupId>com.kk.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency>
- 额外创建一个controller类
1
2
3
4
5
6
7
8
9
10
11
12@RestController public class RateLimitcontroller{ @GetMapping("/byResource") @SentinelResource(value="byResource",blockHandler="handleException") public CommonResult byResource(){ return new CommonResult(200, "按资源名称限流测试ok", new Payment(2020L, "serial001")); } public CommonResult handleException(BlockException exception) { return new CommonResult(444, exception.getClass().getCanonicalName() + "t服务不可用"); } }
- 启动
- 配置限流规则,这里资源名可以是@SentinelSource的value属性值,也可以是@GetMapping的value值
- 访问测试,可看到超过限流规则,会进入我们定义的限流方法
现在我们关闭8401服务,会发现我们配置的限流规则也消失了,关闭服务,规则就没有了。
出现了Hystrix中出现的问题,降级方法与业务方法耦合,一个服务需要配置一个降级方法,并且配置没有实现持久化
自定义限流处理逻辑
- 单独创建一个类,用于处理限流,里面定义了
handlerException1
和handlerException2
1
2
3
4
5
6
7
8
9public class CustomerBlockHandler { public static CommonResult handlerException1(BlockException exception) { return new CommonResult(4444, "按客户自定义,global handlerException---1"); } public static CommonResult handlerException2(BlockException exception) { return new CommonResult(4444, "按客户自定义,global handlerException----2"); } }
- 在controller中,指定使用自定义类中的方法作为降级方法
关键点:
blockHandlerClass = CustomerBlockHandler.class
,指定处理类
blockHandler = "handlerException2"
,指定降级方法
1
2
3
4
5
6@GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2") public CommonResult customerBlockHandler() { return new commonResult(code:200, message:"按客户自定义", new Payment(id:2020L, serial:"serial003")); }
- 启动服务,配置限流规则,然后测试方法是否会按我们配置的那样,由我们指定的处理类和方法进行降级
- 测试成功
注意:降级方法必须与原方法参数一致,并且多一个BlockException exception参数
sentinel的熔断
- 建模块
cloudalibaba-provider-payment9003
和cloudalibaba-provider-payment9004
- 导依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!-- SpringCloud ailibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
- 改yml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17server: port: 9003 spring: application: name: nacos-payment-provider cloud: nacos: discovery: server-addr: localhost:8848 #配置Nacos地址 management: endpoints: web: exposure: include: "*"
- 主启动类加
@EnableDiscoveryClient
注解 - controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18@RestController public class PaymentController{ @Value("${server.port}") private String serverPort; public static HashMap<Long, Payment> hashMap = new HashMap<>(); static{ hashMap.put(1L,new Payment(1L,"28a8cle3bc2742d8848569891fb42181")); hashMap.put(2L,new Payment(2L,"bba8cle3bc2742d8848569891ac32182")); hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183")); } @GetMapping(value="/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){ Payment payment = hashMap.get(id); CommonResult<Payment>result = new CommonResult(200,"frommysql,serverPort:"+serverPort,payment); return result; } }
- 新建一个
cloudalibaba-consumer-nacos-order84
消费者模块 - 导依赖,(nacos-discovery中自动集成了Ribbon)
1
2
3
4
5
6
7
8
9
10<!-- SpringCloud ailibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
- 写yml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17server: port: 84 spring: application: name: nacos-order-consumer cloud: nacos: discovery : server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 #消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者) service-url: nacos-user-service: http://nacos-payment-provider
- 主启动类加
@EnableDiscoveryClient
- 添加RestTemplate配置类
1
2
3
4
5
6
7
8
9@Configuration public class ApplicationContextConfig{ @Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
- controller,为业务方法添加
fallback
来指定降级方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24@RestController public class CircleBreakerController { public static final String SERVICE_URL = "http://nacos-payment-provider"; @Resource private RestTemplate restTemplate; @RequestMapping("/consumer/fallback/{id}") @SentinelResource(value = "fallback", fallback = "handlerFallback") public CommonResult<Payment> fallback(@PathVariable Long id) { CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentsQL/" + id, CommonResult.class, id); if (id == 4) { throw new IllegalArgumentException("rllegalArgumentException,非法参数异常......"); } else if (result.getData() == null) { throw new NullPointerException("NullpointerException,该ID没有对应记录,空指针异常"); } return result; } public CommonResult handlerFallback(@PathVariable Long id, Throwable e) { Payment payment = new Payment(id, "null"); return new CommonResult<>(444, "兜底异常handlerFallback,exception内容" + e.getMessage(), Payment.class); } }
- 启动
cloudalibaba-consumer-nacos-order84
服务
- 访问
http://localhost:84/consumer/fallback/1
,发现调到了9003和9004服务,并且实现了轮询。 - 访问
http://localhost:84/consumer/fallback/4
,会报我们定义的运行时异常,此时会走我们自定义的fallback降级方法。
如果去掉fallback方法,为业务方法添加blockHandler降级方法,看看是什么效果?
①定义blockHandler
方法
1
2
3
4
5public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) { Payment payment = new Payment(id, " null "); return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水:blockException" + blockException.getStackTrace()); }
②在@SentinelSource
注解后加上blockHandler = "blockHandler"
③重新启动84服务,并且定义限流规则或者降级规则
④访问http://localhost:84/consumer/fallback/4
,发现由于去掉了fallback方法,运行时异常没人管了,直接Error Page显示给用户了。但是如果快速访问,QPS达到我们设置的限流规则的话,就能跳转到blockHandler的降级方法
如果是fallback和blockHandler都定义的话会如何呢
测试可以看到,若blockHandler和fallback都进行了配置,快速点击进行访问导致错误的,会由blockhandler
降级方法处理
fallback管运行异常、blockHandler管配置违规
sentinel整合ribbon+openFeign+fallback
- 84模块中导入OpenFeign的依赖
1
2
3
4
5<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
- 配置yml文件,激活sentinel对feign的支持
1
2
3
4
5#激活sentinel对feign的支持 feign : sentinel: enabled: true
- 主启动类上加
@EnableFeignClients
注解 - 创建远程调用pay模块的接口
1
2
3
4
5
6@FeignClient(value = "nacos-payment-provider") public interface PaymentService { @GetMapping(value = "/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id); }
- 创建这个接口的实现类,用于降级
1
2
3
4
5
6
7
8@Component public class PaymentFallbackService implements PaymentService { @Override public CommonResult<Payment> paymentSQL(Long id) { return new CommonResult<>(444, "服务降级返回,--PaymentFallbackservice"); } }
- 再次修改接口,指定降级类,就是
@FeignClient
注解后添加fallback= PaymentFallbackService.class
属性 - controller添加远程调用
1
2
3
4
5
6
7@Resource private PaymentService paymentservice; @GetMapping(value="/consumer/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){ return paymentservice. paymentSQL(id); }
- 启动9003和84,关闭9003,测试84服务是否会降级,测试成功
sentinel的规则持久化
将限流配置规则持久化进Nacos保存
,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
- 在pom文件中导入相关依赖
1
2
3
4
5
6<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
- yml配置文件
实际上就是指定,我们的规则要保证在哪个名称空间的哪个分组下,这里没有指定namespace,但是是可以指定的,注意这里的dataid要与服务名一致
1
2
3
4
5
6
7
8
9
10
11
12spring: cloud: sentinel: datasource: ds1: nacos: server-addr: localhost:8848 dataId: cloudalibaba-sentinel-service(服务名) groupId: DEFAULT_GROUP data-type: json rule-type: flow
- 在nacos中创建一个配置文件,dataId就是上面配置文件中配置的dataId:
cloudalibaba-sentinel-service
(不用加yaml),将规则配入,以json格式
1
2
3
4
5
6
7
8
9
10[{ "resource": "访问的路径", "limitApp": "default", "grade": 1, "count": 1, "strategy": 0, "controlBehavior": 0, "clusterMode": false }]
resource:资源名称;(说白了就是ip:端口
后面的那一串controller定义的访问uri)
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。
这样配置后,服务重启,可看到所配置的规则会恢复,从而实现规则配置的持久化
最后
以上就是哭泣蜻蜓最近收集整理的关于SpringCloud Alibaba服务熔断降级与限流之【Sentinel】Sentinel: 分布式系统的流量防卫兵的全部内容,更多相关SpringCloud内容请搜索靠谱客的其他文章。
发表评论 取消回复