首先簡單介紹一下業務場景,物聯網設備,關注公眾號,免費領取環保袋。
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%以下,至此,本次優化取得圓滿成功,又可以撐一段時間了。