某測試模擬器性能優化-從系統角度思考問題
再次分析
前面的文章提到,在學會使用yappi之後,我們立即重新做了一輪profile。結果發現最佔用CPU的果然是kafka寫操作和redis讀操作,這都是調用的第三方的庫,基本上很難再進行優化。
那接下來怎麼辦呢?log_generator組件佔用CPU資源嚴重的問題該如何解決呢?
既然單個組件不好優化,那麼我們必須從系統的角度來分析下,為何該組件會大量佔用系統資源。
首先,考慮該組件的角色。log_generator實際是一個消費者,它不停地監聽redis的某些channel,當其他組件發布數據到redis的channel上時,log_generator就寫log文件並且寫kafka。通過觀察發現,log_generator會在redis channel有數據的時候,大量佔用系統資源,想想這非常合理,如果它突然收到20w條數據,當然會不停地讀,處理,然後進行寫操作。但是,當這些數據基本處理完後,log_generator就會進入空閑狀態,是典型的忙時忙死,閑時閑死。雖說,一段時間內,系統的總負載是一定的,但是突發的這種大量佔用CPU的情況,會導致整個系統不穩定,肯定沒有將負載平均消耗到一段時間上來的可靠。所以,我們能不能想辦法讓redis數據的生產者使用慢速持續生產的方式代替突發生產的方式呢?
其次,我們提到過log_generator是一個運行在4核16G伺服器上的程序,採用了多線程模型。那麼由於GIL的存在,必然無法充分利用多核資源。Python建議在性能敏感的場景下使用多進程模型代替多線程模型,我們何不嘗試一下呢?
實戰
說干就干,首先我們重構了redis數據的生產者程序-simulator,讓它將一次性發送給redis的數據分批均勻地發送給redis。它的代碼類似如下, 其中YourTaskToConsumeCPU()函數指代分批發送數據的任務。源碼可以在github上找到https://github.com/jumper2014/Asgard/blob/master/tools/time_split.py
import mathnimport timennnif __name__ == "__main__":nn # task listn elements = range(1, 998)n #n section_count = 100n total_time = 10 # secondn print("len:", len(elements))nn elements_each_section = len(elements) / (section_count * 1.0)n elements_each_section = int(math.ceil(elements_each_section))n time_each_section = total_time / (section_count * 1.0)nn print("elements_each_section:", elements_each_section)n print("section_count:", elements_each_section)n print("time_each_section:", time_each_section)nn start_time = time.time()n for i in range(section_count):n start_index = i * elements_each_sectionn n = i + 1n end_index = n * elements_each_sectionn print(start_index, end_index)n print(elements[start_index: end_index])nn #############n # TODOn # YourTaskToConsumeCPU()n #############nn # time compensationn now = time.time()n if now - start_time < n * time_each_section:n duration = n * time_each_section - (now - start_time)n print("sleep:", duration)n time.sleep(duration)nn end_time = time.time()n print(end_time - start_time)n
接著,我們將原來的多線程程序-log_generator改成多進程程序。Python就是好,從多線程改成多進程,簡單的不行。 原來代碼
import threadingnfor reviewer in reviewers:n p = threading.Thread(target=reviewer.run, args=())n p.setDaemon(True)n p.start()n
現在代碼
from multiprocessing import Processnfor reviewer in reviewers:n p = Process(target=reviewer.run, args=())n p.daemon = Truen p.start()n
結果
改完後上機一運行,哇撒,log_generator的峰值神奇地消失了,最高時候的CPU負載,由120%降到了60%,運行十分穩定,輕輕鬆鬆就達到了模擬20w節點在線的要求。
20w節點在線搞定了,那我們是如何搞定模擬100w節點在線的呢?且聽下回分解。
如果覺得本文對您有幫助,敬請點贊。
推薦閱讀: