一般的なフォールトトレラントソリューション
マイクロサービス分散アーキテクチャでは、サービスのフォールトトレランスは、一般的な問題であり、我々はすべてマイクロマイクロサービスアーキテクチャではより多くのサービスがあるでしょうし、場合により根本的に、マイクロサービスの大半間のコール関係があるだろう知っていますサービストップサービスがクラッシュ、障害、アバランシェ効果又はカスケードの障害と呼ばれる現象の一連につながる連鎖反応で得られた利用不可能。下図のように:
以下のようにマイクロサービス分散アーキテクチャでは、フォールトトレラントサービスプログラムのアバランシェ効果に対して防御する能力は不可欠であり、一般的なフォールトトレラントスキームは次のとおりです。
1、タイムアウト:
スレッドは、要求が十分に速く、それがスレッドを作成し続け、原因と資源の枯渇につながることはありませんいくつかのシナリオスレッドのリリースでは、より後の一定期間よりもスレッドが待っている要求を解放要求に失敗したと判断されるように、要求タイムアウトを設定サービスのクラッシュ
制限2:
例えば、サービス上の画像は、わずか約1K QPSに耐えることができ、その後ザこの後に到達1K QPS要求を拒否した場合に、要求の最大しきい値数を設定します。私は、私は3つのボウルボウルを食べて、私が持っていても、ご飯を食べるのが好き
3、隔壁モード:
バルクヘッドモードは、実際にキャビン構造と現実の生活の設計から借用され、そう簡単ではないにも場所限り、「フォールトトレラント」の能力をある程度持っている必要があり、早期船に起因する設計の欠如にボートシンクをたい水は徐々に全体のキャビンに拡散する水は、その後、このような構造の船はほとんどなく、「フォールトトレラント」機能ですので、シンクが容易です。だから、一部の人々は、この時間は、のスチール隔壁と呼ばれる死によって分離キャビンの間に溶接鋼板を使用し、独立したキャビンに分かれ、元キャビンのいずれかになると思います。2つのキャビンの1が浸水した場合でも、この設計では、それは他のキャビンには影響しません、船はまだ正常な運転が可能です。
ソフトウェアレベルでは、我々は独自の個別のスレッドプールのスレッドプール内の各サービスの実行を行うことができます。この考え方から学ぶことは、サービス間の干渉はありませんサービスBには影響しませんスレッドプールのリソースの枯渇 このとき、スレッドプールは、それが実行されている他のサービスには影響ありませんハング、このようなサービスを隔離するためにキャビンバルクヘッドなど異なるサービスリソースのようなものです
図4に示すように、ブレーカモード:
断路器模式的思想实际上和家里的断路器一样,在软件层面大致就是对某个服务的API进行监控,若在一定时间内调用的失败率或失败次数达到指定的阈值就认为该API是不可用的从而触发“跳闸”,即此时断路器就处于打开状态。过了一段时间后断路器会处于一个半开状态,若在半开状态时尝试调用该API成功后就会关闭断路器,否则依旧认为不可用让断路器继续处于打开状态
断路器三态转换如下图:
断路器模式原文:CircuitBreaker
而Spring Cloud已经提供了相关的服务容错组件,组件里已经整合了这些常用的方案,不需要我们手动去实现。在此之前Spring Cloud提供的唯一服务容错组件是Hystrix,不过现在多了一个选择,那就是Spring Cloud Alibaba的Sentinel组件。关于Hystrix可以参考如下文章,本文主要介绍Sentinel:
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 Dashboard控制台
在上一小节中,我们已经为项目成功整合了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:
Sentinel 相关配置项小结
客户端(微服务)连接控制台相关配置项:
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,就会返回如下信息:
关于流控规则中的流控模式:
- 直接:当前资源的QPS达到设定的阈值,就触发限流
- 关联:当关联的资源的QPS达到设定的阈值,就触发限流。例如,
/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
- 相关源码:
- Warm Up(预热):会根据codeFactor(默认3)的值,从阈值除以codeFactor,经过预热时长,才到达设置的QPS阈值。适用于将突然增大的流量转换为缓步增长的场景
- 相关的官方文档:限流 - 冷启动
- 相关源码:
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
- 排队等待:匀速排队,让请求以均匀的速度通过,若请求等待时间超过设置的超时时间则抛弃该请求,阈值类型必须设置成QPS,否则无效。适用于突发流量的场景
- 相关的官方文档:限流 - 匀速器
- 相关源码:
com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
关于流控的官方文档:
降级规则
服务降级实际就是断路器模式的应用,相对于流控规则,降级规则要简单一些。降级规则可以在“簇点链路”或“降级规则”中添加:
例如,这里给/shares/1
添加降级规则,降级策略先以RT为例:
该降级规则的含义如下图:
此时访问/shares/1
接口,秒级平均响应时间超出阈值1,并且在时间窗口内通过的请求大于等于5,就会返回如下信息:
关于RT这种降级策略需要注意的点:
- RT默认最大为4900ms,所以即便设置的值大于4900ms也依旧会按照4900ms计算
- 可以通过参数修改:
-Dcsp.sentinel.statistic.max.rt=xxx
- 可以通过参数修改:
若将降级策略改为异常比例,则含义如下:
若将降级策略改为异常数,则含义如下:
关于异常数这种降级策略需要注意的点:
- 若将时间窗口的值设置小于60秒则可能会出问题,因为异常数的统计是分钟级别的,时间窗口小于60秒就有可能不断进入降级状态
降级规则的相关源码:
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添加热点规则,如下:
- Tips:参数索引从0开始,对应到代码中的话,则参数a的索引为0,参数b的索引为1,所以该规则是作用于参数a
添加完该规则后,此时访问这个接口,两个参数都传值,当QPS达到阈值时,就会抛出如下异常信息:
如果不传参数a,仅传参数b的话,则不会受到该规则的限流,如下:
说明该规则表达的含义是:在时间窗口内,一旦该规则指定的索引参数QPS达到了阈值,则会触发限流
除此之外,还有高级选项,在这里可以添加参数例外项,如下示例:
添加完成后,此时将参数a的值设置为5,然后频繁发送请求,会发现即便QPS超过1也不会触发限流:
这是因为参数a的值设置为5时,限流阈值是1000,设置为其他值时,限流阈值才是1。这就是所谓的参数例外项了,即参数的为某个特定的值时,只受参数例外项里的限流阈值影响。
热点规则适用的场景:
- 适用于存在热点参数并希望提升API可用性的场景,即某个特定请求参数QPS偏高于其他请求参数时,仅对该参数的请求限流,使用其他请求参数则可以正常响应,这样可以提高一定的可用性
使用热点规则需要注意的点:
- 参数必须是基本类型或者String类型,否则将不会生效
热点规则相关源码:
com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker#passCheck
(对热点参数规则的判断逻辑都在这个方法里)
系统规则
系统规则全称为系统保护规则,从名称可以得知该规则是用于保护系统、防止系统负载过高而崩溃的,所以触发系统规则后会对整个系统限流。添加系统规则如下图所示:
设置系统规则比较简单,选择一个合适的阈值类型并填写阈值即可:
关于阈值类型:
- LOAD(负载):当系统load1(1分钟的load)超过阈值,且并发线程数超过系统容量时触发,建议设置为CPU核心数 2.5(注意:仅对 Linux/Unit-like 机器生效)。例如CPU核心数为4,`4 2.5 = 10`
- 系统容量 =
maxQPS * minRT
;(由Sentinel计算 )- maxQPS:秒级统计出来的最大QPS
- minRT:秒级统计出来的最小响应时间
- 相关源码:
com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkBbr
- 系统容量 =
- RT:所有入口流量的平均RT达到阈值时触发
- 线程数:所有入口流量的并发线程数达到阈值时触发
- 入口QPS:所有入口流量的QPS达到阈值时触发
- CPU使用率:系统CPU使用率达到阈值时触发
系统规则的判断逻辑所在的源码如下:
com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkSystem
授权规则
授权规则用于限制某个资源仅允许哪个服务访问,所以通常用于对服务消费者的访问权进行控制。我们可以在簇点链路中为某个接口添加授权规则,这里以/shares/1
接口为例,如下:
新增授权规则:
- 该授权规则的含义为:仅允许test服务访问
/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中了,证明测试成功。至于其他的规则也可以使用类似的方式添加,并且也都给出了代码,这里就不一一去演示了。
Sentinel规则参数总结
下面总结一下Alibaba Sentinel各种规则的参数,并且提供了官方文档的链接,若未来本文不再适用,可以自行点击链接前往官方文档查看
1、流控规则:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | 无 |
count | 限流阈值 | 无 |
grade | 限流阈值类型,QPS 或线程数模式 | QPS 模式 |
limitApp | 流控针对的调用来源 | default ,代表不区分调用来源 |
strategy | 判断的根据是资源自身,还是根据其它关联资源 (refResource ),还是根据链路入口 |
根据资源本身 |
controlBehavior | 流控效果(直接拒绝 / 排队等待 / 慢启动模式) | 直接拒绝 |
官方文档:
2、降级规则:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即限流规则的作用对象 | 无 |
count | 阈值 | 无 |
grade | 降级模式,根据 RT 降级还是根据异常比例或异常数降级 | RT |
timeWindow | 降级的时间,单位为 s | 无 |
官方文档:
3、热点规则:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即热点规则的作用对象 | 无 |
count | 限流阈值,必填 | 无 |
grade | 限流模式 | QPS 模式 |
durationInSec | 统计窗口时间长度(单位为秒),1.6.0 版本开始支持 | 1s |
controlBehavior | 流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持 | 快速失败 |
maxQueueingTimeMs | ロングバージョン1.6.0以降で使用可能、(のみ均一なラインモードで有効にするに)最大キューイングを待っています | 0ms |
paramIdx | 必要インデックスホットパラメータ、対応するパラメータインデックス位置SphU.entry(XXX、引数) | ノー |
paramFlowItemList | 例外パラメータは、パラメータの値は、個々の指定された限界閾値、無制限フロントカウント閾値を設定してもよいです。それだけで基本的なタイプ、および文字列をサポートしています | ノー |
clusterMode | それは、フロー制御ルールのクラスタパラメータであるかどうか | false |
CLUSTERCONFIG | フロー制御のクラスタ構成 | ノー |
公式文書:
図4に示すように、システムルール:
フィールド | 説明 | デフォルト値 |
---|---|---|
highestSystemLoad | 最大のload1 基準値、 |
1(有効でありません) |
avgRt | すべての入口の流れの平均応答時間 | 1(有効でありません) |
maxThread | 同時着信トラフィックの最大数 | 1(有効でありません) |
QPS | すべての入り口のリソースQPS | 1(有効でありません) |
公式文書:
5、認可規則:
フィールド | 説明 | デフォルト値 |
---|---|---|
リソース | オブジェクトのリソース名の役割、その認可規則 | ノー |
Limitapp | (発呼者の、すなわちサービスコンシューマ)フロー制御、ブラックリスト/ホワイトリストに対応する、異なる起源(コンマのアプリケーションが, )として、分離しますappA,appB |
ノー |
戦略 | 制限モード、AUTHORITY_WHITE ホワイトリストモード、AUTHORITY_BLACK デフォルトでブラックリストモード、ホワイトリストモード |
AUTHORITY_WHITE |
公式文書: