非常抱歉,今天上午的部落格站點故障給大家帶來了很大的麻煩,請大家諒解。這次故障是我們釋出 .NET Core 版部落格站點引起的,雖然我們進行了充分的準備,但還是低估了高併發下的複雜問題。
以下是故障背景與大致經過:
在這個炎炎夏日,我們正熱火朝天地忙著整個 .NET Core 遷移工程的收官 —— 釋出 .NET Core 版部落格站點與部落格後臺。我們的其他系統都早已遷移至 .NET Core 並已線上上工作一番時日,只剩下最難啃的硬骨頭 —— 部落格系統,到這個月這根鋼鐵般堅硬的硬骨頭也被啃得差不多了,它的釋出上線將為我們整個 .NET Core 遷移工程畫上完美的句號,並順帶以此里程碑迎接 .NET Core 3.0 正式版的釋出。
所以,釋出 .NET Core 版部落格站點與部落格後臺成為我們8月份最重要的工作。.NET Core 版部落格站點7月份就已經完成開發,這段時間一邊進行更進一步的內測,一邊進行灰度釋出,接入一些生產流量以發現我們測試中未能發現的問題並進行修復,在上個週末接入更多生產流量進行測試與修復後,我們已經很有信心,評估後認為已具備正式釋出條件,除了我們無法在測試環境中模擬的部落格系統所處的複雜高併發場景。
於是一邊帶著信心,一邊帶著對高併發問題的擔心,我們決定在今天一大早進行釋出。
釋出時的部署場景是這樣的,部落格系統基於 .NET Core 3.0 Preview 7 (EF Core 用的還是 3.0 Preview 5),7臺阿里雲 centos 伺服器組建了 docker swarm 叢集,6臺4核8G伺服器作為 worker 節點跑部落格站點的應用容器,1臺2核4G的伺服器作為 manager 節點(不部署任何容器),每個 worker 節點都部署 1 個 nginx 與 .net core 部落格應用容器,所有請求都由阿里雲均衡轉發到 nginx 容器,再由 nginx 容器轉發給 .net core 應用容器,nginx 通過埠對映的方式監聽 worker 節點伺服器的 80 埠。
這樣的部署環境也是我們經過長期驗證的,唯一沒有經過驗證的就是部落格系統這麼高的併發。
頂著2個高併發問題的風險(docker swarm 與 .net core ),我們在今天早上 5:30 左右進行了釋出。
開始訪問量小,併發低,沒出現問題,但到 8:30 左右出現問題了,開啟很多部落格頁面要1秒多(正常情況是幾十毫秒),而在容器內用 curl 命令請求都不到10毫秒。
$ docker exec -t $(docker ps -f name=blog_web -q) curl -H 'X-Forwarded-Proto:https' -w %{time_total} -o /dev/null -s localhost 0.002876
懷疑是 nginx 的問題,準備重新建立一個 docker 叢集,不用 nginx 直接用 kestrel 監聽 80 埠。
後來同事指出,不是 nginx 的問題,是 docker swarm 埠對映在高併發下的效能問題,只有將埠對映改為 host 網路模式才能解決這個問題。
9:30 左右,隨著併發越來越高,nginx 容器開始報 500 錯誤,開始以為是叢集中的伺服器負載過高,於是向 docker swarm 叢集中新增伺服器,但於事無補,500 錯誤越來越多。
出現 500 錯誤時,有時重新整理一次就會好,有時要重新整理好幾次,懷疑是叢集中某些伺服器不穩定,於是一臺一臺登入叢集中的伺服器進入容器用 curl 命令進行測試,除了1臺伺服器不穩定,其他伺服器 curl 命令測試時響應速度都正常,將那臺不太穩定的伺服器下線,問題依舊,隨著併發量繼續增大,500 錯誤也繼續增多。
進一步分析後,懷疑 500 錯誤是因為高併發下 nginx 容器與 .net core 應用容器之間的網路通訊出現問題,於是 10:30 左右決定放棄這次釋出,回退至跑在 Windows 上的 .net framework 版本部落格站點,恢復了正常。