在微服务等分布式架构中,服务容错是老生常谈的问题了,咱们都知道在微服务架构中会存在多个微服务,而绝大部分微服务之间都会存在调用关系,若因为某个底层服务不可用从而产生连锁反应,致使一系列的上层服务崩溃、故障,这种现象被称为雪崩效应或级联故障。以下图所示:html
因此在微服务等分布式架构中,可以防护服务雪崩效应的容错方案是必不可少的,常见的容错方案以下:java
一、超时:node
设置请求超时时间,让请求线程在等待超过必定的时间后就断定为请求失败从而释放请求线程,在某些场景下线程释放得够快的话,就不会由于不断建立线程而致使资源耗尽引发的服务崩溃git
二、限流:github
例如,上图中的服务A只能承受1k左右的QPS,那么就设置一个最大请求数量阈值,当QPS达到1k时就拒绝在这以后的请求。就像是我只能吃一碗饭,就算给我三碗我也只吃一碗web
三、舱壁模式:spring
舱壁模式实际上就是借鉴于现实生活中的船舱结构而设计,一艘船想要不那么容易沉也须要具有有必定的”容错“能力,而早期的船因为设计上的欠缺,只要一个地方进水了,那么水就会逐渐漫进整个船舱,这种结构的船几乎没有“容错”能力,因此就比较容易沉。因而此时就有人想到将本来一体的船舱分隔成一个个独立的船舱,船舱之间都使用钢板焊死隔开,这些钢板就是所谓的舱壁了。采用这种设计后,就算当其中一个两个船舱进水了,也不会影响到其余船舱,这艘船依旧可以正常行驶。浏览器
在软件层面上借鉴这种思想,咱们可让每一个服务都运行在本身独立的线程池中,线程池之间是互不干扰的,服务A的线程池资源耗尽也不会影响到服务B。此时线程池就像船舱的舱壁同样将不一样的服务资源隔离开来,这样某个服务挂掉也不会影响其余服务的运行bash
四、断路器模式:session
断路器模式的思想实际上和家里的断路器同样,在软件层面大体就是对某个服务的API进行监控,若在必定时间内调用的失败率或失败次数达到指定的阈值就认为该API是不可用的从而触发“跳闸”,即此时断路器就处于打开状态。过了一段时间后断路器会处于一个半开状态,若在半开状态时尝试调用该API成功后就会关闭断路器,不然依旧认为不可用让断路器继续处于打开状态
断路器三态转换以下图:
断路器模式原文:CircuitBreaker
而Spring Cloud已经提供了相关的服务容错组件,组件里已经整合了这些经常使用的方案,不须要咱们手动去实现。在此以前Spring Cloud提供的惟一服务容错组件是Hystrix,不过如今多了一个选择,那就是Spring Cloud Alibaba的Sentinel组件。关于Hystrix能够参考以下文章,本文主要介绍Sentinel:
Sentinel 是什么,官方描述以下:
随着微服务的流行,服务和服务之间的稳定性变得愈来愈重要。Sentinel 是面向分布式服务架构的轻量级流量控制、熔断降级框架,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助您保护服务的稳定性。
官方GitHub仓库地址以下:
如今咱们来为项目整合Sentinel,第一步添加以下依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!-- actuator,用于暴露监控端点 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
Tips:该项目使用的Spring Cloud版本为Greenwich.SR1,Spring Cloud Alibaba版本为0.9.0.RELEASE
第二步配置actuator:
# 暴露全部端点 management: endpoints: web: exposure: include: '*'
完成以上两步后,启动项目,使用浏览器访问http://localhost:8080/actuator/sentinel
,返回以下结果表明整合成功:
在上一小节中,咱们已经为项目成功整合了Sentinel,但这也只不过是完成了第一步。由于此时没有一个可视化的界面能让咱们看到Sentinel具体的监控信息,因此还须要搭建官方提供的可视化Sentinel控制台,而后在控制台中整合项目的监控信息。
Sentinel控制台的下载地址以下:
Sentinel Dashboard有多个release版本,应该选择哪一个呢?若是你是用在生产环境则选择与项目中sentinel-core版本对应的便可,以下:
若只是学习或测试使用那就能够随便选择了,只要能用就行,因此我这里选择最新版本1.6.3,注意这里选择jar包进行下载:
下载完成后,存放到一个你以为ok的目录下,而后打开cmd,经过命令运行该jar包。以下:
E:\Spring Cloud Alibaba\Sentinel>java -jar sentinel-dashboard-1.6.3.jar
启动成功,监听的端口是8080:
使用浏览器访问http://localhost:8080
进入到登陆页面,默认的帐户密码都是sentinel:
登陆成功,此时控制台上是空白的,由于尚未监控任何的项目:
因此接着到项目中整合一下Sentinel Dashboard的请求地址,在配置文件中添加以下配置:
spring: cloud: sentinel: transport: # 配置sentinel控制台的地址 dashboard: 127.0.0.1:8080
配置完成启动项目后须要先访问一下该项目的接口,由于Sentinel Dashboard是懒加载的,只有监控的项目被访问后才会收集监控信息。这样才能看到下图的实时监控信息,我这里的服务名是content-center:
客户端(微服务)链接控制台相关配置项:
spring: cloud: sentinel: transport: #指定控制台的地址 dashboard: localhost:8080 #指定和控制台通讯的IP #如不配置,会自动选择一个IP注册 client-ip: 127.0.0.1 #指定和控制台通讯的端口,默认值8719 #如不设置,会自动从8719开始扫描,依次+1,直到找到未被占用的端口 port: 8719 #心跳发送周期,默认值null #但在S impleHttpHeartbeatSender会用默认值10秒 heartbeat- interval-ms : 10000
控制台相关配置项:
配置项 | 默认值 | 最小值 | 描述 |
---|---|---|---|
sentinel.dashboard.app.hideAppNoMachineMillis | 0 | 60000 | 是否隐藏无健康节点的应用,距离最近一次主机心跳时间的毫秒数,默认关闭 |
sentinel.dashboard.removeAppNoMachineMillis | 0 | 120000 | 是否自动删除无健康节点的应用,距离最近一次其下节点的心跳时间毫秒数,默认关闭 |
sentinel.dashboard.unhealthyMachineMillis | 60000 | 30000 | 主机失联断定,不可关闭 |
sentinel.dashboard.autoRemoveMachineMillis | 0 | 300000 | 距离最近心跳时间超过指定时间是否自动删除失联节点,默认关闭 |
server.port | 8080 | - | 指定端口 |
csp.sentinel.dashboard.server | localhost:8080 | - | 指定地址 |
project.name | - | - | 指定程序的名称 |
sentinel.dashboard.auth.username [1.6版本支持] | sentinel | - | Sentinel Dashboard登陆帐号 |
sentinel.dashboard.auth.password [1.6版本支持] | sentinel | - | Sentinel Dashboard登陆密码 |
server.servlet.session.timeout [1.6版本支持] | 30分钟 | - | 登陆Session过时时间。配置为7200表示7200秒;配置为60m表示60分钟 |
控制台配置项需在启动命令中指定,例如指定帐户密码,以下:
java -jar -Dsentinel.dashboard.auth.username=admin -Dsentinel.dashboard.auth.password=123456 sentinel-dashboard-1.6.3.jar
咱们能够在Sentinel控制台中给某个接口添加流控规则,点击簇点链路,能够看到该服务曾经被访问过的路径:
而后点击接口右边的流控按钮就能够添加流控规则:
添加成功:
此时访问该服务的接口,QPS超过设定的阈值1,就会返回以下信息:
关于流控规则中的流控模式:
/shares/1
关联了/query
,那么/query
达到阈值,就会对/shares/1
限流链路模式稍微有些抽象,这里举个简单的例子说明一下。下图中有两个调用链路,图中的/test-b
和/test-a
实际就是两个接口,它们都调用了同一个common
资源,因此/test-b
和/test-a
就称为common
的入口资源:
此时我为common
添加一个限流规则以下:
能够看到流控模式选择链路后,须要填写一个入口资源,我这里填的是/test-a
,那么这意味着什么呢?意味着当/test-a
的QPS达到该规则的阈值后,就会对/test-a
限流,同时/test-b
不会受到任何影响。说明这种流控模式能够针对接口级别的来源进行限流,而“针对来源”则是对微服务级别的来源进行限流。
关于流控规则中的监控效果:
com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
关于流控的官方文档:
服务降级实际就是断路器模式的应用,相对于流控规则,降级规则要简单一些。降级规则能够在“簇点链路”或“降级规则”中添加:
例如,这里给/shares/1
添加降级规则,降级策略先以RT为例:
该降级规则的含义以下图:
此时访问/shares/1
接口,秒级平均响应时间超出阈值1,而且在时间窗口内经过的请求大于等于5,就会返回以下信息:
关于RT这种降级策略须要注意的点:
-Dcsp.sentinel.statistic.max.rt=xxx
若将降级策略改成异常比例,则含义以下:
若将降级策略改成异常数,则含义以下:
关于异常数这种降级策略须要注意的点:
降级规则的相关源码:
com.alibaba.csp.sentinel.slots.block.degradeDegradeRule#passCheck
(对降级的判断都在这个方法里完成)在文章的开头咱们介绍过断路器有三个状态,因此这里须要说起一下的是目前Sentinel的降级断路器是不支持半开状态的,只有打开和关闭两个状态,据官方人员描述说是会预计在将来添加半开的支持。
关于降级的官方文档:
热点规则全称是热点参数限流规则,从名称能够得知,须要有参数的接口才可以使用热点规则。例如,有一个接口的代码以下:
@GetMapping("/test-hot") @SentinelResource("hot") // 该注解用于声明是Sentinel须要监控的资源 public String testHot(@RequestParam(required = false) String a, @RequestParam(required = false) String b) { return a + " " + b; }
在控制台中为hot添加热点规则,以下:
添加完该规则后,此时访问这个接口,两个参数都传值,当QPS达到阈值时,就会抛出以下异常信息:
若是不传参数a,仅传参数b的话,则不会受到该规则的限流,以下:
说明该规则表达的含义是:在时间窗口内,一旦该规则指定的索引参数QPS达到了阈值,则会触发限流
除此以外,还有高级选项,在这里能够添加参数例外项,以下示例:
添加完成后,此时将参数a的值设置为5,而后频繁发送请求,会发现即使QPS超过1也不会触发限流:
这是由于参数a的值设置为5时,限流阈值是1000,设置为其余值时,限流阈值才是1。这就是所谓的参数例外项了,即参数的为某个特定的值时,只受参数例外项里的限流阈值影响。
热点规则适用的场景:
使用热点规则须要注意的点:
热点规则相关源码:
com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker#passCheck
(对热点参数规则的判断逻辑都在这个方法里)系统规则全称为系统保护规则,从名称能够得知该规则是用于保护系统、防止系统负载太高而崩溃的,因此触发系统规则后会对整个系统限流。添加系统规则以下图所示:
设置系统规则比较简单,选择一个合适的阈值类型并填写阈值便可:
关于阈值类型:
maxQPS * minRT
;(由Sentinel计算 )
com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkBbr
系统规则的判断逻辑所在的源码以下:
com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkSystem
受权规则用于限制某个资源仅容许哪一个服务访问,因此一般用于对服务消费者的访问权进行控制。咱们能够在簇点链路中为某个接口添加受权规则,这里以/shares/1
接口为例,以下:
新增受权规则:
/shares/1
接口,若是受权类型设置为黑名单则表示/shares/1
接口不容许test服务访问。即白名单是受权某个服务访问,黑名单则是限制某个服务访问,从而实现访问控制的效果。上面几个关于规则的小节中已经介绍了如何在Sentinel控制台中配置各类规则,除此以外,Sentinel还支持在代码中配置这些规则,因此本小节将简单介绍一下如何在代码中进行配置。
代码以下(Tips:代码基于sentinel-core 1.5.2版本):
package com.zj.node.contentcenter.controller.content; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * 添加Sentinel规则 * * @author 01 * @date 2019-07-31 **/ @Slf4j @RestController public class SentinelRuleController { /** * 测试添加流控规则 */ @PostMapping("/test-add-flow-rule") public String testAddFlowRile(String resourceName) { log.info("add flow rule. resourceName is {}", resourceName); addFlowQpsRule(resourceName); return "add flow rule success!"; } /** * 添加流控规则 * * @param resourceName 资源名称 */ private void addFlowQpsRule(String resourceName) { // 规则列表 List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(resourceName); // 针对来源 rule.setLimitApp("default"); // 设置阈值类型为QPS rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 单机阈值 rule.setCount(20); // 将规则添加到规则列表 rules.add(rule); // 加载规则列表 FlowRuleManager.loadRules(rules); } /** * 添加降级规则 * * @param resourceName 资源名称 */ private void addDegradeRule(String resourceName) { List<DegradeRule> rules = new ArrayList<>(); DegradeRule rule = new DegradeRule(resourceName); // 设置降级策略为 RT rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // set threshold RT, 10 ms(设置RT时间阈值) rule.setCount(10); // 时间窗口 rule.setTimeWindow(10); rules.add(rule); DegradeRuleManager.loadRules(rules); } /** * 添加热点规则 * * @param resourceName 资源名称 */ private void addHotRule(String resourceName) { ParamFlowRule rule = new ParamFlowRule(resourceName); // 参数索引 rule.setParamIdx(0); // 单机阈值 rule.setCount(5); // 添加参数例外项 ParamFlowItem item = new ParamFlowItem(); // 参数类型 item.setClassType(int.class.getName()); // 参数值 item.setObject("5"); // 限流阈值 item.setCount(10); rule.setParamFlowItemList(Collections.singletonList(item)); ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); } /** * 添加系统规则 */ private void addSystemRule() { List<SystemRule> rules = new ArrayList<>(); SystemRule rule = new SystemRule(); // 设置系统最高负载阈值 rule.setHighestSystemLoad(10); rules.add(rule); SystemRuleManager.loadRules(rules); } /** * 添加受权规则 * * @param resourceName 资源名称 * @param limitApp 流控应用(指调用方,多个调用方名称使用英文逗号分隔) */ private void addAuthorityRule(String resourceName, String limitApp) { AuthorityRule rule = new AuthorityRule(); // 资源名称 rule.setResource(resourceName); // 流控应用 rule.setLimitApp(limitApp); // 设置受权类型为白名单 rule.setStrategy(RuleConstant.AUTHORITY_WHITE); AuthorityRuleManager.loadRules(Collections.singletonList(rule)); } }
咱们来测试添加流控规则,使用postman访问测试接口,以下:
添加成功后,到Sentinel控制台中,查看是否存在该规则:
从上图中能够看到该流控规则已经成功添加到Sentinel中了,证实测试成功。至于其余的规则也可使用相似的方式添加,而且也都给出了代码,这里就不一一去演示了。
下面总结一下Alibaba Sentinel各类规则的参数,而且提供了官方文档的连接,若将来本文再也不适用,能够自行点击连接前往官方文档查看
一、流控规则:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的做用对象 | 无 |
count | 限流阈值 | 无 |
grade | 限流阈值类型,QPS 或线程数模式 | QPS 模式 |
limitApp | 流控针对的调用来源 | default ,表明不区分调用来源 |
strategy | 判断的根据是资源自身,仍是根据其它关联资源 (refResource ),仍是根据链路入口 |
根据资源自己 |
controlBehavior | 流控效果(直接拒绝 / 排队等待 / 慢启动模式) | 直接拒绝 |
官方文档:
二、降级规则:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即限流规则的做用对象 | 无 |
count | 阈值 | 无 |
grade | 降级模式,根据 RT 降级仍是根据异常比例或异常数降级 | RT |
timeWindow | 降级的时间,单位为 s | 无 |
官方文档:
三、热点规则:
Field | 说明 | 默认值 |
---|---|---|
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 | 集群流控相关配置 | 无 |
官方文档:
四、系统规则:
Field | 说明 | 默认值 |
---|---|---|
highestSystemLoad | 最大的 load1 ,参考值 |
-1 (不生效) |
avgRt | 全部入口流量的平均响应时间 | -1 (不生效) |
maxThread | 入口流量的最大并发数 | -1 (不生效) |
qps | 全部入口资源的 QPS | -1 (不生效) |
官方文档:
五、受权规则:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即受权规则的做用对象 | 无 |
limitApp | 流控应用(指调用方,即服务消费者),对应的黑名单/白名单,不一样 origin 用英文逗号(, )分隔,如 appA,appB |
无 |
strategy | 限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式 |
AUTHORITY_WHITE |
官方文档: