工做后很久没上博客园了,虽然不是很忙,但也没学生时代闲了。今天上博客园,发现好多的文章都是年终总结,想一想是否是本身也应该总结下,不过如今还没想好,等想好了再写吧。今天写写本身在工做后用到的技术干货,争取之后多上博客园写写总结吧,真是怀念学生时代啊!!!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语言,其实还有不少其它的实用的线上技术,欢迎知道的补充哈~~~。