首先簡單介紹一下業務場景,物聯網裝置,關注公眾號,免費領取環保袋。
12月8號,也就是昨天上午,突然接到大量客戶投訴反饋下單介面點選下單一直在“轉圈”,最後超時。緊急排查!
第一步檢視網路,伺服器ping值正常,然後查詢伺服器頻寬佔用率正常。
第二步,檢視應用伺服器負載,很低,基本沒問題。
第三步,重點來了,檢查資料庫效能。
show processlist; 發現連線數維持在97-99,懷疑,是否是受到最大連線數限制,導致新的查詢在排隊,查詢得知,最大連線數設定為800,所以排除連線數限制問題。又掃了一眼processlist列表,發現大量耗時很長的查詢,初步定位問題。把sql拎出來看一下,發現該查詢沒有建索引,系統上線時間不長,業務發展迅猛,問題一下子暴露出來,建索引完事。
索引建完之後,再次檢查資料庫,發現有很多警告,CPU佔用率一直居高不下,高峰期直接100%,這個問題就比較嚴重了。首先檢視了慢查詢日誌,發現慢查詢的時間閾值還停留在10秒,這肯定不行,於是設定為4秒,改為4秒之後發現,依然沒有慢查詢,再看了一下sql執行情況,高峰期,qps 為1000左右,tps大概40+,比較高了,但讀請求明顯多於寫請求。決定,再次對系統進行優化。
1. 分析了一下sql執行日誌,對一段時間內執行的sql按執行次數進行了一個排序,過了一遍所有的sql,進行了少量優化,但優化空間不大。
2. 維護裝置線上狀態的模組,分佈在各地的裝置每分鐘或每30秒會發一個心跳包,心跳包用於維持裝置的線上狀態,現在規定是5分鐘內沒有收到心跳包則認為裝置離線,收到心跳包後每次都會去更新裝置最後心跳時間欄位。開始想把裝置線上狀態維護完全放到redis裡面,直接砍掉這部分的資料庫IO,後來分析了一下,發現業務不允許,因此查詢的時候需要按照裝置線上狀態來查詢。最後解決方案,由於裝置每分鐘會傳送1-2次心跳包,每次都去更新資料庫,而業務允許5分鐘的掉線狀態延遲,因此,利用redis快取過濾一下,在5分鐘內,僅僅更新一次資料庫也可以達到同樣的效果。最後看了一下優化效果,發現,好像不太理想,首先,因為該update操作本來就執行的很快,資源佔用很小,基於看不出CPU佔用率曲線有明顯變化。
3. 下一步,繼續尋找優化點。接受前面優化的教訓,接下來尋找優化的點的時候,從耗時長的操作入手,這樣達到的效果應該是最好的。首先把慢查詢時間閾值改為2秒,這時,一個新的慢查詢sql出現了,就是在每次建立訂單時,需要先查詢一下該公眾號當前已經成功吸粉的數量,因為業務規定達到吸粉數量目標之後需要停止吸粉,這查詢操作進行了全表掃描,而且sql沒有可優化空間了。但這裡很明顯可以通過快取去優化,將公眾號當前已吸粉數快取起來,當訂單完結時,對快取執行+1操作,+1操作如果直接使用redis的incr操作,會有問題:想象一下,快取過期,這時恰好執行了incr,由於incr當key不存在時,會建立key,並初始化為0再+1,而且該key永不過期,這樣就達不到限制吸粉數量的效果了。因此通過lua指令碼來進行+1操作,只有當key存在時,才執行+1,程式碼如下:
local exists = redis.call('exists', KEYS[1]); if (exists == 1) then return redis.call('incr', KEYS[1]); end return nil;
釋出之後,再次檢視優化效果,震驚,CPU佔用率曲線斷崖式下跌,從100%掉到了10%以下,至此,本次優化取得圓滿成功,又可以撐一段時間了。