簡體   English   中英

登錄異步 Tornado (python) 服務器

[英]Logging in an asynchronous Tornado (python) server

我正在開發一個應用程序,我可能需要在其中記錄到達服務器的整個流量。 此功能可以打開或關閉,也可以在捕獲異常時使用。

無論如何,我擔心磁盤 I/O 操作的阻塞性質及其對服務器性能的影響。 處理請求(主要是 POST http 請求)時應用的業務邏輯是異步的,因此每個網絡或數據庫調用都是異步執行的。

另一方面,我擔心線程在等待磁盤 IO 操作完成時的延遲。 記錄的消息可以是幾個字節到幾 KB,但在某些情況下可能只有幾 MB。 數據寫入磁盤時線程沒有真正需要暫停,http請求肯定可以在此時完成,並且在數據寫入磁盤時ioloop線程沒有理由不處理另一個任務。

所以我的問題是:

  1. 我是否過度擔心這個問題? 是否記錄到標准輸出然后將其重定向到“足夠好”的文件?
  2. 什么是常用方法,或者您認為登錄基於 Tornado 的應用程序最實用的方法是什么? 即使是簡單的日志記錄而不是我上面概述的(極端)情況?
  3. 這基本上是將日志消息排隊並從專用線程使用它們的理想情況嗎?
  4. 假設我確實將日志記錄卸載到不同的線程(如 Homer Simpson 的“其他人不能做嗎?”),如果執行磁盤日志記錄的線程正在等待磁盤 io 操作完成,Linux 內核是否會接受它點作為一個機會上下文切換?

非常感謝任何意見或建議,

埃雷茲

對於“正常”日志記錄(每個請求幾行),我總是發現直接記錄到文件就足夠了。 如果您將所有流量都記錄到服務器,則情況可能並非如此。 有一次我需要做類似的事情,我只是用tcpdump從外部捕獲流量,而不是修改我的服務器。

如果您想在進程中捕獲它,請從主線程寫入文件開始。 與往常一樣,在采取激烈行動之前衡量您自己環境中的事情( IOLoop.set_blocking_log_threshold可用於確定您的日志記錄是否有問題)。

如果從主線程阻塞太長時間,您可以寫入由另一個線程處理的隊列,或者異步寫入管道或套接字到另一個進程(系統日志?)。

“異步寫入管道或套接字到另一個進程(系統日志?”

怎么會這樣? log_request是一個普通函數 - 不是協程,並且所有默認的 python 處理程序都不是由 asyncio 事件循環驅動的,因此它們不是真正的異步。 恕我直言,這是導致 Tornado 性能低於 ie 的因素之一。 aiohttp。 寫入內存或使用 udp 速度很快,但無論如何它都不是異步的。

歷史參考

從大約 2016 年的問題開始,我就有了運行在像樣的裸機機器上的 Python 3.4 Tornado 4 應用程序的操作經驗。 該應用程序與很少的 3rd 方 HTTP API 交互,並記錄了一些交互,以便將來進行潛在的故障排除(這類似於 OP 的要求)。 這台機器有一個 HDD RAID。 據我所知,該應用程序的流量並不高。

Tornado 4 有自己的 IO 循環實現(Tornado 5+ 現在使用asyncio ),並且有一個有趣的代碼檢測,由IOLoop.set_blocking_log_threshold控制。 基本上,只要循環被阻塞的時間超過threshold秒,它就會在堆棧跟蹤中記錄一條WARNING記錄。 我可以從 Sentry 時間線中找到當時的幾個屏幕截圖,用於將閾值設置為 1 秒的警告。

前

大多數警告都有以日志文件處理程序刷新結束的堆棧跟蹤。 這是一個旋轉和 gzip 文件處理程序。 后者可能會解釋什么可能需要更長的時間,但無論如何對於應用程序來說,希望對日志記錄負全部責任。 解決方案是 stdlib 對logging.handlers.QueueHandlerlogging.handlers.QueueListener

后

日志隊列

Python 日志記錄手冊有專門的部分介紹處理阻止. 這是它的示例(其中listener.start啟動一個線程,讀取隊列並將記錄委托給handler ):

que = queue.Queue(-1)  # no limit on size
queue_handler = QueueHandler(que)
handler = logging.StreamHandler()
listener = QueueListener(que, handler)
root = logging.getLogger()
root.addHandler(queue_handler)
formatter = logging.Formatter('%(threadName)s: %(message)s')
handler.setFormatter(formatter)
listener.start()
# The log output will display the thread which generated
# the event (the main thread) rather than the internal
# thread which monitors the internal queue. This is what
# you want to happen.
root.warning('Look out!')
listener.stop()

對於涵蓋邊緣情況的QueueHanlder實現的實際參考,可以使用chronologer.client.QueueProxyHandler

asyncio檢測

asyncio有一個調試模式

默認情況下 asyncio 在生產模式下運行。 為了方便開發,asyncio 有一個調試模式。 [...] 啟用調試模式時:

  • asyncio 檢查未等待的協程並記錄它們; 這減輕了“忘記等待”的陷阱。
  • 許多非線程安全的異步 API(例如loop.call_soon()loop.call_at() methods )如果從錯誤的線程調用它們會引發異常。
  • 如果執行 I/O 操作所需的時間過長,則會記錄 I/O 選擇器的執行時間。
  • 記錄超過 100 毫秒的回調。 loop.slow_callback_duration屬性可用於設置被視為“慢”的最小執行持續時間(以秒為單位)。

它可能看起來比 Tornado 4 更豐富,但實際上並非如此。 首先,它不適用於生產(並且缺少一個非常重要的指標)。 此外,這是一個沒有堆棧跟蹤的事后警告,而 Tornado 的實現基於signal.SIGALRM在閾值 hit 處提供堆棧跟蹤

對於好奇的

您是否注意到警告並沒有完全消失? 但我可以向您保證,日志記錄問題已修復。 令我驚訝的是,導致這些罕見問題的原因是uuid.uuid4 ,它可能會阻塞在具有空熵池的機器上,但那是另一回事了。

進一步閱讀

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM