Sentinel 介绍
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。
本文通过Sentinel_dashBoard进行讲解,当然不引入监控看板也能实现限流熔断降级功能,但是监控看板能够直观的看到请求的QPS,成功率等等,同时可以实时的进行降级限流策略的修改与新建。
sentinel_dashboard的引入
https://github.com/alibaba/Sentinel/releases,本人下载sentinel-dashboard-1.8.0.jar
该项目是纯纯的springboot项目,在CMD模式下使用命令
java -Dserver.port=8181 -Dcsp.sentinel.dashboard.server=localhost:8181
-Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
其中,-Dserver.port=8181 代表sentinel控制台项目的端口号,
-Dcsp.sentinel.dashboard.server=localhost:8181 代表本看板服务将会注册到自己的看板上,
-Dproject.name=sentinel-dashboard代表本看板服务的项目名称。
访问localhost:8181;用户名,密码均是sentinel
如果要自定义用户名和密码,在启动命令加上-Dsentinel.dashboard.auth.username=sentinel,
-Dsentinel.dashboard.auth.password=123456即可。
可以看到自身的服务已经注册到了控制台上。
springboot工程快速接入dashboard
com.alibaba.csp sentinel-core 1.8.2 com.alibaba.csp sentinel-annotation-aspectj 1.8.2 com.alibaba.csp sentinel-transport-simple-http 1.8.2
yml配置
spring: application: name: demo cloud: sentinel: transport: post: 8722 #跟控制台交流的端口,随意指定一个未使用的端口即可 client-ip: localhost dashboard: localhost:8181 #控制台地址
重启自己项目,回到控制台,,调用我们项目中的一个接口,,刷新控制台,,就会发现demo项目,里面会有项目中实时被调取的接口,在这里我们就可以去给想要接口去配置限流 降级 热点 权限的规则
现在在控制台配置的规则是非持久化的,当我们项目重启后,所创建的规则就会消失。
降级规则持久化mysql(慢调用)
实体类: @Data @TableName("degrade_rule") public class DegradeRuleEntity { @TableId private Integer id; private String resourceName; private Double count; private Double slowRatioThreshold; private Integer minRequestAmount; private Integer statIntervalMs; private Integer timeWindow; private Date createTime; private Date updateTime; }
建表语句:
CREATE TABLE `degrade_rule` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`resource_name` varchar(256) NOT NULL COMMENT '资源名称',
`count` double NOT NULL COMMENT '慢调用时长,单位 毫秒',
`slow_ratio_threshold` double NOT NULL COMMENT '慢调用比例阈值',
`min_request_amount` int(11) NOT NULL COMMENT '熔断触发的最小请求数',
`stat_interval_ms` int(11) NOT NULL COMMENT '统计时长,单位 毫秒',
`time_window` int(11) NOT NULL COMMENT '熔断时长,单位为 s',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_resource_name` (`resource_name`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='熔断规则表';
熔断规则加载:
@Override public Boolean updateDegradeRule() { ListdegradeRuleList = baseMapper.queryAllRule(); if (!CollectionUtils.isEmpty(degradeRuleList)) { List rules = null; rules = degradeRuleList.stream().map(degradeRuleEntity -> { //资源名,即规则的作用对象 return new DegradeRule(degradeRuleEntity.getResourceName()) // 熔断策略,支持慢调用比例/异常比例/异常数策略 .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()) //慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 .setCount(degradeRuleEntity.getCount()) // 熔断时长,单位为 s .setTimeWindow(degradeRuleEntity.getTimeWindow()) // 慢调用比例阈值 .setSlowRatioThreshold(degradeRuleEntity.getSlowRatioThreshold()) //熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断 .setMinRequestAmount(degradeRuleEntity.getMinRequestAmount()) //统计时长(单位为 ms) .setStatIntervalMs(degradeRuleEntity.getStatIntervalMs()); }).collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(rules)) { DegradeRuleManager.loadRules(rules); } } return true; }
项目启动初始化降级规则:
@PostConstruct void initDegradeRule(){ if(degradeRuleService.updateDegradeRule()) { LOG.warn("-------------------初始化DegradeRule success----------------------"); }else { LOG.warn("-------------------初始化DegradeRule error----------------------"); } }
注:根据自己项目可对通过添加编辑时重新加载降级规则: 或者做定时任务监控数据库该表是否发生变化来加载规则()
@Scheduled(cron = "0 0/2 * * * ?") public void loadDegradeRule() { LOG.info("进入degradeRule定时任务"); ListdegradeRuleList = degradeRuleDao.queryAllRule(); if (CollectionUtils.isEmpty(degradeRuleList)) { return; } String lastMd5Hex = stringRedisTemplate.opsForValue().get(RedisKeys.degradeRuleKey); if (StringUtils.isEmpty(lastMd5Hex)) { lastMd5Hex = ""; } String newMd5Hex = DigestUtils.md5Hex(JSON.toJSONString(degradeRuleList)); if (StringUtils.isBlank(newMd5Hex) || StringUtils.equals(lastMd5Hex, newMd5Hex)) { LOG.info("degradeRule无更新>>>>>>>>>>>>>>>>>>>"); return; } LOG.info(">>>>>>>>>>>>>>>>>>>>>>>>>degradeRule更新"); List rules = null; rules = degradeRuleList.stream().map(degradeRuleEntity -> { //资源名,即规则的作用对象 return new DegradeRule(degradeRuleEntity.getResourceName()) // 熔断策略,支持慢调用比例/异常比例/异常数策略 .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()) //慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 .setCount(degradeRuleEntity.getCount()) // 熔断时长,单位为 s .setTimeWindow(degradeRuleEntity.getTimeWindow()) // 慢调用比例阈值 .setSlowRatioThreshold(degradeRuleEntity.getSlowRatioThreshold()) //熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断 .setMinRequestAmount(degradeRuleEntity.getMinRequestAmount()) //统计时长(单位为 ms) .setStatIntervalMs(degradeRuleEntity.getStatIntervalMs()); }).collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(rules)) { DegradeRuleManager.loadRules(rules); stringRedisTemplate.opsForValue().set(RedisKeys.degradeRuleKey, newMd5Hex); } LOG.info("[DegradeRuleConfig] 熔断规则加载: " + rules); }
@SentinelResource(value = "demo", fallback = "exceptionHandler")在所需要降级的接口加上该注解,,,value为数据库中的资源名 fallback为降级后所走的方法
至此,,springboot集成sentinel降级熔断持久化到数据库
如若我们想对sentinel设置的规则统一处理的话
例如我们在控制台发现某个接口有问题,我们想要给它来一些规则熔断,,我们可提前在项目中做统一处理
public class CustomUrlBlockHandler implements UrlBlockHandler{ @Override public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException { Mapmap = new HashMap<>(); if (ex instanceof FlowException){ map.put("code", 100); map.put("msg", "限流了"); } else if (ex instanceof DegradeException){ map.put("code", 100); map.put("msg", "降级了"); } else if (ex instanceof ParamFlowException){ map.put("code", 100); map.put("msg", "热点参数限流了"); } else if (ex instanceof SystemBlockException){ map.put("code", 100); map.put("msg", "系统规则(负载...) 不满足规则"); } else if (ex instanceof AuthorityException){ map.put("code", 100); map.put("msg", "授权规则不通过"); } response.setStatus(500); response.setCharacterEncoding("utf-8"); response.setHeader("Content-Type", "application/json;charset=utf-8"); response.setContentType("application/json;charset=utf-8"); new ObjectMapper().writeValue(response.getWriter(), map); }
我们只需在项目启动初始化此方法
@PostConstruct void initUrlBlockHandler() { LOG.warn("-------------------Sentinel统一异常处理初始化----------------------"); WebCallbackManager.setUrlBlockHandler(new CustomUrlBlockHandler()); }
这样,,我们临时在控制台添加的一些规则,就会统一处理了,,当然,我们需跟前端商量好出现code: 100时所要处理的方法即可