🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ***** # 1. 雪崩效应 ``` 服务雪崩效应是一种因“服务提供者的不可用”(原因)导致“服务调用者不可用”(结果),并将不可用逐渐放大的现象 ``` ``` 形成原因 服务雪崩的过程可以分为三个阶段: 服务提供者不可用; 重试加大请求流量; 服务调用者不可用; 服务雪崩的每个阶段都可能由不同的原因造成,总结如下: ``` ![](https://img.kancloud.cn/e4/e8/e4e8fd226368a0a3d9e2b08d4738b385_1048x424.png) **应对策略** ![](https://img.kancloud.cn/0c/ba/0cbaa264138e77b150d4be1277ac9e71_739x560.png) # 2. 常见容错方案 ``` 1. 超时: 释放够快就不会挂掉 2. 限流: 只有一碗的饭量,给我三碗我吃不下 3. 仓壁模式: 如每个controller都有自己比较小的线程池,当线程池满了就会拒绝请求(线程池本身有拒绝机制),不把鸡蛋放在一个篮子里 4. 断路器模式: 监控+开关, 如对API进行监控,5s以内错误率/错误次数达到一定的阈值,就跳闸,不去调用其他服务,并且有半开机制 ``` **断路器模式:** ![](https://img.kancloud.cn/a3/a1/a3a16d612ed87c387e8ef0af999dc47c_742x432.png) # 3. 使用Sentinel实现容错 ``` 1. Sentinel是什么? 参考 [http://blog.didispace.com/spring-cloud-alibaba-sentinel-1/] ``` ``` 2. 应用整合Sentinel (熔断/ 降级) 加依赖 compile("org.springframework.cloud:spring-cloud-starter-alibaba-sentinel") ``` # 4. Sentinel控制台 ``` 下载sentinel, 1.6.0版本之后才有登录页面 启动 java -jar sentinel-dashboard-1.6.0.jar 登录 http://localhost:8080 用户名/密码=[sentinel/sentinel] ``` ![](https://img.kancloud.cn/3c/0a/3c0a129c01b42c0f4e0d4109b091bfb1_1083x521.png) ``` 添加配置: spring: cloud: sentinel: transport: #指定sentinel 控制台地址 dashboard: localhost:8080 ``` ![](https://img.kancloud.cn/3a/b5/3ab54d6c7a4abebe0e6afbfeae97ab66_828x482.png) ## 4.1 流控规则 ``` 资源名: 控制台自动填写我们请求的路径(唯一的名称) 针对来源: 比如有俩个服务(A / B)调用用户中心,可以为A设置200QPS, B设置300QPS,针对来源设置不同值,(default默认表示不区分来源) 阈值类型: QPS表示当达到QPS阈值时就是限流; 线程数表示当达到线程数的阈值时就是限流 是否集群: 表示是否支持集群模式 流控模式: 直接模式: 如把QPS设置成1, 当请求达到1次就限流,会报Blocked by sentinel (flow limiting)的异常 关联模式: [关联资源:当关联资源达到一定值时就限流自己],比如关联资源设置为/users/getUerInfo,请求路径达到阈值就限流当前资源 使用场景: 有俩个API(查询/修改),衡量优先读还是优先修改,如优先修改,关联资源处设置成[修改的API],资源名处设置成[查询的API] 链路模式: [只记录指定链路上的流量]API级别 流控效果: 快速失败: 直接抛异常 WarmUp: 预热, 根据codefactor(冷加载因子|默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值 如 阈值设置为100,预热时长设置为10s, 那么就会 100/3 作为最初阈值,经过10s后才达到100限流 场景: 如秒杀服务平时流量不会很高,但有活动的某一瞬间流量激增,不处理服务会挂掉,预热可以让流量缓慢的增加 排队等待: 匀速排队,让请求以均匀的速度通过,阈值类型必须设置成QPS,否则无效 原理: 如阈值类型设置QPS,单机阈值设置1,超时时间设置为200s;相当于1s请求1次,当时间超过200s后才会被丢弃 场景: (应对突发流量)一会流量很大,一会很空闲,希望应用能够在空闲时处理请求而不是直接拒绝请求 ``` ![](https://img.kancloud.cn/44/d7/44d741e756d3f28eea592f7ea02b5c74_829x440.png) ## 4.2 降级规则详解 (断路器) ``` 资源名: 当前资源路径 降级策略: RT: 若持续进入 5 个请求,它们资源的平均响应时间都超过阈值(秒级平均 RT,以 ms 为单位),资源调用会被熔断。 在接下的降级时间窗口(在降级规则中配置,以s为单位)之内,对这个方法的调用都会自动地返回(抛出 DegradeException) 注意点: RT默认最大4900ms -- 通过 -Dcsp.sentinel.statistic.max.rt=xxx 修改 异常比例模式: 当资源的每秒异常数占通过量的比值超过阈值之后,资源进入降级状态,即在接下的降级时间窗口(在降级规则中配置,以s为单位)之内, 对这个方法的调用都会自动地返回.异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%. 异常数模式: 当资源最近 1 分钟的异常数目超过阈值之后会进行熔断. 注意: 时间窗口<60s可能会出现问题 注意: 断路器有三种状态: 打开 | 关闭 | 半开, 但目前sentinel没有设置半开状态. ``` ![](https://img.kancloud.cn/ab/0b/ab0b364e8e80d2aa9eeed0eccf8f3601_866x414.png) ## 4.3 热点规则详解 ``` ** 热点规则就是对指定的参数或者参数值限流; sentinel默认显示的端点是不支持热点规则的,需要自定义代码实现 @GetMapping("test-hot") @SentinelResource("hot") //使用该注解,请求刷新Sentinel控制台,才会出现热点规则配置 public String testHost(@RequestParam(required = false) String a, @RequestParam(required = false) String b) { return a + ":" + b; } ``` ``` 资源名: @SentinelResource中定义的值即hot 参数索引: 参数a的索引就是0, 参数b的索引就是1, 若设置为0代表限流API带参数a的请求,若不带a,则不会被限流. 在时间窗口以内指定参数的QPS达到一定的阈值就会触发限流 高级选项: 如参数类型设置String, 参数值设置5, 限流阈值设置1000ms, 代表参数值是不是5时阈值就是1ms; 参数值是5时阈值就是1000ms 场景: API的某参数的QPS很高时, 可以使用热点规则,提高API的可用性;即对传递该参数或参数值的API限流,不影响调用该API传递其他参数或参数值 注意: 参数索引中配置的参数必须是[基本类型或者String,否则不会生效] ``` ![](https://img.kancloud.cn/f9/7b/f97b79c6a0dc3938e45c63b09b911042_905x443.png) ## 4.4 系统规则详解 ``` 阈值类型: LOAD(负载): 当系统load1(1分钟的负载)超过阈值,且并发线程数超过系统容量时触发,建议设置为CPU核心数*2.5(仅对linux/unix-like机器生效) 命令查看load: uptime RT: 所有入口流量的平均RT达到阈值触发 线程数: 所有入口流量的并发线程数达到阈值触发 入口QPS: 所有入口流量的QPS达到阈值触发 ``` ![](https://img.kancloud.cn/9d/8c/9d8cbce837d42c75c30f2079f7d8e7fe_766x106.png) ![](https://img.kancloud.cn/76/78/7678254ce0a71b1b80fbfd6d33e57419_913x394.png) ## 4.5 授权规则详解 ![](https://img.kancloud.cn/62/2e/622e08250ffc0ad85a7d5e71c081b7d2_912x443.png) # 5. 代码配置规则 ``` 参考: https://www.imooc.com/article/289345 ``` # 6. Sentinel与控制台通信原理剖析 ``` 控制台是如何获取到微服务的监控信息的? 用控制台配置规则时,控制台是如何将规则发送到各个微服务的呢? http://ip:sentinel端口/api 源码: 注册/心跳发送的API: com.alibaba.csp.sentinel.transport.heartbeat.SimpleHttpHeartbeatSender 通信的API: com.alibaba.csp.sentinel.command.CommandHandler的实现类, 如: ApiCommandHandler ``` ![](https://img.kancloud.cn/18/f2/18f2364a9a70cb774865ea2a912afdee_816x461.png) # 7. 控制台相关配置项 **7-1. 应用端连接控制台配置项** ![](https://img.kancloud.cn/6f/ac/6facc05fb8295f2375a38101701814f4_571x305.png) **7-2. 控制台的配置项** ![](https://img.kancloud.cn/97/cb/97cb8528af0abc102384a2047bfea138_855x367.png) **7-3. 修改控制台的配置项操作 ** ``` 如:修改控制台的用户名和密码,如下图 ``` ![](https://img.kancloud.cn/4a/42/4a4245b645df02aecb28cc1426acb4fa_925x73.png) # 8. SentinelAPI详解 ``` 核心API: 1. SphU: 定义资源,让资源受到监控并且可以保护资源 2. Tracer: 对我们想要的异常进行统计 3. ContextUtil: 可以实现调用来源,标记调用 ``` ``` spring: cloud: sentinel: transport: #指定sentinel 控制台地址 dashboard: localhost:8080 filter: #关闭掉spring MVC端点的保护,测试为了防止干扰,正常要为true(或者默认) enabled: false ``` ``` //代码实现降级及限流及来源功能 String resourceName = "test-sentinel-api"; ContextUtil.enter(resourceName, "test-wfw"); //定义一个Sentinel保护的资源,名称可以任意,唯一即可 Entry entry = null; try { entry = SphU.entry(resourceName); if(StringUtils.isEmpty(a)) { throw new IllegalArgumentException("参数不合法"); } // 被保护的业务逻辑 //TODO: return a; } catch (BlockException e) { //如果被保护的资源被限流或者降级会抛出BlockException } catch (IllegalArgumentException e) { //统计IllegalArgumentException[发生的次数,发生占比...] Tracer.trace(e); return "参数不合法"; } finally { if(entry != null) { entry.exit(); } ContextUtil.exit(); } ``` # 9. @SentinelResource注解详解 ``` 参考: https://www.imooc.com/article/289384 ``` ``` 重写上面代码: @GetMapping("test-sentinel-api") @SentinelResource(value = "test-sentinel-api", blockHandler = "block", fallback = "fallback") public String testSentinelAPI(@RequestParam(required = false) String a) { if(StringUtils.isEmpty(a)) { throw new IllegalArgumentException("参数不合法"); } return a; } //处理限流或者降级 public String block(String a, BlockException e) { return "限流或者降级了 block"; } //1.5处理降级 sentinel 1.6可以处理Throwable public String fallback(String a) { return "限流或者降级了 fallback"; } ``` ``` 源码: com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect com.alibaba.csp.sentinel.annotation.aspectj.AbstractSentinelAspectSupport ``` # 10. RestTemplate整合Sentinel ``` 源码: org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor ``` ``` @Bean @LoadBalanced @SentinelRestTemplate public RestTemplate restTemplate() { return new RestTemplate(); } 配置开关: resttemplate: sentinel: # 关闭@SentinelRestTemplate注解 enabled: false ``` # 11. Feign整合Sentinel ``` 源码: org.springframework.cloud.alibaba.sentinel.feign.SentinelFeign ``` ``` 1. 只需要添加下面的配置: feign: sentinel: #为feign整合sentinel enabled: true ``` ``` 2. 限流降级发生时,如何定制自己的处理逻辑? @FeignClient(name = "ali-pay-service", configuration = UserCenterFeignConfiguration.class, fallback = UserCenterFeginClientFallback.class) public interface UserCenterFeginClient { /** * http://ali-pay-service/users/{id} * @param id * @return */ @GetMapping("/users/{id}") String findById(@PathVariable Integer id); } @Component public class UserCenterFeginClientFallback implements UserCenterFeginClient { @Override public String findById(Integer id) { return null; } } ``` ``` 3. 进入到Fallback中是异常情况,如何获取到异常? /** * name: 请求服务的名称 * fallback与fallbackFactory不可同时用 * fallbackFactory可以获取到异常 */ @FeignClient(name = "ali-pay-service", configuration = UserCenterFeignConfiguration.class, // fallback = UserCenterFeginClientFallback.class, fallbackFactory = UserCenterFeginClientFallbackFactory.class ) public interface UserCenterFeginClient { /** * http://ali-pay-service/users/{id} * @param id * @return */ @GetMapping("/users/{id}") String findById(@PathVariable Integer id); } @Component public class UserCenterFeginClientFallbackFactory implements FallbackFactory<UserCenterFeginClient> { @Override public UserCenterFeginClient create(Throwable cause) { return new UserCenterFeginClient() { @Override public String findById(Integer id) { return null; } }; } } ``` # 12. 规则持久化 ``` 1. 规则持久化-拉模式 参考: https://www.imooc.com/article/289402 2. 规则持久化-推模式 参考: https://www.imooc.com/article/289464 3. 阿里云上提供免费的Sentinel的使用(AHAS) 开通地址: https://ahas.console.aliyun.com/ 开通说明: https://help.aliyun.com/document_detail/90323.html ``` # 13. 集群流控 ``` 配置规则时设置集群模式,如控制台配置: ``` ![](https://img.kancloud.cn/f1/92/f19254c0bcfd40729d111f9effc57339_887x469.png) ``` 要想集群模式需要引入Token Client组件去和Token Server通信, 如何部署Token Server? 微服务如何继承Token Client? 参考官方https://github.com/alibaba/Sentinel/wiki 注: 目前Token Server还不能用在生产环境,很多功能需要自己实现,集群流控搭建参考:https://www.jianshu.com/p/bb198c08b418 但目前[实现集群的流控方案可以在网关处进行设置]更加简单,实用. 下图为集群流控模式的架构图: ``` ![](https://img.kancloud.cn/ea/cc/eacc11adc16a14b52b975304e14c4b5b_732x359.png) # 14. 扩展Sentinel ``` UrlBlockHandler : 提供Sentinel异常处理 RequestOriginParser : 区分来源 UrlCleaner : 重新定义资源名称 本质都是通过CommonFilter过滤器调用的,源码参照: com.alibaba.csp.sentinel.adapter.servlet.CommonFilter ``` ``` 1. 扩展Sentinel-错误页优化 @Component public class MyUrlBlockHandler implements UrlBlockHandler { @Override public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException { ErrorMsg message = null; //限流异常 if(ex instanceof FlowException) { message = ErrorMsg.builder().code(100).msg("限流异常").build(); } //降级异常 else if(ex instanceof DegradeException) { message = ErrorMsg.builder().code(101).msg("降级异常").build(); } //系统规则异常 else if(ex instanceof SystemBlockException) { message = ErrorMsg.builder().code(102).msg("系统规则异常").build(); } //参数热点规则异常 else if(ex instanceof ParamFlowException) { message = ErrorMsg.builder().code(103).msg("参数热点规则异常").build(); } //授权规则异常 else if(ex instanceof AuthorityException) { message = ErrorMsg.builder().code(104).msg("授权规则异常").build(); } //TODO: 通过response将异常信息写出去 } } @Data @Builder @AllArgsConstructor @NoArgsConstructor class ErrorMsg { private Integer code; private String msg; } ``` ``` 2. 扩展Sentinel-实现区分来源 @Component public class MyRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest request) { //来源的参数在实际应用中可以放在Header中 //从请求参数中过去名为 origin 的参数并返回 //如果获取不到 origin 参数,那么就抛出异常 String origin = request.getParameter("origin"); //String origin = request.getHeader("origin"); if (StringUtils.isEmpty(origin)) { throw new IllegalArgumentException("origin must be set"); } return origin; } } ``` ``` 3. 扩展Sentinel-RESTfulURL支持(针对所有资源设置相同的流控规则) @Component public class MyUrlCleaner implements UrlCleaner { @Override public String clean(String originUrl) { //目标:path/1和path/2的返回值相同,返回/path/{number} String[] split = originUrl.split("/"); return Arrays.stream(split).map(string -> { if (NumberUtils.isNumber(string)) { return "{number}"; } return string; }).reduce((a,b) -> a+"/"+b).orElse(""); } } ``` # 15. Sentinel全部配置项 ``` 参照: https://www.imooc.com/article/289562 ```