接下來要設定我們 odoo.conf 模板中 "Performance and Limits" 的部分,不過在那之前,我們要先稍微了解多執行緒(Multi-Thread)和多行程(Multi-Process)的概念。
多執行緒(Multi-Thread):
在多執行緒的架構中,一個單一的程式(Process)可以同時執行多個執行緒(Thread)。這些執行緒共享同一個記憶體空間和資源,因此彼此之間的溝通和資料共享相對容易。然而,因為資源是共享的,如果沒有妥善管理,可能會導致資源競爭或死結的問題。此外,由於執行緒是輕量級的,建立和切換的成本較低,適合處理大量的小型任務。
多行程(Multi-Process):
多行程架構則是同時運行多個獨立的程式,每個行程都有自己的記憶體空間和資源。這樣的好處是可以充分利用多核心 CPU,同時執行多個任務,彼此之間互不干擾。然而,由於行程之間不共享記憶體,溝通需要透過跨行程通訊機制,可能會比較複雜,也增加了系統的複雜度和資源消耗。
什麼是 GIL?
GIL(Global Interpreter Lock,全域解譯器鎖)是 Python 的 CPython 實作中的一個機制。它確保在任何時刻,只有一個執行緒在執行 Python 的 Bytecode。這個鎖的主要目的是保護 Python 解譯器中的記憶體管理,防止多個執行緒同時修改物件,導致資料不一致或系統崩潰。
GIL 對多執行緒的影響
由於 GIL 的存在,即使在多核心 CPU 上,Python 的多執行緒也無法真正同時執行 CPU 密集型的任務。執行緒需要輪流獲得 GIL,這導致無法充分利用多核心的優勢,效能提升有限。在 CPU 密集型的應用中,這成為一個明顯的瓶頸。然而,在 I/O 密集型的應用中,因為執行緒在等待 I/O 操作時會釋放 GIL,其他執行緒可以繼續執行,所以多執行緒仍然有其優勢。
如何解決 GIL 的限制
為了克服 GIL 帶來的限制,可以採用多行程(Multi-Process)模式。透過建立多個行程,每個行程都有自己的 Python 解譯器和 GIL,能夠在不同的 CPU 核心上同時執行,充分利用多核心的效能。此外,也可以使用其他沒有 GIL 限制的 Python 實作,如 Jython 或 IronPython,但這些實作可能與 CPython 不完全相容,需評估相容性問題。
當我們使用預設的 odoo.conf 設定時,系統預設採用多執行緒(Multi-Thread)模式。也就是說,只有一個行程,所有的執行緒共享同一個 GIL,導致各個執行緒需要競爭 GIL,造成效能瓶頸。
官方不建議在正式部署中使用多執行緒模式,原因如下:首先,效能問題。由於 GIL 的限制,無法充分利用多核心 CPU,效能提升有限。其次,即時應用的影響。某些需要即時處理的應用(如 LiveChat)在多執行緒模式下可能無法正常運作,因為執行緒可能被阻塞。最後,穩定性考量。一個執行緒的錯誤可能影響整個行程,降低系統的穩定性。
多行程(Multi-Process)模式的優勢在於效能提升。每個行程都有自己的 GIL,能夠在多核心上同時執行,充分利用硬體資源。此外,穩定性也提高了,因為個別行程的問題不會影響其他行程,系統更為穩定。官方建議在生產環境中使用多行程模式,以獲得最佳的效能和穩定性。
需要注意的是,多行程模式僅支援 Linux,不適用於 Windows。不過,我們的部署策略透過 Docker,即使開發環境在 Windows,我們的應用實際上運行在 Linux 虛擬機中的 container 環境中,因此可以使用多行程模式。
在開始實際的實驗之前,我先把官方建議的相關設定計算方式貼在下面,但因為我們是跑在 Docker 中,所以也不太準確,僅供參考,提供一個感覺的基準。
經驗法則:
Worker 數量 = CPU 核心數 * 2 + 1
這個公式已考量到排程任務(cron),並假設每個 worker 大約可以支援 6 個同時使用者。
根據系統設計的經驗,我們預估使用者請求的比例如下:
計算公式:
所需記憶體 = Worker 數量 * ((輕量請求比例 * 輕量記憶體需求) + (重量請求比例 * 重量記憶體需求))
假設一台伺服器有 4 個 CPU 核心和 8 個執行緒,並有 60 名同時使用者。
計算所需的 Worker 數量:
根據使用者數量:
60 用戶 / 6 = 10(需要 10 個 worker)
根據 CPU 核心數:
(4 核心 * 2) + 1 = 9(最多可設定 9 個 worker)
因此,我們可以設定 8 個 worker,再加 1 個用於 cron 的 worker。
計算記憶體需求:
計算每個 worker 的平均記憶體需求:
平均記憶體 = (0.8 * 150MB) + (0.2 * 1GB) = 120MB + 204.8MB ≈ 325MB
總記憶體需求:
總記憶體 = 9 個 worker * 325MB ≈ 2.93GB
所以,我們需要大約 3GB 的記憶體。
odoo.conf 設定範例:
[options]
limit_memory_hard = 1717986918 ; 超過此限制(1.6GB)會強制終止 worker
limit_memory_soft = 858993459 ; 超過此限制(800MB)會在請求完成後回收資源
limit_request = 8192 ; 單個 worker 處理的最大請求數
limit_time_cpu = 600 ; 單個請求允許的最大 CPU 執行時間(秒)。如果請求的 CPU 執行時間超過這個限制,系統會終止該請求,以防止單一請求佔用過多的 CPU 資源。
limit_time_real = 1200 ; 單個請求允許的最大實際執行時間(秒),包括所有等待時間(如 I/O 操作)。如果請求的實際執行時間超過這個限制,系統也會終止該請求。
max_cron_threads = 1 ; cron worker 的數量
workers = 8 ; HTTP worker 的數量
相關的介紹先到這邊,下一章我們就會實際來修改設定並進行實驗。