我們被一個 kong 的性能 bug 折騰了一個通宵
故事背景
在 Erda 的技術架構中,我們使用了 kong 作為 API 網關的技術選型。因其具備高併發低延時的特性,同時結合了 Kubernetes Ingress Controller,基於雲原生的聲明式配置方式,能夠實現豐富的 API 策略。
在我們最早交付的集羣中,kong 還是較為早期的 0.14 版本,隨着業務層面對安全的要求日益趨增,我們需要基於 kong 實現安全插件,幫助系統能夠具備更好的安全能力。由於較為早期的 0.14 版本不能使用 go-pluginserver 來擴展 kong 的插件機制,我們不得不在古老的集羣中將 kong 升級為相對較新的 2.2.0 版本。
升級過程就不在此贅述了,基本就是照着官方文檔一步步順利的升級上去,但是在升級上去之後的幾天裏,我們的 SRE 團隊收到了非常密集的諮詢甚至是聲討,部署在該集羣上的業務 間歇性的無法訪問,延遲非常高 。
一系列失敗的嘗試
參數調優
最開始為了快速修復這個問題,我們對 kong 的 NGINX_WORKER_PROCESSES
、 MEM_CACHE_SIZE
、 DB_UPDATE_FREQUENCY
、 WORKER_STATE_UPDATE_FREQUENCY
參數以及 postgres 的 work_mem
、 share_buffers
都進行了適當的調優。
但是,沒有任何效果 :sweat:。
清理數據
由於這個集羣的歷史原因,會頻繁的註冊或者刪除 api 數據,因此集羣中累計了大約 5 萬多條 route 或者 service 數據。
我們懷疑是數據裏量大導致的性能下降,於是結合 erda 中的數據對 kong 中的歷史數據進行刪除,在刪除的過程中出現的刪除較慢並且同時 kong 的性能急劇下降的現象。
反覆測試了幾次後我們確定了 「 只要調用 admin 的接口導致 kong 性能下降 」 這一結論,跟社區的這個問題非常相似,鏈接如下:
https://github.com/Kong/kong/...
kong 實例的讀寫分離
確定了是 admin 接口的原因後,我們決定將 admin 跟業務的 kong 實例分開,希望 admin 的調用不會影響到業務的正常流量訪問,期待達到 kong 的 admin 接口慢就慢吧,但是不要影響業務的訪問性能。
然而,沒有任何效果:tired_face:。
postgres 遷移 RDS
kong 層面的努力無果之後,我們在測試過程中同時觀察到了當調用 admin 接口試,postgres 的進程也增多了很多,CPU使用率也漲了起來,也是決定將 pg 遷移到 更為專業的RDS中。
還是,沒有任何效果:sob:。
回滾
最終我們還是回滾到了 0.14 版本,追求暫時“心靈的寧靜”。
至此,線上的嘗試基本搞一段落,也大致摸清了問題復現的條件,於是我們決定在線下構造一個環境來繼續找出問題的原因。
問題的復現之路
我們將出問題的 kong 的 postgres 數據導一份到開發環境中,模擬 「 調用 admin 接口是性能急劇下降 」的情況,並尋找解決之道。
導入數據
我們將有問題的集羣中的 postgre 的數據備份之後然後在一個新的集羣中導入:
psql -h 127.0.0.1 -U kong < kong.sql
並且開啟 kong 的 prometheus 插件, 方便使用 grafana 來查看性能圖標:
curl -X POST http://10.97.4.116:8001/plugins --data "name=prometheus"
現象一
調用 admin 服務的 / 同樣的慢,跟線上的現象吻合,當數據量大的時候調用 admin 的 / 目錄需要耗費更多的時間。
curl http://10.97.4.116:8001
現象二
然後我們來模擬在線上遇到的調用 admin 接口後業務訪問性能變差的現象,先調用 admin 接口創建個業務的 api,以供測試,我們創建了一個 service 以及一個 routes:
curl -i -X POST http://10.97.4.116:8001/services/ -d 'name=baidu2' -d 'url=http://www.baidu.com' curl -i -X POST http://10.97.4.116:8001/services/baidu2/routes \ -d "name=test2" \ -d "paths[1]=/baidu2"
後面可以使用 curl http://10.97.4.116:8000/baidu2
來模擬業務接口進行測試。
準備 admin 接口的測試腳本, 創建並刪除一個 service/route,中間穿插一個 service list。
#!/bin/bash curl -i -X POST http://10.97.4.116:8001/services/ -d 'name=baidu' -d 'url=http://www.baidu.com' curl -i -X POST http://10.97.4.116:8001/services/baidu/routes \ -d "name=test" \ -d "paths[1]=/baidu" curl -s http://10.97.4.116:8001/services curl -i -X DELETE http://10.97.4.116:8001/services/baidu/routes/test
然後持續調用該腳本:
for i in `seq 1 100`; do sh 1.sh ; done
在持續調用該腳本的過程中去訪問一個業務的接口,會發現非常慢,跟線上的現象完全吻合。
curl http://10.97.4.116:8000/baidu2
PS:精簡腳本,後只觸發一條寫入,或者刪除也會觸發該現象
伴隨現象
- kong 實例的 cpu 跟 mem 都持續上漲,且當 admin 接口調用結束後此現象依然沒有結束。mem 上漲到一定程度會到時 nginx worker 進程 oom 掉,然後重啟,這個也許是訪問慢的原因;
- 我們設置了
KONG_NGINX_WORKER_PROCESSES
為 4,並且為 pod 的內存為 4G 的時候,pod 的整體內存會穩定在 2.3G, 但是 調用 admin 接口試,pod 的內存就會一直上漲至超過 4G,觸發 worker 的 OOM,於是我將 pod 的內存調整到了 8G。再次調用 admin 接口,發現 pod 的內存依然一直上漲,只是上漲到了 4.11 G 就結束了,這似乎意味着我們是要設置 pod 的內存為KONG_NGINX_WORKER_PROCESSES
兩倍,這個問題就解決了(但是還有個重要的問題是為什麼調用一次 admin 接口,會導致內存漲了那麼多); - 另外,當我持續調用 admin 接口的時候, 最終的內存會持續增長並且穩定到 6.9G。
這個時候我們將問題抽象一下:
調用「kong admin 接口」導致內存一直上漲,進而觸發 oom 導致 worker 被 kill 掉,最終導致業務訪問慢。
繼續調查內存是被什麼佔用了:
我使用 pmap -x [pid]
前後兩次查看了 worker 進程的內存分佈,變化的是第二張如中框起來來的部分,從地址上看整塊內存都被換過了,但是將內存數據導出並且字符串化之後,沒有什麼有效的信息可供進一步排查。
結論
- 該問題跟 kong 的升級(0.14 --> 2.2.0) 沒有關係,直接使用 2.2.0 版本也會有這個問題;
- kong 每隔
worker_state_update_frequency
時間後會在內存中重建 router,一旦開始重建就會導致 內存上漲,看了下代碼問題出在了Router.new
的方法這裏,會申請 lrucache 但是使用後沒有 flush_all,根據最新的 2.8.1 版本的 lrucache 進行了釋放問題依然存在; - 也就是 kong 的
Router.new
方法裏的其他邏輯到時的內存上漲;
- 這也就表明這個問題是 kong 存在的一個性能 bug,及時在最新的版本中依然存在,當 route 跟 service 達到某個量級的時候會出現調用 admin 接口,導致 kong 的 worker 內存迅速上漲,使得 oom 進而引發業務訪問性能變差的現象,臨時的解決辦法可以是減少
NGINX_WORKER_PROCESSES
的數量並且增加 kong pod 的內存量,保證調用 admin 接口後需要的內存足夠使用不觸發 oom,才能保證業務的正常使用。
最後,我們會在 https://github.com/Kong/kong/... 這個 issue 中將該現象補充進去,歡迎大家持續關注,一起討論~
更多技術乾貨請關注【爾達 Erda】公眾號,與眾多開源愛好者共同成長~
- 天翼雲全場景業務無縫替換至國產原生操作系統CTyunOS!
- 以羊了個羊為例,淺談小程序抓包與響應報文修改
- 這幾種常見的 JVM 調優場景,你知道嗎?
- 如此狂妄,自稱高性能隊列的Disruptor有啥來頭?
- 為什麼要學習GoF設計模式?
- 827. 最大人工島 : 簡單「並查集 枚舉」運用題
- 手把手教你如何使用 Timestream 實現物聯網時序數據存儲和分析
- 850. 矩形面積 II : 掃描線模板題
- Java 併發編程解析 | 基於JDK源碼解析Java領域中的併發鎖,我們可以從中學習到什麼內容?
- 【手把手】光説不練假把式,這篇全鏈路壓測實踐探索
- 大廠鍾愛的全鏈路壓測有什麼意義?四種壓測方案詳細對比分析
- 寫個續集,填坑來了!關於“Thread.sleep(0)這一行‘看似無用’的代碼”裏面留下的坑。
- 857. 僱傭 K 名工人的最低成本 : 枚舉 優先隊列(堆)運用題
- Vue3 實現一個自定義toast(小彈窗)
- 669. 修剪二叉搜索樹 : 常規樹的遍歷與二叉樹性質
- 讀完 RocketMQ 源碼,我學會了如何優雅的創建線程
- 性能調優——小小的log大大的坑
- 1582. 二進制矩陣中的特殊位置 : 簡單模擬題
- elementui源碼學習之仿寫一個el-switch
- 646. 最長數對鏈 : 常規貪心 DP 運用題