合理的timeout设置,让系统的SLA大幅提升

2020-08-21  本文已影响0人  小石桥胡同

如何更好的设置timeout

为什么会有timeout

百度了一下timeout的字面意思,就是简单的“超时”,那么timeout为什么跟我们编程息息相关,我没有找到timeout的最初的出处,但是我自己想了一下,这个应该是跟tcp/ip协议一起出现的,timeout应该是伴随着io出现的,io又分为网络io和磁盘io,当时我们不太关注磁盘io,主要关注的是网络io,所以我感觉是跟tcp/ip协议一起出现的,这个具体还要在查一下。如果网络交互没有timeout会出现一个什么情况?我不知道对端是否存活,那么有人说了我可以通过heartbeat来保持长连接,那么问题又来了,心跳间隔要设置多长?心跳间隔设置了我怎么来确定是否有心跳过来,那么就设计到了read心跳包,read心跳包有回到了最初的问题,如果我们不设置timeout会有啥结果?

不设置timeout的危害

如果我们不设置timeout会有什么影响呢?

例子1:以前在做长连接push服务的时候遇到了一个问题,就是一台服务器的长连接服务的内存在承载了100w连接的时候内存使用非常高,当时是用golang语言开发的,每条连接会有两个goroutine(读和写)来维护长连接的交互,每个goroutine占用4KB(golang1.4以后已经变为2KB,轻量的goroutine+channel是golang语言适合并发开发的优势)的大小,那么我们可以估算一下不到4G的内存使用,加上其他的一些信息总共不应该超过5G,但是当时的内存使用都是10多G,一直比较纳闷,好在golang有比较好的runtime的pprof的监测,能够很容易的dump出来整个goroutine的运行情况(类似于java的jstack),当拿到这些信息的时候,通过分析看到有很多的goroutine停留在socket的read上,而且是刚刚建立连接,等到握手信息的read上边,由于golang的并发比较简单,我们对于read采用了阻塞read,看到这个异常以后就开始检查代码,发现read的时候没有设置timeout,如果没有设置read会有什么结果呢,这个等待read的goroutine就会一直在阻塞read(一直到操作系统层面的tcp超时,一般默认是两个小时),那么就造成了goroutine(java里边可能是thread)的泄露,从而造成了内存泄露,也就能理解为什么内存过高了。找到问题修改代码,只加了一行代码就解决了这个问题。

例子2:我们的线上系统,经常会遇到一个错误,就是redis获取连接超时,遇到这种问题,很多人就来找我说是不是redis挂了,我看到这个问题一般的反应:

例子3:某核心模块A,出现过两次获取mysql连接超时,当时看到以后由于影响比较大,直接重启服务,然后开始追查问题,首先是检查mysql的慢日志,发现没有慢日志,然后检查mysql事务里边除了mysql的操作,还有没有其他的阻塞操作,发现很多的redis的操作,而且redis的value比较大,操作比较复杂,而且当时redis没有设置超时时间,第一反应就是redis的阻塞导致了mysql的事务的阻塞,从而导致了连接不够用。商户中心改掉了redis的处理,增加了超时时间,结果没过几天又出现了,这次出现问题以后,直接先jstack了一份信息,然后重启恢复,分析jstack的信息,发现一个奇怪的现象,很多的线程都在等着写文件的锁。分析代码,发现一个问题,就是mybatis开启了debug模式,有大量的mysql日志输出,而且是输出到了console,默认的docker容器的console就是linux操作系统的messages文件,由于写的数量巨大,导致了磁盘io的卡顿,从而导致了mysql事务的阻塞,从而获取连接失败,那么找到问题,修改就是改个配置的事情,把mybatis的debug关闭。从这个问题我们看到的是磁盘io的问题,磁盘io的问题遇到的很少不过也会遇到,这种情况我们应该如何避免呢?

如何更好的设置自己的timeout

如何设置好timeout,这个题目比较大,我大概结合着自己的经验提供一些参考,当时我不是说这个请求就是设置多少s,或者多少ms,这个是没有意义的,也不能拍着脑袋说。

总结一下:有io的地方就必须有timeout,这个可以当成是一个编程的惯例或者一个规则。好的timeout的设计可以使系统更加的健壮,对用户更加的优雅。timeout其实也是对系统的一种熔断降级。

上一篇下一篇

猜你喜欢

热点阅读