熔断一词我们可以联想到保险丝的熔断,保险丝也是一种应急机制,当我们线路中某处发生短路造成瞬间的电流过大,保险丝就会熔断以保护我们的线路和其他用电器。微服务中的熔断和保险丝的熔断起到的是类似这作用
我们之前讲解了使用了Consul做服务治理和健康检查。即使有健康检查,如果某个服务器或者服务挂掉,会在指定时间内对该服务进行清理。但是也无法做到100%正在这个坏掉的服务杯清理之前还被继续的访问。还有一个场景就是,我们的服务器并没有挂,consul健康检查的时候也是正常响应的,可能是业务逻辑问题造成的响应时间太长等等问题,在高并发场景下,一但某个服务一直超时最终肯定也会导致服务崩溃然后波及其他服务。
那什么是降级呢?举个例子,高考后的择校,选择大学肯定是优先考虑清华北大等985/211等重点大学,但是我们的分数达不到这个层次的时候只能降低等级选择一二本学校,分数再达不到,我们只能再次降低标准就读专科学校。总之虽然不能满足最高期望,但还是能够有大学上的,除非交白卷那没法子。在我们的系统中也是如此,我们当然希望给用户最好的体验和服务,再举个例子,查询最新的数据。最新最权威的数据肯定是在数据库中,缓存中的数据可能存在延迟。但是如果我们的数据库服务器宕机了,不应立马告诉用户查询失败,而是降级到缓存中查找,如果缓存也没了实在没办了,再返回查找失败。这样无形就提升了用户体验。
Polly是一个被.Net基金会认可的库,可以用来简化熔断降级的处理。主要功能包括:
重试(Retry)
断路器(Circuit-breaker)
超时检测(Timeout)
缓存(Cache)
降级(FallBack)
官网:http://www.thepollyproject.org/
安装:通过Nuget安装
polly的策略由“问题故障”和“应对动作”两个部分组成 。故障包括:异常、超时等情况,动作包括:重试(Retry)、断路器(Circuit-breaker)、超时检测(Timeout)、缓存(Cache)、降级(FallBack)。
static void Main(string[] args)
{
//声明Policy处理ArgumentException异常
Policy policy = Policy.Handle()
//出现异常时的动作
.Fallback(() =>
{
Console.WriteLine("出错了");
});
//没有异常时正常正常的业务逻辑
policy.Execute(() =>
{
Console.WriteLine("正常情况下:开始执行");
Console.WriteLine("正常情况下:执行结束");
});
}
以上代码声明Policy处理ArgumentException异常,处理异常大动作为降级,policy.Execute()方法是指没有发生异常时,我们的业务逻辑。
运行代码
手动抛出ArgumentException异常
policy.Execute(() =>
{
Console.WriteLine("正常情况下:开始执行");
throw new ArgumentException();
Console.WriteLine("正常情况下:执行结束");
});
可以看到,出现 ArgumentException异常后程序执行了Fallback(),我们上面定义的Policy监控的是ArgumentException异常,如果抛出的不是ArgumentException异常,还会执行Fallback()吗?
policy.Execute(() =>
{
Console.WriteLine("正常情况下:开始执行");
throw new Exception();
Console.WriteLine("正常情况下:执行结束");
});
显然 当异常类型与定义监控异常类型不匹配的情况下是不会执行Fallback()的
这里以 Retry(N)为例子
Policy policy = Policy.Handle()
.Retry(5);
policy.Execute(() =>
{
Console.WriteLine("正常情况下:开始执行");
throw new Exception();
Console.WriteLine("正常情况下:执行结束");
});
运行结果可以看到重试5次加上第一次一共执行了6次。
出现N次连续错误,则将服务熔断,等待一段时间,等待这段时间内如果在再Execute()则会直接抛出BrokenCircuitException异常。等待时间过后,再执行Execute()的时候还是错误的话,那么继续熔断一段时间,如果正确则回复正常。我们可以联想一下类似场景,登录的时候如果密码错误超过三次,将需要等待一段时间才可以继续执行登录,在这段时间内点击登录按钮,直接告知当前不可以登录。等待时间后进行登录,如果还是错误那么又得继续等待。如果这一次正确了,那就回复正常登录,并且重新获得3次错误机会。这样避免了一个服务已经不可用了,还不断接受请求给系统造成更大压力。
Policy policy = Policy.Handle()
.CircuitBreaker(3, TimeSpan.FromSeconds(10));
policy.Execute(() =>
{
Console.WriteLine("正常情况下:开始执行");
throw new Exception();
Console.WriteLine("正常情况下:执行结束");
});
策略封装就是把多个ISyncPolicy合并一起执行,
policy3=policy1.Wrap(policy2);
policy3会把policy1和policy2封装一起执行。
这样说可能比较抽象,举个例子,要实现“出现异常则重试3次,如果还错那就进行降级”。
//重试3次
Policy policyRetry = Policy.Handle()
.Retry(3);
//降级
Policy policyFallback = Policy.Handle()
.Fallback(() =>
{
Console.WriteLine("降级了");
});
//重试三次后降级
Policy policyWarp = policyFallback.Wrap(policyRetry);
policyWarp.Execute(() =>
{
Console.WriteLine("开始执行");
for (int i = 0; i < 100; i++)
{
if (i % 2 == 0)
throw new Exception("出错了");
}
Console.WriteLine("执行结束");
});
上面说的都是业务逻辑错误抛出异常的处理,但实际上还有一种场景就是,程序并没有报错,只是执行时间过长,这种没有具体的异常的超时场景,Polly提供了超时策略。但是超时策略得配合其他策略使用。
//超时
Policy policyTimeout = Policy.Timeout(3, TimeoutStrategy.Pessimistic);
//降级
Policy policyFallback = Policy.Handle()
.Fallback(() =>
{
Console.WriteLine("降级");
});
Policy policy = policyFallback.Wrap(policyTimeout);
policy.Execute(() =>
{
Console.WriteLine("开始执行");
Thread.Sleep(5000);//阻塞5秒钟,模拟超时
Console.WriteLine("执行结束");
});
我们人为的让线程阻塞5秒钟,模拟超时。程序最终会去执行降级操作。
Polly提供的远不止这些,每种策略都有着很多重载。限于篇幅,无法一一列举,我们可以在工作中根据业务需求再根据文档选择合适的重载实现。