一,Sentinel引入原因
1,分布式系统遇到的问题
通常情况下,一个业务逻辑往往会依赖多个服务,如一个商品展示服务会依赖商品服务,价格服务,商品评论服务等。在用户发起请求之后,在一个商品详情服务,会通过一个创建线程池,比如说里面创建100个线程,用来加载商品服务,价格服务,商品评论服务。
当用户去大量评论的时候,那么线程全部会在这个商品评论服务上面,那么会导致其他服务不可用,那么就会造成一个服务雪崩状态。
造成服务雪崩的原因
1,大流量请求:如在秒杀的时候,准备不充分,瞬间大量请求进来,导致服务提供者不可用
2,硬件故障:服务器出现宕机情况
3,出现缓存击穿:热点数据同一时间失效
2,解决服务雪崩问题
2.1,超时机制
加入一个超时机制,当一个服务在一定时间内没有得到响应,就会释放资源,从而抑制资源耗尽的问题。
2.2,服务限流
限制请求核心服务提供者的流量,使大流量拦截在核心服务之外。可以通过线程池加队列的方式实现,队列可以是一个阻塞队列。
2.3,服务熔断
和现实中的断路器一样,用于监控电路情况,如果发现电路电流异常,就会跳闸,从而防止这个电路被烧毁。
断路器的基本原理:就是说在一段时间内,如果发现失败的次数或者失败率达到一个阈值的一个情况,那么就会出现一个跳闸的情况,这个断路器就会打开,请求就会直接return返回;跳闸一段时间之后,断路器就会进入一个半开的状态,就是一个瞬时态,允许发起一次请求,如果成功,则断路器关闭,走原来的正常流程,如果不成功,那么这个断路器又会打开 ,一段时间之后又会进入一个半开的状态,如此循环下去。
2.4,服务降级
有熔断就一定会有降级。就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的fallback(回退)回调,返回一个缺省值。或者流量前面的流量太大,后面的服务处理能力不足,会对前面的服务进行一个降级操作
二,Sentinel工作原理
1,什么是Sentinel
Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。只要通过Sentinel API 定义的代码,就是资源,能够被 Sentinel保护起来
2,sentinel控制台安装
1,下载这个jar包,在本地跑即可。
[ Releases · alibaba/Sentinel · GitHub ]( Releases · alibaba/Sentinel · GitHub )
2,启动jar包
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.5.jar
3,默认端口为8080,输入localhost:8080,然后登录, 默认用户名和密码都是 sentinel
4,登陆完成之后,可以看到这个sentinel的主界面。可以发现里面有大量的规则,比如说流控规则,熔断规则,热点规则,授权规则等。
3,客户端连接这个sentinel控制台
1,需要的依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2021.1</version> </dependency>
2,yml配置文件
spring: cloud: sentinel: transport: #注册的地点 dashboard: 127.0.0.1:8080 filter: enabled: false
4,sentinel工作流程
4.1,资源
由应用程序提供的服务,比如说是一个接口,甚至是一段代码等。就是说只要被这个sentinel API定义了的代码,就是一个资源,就可以被这个sentinel保护起来。所有的资源都对应一个资源名称(resourceName),每次资源在调用时都会创建一个 Entry 对象
4.2,规则
就是最绕这个资源所设定的一些规则,如一些限流规则,降级规则,熔断规则等。并且所有的规则可以动态调整。
4.3,插槽
因此在这个资源创建的时候Entry 创建的时候,同时也会创建一系列功能插槽(slot chain),这个插槽有不同的职责。
FlowSlot:限流
DegradeSlot:熔断
AuthoritySlot:授权
4.4,工作流程
模拟一个工作流程,对一个接口进行一个限流操作,大致流程如下,首先会定义一个资源,其次会对这个资源定义一个规则,最后会去检测这个规则是否生效。
1,传统api的方式
private static final String RESOURCE_NAME = "getUserMessage"; #这个接口就是一个限流的一个对象 @RequestMapping(value = "/getUserMessage") public String getPrice(){ Entry entry = null; try{ //资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。 //会将这个资源封装成一个Entry的一个对象 entry = SphU.entry(RESOURCE_NAME); }catch (BlockException e1) { //限流异常,如果达到一个阈值,就会进入这个异常 return "被流控了!"; }catch (Exception ex) { //正常的业务逻辑的异常捕获 Tracer.traceEntry(ex, entry); } finally { //关闭资源 if (entry != null) { entry.exit(); } } return null; }
规则可以在容器初始化的时候就可以进行一个制定,通过这个**@PostConstruct**注解提前对这个规则进行一个初始化。
@PostConstruct private static void initFlowRules(){ List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); //设置受保护的资源 rule.setResource(RESOURCE_NAME); // 设置流控规则 QPS,每秒的请求数 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 设置受保护的资源阈值,每秒只能访问一次 rule.setCount(1); rules.add(rule); // 加载配置好的规则 FlowRuleManager.loadRules(rules); }
这样的话就简单的实现了一个限流的一个应用。同时也可以再控制台上面看到
2,注解的方式
@RequestMapping("/info/{id}") @SentinelResource(value = "userinfo", blockHandlerClass = DefaultSentinelHandler.class, //用于流控的配置类 blockHandler = "handleException2", //流控类里面的配置方法 fallbackClass = DefaultFallback.class, //回调方法 fallback = "fallback" //回调类 ) public R info(@PathVariable("id") Integer id){ User user = userService.getById(id); if(id==4){ throw new IllegalArgumentException("异常参数"); } return R.ok().put("user", user); } //流控类里面的配置方法 public R handleException2(@PathVariable("id") Integer id, BlockException exception){ return R.error(-1,"===被限流降级啦==="); } //回调类里面的方法 public R fallback(@PathVariable("id") Integer id,Throwable e){ return R.error(-1,"===被熔断降级啦==="+e.getMessage()); }
也可以使用使用一个统一管理的配置类,将几种规则统一管理
@Slf4j @Component public class MyBlockExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { log.info("BlockExceptionHandler BlockException================"+e.getRule()); R r = null; if (e instanceof FlowException) { r = R.error(100,"接口限流了"); } else if (e instanceof DegradeException) { r = R.error(101,"服务降级了"); } else if (e instanceof ParamFlowException) { r = R.error(102,"热点参数限流了"); } else if (e instanceof SystemBlockException) { r = R.error(103,"触发系统保护规则了"); } else if (e instanceof AuthorityException) { r = R.error(104,"授权规则不通过"); } //返回json数据 response.setStatus(500); response.setCharacterEncoding("utf-8"); response.setContentType(MediaType.APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(response.getWriter(), r); } }
5,规则
5.1,流控规则
主要是通过这个应用流量的qps或者并发线程两个指标,当达到阈值的时候对这个流量进行一个控制,避免大流量的冲击,从而保证这种应用的高可用
5.1.1,流控模式
流控模式主要有直接模式,关联模式和这个链路模式。
直接模式
正常来讲都是使用这个直接模式,就是说当这个资源调用达到设置的阈值后,则会直接被流控抛出异常。
关联模式
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢。关联的话可以设置这个关联的资源,如关联一个接口。
链路模式
根据这个调用链路进行一个限流操作
5.1.2,流控效果
就是当这个qps超过某个阈值的时候,对这个流量控制采取的一系列措施。主要有快速失败,预热,匀速排队等具体实现。
快速失败
默认方式就是这种。就是说当这个qps达到这个阈值之后,其他新进来的请求就会立马拒绝策略,通过一个抛异常的方式实现。适用于一些压测后,了解系统水平能力的场景
预热
给一个缓冲时间,让流量缓慢增加,在一定时间内组件增加到这个阈值的上限,相当于给系统一个预热时间,避免系统直接被大流量压垮。
匀速排队
严格控制请求通过的时间,就是让这个请求匀速的通过,对应的是漏桶算法。
5.2,熔断规则
主要是针对链路中一些不稳定的资源进行一个熔断降级,就是暂时切断一些不稳定的调用,避免因为局部不稳定的因素导致整体服务的雪崩。
5.2.1,熔断策略
主要有慢调用比例,异常比例,异常数等
慢调用比例
就是说会给这个服务的响应设置一个时间,如果外面的服务来请求的时候,该服务的响应时间大于设置的时间,则统计为一个慢调用。如果单位时间内,请求个数大于这个最小请求数,并且这个慢调用的个数或者比例超过这个阈值的时候,那么该服务就会进入到一个熔断的状态,相当于一个全开状态;一段时间后,会发起一个很小的请求,相当于一个半开状态,会去判断这个响应的时间,如果还是超过最大时间,那么又会进入全开状态,否则执行正常业务流程
异常比例
就是在单位时间内,请求个数大于这个最小请求数,并且这个异常的比例大于这个设置的阈值,那么就会触发这个熔断机制。
异常数
就是在单位时间内,请求个数大于这个最小请求数,并且这个异常个数大于这个设置的阈值,那么就会触发这个熔断机制。
5.3,热点规则
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流
这个参数值只支持这个基本数据类型,并且只能通过这个@SentinelResource这个注解生效。
5.4,系统规则
会从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性
5.5,授权规则
主要是用于这个授权,增加这个黑白名单。会根据调用来源来判断该次请求是否允许放行,白名单会进行一个放行操作,黑名单不放行。
5.6,集群规则
集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。
适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性