指数补偿(exponential backoff)算法以及关于代码质量的思考

我一直对两件事情耿耿于怀,一个是bug 能不能避免,一个是代码质量。 —— 题记

背景

通常,当我们在代码中尝试进行一些网络请求时,出于一些原因,请求可能会失败。因此,我们在编写代码时,就不得不对这类情况加以考虑,选择一个应对策略。

指数补偿算法

在《APUE》16.4小节中介绍了一种指数补偿(exponential backoff)算法,可以在请求失败时,进行重试,而且每次重试的时间间隔是以指数级增加的。下面是我用 Golang 语言进行的实验:

// MaxSleep -
const MaxSleep = 20

// ConnectRetry try to connect with exponential backoff
func ConnectRetry(network string, address string) (conn net.Conn, err error) {
    for numsec := 1; numsec <= MaxSleep; numsec <<= 1 {
        fmt.Printf("connect to [%s]%s ", network, address)
        if conn, err = net.DialTimeout(network, address, time.Duration(1)); err == nil {
            // connect success, return
            fmt.Printf("success!\n")
            break
        }
        fmt.Printf("faild.\n")

        // delay before trying again
        if numsec <= MaxSleep/2 {
            fmt.Println("sleep second:", numsec)
            time.Sleep(time.Duration(numsec) * time.Second)
        }
    }
    return conn, err
}

上面这段代码的测试输出如下:

[ConnectRetry]connect to [tcp]192.168.0.1:8078 faild.
[ConnectRetry]sleep second: 1
[ConnectRetry]connect to [tcp]192.168.0.1:8078 faild.
[ConnectRetry]sleep second: 2
[ConnectRetry]connect to [tcp]192.168.0.1:8078 faild.
[ConnectRetry]sleep second: 4
[ConnectRetry]connect to [tcp]192.168.0.1:8078 faild.
[ConnectRetry]sleep second: 8
[ConnectRetry]connect to [tcp]192.168.0.1:8078 faild.

代码源文件可以在这里获取:https://github.com/xsddz/Advanced-Golang-Programming/tree/master/tinylib/exponentialbackoff

关于代码质量问题的思考

不晓得你有没有注意到,在上面那段演示代码里的Printf/Println输出信息中,我在每一行的输出信息前面,加了[ConnectRetry]这样的字符串前缀,什么用意呢?

实际的搬砖工作中,我见过太多的糟糕代码(擦屁股的活干多了),其中最明显的一类便是体现在(debug/info/warning等)日志输出上。我可以这么说,糟糕的日志输出,对于排查代码问题,一点用处没有。所以在分布式系统中,演化出这么一种技术:链路追踪。由此可见,我的用意,便是利用这种链路追踪的思想,把函数内部的执行逻辑流程,通过输出信息体现出来,这样,如果放眼到大的业务代码中,便可以通过查找[functionName]这样的字符串,把函数内部的执行逻辑流程梳理出来,以此快速定位问题点。这便是我这里想说的,关于代码质量问题系列中的一个。

后记

上面的示例,只是指数补偿算法的一个应用,在具体的业务场景中,还有很多可以应用的地方。如果你是一位对自己负责的程序员,那么可以想想在自己写过的代码中有那些是可以优化的。

最后,上面那段带有重试机制的演示代码逻辑,是否可以抽象出通用模式,以适配不同的业务场景需求。这是我想说的,关于代码质量问题系列中的第二个。