小程序的接口剛寫完時,一個接口的調用時間大概是700ms左右,100並發的100請求的壓測結果是9s,用的都是項目的底層方法(有緩存),框架是ci.而上線城市服務的三星基准是500並發500ms以下,遠遠不達標,於是乎開始了漫長的優化.在此記錄一下從10+s優化到300ms的過程.
- 用xhprof分析了代碼,發現ci的cache的redis驅動做了這樣一件事,耗費了大量的性能
-
$serialized = $this->_redis->sMembers('_ci_redis_serialized'); if ( ! empty($serialized)) { $this->_serialized = array_flip($serialized); }
ci的redis驅動初始化程序里有這樣一段代碼,這段代碼的含義是將"_ci_redis_serialized"這個集合中的所有成員都取出來.然后反轉鍵值對.而_ci_redis_serialized這個集合對於ci的涵義就是為了記錄序列化后的數據.因為ci的cache可以把任何數據都放入cache中,包括字符串,數組,對象等,而redis只能存儲一種數據格式.所以就需要將數組或者對象序列化存入redis中(當然如果你不打算存儲對象完全可以用json格式來存儲數據).問題就出在這里,一個項目的key往往是數量級的增長的,像我們就有16w的key是經過序列化存放在這里的(除了數據緩存,還有大量的用戶session數據也是需要序列化的),在大量的cache操作下,這個過程就耗費了大量的性能.解決方案是:既然用了集合,直接用SISMEMBER來判斷這個集合中是否有這個成員就行了,redis的內置方法性能是非常好的(好到可以忽略)這次優化后從10+s下降到了3s左右.
-
- 在獲取數據的底層方法上,在小程序再封裝一層
- 只提供給前端必要的字段
- 這兩個步驟讓時間下降到了1s+,很簡單的邏輯,減少不必要的數據傳遞,減少冗余數據.
- 去除session中的_get_lock,這個是防止兩個進程同時寫一個key,因為基本發生不了所以先去除
- 不經過service層,直接訪問數據庫,為了能讓service層(數據層)能單獨部署,所以內部訪問模型是通過curl來請求的.現在改造成直接通過ci的model來訪問(也就是直連數據庫了)
- 這兩個步驟讓時間下降到了600~700ms,基本上沒有太大的優化.
- 最后的優化是OPcache,讓服務器來緩存php文件.
- 最后這個步驟讓時間優化到了300ms,單獨訪問一次接口只要30ms左右.弊端是當你發布文件后,可能最多會有1分鍾的生效期(類似於緩存了1分鍾的本地文件)
最后的結果就是小程序的壓測結果通過啦~