Puppeteer效能優化與執行速度提升
之前一篇關於無頭瀏覽器的文章可以看這裡無頭瀏覽器效能對比與Puppeteer的優化文件 ,這篇文章提了部分優化思路和使用場景,就是沒具體的程式碼。今天整理了使用技巧與程式碼分享給大家(為什麼我要說大家)。下文中的程式碼都用在了生產環境中,單臺機器能達到100QPS(沒優化之前是20QPS)。平均請求響應時間在400ms左右,測試程式為執行一段Echarts程式碼生成柱狀圖然後截圖返回,400ms比請求某些靜態資源的時間都短。
Puppeteer自身不會消耗太多資源,耗費資源的大戶是Chromium Headless。所以需要理解Chromium執行的原理,才能方便優化。
Chromium消耗最多的資源是CPU,一是渲染需要大量計算,二是Dom的解析與渲染在不同的程序,程序間切換會給CPU造成壓力(程序多了之後特別明顯)。其次消耗最多的是記憶體,Chromium是以多程序的方式執行,一個頁面會生成一個程序,一個程序佔用30M左右的記憶體,大致估算1000個請求佔用30G記憶體,在併發高的時候記憶體瓶頸最先顯現。
優化最終會落在記憶體和CPU上(所有軟體的優化最終都要落到這裡),通常來說因為併發造成的瓶頸需要優化記憶體,計算速度慢的問題要優化CPU。使用Puppeteer的使用者多半會更關心計算速度,所以下面我們談談如何優化Puppeteer的計算速度。
優化Chromium啟動項
通過檢視Chromium啟動時都有哪些引數可以配置,能找到大部分線索,因為Chromium這種頂級的開源產品,文件與介面都是非常清晰的,肯定可以找到相關配置項來定製啟動方式。Chromium 啟動引數列表
我們需要找到下面幾種配置來提升速度:
single-process
最後配置如下:
const browser = await puppeteer.launch( { headless:true, args: [ ‘–disable-gpu’, ‘–disable-dev-shm-usage’, ‘–disable-setuid-sandbox’, ‘–no-first-run’, ‘–no-sandbox’, ‘–no-zygote’, ‘–single-process’ ] });
Chromium 啟動引數列表 文件中的配置項都可以嘗試看看,我沒有對所有選項做測試,但可以肯定存在某些選項能提升Chromium速度。
Chromium的啟動項優化後能節省200ms左右的請求時間,收益非常可觀。
優化Chromium執行流程
接下來我們再單獨優化Chromium對應的頁面。我之前的文章中提過,如果每次請求都啟動Chromium,再開啟tab頁,請求結束後再關閉tab頁與瀏覽器。流程大致如下:
請求到達->啟動Chromium->開啟tab頁->執行程式碼->關閉tab頁->關閉Chromium->返回資料
真正執行程式碼的只是tab頁面,理論上啟動一個Chromium程式能執行成千上萬的tab頁,可不可以複用Chromium每次只打開一個tab頁然後關閉呢?當然是可以的,Puppeteer提供了puppeteer.connect()
方法,可以連線到當前開啟的瀏覽器。流程如下:
請求到達->連線Chromium->開啟tab頁->執行程式碼->關閉tab頁->返回資料
程式碼如下:
const MAX_WSE = 4;//啟動幾個瀏覽器 let WSE_LIST = []; //儲存browserWSEndpoint列表 init(); app.get('/', function (req, res) { let tmp = Math.floor(Math.random()* MAX_WSE); (async () => { let browserWSEndpoint = WSE_LIST[tmp]; const browser = await puppeteer.connect({browserWSEndpoint}); const page = await browser.newPage(); await page.goto('file://code/screen/index.html'); await page.setViewport({ width: 600, height: 400 }); await page.screenshot({path: 'example.png'}); await page.close(); res.send('Hello World!'); })(); }); function init(){ (async () => { for(var i=0;i<MAX_WSE;i++){ const browser = await puppeteer.launch({headless:true, args: [ '--disable-gpu', '--disable-dev-shm-usage', '--disable-setuid-sandbox', '--no-first-run', '--no-sandbox', '--no-zygote', '--single-process' ]}); browserWSEndpoint = await browser.wsEndpoint(); WSE_LIST[i] = browserWSEndpoint; } console.log(WSE_LIST); })(); }
程式啟動時(使用Express提供Web介面),初始化一定數量的無頭瀏覽器,並儲存WSEndpoint
列表,當收到請求時,通過隨機數做簡單的負載均衡(利用多核特性)。
使用tab方式渲染後請求速度提升了200ms左右,一個tab程序使用記憶體降到20M以內,帶來的收益也非常可觀。不過這裡要注意,官方並不建議這樣做 ,因為一個tab頁阻塞或者記憶體洩露會導致整個瀏覽器阻塞並Crash。萬全的解決辦法是定期重啟程式,可參考php-fpm的做法,當請求1000次或者記憶體超過限制後重啟對應的程序。
合理選擇無頭瀏覽器與版本
Puppeteer當前支援Chromium和Firefox,我測試了Firefox,結論是Firefox headless現在還不夠成熟,相關的資料也比較少,不建議在生產環境使用。Chromium的版本非常多,百花齊放,這裡我建議使用chromium-headless
這是一個獨立的版本,能為你的程式帶來200ms左右的效能提升(這個最爽,啥都不用做)。安裝與使用方式(CentOS):
$ yum install chromium-headless $ /usr/lib64/chromium-browser/headless_shell (呼叫路徑)
其他系統可以自行查詢該版本,找不到可以選擇自己編譯。
最後
Puppeteer(其實是Chromium)的優化空間還非常大,需要不停的去實驗和測試。希望我提供的思路能幫助正在嘗試優化Puppeteer的人。