服务熔断与降级
在分布式环境下,特别是微服务结构的分布式系统中, 一个软件系统调用另外一个远程系统是非常普遍的。这种远程调用的被调用方可能是另外一个进程,或者是跨网路的另外一台主机, 这种远程的调用和进程的内部调用最大的区别是,远程调用可能会失败,或者挂起而没有任何回应,直到超时。更坏的情况是, 如果有多个调用者对同一个挂起的服务进行调用,那么就很有可能的是一个服务的超时等待迅速蔓延到整个分布式系统,引起连锁反应, 从而消耗掉整个分布式系统大量资源。最终可能导致系统瘫痪。断路器(Circuit Breaker)模式就是为了防止在分布式系统中出现这种瀑布似的连锁反应导致的灾难。
上图是断路器(Curcuit Breaker)的结构,它有两个基本状态(close和open)和一个基本trip动作:
- close 状态下, client 向s upplier发起的服务请求, 直接无阻碍通过断路器, supplier 的返回值直接由断路器交回给 client.
- open 状态下,client 向 supplier 发起的服务请求后,断路器不会将请求转到 supplier, 而是直接返回 client, client 和 supplier 之间的通路是断的
- trip: 在 close 状态下,如果 supplier 持续超时报错, 达到规定
上图是断路器的 扩展模式
。
基本的断路器模式下,保证了断路器在 open 状态时,保护 supplier 不会被调用, 但我们还需要额外的措施可以在 supplier 恢复服务后,可以重置断路器。一种可行的办法是断路器定期探测 supplier 的服务是否恢复, 一但恢复, 就将状态设置成 close。断路器进行重试时的状态为半开(half-open)状态。
Swoft 熔断器底层采用 扩展模式设计
,开发者必须先熟悉 熔断器状态变化
,这样有利于对熔断器的理解。
安装
使用熔断器前必须安装 swoft-breaker 组件,安装方式如下:
composer require swoft/breaker
配置
熔断器不使用配置也是可以使用的,但是有些业务场景需要全局配置熔断器,可以参考如下配置:app/bean.php
return [
'breaker' => [
'timeout' => 3,
]
]
详细参数:
- timeout 超时时间
- failThreshold 连续失败多少次状态切换阀门
- sucThreshold 连续成功多少次状态切换阀门
- retryTime 熔断器由开启状态到半开状态尝试切换时间
全局配置会被
@Breaker
上面的局部注解覆盖
使用
熔断器的使用相当简单且功能强大,使用一个 @Breaker
注解即可,Swoft 中的熔断是针对于类里面的方法熔断,只要方法里面没有抛出异常就说明是成功访问的,所以 @Breaker
注解可以在任何 bean 对象方法上面使用。
<?php declare(strict_types=1);
namespace App\Model\Logic;
use Exception;
use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Breaker\Annotation\Mapping\Breaker;
/**
* Class BreakerLogic
*
* @since 2.0
*
* @Bean()
*/
class BreakerLogic
{
/**
* @Breaker(fallback="funcFallback")
*
* @return string
* @throws Exception
*/
public function func(): string
{
// Do something
throw new Exception('Breaker exception');
}
/**
* @Breaker()
*
* @return string
* @throws Exception
*/
public function func2(): string
{
// Do something
return 'func2';
}
/**
* @return string
*/
public function funcFallback(): string
{
return 'funcFallback';
}
/**
* @Breaker()
*
* @return string
* @throws Exception
*/
public function unFallback(): string
{
// Do something
throw new Exception('Breaker exception');
}
/**
* @Breaker(fallback="loopFallback")
*
* @return string
* @throws Exception
*/
public function loop(): string
{
// Do something
throw new Exception('Breaker exception');
}
/**
* @Breaker(fallback="loopFallback2")
*
* @return string
* @throws Exception
*/
public function loopFallback(): string
{
// Do something
throw new Exception('Breaker exception');
}
/**
* @Breaker(fallback="loopFallback3")
*
* @return string
* @throws Exception
*/
public function loopFallback2(): string
{
// Do something
throw new Exception('Breaker exception');
}
/**
* @return string
*/
public function loopFallback3(): string
{
return 'loopFallback3';
}
}
这里示例定义了几种使用场景
- 函数正常没有一次抛出 (
func2
) - 函数抛出异常通过降级函数返回数据 (
func
) - 定义熔断器没有定义降级函数 (
unFallback
) - 循环执行熔断器 (
loop
)
循环执行熔断器这里详细讲解下流程
- 执行
loop
函数,抛出异常,执行loopFallback
降级函数 loopFallback2
降级函数,抛出异常,但是定义了熔断器和降级函数,所以继续执行loopFallback2
降级函数loopFallback2
降级函数,此时这里正常返回降级的数据
fallback
函数必须和 @Breaker
标记的函数完全一样除了名称不一样且在同一个类里面。注解
熔断器就用了一个 @Breaker
注解,该注解,必须定义在 bean 对象里面。
@Breaker
标记方法开启熔断器,如下参数详解:
- fallback 降级函数,必须和
@Breaker
标记的函数完全一样除了名称不一样且在同一个类里面 - sucThreshold 连续成功多少次状态切换阀门
- failThreshold 连续失败多少次状态切换阀门
- timeout 超时时间
- retryTime 熔断器由开启状态到半开状态尝试切换时间
注意:注解上面的配置会覆盖熔断器全局配置