我正在使用Python3 + bottle / UWSGI开发实时REST API。我的代码遇到了延迟,有时是100秒,这在我的应用程序中很重要。
使用logging
模块,我试图识别代码的慢速部分,打印单个代码块运行的时间。我知道这是一种非常糟糕的分析代码的方法,但有时它能够很好地完成这项工作。
即使我发现了一些缓慢的部分,我仍然遗漏了一些东西 - 单个部分似乎需要10秒的ms,但通常它们整体上需要100秒。经过一些越来越疯狂的实验,让我几乎完全疯了,我来了以下几点:
t = round(100*time.time())
logging.info('[%s] Foo' % t)
logging.info('[%s] Bar' % t)
令人惊讶的是,它给出了:
2014-07-16 23:21:23,531 [140554568353] Foo
2014-07-16 23:21:24,312 [140554568353] Bar
虽然这似乎很难相信,但有两个随后的logging.info()
调用,并且出于某种原因,它们之间存在差不多800毫秒的差距。谁能告诉我发生了什么事?值得注意的是,如果有多个info()
调用,则延迟仅在整个API方法中出现一次,最常见于其开始时(在第一次调用之后)。我唯一的假设是磁盘延迟,有几个(但不是很多!)工人同时运行,我使用的是旋转磁盘,没有固态硬盘。但我认为有缓冲区,操作系统将为我异步执行磁盘刷新。我的假设错了吗?我应该避免完全记录以避免延迟吗?
修改
基于Vinay Sajip的建议,我切换到以下初始化代码:
log_que = queue.Queue(-1)
queue_handler = logging.handlers.QueueHandler(log_que)
log_handler = logging.StreamHandler()
queue_listener = logging.handlers.QueueListener(log_que, log_handler)
queue_listener.start()
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(message)s", handlers=[queue_handler])
似乎它工作正常(如果我评论queue_listener.start()
,没有输出),但仍然会出现相同的延迟。我不知道怎么可能,呼叫应该是非阻塞的。我还在每个请求结束时放置gc.collect()
以确保问题不是由垃圾收集器引起的 - 没有任何影响。我也试图关闭整天的日志记录。延迟消失了,所以我认为他们的来源必须在logging
模块......
答案 0 :(得分:16)
您可以使用异步处理程序(QueueHandler
和相应的QueueListener
,在Python 3.2中添加,并在this post中描述)并在单独的I / O处理日志事件线程或过程。
答案 1 :(得分:10)
正如hasan所提出的,异步日志处理程序可以成为现实。
最近我尝试使用Logbook
,可以说,它会为您提供所需的一切 - ZeroMQHandler以及ZeroMQSubscriber
答案 2 :(得分:4)
它可能取决于日志记录处理程序。我的经验是,例如用作记录处理程序的PostgreSQL是一个非常糟糕的速度选择。 FileHandler可能会给你很好的结果但是如果你的系统非常I / O很重,那么即使简单的文件写入也会很慢。我建议使用一些异步处理程序,例如通过UDP将日志发送到专用进程。
答案 3 :(得分:2)
首先,从逐出队列(循环缓冲区)开始....这样可以确保队列处理程序无法消耗所有可用的RAM。
class EvictQueue(Queue):
def __init__(self, maxsize):
self.discarded = 0
super().__init__(maxsize)
def put(self, item, block=False, timeout=None):
while True:
try:
super().put(item, block=False)
except queue.Full:
try:
self.get_nowait()
self.discarded += 1
except queue.Empty:
pass
然后替换您根目录下的所有处理程序...正常配置后的处理程序...
def speed_up_logs():
rootLogger = logging.getLogger()
log_que = EvictQueue(1000)
queue_handler = logging.handlers.QueueHandler(log_que)
queue_listener = logging.handlers.QueueListener(log_que, *rootLogger.handlers)
queue_listener.start()
rootLogger.handlers = [queue_handler]
效果:
记录将非常快
如果登录速度比写入驱动器快,则旧的未写条目将被静默丢弃。
通过记录每分钟左右丢弃的条目数(交换丢弃为零)来记录单个条目,可能会很不错。