工做後很久沒上博客園了,雖然不是很忙,但也沒學生時代閒了。今天上博客園,發現好多的文章都是年終總結,想一想是否是本身也應該總結下,不過如今還沒想好,等想好了再寫吧。今天寫寫本身在工做後用到的技術乾貨,爭取之後多上博客園寫寫總結吧,真是懷念學生時代啊!!!python
項目組開發的遊戲客戶端使用的腳本是python,服務器也是python。之因此選擇python,主要仍是基於開發效率的考慮,畢竟這是腳本語言天生的優點;其次就是有不少庫,不用本身再造輪子了。可能使用過python的同窗都會認爲python比較耗,運行效率不高,一個簡單的賦值語句就包含了多個對象的生成和釋放。但其實如今服務器的性能很是好,一般性能都是過剩的,因此python在服務器上高效地跑是徹底沒問題的;至於客戶端,性能的瓶頸主要仍是在引擎層,在一幀中最多也就20%的時間在執行腳本,超過太多說明邏輯寫的有問題或者能夠分攤到多幀去執行。本文主要介紹下在使用python腳本的狀況下解決線上問題的幾種有效技術,其它語言應該也有相似的技術,特別是腳本語言,這裏只是作個拋磚引玉~~linux
這種技術主要是針對狀況比較緊急,而且bug是腳本邏輯錯誤致使的。如客戶端邏輯寫的有問題致使出現exception,使得玩家某個玩法不能玩,或者是服務端某個代碼邏輯寫的有問題。這種技術實現的主要思路是(以熱更新客戶端爲例):服務器將修正的代碼發送到客戶端,客戶端動態執行這段代碼來修復bug。用python來實現這個其實很是簡單,只須要在客戶端內嵌的python虛擬機中動態編譯服務端發過來的代碼,並執行這段代碼就好了。例如:如今客戶端有下面一段的代碼,這段代碼是有錯誤的。c++
1 #模塊test 2 3 def not_has_a(x): 4 return hasattr(x, 'a')
原本上面代碼是但願x對象沒有a屬性後返回True,但如今狀況正好反過來了。如今咱們須要寫一段代碼來修正這個問題,也就是寫一段代碼給python虛擬機執行,動態修改test模塊中not_has_a函數的定義。這個在python中很好實現的,由於python中函數也是一個對象,模塊中只是根據函數名來索引對應的函數對象的,因此咱們只須要從新定義一個新的not_has_a函數對象,將模塊中根據not_has_a函數名索引的對象指向新定義的函數對象就行。具體代碼以下:算法
1 import test 2 3 def not_has_a(x) 4 return not hasattr(x, 'a') 5 6 setattr(test, 'not_has_a', not_has_a)
最後就是讓python虛擬機執行上面的代碼。首先服務端會把上面代碼的字符串發送給客戶端,客戶端接收到代碼後編譯這段字符串,而後執行就能夠了,具體代碼以下:服務器
1 def hotfix(self, hotfix_content): 2 compiled_code = compile(hotfix_content, 'hotfix', 'exec') 3 import __main__ 4 exec compiled_code in __main__.__dict__
若是產品上線出現問題,最快定位、發現和解決問題的有效方法就是查看日誌,因此日誌系統應該也必須是線上系統的組成部分之一。python在代碼中輸出日誌很簡單,使用logging模塊就行,不須要本身再超輪子了,獲取模塊日誌器代碼以下:函數
1 def get_logger (moduleName): 2 logger = logging.getLogger(moduleName) 3 logger.setLevel(logging.DEBUG) 4 ch = logging.StreamHandler() 5 ch.setLevel(logging.DEBUG) 6 formatter = logging.Formatter( 7 "%(asctime)s - %(name)s - %(levelname)s - %(message)s") 8 ch.setFormatter(formatter) 9 logger.addHandler(ch) 10 return logger
有了模塊日誌器,咱們就能夠經過日誌器在代碼中輸出日誌信息了。例如打印一些trace信息:工具
1 logger = get_logger('test') 2 try: 3 1 / 0 4 except: 5 import traceback 6 logger.error(traceback.format_exc()) 7 logger.info('info') 8 logger.debug('debug') 9 logger.warning('warning') 10 logger.error('error') 11 logger.critical('critical')
雖然有了上面的日誌系統後,遇到線上問題咱們能夠很快的定位問題,但可能有時候只有這些信息還不夠,咱們還想查看出問題的地方涉及的類或者模塊的一些變量的信息。雖然也能夠經過日誌的方式進行查看,但每次輸出log都要把相關的變量值都輸出一來會致使log信息增多,影響系統性能;二來大部分時間這些變量的信息是沒用的,只有出現了問題才須要。python提供了code.InteractiveConsole類,它的功能相似於python的命令行交互解釋器,能夠將一段python代碼字符串push到code.InteractiveConsole類實例中,code.InteractiveConsole類實例會讓python虛擬機去執行這段代碼,並返回執行結果。爲了作到相似於python命令行交互解釋器那樣直接以命令行方式運行,很方便,不須要運行特殊的客戶端,咱們使用telnet來鏈接python虛擬機,經過telnet將輸入的python代碼發送給code.InteractiveConsole類實例。這種方法須要在系統初始化的時候啓動一個相似telnet服務,用來監聽telnet客戶端的鏈接,並將客戶端發過來的python代碼push到code.InteractiveConsole類實例中去執行。有了這個功能後,經過telnet就能夠鏈接上python虛擬機了,經過導入模塊能夠很容易的得到模塊全局變量的內容。若是須要獲取類實例中變量的內容,能夠經過將類實例存放在模塊的全局變量中的方式來獲取。除了能夠查看變量內容,還能夠修改變量的內容,調用某些函數等,這在debug一些功能的時候很是的方便。性能
相較於傳統C、C++語言,Python語言不存在真正的內存泄漏問題,依靠引用計數機制及標記-清除算法,Python中的gc模塊能夠很好地爲代碼編寫者管理內存。但每次gc須要遍歷全部對象進行標記-清除操做,找到存在循環引用的應該被釋放的對象。這個過程是很是耗時的,因此若是頻繁的gc,將會致使客戶端發過來的請求長時間沒法獲得響應,這是不能容忍的。python的垃圾回收機制是標記-清除算法加分代策略,在這個主體機制下,咱們可以控制的東西很少,主要是對分代策略中的幾個參數進行控制。《python源碼剖析》對於分代策略的描述是:將系統中的全部內存塊根據其存活時間劃分爲不一樣的集合,每個集合就稱爲一個「代」,垃圾收集的頻率隨着「代」的級別的增大而減少。新對象被加入最年輕的一代(0代),當對象在一次垃圾收集過程當中存活下來時,將被移往更老的一代,更老一代的收集頻率相對較低。本代是否應該進行垃圾回收由一個閾值控制,這個經過python提供的gc.set_threshold(threshold0,[, threshold1[, threshold2]])來進行設置,threshold0表明新建對象與銷燬對象的差值上限,threshold1和threshold2均表明上一代運行多少次垃圾收集算法以後,本身這一代則進行垃圾回收。Python對於threshold的默認配置是(700, 10, 10),即第0代最多700個對象,第1代最多7000個,第2代在第一次進行回收時對象最多有70000個。能夠經過這個接口將閾值設置大點減小gc次數,但也不能設置太大,這樣會消耗比較多的內存,而且一次gc所消耗的時間也會更長。即便把閾值設置的比較大,若是代碼中存在不停的產生循環引用對象的話,依然會頻繁觸發gc。爲了下降gc次數,咱們就須要找到產生循環引用的代碼,手動解掉這些循環引用。查循環引用一個很好的工具就是objgraph,裏頭有不少工具函數,好比show_most_common_types,能夠看到實例最多的那些類,大部分狀況下只須要看一眼就知道哪些類實例次數不正常了。還能夠show_growth,看類型的增加速度。例以下面進行了10000次循環,每次循環都會建立A和B的實例,而且它們互相引用,最後經過show_most_common_types能夠看到A和B的實例個數爲10000。spa
1 class A(object): 2 def __init__(self): 3 self.other = None 4 5 def set_other(self, other): 6 self.other = other 7 8 class B(object): 9 def __init__(self, other): 10 self.other = other 11 12 if __name__ == '__main__': 13 gc.disable() 14 for i in xrange(10000): 15 a = A() 16 b = B(a) 17 a.set_other(b) 18 print objgraph.most_common_types(50)
對於在linux作開發的人來講,對strace和gdb確定不陌生,由於咱們常常須要用到它們,無論程序處於線上仍是開發階段。當程序的行爲與咱們的邏輯不符合的時候(寫代碼確定會遇到~~),特別是一些靜態語言,如c/c++,出了問題很麻煩。打log?,須要從新編譯運行,若是是線上程序基本行不通。即便是腳本語言,若是腳本致使虛擬機層出現問題,基本很難排除定位問題。這時候可使用strace來跟蹤程序的系統調用,大體估計程序的行爲。例如當你的程序阻塞在某個IO上時,但不知道具體阻塞在哪一個IO的時候,能夠經過strace很明確的看到程序發送的系統調用信息,獲取IO對應的fd,而後經過lsof查看這個程序的全部fd信息,就能夠定位到具體阻塞在哪一個IO上了。gdb神器更不用說了,debug的利器,即便是線上的程序,也能夠經過attach的方式進行debug,設置斷點,查看變量,堆棧等信息。命令行
上面的這些技術僅僅是個思想,正如開頭說的,只是個拋磚引玉,不只限於python語言,其實還有不少其它的實用的線上技術,歡迎知道的補充哈~~~。