作为我们整个监控和指标系统的一部分,我有一个Python程序,可以登录到我们的IMAP服务器,以确保我们至少可以走到那一步(因为我们过去曾破坏过这种事情)。该程序发出各种普罗米修斯指标,包括这需要多长时间。由于超出本条目范围的原因,我还想得到一些关于IMAP服务器性能的非常基本的信息,比如对测试账户的IMAP收件箱进行IMAP SELECT操作需要多长时间。试图做一个干净的实现,让我遇到了围绕处理超时的问题。
像任何检查可能有问题的系统的合理程序一样,我的程序在与 IMAP 服务器对话的时间上有一个总体超时。在 Python 中,实现总体超时的直接方法是使用 signal.setitimer和一个SIGALRM 信号处理程序,引发一个异常。当你只做一件(概念性的)事情时,通过将你的操作包裹在一个try/except 块中,就可以直接实现:
try:
signal.setitimer(signal.ITIMER_REAL, timeout)
metrics = login_check(host, user, pw)
signal.setitimer(signal.ITIMER_REAL, 0)
report_success(host, metrics)
except Timeout:
report_failure(host)
要么我们在超时间隔内完成,在这种情况下,我们报告我们产生的时间和其他指标;要么我们失败,我们报告超时失败指标。
一旦我想为两个不同的操作报告单独的指标和成功状态,这种简单的方法就会被打破。在试图登录IMAP服务器时超时与成功登录IMAP服务器但在IMAP SELECT操作中超时是完全不同的(而且更严重)。自从我意识到这一点后(在我写新代码的时候),我一直在试图找出正确的结构来使代码自然和干净。
我认为我想要的理论上的干净的抽象是,一旦超时被击中,这就被记录下来,所有进一步的网络IO(或更普遍的IMAP协议操作)立即失败。如果这就是它的工作方式,IMAP登录尝试和IMAP SELECT都将报告成功或失败,这取决于超时发生时的情况,并且我可以在最后报告 "有一个超时 "的指标。这也可以很自然地扩展到做一系列的IMAP操作(例如,SELECT'ing几个不同的邮箱并收集每个邮箱的时间)。代码可以以直线方式生成指标,一切都会顺利完成。不幸的是,Python的网络代码和imaplib并没有提供一个直接的方法来做到这一点,所以我不得不在imaplib之上建立一个层来为我做这件事。
(这种方法受到 Go 的网络包的启发,它支持类似的东西。但即使如此,它也不像看起来那么干净,因为理想情况下,你希望每个检查都能意识到超时的可能性,这样它就能将真正的网络错误与 "我们遇到了时间限制,我的网络操作开始失败 "区分开来)。
我目前的方法是或多或少地通过生成的指标来明确跟踪我走了多远,然后用失败标记来填补任何缺失的指标:
login_metrics = None
select_metrics = None
try:
signal.setitimer(signal.ITIMER_REAL, timeout)
login_metrics, conn = login_check(host, user, pw)
# logging in may have failed
if conn:
select_metrics = select_check(conn, host, "INBOX")
# Let's ignore logging out for now
signal.setitimer(signal.ITIMER_REAL, 0)
did_timeout = False
except Timeout:
did_timeout = True
if not login_metrics:
login_metrics = failed_login(host, user)
if not select_metrics:
select_metrics = failed_select(host, "INBOX")
report_metrics(login_metrics, select_metrics)
report_maybe_timeout(host, did_timeout)
这种方法可行,但它有一个扩展问题;如果我增加更多的IMAP操作,我必须在几个地方增加代码,最好不要漏掉一个。这不是很通用,感觉应该有一个更好的方法。另一方面,这段代码至少是明确的,没有魔法;它可能是蛮力,但它是直接的,可以遵循。对于一个我每六个月最多接触一次的程序来说,高难度的方法可能并不合适。
(我的一些问题可能是因为我在一个函数中生成了几乎所有的登录指标,这在原来的代码中是一个聪明的想法,但现在可能不是了。 不过我不确定什么是更好的方法。)
这种方法也把所有的超时处理放在一个地方,在顶层,而不是强迫所有的单个操作都意识到他们会遇到超时的可能性(或者在超时触发后被调用,所以这就是为什么他们所有的IMAP操作都会失败)。这可能是代码结构的最佳选择,特别是在Python中,异常是我们处理许多全局问题的方式。
(这与相位跟踪有关,可以更好地报告错误。从某种意义上说,我目前的代码正在做的是跟踪变量集合中的 "阶段")。