寫代碼如何合理使用和優化我們的機器資源(CPU、內存、網絡、磁盤)
阿新 • • 發佈:2018-11-23
rontab ava 直接 由於 內存 說我 很多 遇到 工作
寫代碼腦子一定要繃緊一根弦,認知到我們所在的機器資源是有限的。機器資源有哪些?CPU、內存、網絡、磁盤等,如果不做好保護控制工作,一旦某一資源滿負荷,很容易導致出現線上問題。
1 CPU 資源怎麽限制
*
計算算法優化。如果服務需要進行大量的計算,比如推薦排序服務,那麽務必對你的計算算法進行優化,比如筆者曾經對地理空間距離計算這一重度使用的算法進行了優化,取得了較好的效果,詳見《地理空間距離計算優化》一文。
*
鎖。對於很多服務而言,沒有那麽多耗費計算資源的算法,但 CPU 使用率也很高,這個時候需要看看鎖的使用情況,我的建議是如無必要,盡量不用顯式使用鎖。
*
習慣問題。比如寫循環的時候,千萬要檢查看看是否能正確退出,有些時候一不小心,在某些條件下就成為死循環,很著名的案例就是《多線程下HashMap的死循環問題》。比如集合遍歷時候使用性能較差的遍歷方式、String + 檢查,如果有超過多個 String 相加,是否使用 StringBuffer.append?
*
盡量使用線程池。通過線程池來限制線程的數目,避免線程過多造成的線程上下文切換的開銷。
*
JVM 參數調優。JVM 參數也會影響 CPU 的使用,如《發布或重啟線上服務時抖動問題解決方案》。
2 內存資源怎麽限制
*
JVM 參數設置。通過 JVM 參數的設置來限制內存使用,JVM 參數調優比較靠經驗,有一篇朋友寫的好文可以參考《Linux 與 JVM 的內存關系分析》。
*
初始化 Java 集合類大小。使用 Java 集合類的時候盡量初始化大小,在長連接服務等耗費內存資源的服務中這種優化非常重要。
*
使用內存池/對象池
*
使用線程池的時候一定要設置隊列的最大長度。之前看過好多起故障都是由於隊列最大長度沒有限制最後導致內存溢出。
*
如果數據較大避免使用本地緩存。如果數據量較大,可以考慮放置到分布式緩存如 Redis、Tair 等,不然 gc 都可能把自己服務卡死。
*
對緩存數據進行壓縮。比如之前做推薦相關服務時,需要保存用戶偏好數據,如果直接保存可能有 12G,後來采用短文本壓縮算法直接壓縮到 6G,不過這時一定要考慮好壓縮解壓縮算法的 cpu 使用率、效率與壓縮率的平衡,一些壓縮率很高但是性能很差的算法,也不適合線上實時調用。有些時候直接使用 probuf 來序列化之後保存,這樣也能節省內存空間。
*
清楚第三方軟件實現細節,精確調優。在使用第三方軟件時,只有清楚細節後才知道怎麽節約內存,這點我在實際工作中深有體會,比如之前在閱讀過lucene的源碼後發現我們的索引文件原來是可以壓縮的,而這在說明文檔中都找不到,具體參考《lucene索引文件大小優化小結》一文。
3 網絡資源怎麽限制
*
減少調用的次數。經常看到有同學在循環裏用 redis/tair 的 get,如果意識到這裏面的網絡開銷的話就應該使用批量處理;又如在推薦服務中經常遇到要去多個地方去取數據,一般采用多線程並行去取數據,這個時候不僅耗費cpu資源,也耗費網絡資源,一種在實際中常常采用的方法就是先將很多數據離線存儲到一塊 ,這時候線上服務只要一個請求就能將所有數據獲取。
*
減少傳輸的數據量。一種方法是壓縮後傳輸,還有一種就是按需傳輸,比如經常遇到的 getData(int id),如果我們返回該 id 對應的 Data 所有信息,一來人家不需要,二來數據量傳輸太大,這個時候可以改為 getData(int id, Listfields),使用方傳輸相應的字段過來,服務端只返回使用方需要的字段即可。
4 磁盤資源怎麽限制
打日誌要控制量,並定期清理。1)只打印關鍵的異常日誌;2)對日誌大小進行監控報警。我有一次就遇到了第三方服務掛了,然後我這邊就不斷打印調用該第三方服務異常的日誌,本來我的服務有降級方案,如果第三方服務掛了會自動使用其它服務,但是突然收到報警說我服務掛了,登上機器一看才知道是磁盤不夠導致的崩潰;3)定期對日誌進行清理,比如用 crontab,每隔幾天對日誌進行清理;4)打印日誌到遠端,對於一些比較重要的日誌可以直接將日誌打印到遠端HDFS文件系統裏;
備註
基礎原則……控制資源的使用
1 CPU 資源怎麽限制
*
計算算法優化。如果服務需要進行大量的計算,比如推薦排序服務,那麽務必對你的計算算法進行優化,比如筆者曾經對地理空間距離計算這一重度使用的算法進行了優化,取得了較好的效果,詳見《地理空間距離計算優化》一文。
*
鎖。對於很多服務而言,沒有那麽多耗費計算資源的算法,但 CPU 使用率也很高,這個時候需要看看鎖的使用情況,我的建議是如無必要,盡量不用顯式使用鎖。
*
習慣問題。比如寫循環的時候,千萬要檢查看看是否能正確退出,有些時候一不小心,在某些條件下就成為死循環,很著名的案例就是《多線程下HashMap的死循環問題》。比如集合遍歷時候使用性能較差的遍歷方式、String + 檢查,如果有超過多個 String 相加,是否使用 StringBuffer.append?
*
盡量使用線程池。通過線程池來限制線程的數目,避免線程過多造成的線程上下文切換的開銷。
*
JVM 參數調優。JVM 參數也會影響 CPU 的使用,如《發布或重啟線上服務時抖動問題解決方案》。
2 內存資源怎麽限制
*
JVM 參數設置。通過 JVM 參數的設置來限制內存使用,JVM 參數調優比較靠經驗,有一篇朋友寫的好文可以參考《Linux 與 JVM 的內存關系分析》。
*
初始化 Java 集合類大小。使用 Java 集合類的時候盡量初始化大小,在長連接服務等耗費內存資源的服務中這種優化非常重要。
*
使用內存池/對象池
*
使用線程池的時候一定要設置隊列的最大長度。之前看過好多起故障都是由於隊列最大長度沒有限制最後導致內存溢出。
*
如果數據較大避免使用本地緩存。如果數據量較大,可以考慮放置到分布式緩存如 Redis、Tair 等,不然 gc 都可能把自己服務卡死。
*
對緩存數據進行壓縮。比如之前做推薦相關服務時,需要保存用戶偏好數據,如果直接保存可能有 12G,後來采用短文本壓縮算法直接壓縮到 6G,不過這時一定要考慮好壓縮解壓縮算法的 cpu 使用率、效率與壓縮率的平衡,一些壓縮率很高但是性能很差的算法,也不適合線上實時調用。有些時候直接使用 probuf 來序列化之後保存,這樣也能節省內存空間。
*
清楚第三方軟件實現細節,精確調優。在使用第三方軟件時,只有清楚細節後才知道怎麽節約內存,這點我在實際工作中深有體會,比如之前在閱讀過lucene的源碼後發現我們的索引文件原來是可以壓縮的,而這在說明文檔中都找不到,具體參考《lucene索引文件大小優化小結》一文。
3 網絡資源怎麽限制
*
減少調用的次數。經常看到有同學在循環裏用 redis/tair 的 get,如果意識到這裏面的網絡開銷的話就應該使用批量處理;又如在推薦服務中經常遇到要去多個地方去取數據,一般采用多線程並行去取數據,這個時候不僅耗費cpu資源,也耗費網絡資源,一種在實際中常常采用的方法就是先將很多數據離線存儲到一塊 ,這時候線上服務只要一個請求就能將所有數據獲取。
*
減少傳輸的數據量。一種方法是壓縮後傳輸,還有一種就是按需傳輸,比如經常遇到的 getData(int id),如果我們返回該 id 對應的 Data 所有信息,一來人家不需要,二來數據量傳輸太大,這個時候可以改為 getData(int id, Listfields),使用方傳輸相應的字段過來,服務端只返回使用方需要的字段即可。
4 磁盤資源怎麽限制
打日誌要控制量,並定期清理。1)只打印關鍵的異常日誌;2)對日誌大小進行監控報警。我有一次就遇到了第三方服務掛了,然後我這邊就不斷打印調用該第三方服務異常的日誌,本來我的服務有降級方案,如果第三方服務掛了會自動使用其它服務,但是突然收到報警說我服務掛了,登上機器一看才知道是磁盤不夠導致的崩潰;3)定期對日誌進行清理,比如用 crontab,每隔幾天對日誌進行清理;4)打印日誌到遠端,對於一些比較重要的日誌可以直接將日誌打印到遠端HDFS文件系統裏;
寫代碼如何合理使用和優化我們的機器資源(CPU、內存、網絡、磁盤)