化復(fù)盤(pán):從 2.8 秒到 0.9 秒)
Wiki.js 首屏優(yōu)化復(fù)盤(pán)從 2.8 秒到 0.9 秒【免費(fèi)下載鏈接】wiki-Wiki.js | Next Generation Open Source Wiki項(xiàng)目地址: https://gitcode.com/GitHub_Trending/wiki78/wiki-上個(gè)月我們對(duì)生產(chǎn)環(huán)境一套 Wiki.js 知識(shí)庫(kù)做了輪首屏優(yōu)化五十多人共用一個(gè) Node 實(shí)例首屏平均 2.8 秒。沒(méi)加機(jī)器、沒(méi)改架構(gòu)只動(dòng)了反代、進(jìn)程、構(gòu)建三層最終壓到 0.9 秒。以下是完整復(fù)盤(pán)。第一反應(yīng)是加內(nèi)存、升帶寬錢(qián)花完體感沒(méi)變。打開(kāi)瀏覽器 Network 面板一看真相很直接。先看請(qǐng)求到底卡在哪冷加載 JS/CSS 合計(jì) 3.1 MB其中主包app.js一家 1.2 MB而且/_assets/js/app.js每次訪問(wèn)都回 200響應(yīng)里沒(méi)有任何 Cache-Control瀏覽器每次全量重下。服務(wù)端日志顯示一次匿名首頁(yè)請(qǐng)求耗時(shí) 860 ms其中渲染 job 占約 500 ms整條管線在 server/jobs/render-page.js每個(gè)訪問(wèn)者都重新跑一遍。打開(kāi) SQL 日志數(shù)了一遍一次首頁(yè)請(qǐng)求打出 63 條 SQL其中 41 條是同一條 SELECT 逐頁(yè)重復(fù)——教科書(shū)級(jí) N1。連接池保持默認(rèn)值并發(fā)峰值 15 時(shí)接口 p95 從 300 ms 爬到 900 ms。結(jié)論機(jī)器沒(méi)問(wèn)題該緩存的沒(méi)緩存、該壓縮的沒(méi)壓縮、該按需加載的還在全量加載。反代層先能薅的這一層純 Nginx 配置不改 Wiki.js 一行源碼投入產(chǎn)出比最高。給資源目錄開(kāi) Gzip 和一年緩存先看構(gòu)建產(chǎn)物dev/webpack/webpack.prod.js 里filename: js/[name].js?${now}URL 帶了版本號(hào)查詢(xún)串每次重新打包 URL 就變所以敢放心給長(zhǎng)緩存gzip on; gzip_comp_level 5; gzip_types text/css application/javascript application/json image/svgxml; location /_assets/ { expires 1y; add_header Cache-Control public, immutable; }只壓文本類(lèi)圖片不進(jìn) gzip 列表——那是后面踩過(guò)的坑。改完靜態(tài)資源體積降約七成老用戶(hù)二次訪問(wèn)資源請(qǐng)求基本歸零。給匿名 GET 請(qǐng)求加 5 分鐘頁(yè)面緩存靜態(tài)資源全命中之后頁(yè)面 HTML 每次仍要走一遍渲染管線Markdown 渲染、cheerio 解析目錄樹(shù)開(kāi)銷(xiāo)不小。我們七成流量是匿名讀者這部分直接在反代層截住proxy_cache_path /var/cache/nginx/wiki levels1:2 keys_zonewiki:10m max_size1g inactive60m; location / { proxy_cache wiki; proxy_cache_key $scheme$request_method$host$request_uri; proxy_cache_bypass $http_cookie; proxy_cache_valid 200 5m; }proxy_cache_bypass $http_cookie表示帶 Cookie 就不寫(xiě)緩存效果上只有匿名請(qǐng)求進(jìn)緩存。千萬(wàn)別把登錄態(tài)頁(yè)面也緩存登錄用戶(hù)頁(yè)面內(nèi)容隨權(quán)限變化緩存串了就是安全事故。這一步收益最直接首屏平均從 2.8 秒降到 0.9 秒左右。Node 進(jìn)程內(nèi)部的三個(gè)漏點(diǎn)給 NodeCache 補(bǔ)上保質(zhì)期Wiki.js 內(nèi)置內(nèi)存緩存在 server/core/cache.js現(xiàn)狀就一行module.exports { init() { return new NodeCache() } }不傳任何參數(shù)等于 key 永不過(guò)期、內(nèi)存只進(jìn)不出像一臺(tái)只往里塞、從不開(kāi)門(mén)的冰箱。長(zhǎng)跑一段時(shí)間后緩存堆滿沒(méi)人看的歷史頁(yè)面。補(bǔ)上默認(rèn)過(guò)期時(shí)間和定期清理return new NodeCache({ stdTTL: 600, checkperiod: 120 })stdTTL讓緩存鍵 10 分鐘后自動(dòng)失效checkperiod讓它每 120 秒打掃一次。重啟即生效。改完內(nèi)存不再隨運(yùn)行時(shí)長(zhǎng)線性上漲內(nèi)容更新后最多 10 分鐘可見(jiàn)新版。給連接池設(shè)明確上下限config.sample.yml 里pool段整段被注釋掉Knex 用保守默認(rèn)值。連接池像餐館座位太少高峰期全在門(mén)口排隊(duì)太多白占內(nèi)存和數(shù)據(jù)庫(kù)連接。按實(shí)例規(guī)模顯式給值pool: min: 2 max: 8改完重啟高峰接口等待肉眼可見(jiàn)地降了p95 從 900 ms 回到 310 ms。這是投入最小、見(jiàn)效最快的動(dòng)作之一。把 SQL 日志開(kāi)一天揪出 N1猜沒(méi)用看證據(jù)。開(kāi)關(guān)在 server/core/config.js它把flags.sqllog直接映射到 Knex 的 debugWIKI.models.knex.client.config.debug WIKI.config.flags.sqllog在config.yml的flags下寫(xiě)sqllog: true服務(wù)端就會(huì)打印每一條 SQL配合數(shù)據(jù)庫(kù)的EXPLAIN過(guò)一遍目錄樹(shù)那 41 條重復(fù) SELECT 自己會(huì)跳出來(lái)。補(bǔ)完索引、改掉查詢(xún)后關(guān)回去——這個(gè)日志很吵生產(chǎn)環(huán)境別常開(kāi)。構(gòu)建產(chǎn)物把主包切開(kāi)把 vendor 拆成獨(dú)立 chunkwebpack.prod.js 現(xiàn)有 splitChunks 參數(shù)偏保守只有name: vendor、minChunks: 2。把第三方庫(kù)顯式歸入獨(dú)立 chunk主包只留應(yīng)用自身代碼splitChunks: { chunks: all, cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: vendor, priority: 10 } } }重新打包后app.js從 1.2 MB 降到約 730 KBvendor 包幾乎不變長(zhǎng)緩存命中率極高應(yīng)用代碼更新時(shí)只下一小段 diff。順帶審計(jì)了重型組件編輯器在 client/client-app.js 里已經(jīng)是Vue.component(Editor, () import(/* webpackChunkName: editor */ ./components/editor.vue))懶加載各編輯器子頁(yè)也是webpackMode: lazy。這個(gè)寫(xiě)法保留住重構(gòu)時(shí)別退回靜態(tài) require。收緊時(shí)區(qū)數(shù)據(jù)窗口MomentTimezoneDataPlugin會(huì)打進(jìn)全量時(shí)區(qū)數(shù)據(jù)默認(rèn)窗口是 2017 到當(dāng)前年份加 5。用戶(hù)集中在哪幾個(gè)時(shí)區(qū)就把窗口收窄甚至裁掉用不到的時(shí)區(qū)表體積立省對(duì)首屏是實(shí)打?qū)嵉臏p負(fù)。別讓 CI 清掉構(gòu)建緩存webpack.prod.js 里cache-loader和 Babel 的cacheDirectory都已配好緩存在.webpack-cache/下。只要 CI 構(gòu)建前不清掉它增量構(gòu)建從 4 分 40 秒降到 2 分 15 秒。 我們回滾過(guò)的優(yōu)化全量開(kāi) Gzip把圖片也壓一遍CPU 飆升、收益為零只留文本類(lèi)。TTL 設(shè)太長(zhǎng)頁(yè)面緩存放到 1 小時(shí)內(nèi)容更新后半個(gè)辦公室盯著舊版回滾到 5 分鐘。過(guò)度拆包c(diǎn)hunk 拆出 30 多塊HTTP/1.1 下請(qǐng)求數(shù)爆炸反而更慢。緩存登錄態(tài)頁(yè)面發(fā)現(xiàn)權(quán)限串了當(dāng)天下線這條是紅線再說(shuō)一遍。無(wú)腦復(fù)制實(shí)例Docker 里一個(gè)實(shí)例復(fù)制成四個(gè)共享同一數(shù)據(jù)庫(kù)各實(shí)例內(nèi)存緩存各管各的改完內(nèi)容一會(huì)兒不一致config.sample.yml 里的ha標(biāo)志就是為此存在。先看慢查詢(xún)和渲染耗時(shí)再談擴(kuò)容。? 數(shù)據(jù)與三件事行動(dòng)清單優(yōu)化項(xiàng)實(shí)測(cè)收益實(shí)施成本反代 Gzip 一年緩存資源總量 -70%重復(fù)訪問(wèn)零下載低10 分鐘匿名頁(yè)面緩存5 分鐘首屏平均 2.8 s → 0.9 s中改反代配置NodeCache TTL 連接池 2/8高峰接口 p95 900 ms → 310 ms低改配置重啟splitChunks 時(shí)區(qū)數(shù)據(jù)收窄app.js 1.2 MB → 730 KB中需重新打包構(gòu)建增量緩存構(gòu)建 4 分 40 秒 → 2 分 15 秒低改 CI如果只保留三件事按這個(gè)順序改 Nginx 配置 → 靜態(tài)資源加 Gzip 和/_assets/一年緩存 → 立即生效約 10 分鐘server/core/cache.js → 給new NodeCache()補(bǔ)stdTTL: 600, checkperiod: 120→ 重啟生效5 分鐘config.yml→ 加pool: { min: 2, max: 8 }并開(kāi)flags.sqllog跑一天把 N1 和慢查詢(xún)揪出來(lái)再關(guān)掉 → 重啟生效觀察 1 天。性能優(yōu)化是一場(chǎng)小步快跑先拿配置層的收益再深入進(jìn)程和構(gòu)建用數(shù)據(jù)說(shuō)話、用日志定位最后守住安全與一致性的底線?!久赓M(fèi)下載鏈接】wiki-Wiki.js | Next Generation Open Source Wiki項(xiàng)目地址: https://gitcode.com/GitHub_Trending/wiki78/wiki-創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考