非常抱歉,今天上午的博客站點故障給大家帶來了很大的麻煩,請大家諒解。這次故障是我們發布 .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 版本博客站點,恢復了正常。