php熔断,微服务-API熔断原理

article/2025/9/25 16:16:22

伴随微服务,出现了很多新鲜的名词,其实剥去外衣并没有那么高大上。

今天要谈到的,叫做”熔断”,一个典型的开源实现是Hystrix(JAVA实现)。

背景

一个分布式系统中,服务间互相调用错综复杂,假设某个基础服务宕机,那么就会导致若干上游调用方出现访问超时,进而引起上游重试,导致宕机的基础服务遭受到数倍的流量放大,更加无法恢复服务。

这种恶劣的情况并不会就此结束,上游因为调用基础服务超时而变慢,导致上游的上游超时…异常向上蔓延,最终导致整个分布式系统”雪崩”。

“熔断”就是为了避免”雪崩”而生的,它的思路是在调用方增加一种”避让”机制,当下游出现异常时能够停止(熔断)对下游的继续请求,当等待一段时间后缓慢放行部分的调用流量,并当这部分流量依旧正常的情况下,彻底解除”熔断”状态。

听起来,流程不算复杂吧?整个流程图如下,看不懂没关系,继续往下阅读吧。

fccdad2e71a5a59c153eb997e7d5e0fa.png

健康统计

判断下游正常的前提是统计最近一段时间内,下游的调用成功率,因此需要一个健康统计模块,记录最近N秒内的总请求数,成功请求数,失败请求数,是由业务调用后将结果打点到健康统计模块中。

下游健康的标志,是最近N秒的成功率大于某个阀值,那么代表下游健康。

因为时间不停的前进,要统计最近N秒内的成功率,显然仅仅维护3个数字是不足以表达的,因此这里一般会使用”时间窗口”来实现。

如最上面的图片所示,整个时间窗口由10个槽位(bucket)构成,每个槽位代表1秒钟,整个时间窗口表达了最近10秒的健康统计,最右侧的bucket记录了最近1秒的成功/失败请求数量,仅此而已。

随着时间每过去1秒,整个窗口会向右滑动1格,最左侧的1个槽位被淘汰,最右侧加入当前1秒的新槽位,这就是时间窗口的实现原理。

当然,我们在实现的时候不会写一个定时器每秒去更新时间窗口,而是当打点接口被调用的时候进行计算和窗口滑动。为了更清晰的帮助你理解,我写了一个简短的PHP实现:

PHP

// 时间窗口10个桶

define("BUCKET_NUM", 10);

// 成功率大于该值为健康

define("HEALTHY_RATE", 0.8);

// 健康统计

class HealthStats {

private $service = '';

private $buckets = [];

private $curTime = 0;

public function __construct($service)

{

$this->service = $service;

$this->buckets = array_fill(0, BUCKET_NUM, ['success' => 0, 'fail' => 0,]);

}

private function shiftBuckets()

{

$now = time();

$timeDiff = $now - $this->curTime;

if (!$timeDiff) {

return;

}

if ($timeDiff >= BUCKET_NUM) {

$this->buckets = array_fill(0, BUCKET_NUM, ['success' => 0, 'fail' => 0]);

} else {

$this->buckets = array_merge(

array_slice($this->buckets, $timeDiff, BUCKET_NUM - $timeDiff),

array_fill(0, $timeDiff, ['success' => 0, 'fail' => 0])

);

}

$this->curTime = $now;

}

public function success()

{

$this->shiftBuckets();

$this->buckets[count($this->buckets) - 1]['success']++;

}

public function fail()

{

$this->shiftBuckets();

$this->buckets[count($this->buckets) - 1]['fail']++;

}

public function isHealthy()

{

$this->shiftBuckets();

$success = 0;

$fail = 0;

foreach ($this->buckets as $bucket) {

$success += $bucket['success'];

$fail += + $bucket['fail'];

}

$total = $success + $fail;

if (!$total) {

return true;

}

return ($success * 1.0 / $total) >= HEALTHY_RATE;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

// 时间窗口10个桶

define("BUCKET_NUM",10);

// 成功率大于该值为健康

define("HEALTHY_RATE",0.8);

// 健康统计

classHealthStats{

private$service='';

private$buckets=[];

private$curTime=0;

publicfunction__construct($service)

{

$this->service=$service;

$this->buckets=array_fill(0,BUCKET_NUM,['success'=>0,'fail'=>0,]);

}

privatefunctionshiftBuckets()

{

$now=time();

$timeDiff=$now-$this->curTime;

if(!$timeDiff){

return;

}

if($timeDiff>=BUCKET_NUM){

$this->buckets=array_fill(0,BUCKET_NUM,['success'=>0,'fail'=>0]);

}else{

$this->buckets=array_merge(

array_slice($this->buckets,$timeDiff,BUCKET_NUM-$timeDiff),

array_fill(0,$timeDiff,['success'=>0,'fail'=>0])

);

}

$this->curTime=$now;

}

publicfunctionsuccess()

{

$this->shiftBuckets();

$this->buckets[count($this->buckets)-1]['success']++;

}

publicfunctionfail()

{

$this->shiftBuckets();

$this->buckets[count($this->buckets)-1]['fail']++;

}

publicfunctionisHealthy()

{

$this->shiftBuckets();

$success=0;

$fail=0;

foreach($this->bucketsas$bucket){

$success+=$bucket['success'];

$fail+=+$bucket['fail'];

}

$total=$success+$fail;

if(!$total){

returntrue;

}

return($success*1.0/$total)>=HEALTHY_RATE;

}

}

一个HealthStats对象维护某个下游服务的健康统计信息。

默认10个桶,每个桶1秒。

RPC成功调用success,失败调用fail,检查服务健康调用isHealthy。

时间窗口滑动是”懒惰”的,所以在success,fail,isHealthy中都需要先进行窗口滑动的计算。

计算当前时间和窗口上次滑动的时间之间的时间差为timeDiff秒,那么就需要将窗口向右滑动timeDiff个槽位。

在每次RPC结束后,将结果打点到HealthStats中:

function rpc() {

$resp = rand(0, 1); // 模拟调用成功/失败

$resp ? $healthStats->success() : $healthStats->fail();

}

1

2

3

4

functionrpc(){

$resp=rand(0,1);// 模拟调用成功/失败

$resp?$healthStats->success():$healthStats->fail();

}

熔断控制

依靠健康统计,我们随时可以获知某个下游服务是否健康,当我们发现下游不健康的时候需要及时进行”熔断”操作。

所谓”熔断”,是指下游不健康的情况下,停止继续请求下游,避免因为请求超时导致自身处理时间增加,进而将坏的影响继续向上游传播。

那么到底要”熔断”多久?如何知道下游已经恢复了呢?这里涉及到”策略”问题,我们完全可以按照自己的设想来实现多种不同的策略,下面我举一种比较通用的策略作为演示。

初始化状态,熔断未启动,可以正常调用。

在每次RPC调用前,如果健康统计返回不健康,那么立即启动熔断。

在熔断开始的N秒内,不允许继续调用下游,此时没有新的请求继续发送出去。

在N秒过后,并且健康统计返回健康(时间窗口滚动,丢弃了那些历史数据),那么进入”恢复期”。

“恢复期”开始的M秒内,随机允许部分调用发送给下游。

“恢复期”内,如果健康检查返回不健康,那么重新启动”熔断”。

“恢复期”开始的M秒之后,如果健康检查返回健康,那么关闭”熔断”,全部流量恢复调用。

“恢复期”开始的M秒之内,如果健康检查返回健康,那么随着时间的推移,逐渐增加放量的比例,也就是慢慢恢复流量。

下面的代码演示了如何基于健康统计,实现一个熔断控制器:

PHP

// 熔断后停止所有流量5秒

define("BREAK_PERIOD", 5);

// 完全恢复需要再花费3秒

define("RECOVER_PERIOD", 3);

// 熔断器

class CircuitBreaker {

private $healthStats;

private $status = 1; // 1:正常 2:熔断 3:恢复

private $breakTime = 0; // 熔断的时间点

public function __construct(HealthStats $healthStats)

{

$this->healthStats = $healthStats;

}

public function isBreak()

{

$now = time();

$isHealthy = $this->healthStats->isHealthy();

$breakLastTime = $now - $this->breakTime;

$isBreak = false;

switch ($this->status) {

case 1:

if (!$isHealthy) {

$this->status = 2;

$this->breakTime = time();

$isBreak = true;

echo '触发熔断' . PHP_EOL ;

}

break;

case 2:

if ($breakLastTime < BREAK_PERIOD || !$isHealthy) {

$isBreak = true;

} else {

$this->status = 3;

echo '进入恢复' . PHP_EOL;

}

break;

case 3:

if (!$isHealthy) {

$this->status = 2;

$this->breakTime = time();

$isBreak = true;

echo '恢复期间再次熔断' . PHP_EOL;

} else {

if ($breakLastTime >= BREAK_PERIOD + RECOVER_PERIOD) {

$this->status = 1;

echo '恢复正常' . PHP_EOL;

} else {

$passRate = $breakLastTime * 1.0 / (BREAK_PERIOD + RECOVER_PERIOD);

if (mt_rand() / mt_getrandmax() > $passRate) {

$isBreak = true;

}

}

}

break;

}

return $isBreak;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

// 熔断后停止所有流量5秒

define("BREAK_PERIOD",5);

// 完全恢复需要再花费3秒

define("RECOVER_PERIOD",3);

// 熔断器

classCircuitBreaker{

private$healthStats;

private$status=1;// 1:正常 2:熔断 3:恢复

private$breakTime=0;// 熔断的时间点

publicfunction__construct(HealthStats$healthStats)

{

$this->healthStats=$healthStats;

}

publicfunctionisBreak()

{

$now=time();

$isHealthy=$this->healthStats->isHealthy();

$breakLastTime=$now-$this->breakTime;

$isBreak=false;

switch($this->status){

case1:

if(!$isHealthy){

$this->status=2;

$this->breakTime=time();

$isBreak=true;

echo'触发熔断'.PHP_EOL;

}

break;

case2:

if($breakLastTime

$isBreak=true;

}else{

$this->status=3;

echo'进入恢复'.PHP_EOL;

}

break;

case3:

if(!$isHealthy){

$this->status=2;

$this->breakTime=time();

$isBreak=true;

echo'恢复期间再次熔断'.PHP_EOL;

}else{

if($breakLastTime>=BREAK_PERIOD+RECOVER_PERIOD){

$this->status=1;

echo'恢复正常'.PHP_EOL;

}else{

$passRate=$breakLastTime*1.0/(BREAK_PERIOD+RECOVER_PERIOD);

if(mt_rand()/mt_getrandmax()>$passRate){

$isBreak=true;

}

}

}

break;

}

return$isBreak;

}

}

在恢复期间每次计算熔断后健康状态持续的时间与最大恢复时间之间的比率(随着时间推移,逐渐增加为100%),随机数小于比率即可以发起调用,从而实现逐步放大流量。当然,如果恢复期间再次出现了不健康的状态,那么立即重置为熔断状态。

整个测试代码如下:

PHP

// 健康检查

$healthStats = new HealthStats("a.service.com");

// 熔断器

$circuitBreaker = new CircuitBreaker($healthStats);

function rpc() {

global $healthStats, $circuitBreaker;

if (!$circuitBreaker->isBreak()) {

$resp = rand(0, 1); // 模拟调用成功/失败

$resp ? $healthStats->success() : $healthStats->fail();

}

}

for ($i = 0; $i < 100; ++$i) {

rpc();

sleep(1);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

// 健康检查

$healthStats=newHealthStats("a.service.com");

// 熔断器

$circuitBreaker=newCircuitBreaker($healthStats);

functionrpc(){

global$healthStats,$circuitBreaker;

if(!$circuitBreaker->isBreak()){

$resp=rand(0,1);// 模拟调用成功/失败

$resp?$healthStats->success():$healthStats->fail();

}

}

for($i=0;$i<100;++$i){

rpc();

sleep(1);

}

将HealthStats传递给熔断器,每次RPC前先判断熔断器状态:如果熔断器正常,那么发起RPC调用,将结果成功/失败反馈到健康检查。

当熔断器处于恢复中时会逐步随机放量到下游,从而采集到最新的healthStats数据,进而促进熔断器从恢复中变为正常状态。

改进

健康检查的实现存在一个问题,就是如果只有很少的请求打点到HealthStats中,那么此时获取isHealthy其实不太准确,比如:目前只有1个成功和1个失败,成功率就是50%,显然基于这样的样本计算成功率是不准确的。

所以,在健康检查部分,我们设计一个阀值,只有总请求量超过阀值才真正计算成功率,否则总是返回健康,例如:

PHP

public function isHealthy()

{

$this->shiftBuckets();

$success = 0;

$fail = 0;

foreach ($this->buckets as $bucket) {

$success += $bucket['success'];

$fail += + $bucket['fail'];

}

$total = $success + $fail;

if ($total < 10) { // 少于10个请求的样本太少,不计算成功率

return true;

}

return ($success * 1.0 / $total) >= HEALTHY_RATE;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

publicfunctionisHealthy()

{

$this->shiftBuckets();

$success=0;

$fail=0;

foreach($this->bucketsas$bucket){

$success+=$bucket['success'];

$fail+=+$bucket['fail'];

}

$total=$success+$fail;

if($total<10){// 少于10个请求的样本太少,不计算成功率

returntrue;

}

return($success*1.0/$total)>=HEALTHY_RATE;

}

如果文章帮助了你,请帮我点击1次谷歌广告,或者微信赞助1元钱,感谢!

c68972f84f7c4f47f59a1f69f0608e10.png

知识星球有更多干货内容,对我认可欢迎加入:

6c9a48ad74e3675cedd6ca98c1fd0a1f.png


http://chatgpt.dhexx.cn/article/gnMk3bCA.shtml

相关文章

接口熔断 java_SpringCloud(五):服务熔断与熔断监控

一、概念部分 1.什么是熔断器? 熔断,就是断开与服务器的连接,熔断器是在服务不可用的时候主动断开,以免造成更多的雪崩效应,他是保护服务高可用的最后一道防线。 2.为什么需要熔断器? 为保证服务高可用,最先想到的是服务集群,但集群并不能完全的保证服务高可用, 当某个…

分布式系统服务熔断

# 服务熔断 - “熔断器”本身是一种开关装置&#xff0c;当某个服务单元发生故障之后&#xff0c;通过断路器(hystrix)的故障监控&#xff0c;某个异常条件被触发&#xff0c;直接熔断整个服务。向调用方法返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或…

微服务熔断

https://mp.weixin.qq.com/s/cxd0Fol4BBzx4v2qm-hlwQ 我这篇文章来的晚了些&#xff0c;因为hystrix已经进入维护模式。但已经有非常多的同学入坑了&#xff0c;那么本篇文章就是及时雨。本文将说明熔断使用的一些注意事项&#xff0c;可能会细的让你厌烦。 前半段&#xf…

【服务熔断】服务熔断完整说明

文章目录 1.创始人文档2.熔断机制3.原理总结4.断路器打开之后5.服务监控hystrixDashboarda.七色b.一圈c.一线d.单个图说明e.多个图说明 1.创始人文档 大神文档 2.熔断机制 熔断机制概述 熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用…

Istio的熔断

一、理解熔断   熔断&#xff08;Circuit Breaker&#xff09;&#xff0c;原是指当电流超过规定值时断开电路&#xff0c;进行短路保护或严重过载时的一种保护机制。后来熔断也广泛应用于金融领域&#xff0c;指当股指波幅达到规定的熔断点时&#xff0c;交易所为控制风险采…

hystrix熔断

熔断操作放在服务提供层&#xff0c;是在类的方法上&#xff0c;而降级是在消费者的接口层面设置 1.加入jar包 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-hystrix</artifactId><version&…

服务熔断降级

之前给大家讲了很多服务之前的关系&#xff0c;今天主要给大家介绍&#xff0c;当服务出现问题时&#xff0c;我们该如何解决。 首先我们先进行一些场景分析&#xff1a; 场景一 服务提供端提供了A、B、C、D 4个服务&#xff0c;服务调用方调用服务时&#xff0c;D服务出现了…

Hystrix 服务熔断

目录 服务雪崩 一、什么是Hystrix 二、服务熔断 案例 三、服务降级 什么是服务降级 降级工厂类 设置fallbackFactory 开启feign.hystrix 四、服务熔断和降级的区别 五、DashBoard流监控 添加依赖 分布式系统面临的问题&#xff1a; 复杂分布式体系结构中的应用程序…

sentinel 熔断降级

sentinel 熔断降级 官网&#xff1a;https://sentinelguard.io/zh-cn/docs/circuit-breaking.html 熔断降级 熔断降级&#xff1a;服务由于响应慢、异常等原因触发熔断策略后&#xff0c;快速失败&#xff0c;避免线程堆积造成服务雪崩&#xff08;熔断降级通常在调用端配置&am…

kong网关熔断插件

Request Termination经常被作为kong的熔断器使用。以下在自建的konga管理界面里进行了测试配置 配置参数如下&#xff1a; 客户端发起请求&#xff0c;可以通过response进行验证&#xff0c;可以看到响应的状态码和报文都能生效。 以上配置是基于kong 0.12.3&#xff0c;可…

服务熔断的实现

# 0.服务熔断的实现思路 - 引入hystrix依赖,并开启熔断器(断路器) - 模拟降级方法 - 进行调用测试 # 1.项目中引入hystrix依赖 <!--引入hystrix--> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-start…

熔断

我们知道&#xff0c;如果一个软件系统的并发请求数目超过了系统的最佳线程数&#xff0c;那么就会导致激烈的资源竞争&#xff0c;随着资源的匮乏甚至枯竭&#xff0c;整个系统也就面临着灾难。所以&#xff0c;很多软件系统为了保证即使在出现并发用户数>最佳线程数时&…

什么是服务熔断?

一、什么是服务熔断&#xff1f; 考试过程中当断则断的方式&#xff0c;正好符合微服务架构中的一种安全机制&#xff1a;【熔断】 熔断这一概念来源于电子工程中的断路器&#xff08;Circuit Breaker&#xff09;。 在互联网系统中&#xff0c;当下游服务因访问压力过大而响应…

熔断原理分析与源码解读

熔断机制&#xff08;Circuit Breaker&#xff09;指的是在股票市场的交易时间中&#xff0c;当价格的波动幅度达到某一个限定的目标&#xff08;熔断点&#xff09;时&#xff0c;对其暂停交易一段时间的机制。此机制如同保险丝在电流过大时候熔断&#xff0c;故而得名。熔断机…

【C++程序设计语言A视频教程 全12讲 中科院】【下载链接】

C程序设计语言A视频教程 全12讲 中科院 这个是我在淘宝上面买的 杨力祥老师的教程~~ 奉献给大家~~~ *************************************************************************************************************************************** 下面是网上对杨力祥老师的…

国科大杨力祥老师操作系统答案总结

基于网上搜索的版本以及历届师兄的版本&#xff0c;进行了整合和修改 对应参考书籍如下&#xff0c;对应P页数也是指该书的页数。 1.为什么开始启动计算机的时候&#xff0c;执行的是BIOS代码而不是操作系统自身的代码&#xff1f; 最开始启动计算机的时候&#xff0c;计算机…

简述Mean shift 算法及其实现

文章目录 Mean shift 是什么Mean shift 算法的预备知识什么是特征什么是特征空间什么是核密度估计核函数的表示 Mean shift 算法Mean shift算法的公式推导Mean shift算法的流程Mean shift算法图示 Mean shift 算法应用Mean Shift 算法应用在聚类Mean Shift 算法图像分割 Mean s…

Johnson-Trotter算法求全排列

下面我将贴出Johnson-Trotter算法的JAVA代码 package JT;import java.util.Scanner;public class Johnson_Trotter {//求最大的移动元素public static int maxk(int n, int[] array, boolean[] f) {//k存储最大移动元素的下标int k -1, max 0;for(int i 0; i < n; i) {/…

全源最短路Johnson算法

最短路Johnson算法( O ( n m l o g m ) O(nmlogm) O(nmlogm)) 可以求任意两点最短路&#xff0c; 新图的边权改造为&#xff1a; w ( x , y ) h ( x ) − h ( y ) w(x,y)h(x)-h(y) w(x,y)h(x)−h(y) 构造的新图 d 1 ( x , y ) d ( x , y ) h ( x ) − h ( y ) d1(x,y)d(x,y…

流水线作业调度问题-动态规划(运用Johnson算法)

问题描述 n个作业{1&#xff0c;2&#xff0c;…&#xff0c;n},要在由机器M1和M2组成的流水线上完成加工。每个作业加工的顺序都是先在M1上加工&#xff0c;然后在M2上加工。M1和M2加工作业i所需的时间分别为ai和bi。 要求确定这n个作业的最优加工顺序&#xff0c;使得从第一…