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

背景

项目组开发的游戏客户端使用的脚本是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. SharePoint 2013 CSOM creat post in NewsFeed Access Denied

    现象 在用CSOM创建新闻源时候,报错:无访问权限 解决办法 value="true" 改为 value="false" <appSettings> ...

  2. VMware与virtualbox安装centos7连接网络不可达问题解决笔记(连接网络)

    我最初是安装vmware遇到访问不到网络,按网上的配置方法都不能解决.然后我感觉可能跟系统有关,我装的是centos,然后我试着在virtualbox上安装看遇到什么问题. 用virtualbox安装 ...

  3. PC-BSD 9.2 发布,基于 FreeBSD 9.2

    PC-BSD 9.2 发布了,该版本基于 FreeBSD 9.2. 下载地址:PCBSD9.2-RELEASE-p9-10-02-2013-x64-DVD.iso (3,465MB, SHA256). ...

  4. UIScrollView,UIPageControl

    #import <UIKit/UIKit.h> @interface ViewController : UIViewController<UIScrollViewDelegate&g ...

  5. 调试php的soapServer

    用.NET的webservice做调试很轻松. 用soapserver的try和cacth获取不了多少信息

  6. Hibernate 配置详解(8)

    hibernate.generate_statistics 这个配置大家应该都很熟悉,用于开启Hibernate统计信息,便于对Hibernate相关性能调试提供数据依据.在开发过程当中,可以把这个选 ...

  7. spring+hibernate中的Result object returned from HibernateCallback isn&#39;t a List

    Ok the problem is that for executeFind() the return type is List....so there is no way to use unique ...

  8. Java对象和Excel转换工具XXL-EXCEL

    <Java对象和Excel转换工具XXL-EXCEL> 一.简介 1.1 概述 XXL-EXCEL 是一个灵活的Java对象和Excel文档相互转换的工具. 一行代码完成Java对象和Ex ...

  9. 小甲鱼Python第七讲课后习题

    0.if not(money < 100):上边这行代码相当于? if money>=100 1.assert 的作用是什么? assert “断言”,当这个关键字后边的条件为假的时候,程 ...

  10. gitlab 同步小脚本

    gitlab 是公司中的代码仓库,如何保证两台机器同步呢 公司中使用的是docker那么久使用docker进行演示了也方便以后的工作查找资料 附:脚本 #!/bin/bash docker stop ...