工作后好久没上博客园了,虽然不是很忙,但也没学生时代闲了。今天上博客园,发现好多的文章都是年终总结,想想是不是自己也应该总结下,不过现在还没想好,等想好了再写吧。今天写写自己在工作后用到的技术干货,争取以后多上博客园写写总结吧,真是怀念学生时代啊!!!

背景

项目组开发的游戏客户端使用的脚本是python,服务器也是python。之所以选择python,主要还是基于开发效率的考虑,毕竟这是脚本语言天生的优势;其次就是有很多库,不用自己再造轮子了。可能使用过python的同学都会认为python比较耗,运行效率不高,一个简单的赋值语句就包含了多个对象的生成和释放。但其实现在服务器的性能非常好,通常性能都是过剩的,所以python在服务器上高效地跑是完全没问题的;至于客户端,性能的瓶颈主要还是在引擎层,在一帧中最多也就20%的时间在执行脚本,超过太多说明逻辑写的有问题或者可以分摊到多帧去执行。本文主要介绍下在使用python脚本的情况下解决线上问题的几种有效技术,其它语言应该也有类似的技术,特别是脚本语言,这里只是做个抛砖引玉~~

热更新(hotfix)

这种技术主要是针对情况比较紧急,并且bug是脚本逻辑错误导致的。如客户端逻辑写的有问题导致出现exception,使得玩家某个玩法不能玩,或者是服务端某个代码逻辑写的有问题。这种技术实现的主要思路是(以热更新客户端为例):服务器将修正的代码发送到客户端,客户端动态执行这段代码来修复bug。用python来实现这个其实非常简单,只需要在客户端内嵌的python虚拟机中动态编译服务端发过来的代码,并执行这段代码就行了。例如:现在客户端有下面一段的代码,这段代码是有错误的。

 #模块test

 def not_has_a(x):
return hasattr(x, 'a')

本来上面代码是希望x对象没有a属性后返回True,但现在情况正好反过来了。现在我们需要写一段代码来修正这个问题,也就是写一段代码给python虚拟机执行,动态修改test模块中not_has_a函数的定义。这个在python中很好实现的,因为python中函数也是一个对象,模块中只是根据函数名来索引对应的函数对象的,所以我们只需要重新定义一个新的not_has_a函数对象,将模块中根据not_has_a函数名索引的对象指向新定义的函数对象就行。具体代码如下:

 import test

 def not_has_a(x)
return not hasattr(x, 'a') setattr(test, 'not_has_a', not_has_a)

最后就是让python虚拟机执行上面的代码。首先服务端会把上面代码的字符串发送给客户端,客户端接收到代码后编译这段字符串,然后执行就可以了,具体代码如下:

 def hotfix(self, hotfix_content):
compiled_code = compile(hotfix_content, 'hotfix', 'exec')
import __main__
exec compiled_code in __main__.__dict__

日志系统(logging)

如果产品上线出现问题,最快定位、发现和解决问题的有效方法就是查看日志,所以日志系统应该也必须是线上系统的组成部分之一。python在代码中输出日志很简单,使用logging模块就行,不需要自己再超轮子了,获取模块日志器代码如下:

 def get_logger (moduleName):
logger = logging.getLogger(moduleName)
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
logger.addHandler(ch)
return logger

有了模块日志器,我们就可以通过日志器在代码中输出日志信息了。例如打印一些trace信息:

 logger = get_logger('test')
try:
1 / 0
except:
import traceback
logger.error(traceback.format_exc())
logger.info('info')
logger.debug('debug')
logger.warning('warning')
logger.error('error')
logger.critical('critical')

远程连接(telnet)

虽然有了上面的日志系统后,遇到线上问题我们可以很快的定位问题,但可能有时候只有这些信息还不够,我们还想查看出问题的地方涉及的类或者模块的一些变量的信息。虽然也可以通过日志的方式进行查看,但每次输出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一些功能的时候非常的方便。

objgraph

相较于传统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。

 class A(object):
def __init__(self):
self.other = None def set_other(self, other):
self.other = other class B(object):
def __init__(self, other):
self.other = other if __name__ == '__main__':
gc.disable()
for i in xrange(10000):
a = A()
b = B(a)
a.set_other(b)
print objgraph.most_common_types(50)

strace和gdb神器

对于在linux做开发的人来说,对strace和gdb肯定不陌生,因为我们经常需要用到它们,不管程序处于线上还是开发阶段。当程序的行为与我们的逻辑不符合的时候(写代码肯定会遇到~~),特别是一些静态语言,如c/c++,出了问题很麻烦。打log?,需要重新编译运行,如果是线上程序基本行不通。即使是脚本语言,如果脚本导致虚拟机层出现问题,基本很难排除定位问题。这时候可以使用strace来跟踪程序的系统调用,大致估计程序的行为。例如当你的程序阻塞在某个IO上时,但不知道具体阻塞在哪个IO的时候,可以通过strace很明确的看到程序发送的系统调用信息,获取IO对应的fd,然后通过lsof查看这个程序的所有fd信息,就可以定位到具体阻塞在哪个IO上了。gdb神器更不用说了,debug的利器,即使是线上的程序,也可以通过attach的方式进行debug,设置断点,查看变量,堆栈等信息。

总结

上面的这些技术仅仅是个思想,正如开头说的,只是个抛砖引玉,不仅限于python语言,其实还有很多其它的实用的线上技术,欢迎知道的补充哈~~~。

关于解决python线上问题的几种有效技术的更多相关文章

  1. 【Maven篇】---解决Maven线上部署java.lang.ClassNotFoundException和no main manifest attribute解决方法

    一.前述 maven 线上部署的话会出现一些问题比如java.lang.ClassNotFoundException或者no main manifest attribute的话,是因为maven 配置 ...

  2. python文件上传的三种方式

    def upload(request): return render(request, 'upload.html') def upload_file(request): username = requ ...

  3. 记第一次正式线上笔试(Tencent——正式考-技术研发类-综合-2018实习生招聘)

    选择题做的跟傻逼一样,不多说了..大学只打了ACM还不是计算机科班出身的我,连好多名词都不认识..... 三道编程题很简单,下面给出三道题的大致题意以及题解. 1.给出n和m,满足(2m)可以整除n. ...

  4. Android线上Bug热修复分析

    针对app线上修复技术,目前有好几种解决方案,开源界往往一个方案会有好几种实现.重复的实现会有造轮子之嫌,但分析解决方案在技术上的探索和衍变,这轮子还是值得去推动的 关于Hot Fix技术 Hot F ...

  5. Java线上应用故障排查

    线上故障主要2种: CPU利用率很高, 内存占用率很大 一.CPU利用率很高 1. top查询那个进程CPU使用率高 2. 显示进程列表 ps -mp pid -o THREAD,tid,time 找 ...

  6. 线上mysql内存持续增长直至内存溢出被killed分析(已解决)

    来新公司前,领导就说了,线上生产环境Mysql库经常会发生日间内存爆掉被killed的情况,结果来到这第一天,第一件事就是要根据线上服务器配置优化配置,同时必须找出现在mysql内存持续增加爆掉的原因 ...

  7. 一次线上http接口调用不通相关的解决过程

    2016-05-25 08:58:34 昨天线上小白系统因为调用外部http接口,超时不释放,导致页面反应很慢,时间一长,报502错误. 上网查了下,502错误是因为服务对于客户的请求没有得到及时的反 ...

  8. 记一次线上Curator使用过程JVM栈溢出解决

       为了同学们看起来一目了,特按如下思路进行讲解. 1.出现的场景    2.分析及解决的过程    3.总结 最近公司要使用zookeeper做配置管理(后面简称ZK),然后自己就提前用虚拟机进行 ...

  9. python docker 多进程提供 稳定tensorflow gpu 线上服务

    尝试了太多的python多进程的服务,在tensorflow 的线上GPU服务中总是不理想.tensorlfow serving docker服务这些也有些不便. 今天抽空给大家分享一个成功的经验.失 ...

随机推荐

  1. 微信安卓版下载 Android微信各版本列表

    前面ytkah弄了一个iso微信各版本列表,现在就来整一个微信 for Android各版本列表,方便大伙下载.每个版本都放出一些新的功能或修复相关错误,详情可以点击下面的版本链接进行查看.资源收集于 ...

  2. 【CTO讲堂】以API为核心的移动应用云大发展时代

    摘要:CTO线上讲堂5月20日正式登场,CTO俱乐部首期邀请到APICloud联合创始人兼CTO邹达与C粉之家微信群友一起聊聊如何快速玩转App开发,分享技术人的职场成长. 为了帮助IT从业者职业之路 ...

  3. 浅谈VC++中预编译的头文件放那里的问题分析

    用C++写程序,肯定要用预编译头文件,就是那个stdafx.h.不过我一直以为只要在.cpp文件中包含stdafx.h 就使用了预编译头文件,其实不对.在VC++中,预编译头文件是指放到stdafx. ...

  4. mysql 根据生日计算年龄,并查询在18-25之间的语句

    select id, DATE_FORMAT(birthday,"%Y-%m-%d") birthday, CURDATE() , (year(now())-year(birthd ...

  5. Client–server model

    Client–server model From Wikipedia, the free encyclopedia The client–server model of computing ] Oft ...

  6. discuz问题综合

    1.discuz x2搬家问题:搬家后,需要修改数据库的配置文件,包含:      主要的配置文件有三个:config目录下的config_global.php和config_ucenter.php以 ...

  7. 用C#开发一个WinForm版的批量图片压缩工具

    我们在实际项目开发过程中,曾经遇到过一个需求,就是要开发一个对大量图片进行整理(删除掉一些不符合要求的图片).归类(根据格式进行分类,比如jpg格式.bmp格式等).压缩(因为有的图片很大很占空间,看 ...

  8. Covariance and Contravariance in C#, Part Two: Array Covariance

    http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-a ...

  9. EF 如何code first

    首先配置连接数据.sql server <connectionStrings> <add name="Model1" connectionString=" ...

  10. SPOJ DIVCNT2 [我也不知道是什么分类了反正是数论]

    SPOJ DIVCNT2 - Counting Divisors (square) 题意:求 \[ \sum_{i=1}^n\sigma_0(i^2) \] 好棒啊! 带着平方没法做,考虑用其他函数表 ...