采用断路器设计模式来保护软件

程序员的人生就像在一个快车道上行驶。几周甚至几小时完成某些特性编码,打包测试没有问题,盖上QA认证,代码部署到生产环境。然而最坏的事情发生 了,你所部署的软件在运行中挂掉了。用墨菲法则来说,就是“会出错的,终将出错”。但是,如果我们在写代码时就能考虑到这些问题会怎样?

那么我们该如何应对,将不好的事情转变为好的事情呢?

电子技术拯救了我们

至今记得我和哥哥因为电涌不得不更换家里的保险丝情景,那时我对事件的严重程度一无所知,而他却已经是电力方面的小能手了。保险丝完全烧坏了,但它 却保护了我家的电视机。在电子工程领域,保险丝和断路器用(Circuit Breaker)来处理这样的问题,即超大功率可能带来一些严重的破坏,譬如毁坏电子设备甚至烧掉整个屋子。保险丝包含一个小电线丝,电量过大时就会融 化,就像烧掉的电灯泡,阻止危险的电流,保护了电器和房屋。

保险丝演变成断路器,通常利用电磁铁就可以断开电路,而不用烧掉它,这样断路器就可以重置反复地用。不过,它们的功能都是一样的,检测负载,接着迅速停止工作,保全其它部分不受破坏。

回过头再想,这是一个多么神奇的概念。仅仅坏掉某个控件——保险丝彻底坏掉,就可以避免了整个系统的严重损坏。多亏电涌后保险丝自熔,保住了电视 机。那么我们可不可在软件中做同样的事情?坏事发生后,软件中的某个控件会迅速停止工作。模仿现实生活中的场景,由此我们创造了断路器设计模式

在分布式系统中,某些故障是短暂的,通过快速连续重试就可以解决问题;但在某些场景中,关键依赖的连接丢失了,短时间无法恢复。比如,某个应用失去 了与云中的持续化存储连接。在这样的场景中,关闭服务就可以避免错误的数据处理过程、甚至数据丢失或者级联故障,进而防止对系统其它部分的进一步损坏。

借助于迅速停止工作(failing fast),运维系统就可以容易地进行监控和响应。在它们重视起来之前,那些徒劳尝试重新连接的服务看起来仍然是正常的,因为本应该拉响的警报没有响起。 倘若某个服务在恰当的时候彻底失效,警告灯熄灭了,运维人员就会知晓问题所在,并及时做出响应。

断路器设计模式

在系统中可重用基础架构实现断路器设计模式是很容易实现的,它是这么发挥作用的:

1 定义一个可重用的CircuitBreaker类,包含Trip和Reset方法,以及断路器跳闸就可以调用的action
2 利用CircuitBreaker去监控系统依赖。针对每个单一的故障,断路器跳闸就会将其设置在一种布防状态,就像电涌出现时那样。
3 倘若接下来在特定的时间窗口内尝试成功,那么就重置此断路器,一切恢复正常。
4 倘若断路器没有在特定的时间重置,异常会持续发生,此时断路器就会调用你提供的action。你可以在断路器跳闸时选择快速停止工作(终止进程)或者其他action。

应用案例

本例中ExternalServiceAdapter类帮助系统与外部依赖建立连接。或许有个网络程序产生请求频繁地执行DoStuff操作。一旦 执行,若此时GetConnection执行出错,异常就会发生,断路器就会被跳闸。倘若连接重新建立起来,断路器就会被重置。不过连接异常持续发生时, 断路器就会跳闸,特定的跳闸action就会执行,在本例中将会迅速停止工作。

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
publicclassExternalServiceAdapter
{
    privateCircuitBreaker circuitBreaker;
 
    publicExternalServiceAdapter()
    {
        circuitBreaker =newCircuitBreaker("CheckConnection",/*断路器名称 */
            exception => /* 一旦断路器跳闸此action就会被调用 */
            {
                Console.WriteLine("Circuit breaker tripped! Fail fast!");
                //终止进程,略过接下来的任何try/finally块或者finalizers
                Environment.FailFast(exception.Message);
            },
        3, /* 断路器跳闸前的最大阈值*/
        TimeSpan.FromSeconds(2)); /* Time to wait between each try before attempting to trip the circuit breaker */
    }
 
    publicvoidDoStuff()
    {
        var externalService = GetConnection();
        externalService.DoStuff();
    }
 
    ConnectionDependency GetConnection()
    {
        try
        {
            var newConnection =newConnectionDependency();
            circuitBreaker.Reset();
            returnnewConnection;
        }
        catch(Exception exception)
        {
            circuitBreaker.Trip(exception);
            throw;
        }
    }
}

断路器模式简单实现

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
using System;
using System.Threading;
 
publicclassCircuitBreaker
{
    publicCircuitBreaker(string name,/*操作名称*/
        Action<Exception> tripAction, /* 一旦断路器跳闸action就会被调用*/
        int maxTimesToRetry, /* 断路器跳闸前重试的时间*/
        TimeSpan delayBetweenRetries /*每一次重试的时间间隔*/)
    {
        this.name = name;
        this.tripAction = tripAction;
        this.maxTimesToRetry = maxTimesToRetry;
        this.delayBetweenRetries = delayBetweenRetries;
 
        // 一旦用户迫使断路器跳闸,计时器就会开启
        timer =newTimer(CircuitBreakerTripped,null, Timeout.Infinite, (int)delayBetweenRetries.TotalMilliseconds);
    }
 
    publicvoidReset()
    {
        var oldValue = Interlocked.Exchange(ref failureCount,0);
        timer.Change(Timeout.Infinite, Timeout.Infinite);
        Console.WriteLine("The circuit breaker for {0} is now disarmed", name);
 
    }
 
    publicvoidTrip(Exception ex)
    {
        lastException = ex;
        var newValue = Interlocked.Increment(ref failureCount);
 
        if(newValue ==1)
        {
            // 开启重试计时器.
            timer.Change(delayBetweenRetries, TimeSpan.FromMilliseconds(-1));
 
            // 记录已触发的断路器.
            Console.WriteLine("The circuit breaker for {0} is now in the armed state", name);
        }
    }
 
    voidCircuitBreakerTripped(object state)
    {
        Console.WriteLine("Check to see if we need to trip the circuit breaker. Retry:{0}", failureCount);
        if(Interlocked.Increment(ref failureCount) > maxTimesToRetry)
        {
            Console.WriteLine("The circuit breaker for {0} is now tripped. Calling specified action", name);
            tripAction(lastException);
            return;
        }
        timer.Change(delayBetweenRetries, TimeSpan.FromMilliseconds(-1));       
    }
 
    readonly string name;
    readonlyintmaxTimesToRetry;
    longfailureCount;
    readonly Action<Exception> tripAction;
    Exception lastException;
    readonly TimeSpan delayBetweenRetries;
    readonly Timer timer;
}

断路器单元测试

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
[TestFixture]
publicclassCircuitBreakerTests
{
    [Test]
    publicvoidWhen_the_circuit_breaker_is_tripped_the_trip_action_is_called_after_reaching_max_threshold()
    {
        bool circuitBreakerTripActionCalled =false;
        var connectionException =newException("Something bad happened.");
 
        var  circuitBreaker =newCircuitBreaker("CheckServiceConnection", exception =>
        {
            Console.WriteLine("Circuit breaker tripped - fail fast");
            circuitBreakerTripActionCalled =true;
            // You would normally fail fast here in the action to faciliate the process shutdown by calling:
            // Environment.FailFast(connectionException.Message);
        },3, TimeSpan.FromSeconds(1));
 
        circuitBreaker.Trip(connectionException);
        System.Threading.Thread.Sleep(5000);
        Assert.IsTrue(circuitBreakerTripActionCalled);
    }
 
    [Test]
    publicvoidWhen_the_circuit_breaker_is_reset_the_trip_action_is_not_called()
    {
        bool circuitBreakerTripActionCalled =false;
        var connectionException =newException("Something bad happened.");
 
        var circuitBreaker =newCircuitBreaker("CheckServiceConnection", exception =>
        {
            Console.WriteLine("Circuit breaker tripped - fail fast");
            circuitBreakerTripActionCalled =true;
            // You would normally fail fast here in the action to faciliate the process shutdown by calling:
            // Environment.FailFast(connectionException.Message);
        },3, TimeSpan.FromSeconds(2));
 
        circuitBreaker.Trip(connectionException);
        System.Threading.Thread.Sleep(1000);
        circuitBreaker.Reset();
        Assert.False(circuitBreakerTripActionCalled);
    }
}

上面代码案例采用Console.WriteLine,你可以选择自己喜欢的logger。

最后结语

断路器是现代社会重要的组成部分,可以说是最重要的安全设备之一。不论是一个熔化的保险丝,或者是跳闸的断路器,它们的存在背后都有其充足的理由。

监控重要的资源,一旦它们无法响应,断路器就迅速停止工作,进而确保整个运维团队做出正确的响应。

如果你想进一步了解这些设计模式,请看Michael T. Nygard 的《Release It》,这是一本相当不错的读物。

优秀到卓越
分享到:
共 0 条  此列表为空  当前1/1页

© 2014 究问社区 copyRight 豫ICP备13003319号-1