文章内容输出来源:拉勾教育Java高薪训练营
服务熔断:当下游的服务因为某种原因突然变得不可用或响应过慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
服务降级:当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务有策略的降低服务级别,以释放服务器资源,保证核心任务的正常运行。
一般服务的熔断和降级在系统中都是结合着使用的。
(1)dubbo的容错策略和服务动态降级
(2)Hystrix
(3)Sentinel
Dubbo的容错: Dubbo的消费者在提供者数据的时候,它timeout=0 代表永不超时,这样就很容易阻塞过多,为了防止这种服务雪崩的情况,Dubbo提供了一些容错处理策略。
Dubbo 主要提供了这样几种容错方式:
Failover,失败自动切换,失败时会重试其它服务器,可以设置重试次数。
Failfast, 快速失败,请求失败后快速返回异常结果,不进行重试。
Failsafe,失败安全,出现异常,直接忽略,会对请求做负载均衡。
Failback, 失败自动恢, 请求失败后,会自动记录请求到失败队列中,通过定时线程扫描该队列,并定时重试。
Forking, 并行调用多个服务提供者,其中有一个返回,则立即返回结果。
Broadcast,广播调用所有可以连接的服务,任意一个服务返回错误,就任务调用失败。
Mock,响应失败时返回伪造的响应结果。
Available,通过遍历的方式查找所有服务列表,找到第一个可以返回结果的节点,并且返回结果。
Mergable,将多个节点请求合并进行返回。
策略名称 | 优点 | 缺点 | 主要应用场景 |
---|---|---|---|
Failover | 对调用者屏蔽调用失败的信息 | 增加RT,额外资源开销,资源浪费 | 对调用RT不敏感的场景 |
Failfast | 业务快速感知失败状态进行自主决策 | 产生较多报错的信息 | 调用非幂等性接口,需要快速感知失败的场景 |
Failsafe | 即使失败了也不会影响核心流程 | 对于失败的信息不敏感,需要额外的监控 | 旁路系统,失败不影响核心流程正确性的场景 |
Failback | 失败自动异步重试 | 重试任务可能堆积 | 对于实时性要求不高,且不需要返回值的一些异步操作 |
Forking | 并行发起多个调用,降低失败概率 | 消耗额外的机器资源,需要确保操作幂等性 | 资源充足,且对于失败的容忍度较低,实时性要求高的场景 |
Broadcast | 支持对所有的服务提供者进行操作 | 资源消耗很大 | 通知所有提供者更新缓存或日志等本地资源信息 |
Dubbo服务动态降级: 在dubbo中提供了多种服务降级方式,其本质并不是对 provider 进行操作,而是告诉consumer,调用服务时要做哪些动作,具体方式如下。
(1)在Dubbo管理控制台配置服务降级(即屏蔽和容错)
mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
(2)指定返回简单值或者null
<dubbo:reference id="xxService" check="false" interface="com.xx.XxService"
timeout="3000" mock="return null" />
<dubbo:reference id="xxService2" check="false" interface="com.xx.XxService2"
timeout="3000" mock="return 1234" />
如果是标注 则使用@Reference(mock=“return null”) @Reference(mock=“return 简单值”),也支持 @Reference(mock=“force:return null”)
(3)使用java代码动态写入配置中心
RegistryFactory registryFactory =
ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://IP:PORT"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?
category=configurators&dynamic=false&application=foo&mock=force:return+null"));
(4)整合hystrix实现服务降级
先在服务消费端引入Hystrix依赖,然后在服务端启动类和消费端启动类上添加@EnableHystrix注解,启动Hystrix服务,最后在服务端配置降级超时等信息,这里配置Hystrix超时时间为2s,睡眠时间为3s,如下:
@Override
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") })
public String sayHello(String message) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("服务被调用,方法执行了...");
return "hello," + message;
}
在服务消费端配置降级方法等信息,如下:
@HystrixCommand(fallbackMethod = "sayHelloError")
@GetMapping("/sayHello")
public String sayHello(){
System.out.println("调用了sayHello方法了...");
return userService.sayHello("xiao xi yuan");
}
public String sayHelloError(){
return "hystrix fallback value...";
}
从上,可知当调用服务端方法时,会出现超时,然后会调用降级方法。测试为,在浏览器上输入:http://localhost:8081/sayHello 输出如下:
hystrix fallback value...
Hystrix的关注点在于以隔离和熔断为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供fallback机制。
Hystrix工作原理:
Hystrix 的资源模型设计上采用了命令模式,将对外部资源的调用和 fallback 逻辑封装成一个命令对象 HystrixCommand
或 HystrixObservableCommand
,其底层的执行是基于 RxJava 实现的。每个 Command 创建时都要指定 commandKey
和 groupKey
(用于区分资源)以及对应的隔离策略(线程池隔离 or 信号量隔离)。线程池隔离模式下需要配置线程池对应的参数(线程池名称、容量、排队超时等),然后 Command 就会在指定的线程池按照指定的容错策略执行;信号量隔离模式下需要配置最大并发数,执行 Command 时 Hystrix 就会限制其并发调用。
Hystrix的隔离策略:
Hystrix 提供两种隔离策略:线程池隔离和信号量隔离,其中最推荐也是最常用的是线程池隔离。Hystrix 的线程池隔离针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败,并可以提供 fallback 机制。线程池隔离的好处是隔离度比较高,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的资源消耗比较大,特别是对低延时的调用有比较大的影响。
但是,实际情况下,线程池隔离并没有带来非常多的好处。最直接的影响,就是会让机器资源碎片化。考虑这样一个常见的场景,在 Tomcat 之类的 Servlet 容器使用 Hystrix,本身 Tomcat 自身的线程数目就非常多了(可能到几十或一百多),如果加上 Hystrix 为各个资源创建的线程池,总共线程数目会非常多(几百个线程),这样上下文切换会有非常大的损耗。另外,线程池模式比较彻底的隔离性使得 Hystrix 可以针对不同资源线程池的排队、超时情况分别进行处理,但这其实是超时熔断和流量控制要解决的问题,如果组件具备了超时熔断和流量控制的能力,线程池隔离就显得没有那么必要了。
Hystrix 的信号量隔离限制对某个资源调用的并发数。这样的隔离非常轻量级,仅限制对某个资源调用的并发数,而不是显式地去创建线程池,所以资源消耗比较小,效果不错。但缺点是无法对慢调用自动进行降级,只能等待客户端自己超时,因此仍然可能会出现级联阻塞的情况。
Sentinel 是阿里中间件团队研发的面向分布式服务架构的轻量级高可用流量控制组件,Sentinel 主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户提升服务的稳定性。
Sentinel 的资源定义与规则配置的耦合度更低,Sentinel 还支持基于注解的资源定义方式,可以通过注解参数指定异常处理函数和 fallback 函数。Sentinel 提供多样化的规则配置方式。除了直接通过 loadRules
API 将规则注册到内存态之外,用户还可以注册各种外部数据源来提供动态的规则。用户可以根据系统当前的实时情况去动态地变更规则配置,数据源会将变更推送至 Sentinel 并即时生效。
Sentinel 可以通过并发线程数模式的流量控制来提供信号量隔离的功能。并且结合基于响应时间的熔断降级模式,可以在不稳定资源的平均响应时间比较高的时候自动降级,防止过多的慢调用占满并发数,影响整个系统。
对于使用了dubbo服务的项目,可以使用dubbo提供的容错策略和动态服务降级,也可以整合hystrix来使用;Hystrix和Sentinel的比较如下:
# | Sentinel | Hystrix |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 不支持 |
流量整形 | 支持慢启动、匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC | Servlet、Spring Cloud Netflix |
对于使用springcloud netflix的组件的项目,推荐整合hystrix实现熔断降级控制,对于使用springcloud alibaba组件的项目,推荐使用sentinel实现服务的熔断降级控制。
文章内容输出来源:拉勾教育Java高薪训练营
若有错误之处,欢迎留言指正~~~