最近在项目里用 Go 语言对一些微服务进行重构,由于 Go 对我来说仍然算是一种新语言,因此在项目过程中碰到的一些比较不同一些写法,将会以 Tips 的方式记录在这里。今天想记录的是 Go 语言里的超时处理。
超时在一些业务场景里非常普遍,例如:
– 数据库访问操作,进行网络连接时通常都有超时时间。
– 本地客户端用阻塞方式异步访问远程,等待一段时间之后,如果远程没有返回结果,则认为超时。
– 微服务在启动时,需要初始化某些数据,例如等待服务注册表返回的总线密码等等,如果在规定的时间内未获得需要的内容,则认为超时,服务启动失败。
在 Java 等传统语言里,通常会首先记录当前时间,然后使用一个无限循环进行资源等待检查,如果需要等待的资源无法获取到,则将当前线程休眠一段时间,当休眠恢复后检查时间是否超过规定时间,如果超时则抛出异常,否则继续进入循环进行等待。用伪代码表述如下:
long startTime = Time.currentTimeMillis();
long EXPIRE = 1000*30; // 30 seconds to expire
while (true) {
resource = getResourceFromRemote();
if (resource == null) {
Thread.sleep(1000); // Sleep 1 second
}
long currentTime = Time.currentTimeMillis();
if (currentTime - startTime > EXPIRE)
throw new RuntimeException("Expired to get remote resource");
}
上述 Java 实现中,需要手工处理超时时间以及线程休眠等等,看上去也是非常的清晰,但是在 Go 语言的版本里,整个过程会显得更加简洁:
<pre class="wp-block-syntaxhighlighter-code">
for resource := getResourceFromRemote(); resource == nil {
select {
case <-time.After(time.Second * 30):
panic("Unable to receive resource within 30 seconds.")
default:
logs.Info("Resource is not received, waiting for 1 seconds...")
time.Sleep(time.Second) // Wait for 1s until the resource is received.
}
}
</pre>
首先也是进入一个循环,循环条件是可以获取需要的资源,在循环内部使用 select 语句试图从 case 语句指定的通道里读取数据,如果无法读取到数据则进入下一个 case, <- time.After(time.Second * 30) 表示在 30 秒之后从通道内返回数据,在未到达规定的时间执行 ‘default’ 语句并且使用 time.Sleep(time.Second) 休眠 1 秒。
在这个 Go 语言的版本里,虽然看上去代码更加简洁,更多是利用 Go 语言本身的的一些特性,而不是过多依赖于函数库,这体现了 Go 语言设计时将一些互联网时代常用的功能变成语言本身的实用主义特点。