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

背景

项目组开发的游戏客户端使用的脚本是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. git冲突解决、线上分支合并、luffy项目后台登陆注册页面分析引入

    今日内容概要 git冲突解决 线上分支合并 登陆注册页面(引入) 手机号是否存在接口 腾讯云短信申请 内容详细 1.git冲突解决 1.1 多人在同一分支开发,出现冲突 # 先将前端项目也做上传到 g ...

  3. vue3 迫不得已我硬着头皮查看了keepalive的源代码,解决了线上的问题

    1.通过本文可以了解到vue3 keepalive功能 2.通过本文可以了解到vue3 keepalive使用场景 3.通过本文可以学习到vue3 keepalive真实的使用过程 4.通过本文可以学 ...

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

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

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

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

  6. Android线上Bug热修复分析

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

  7. Java线上应用故障排查

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

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

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

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

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

随机推荐

  1. Linux scp 设置nohup后台运行

    Linux scp 设置nohup后台运行 1.正常执行scp命令 2.输入ctrl + z 暂停任务 3.bg将其放入后台 4.disown -h 将这个作业忽略HUP信号 5.测试会话中断,任务继 ...

  2. Android笔记——Button点击事件几种写法

    Button点击事件:大概可以分为以下几种: 匿名内部类 定义内部类,实现OnClickListener接口 定义的构造方法 用Activity实现OnClickListener接口 指定Button ...

  3. JavaScript function函数种类

    本篇主要介绍普通函数.匿名函数.闭包函数 目录 1. 普通函数:介绍普通函数的特性:同名覆盖.arguments对象.默认返回值等. 2. 匿名函数:介绍匿名函数的特性:变量匿名函数.无名称匿名函数. ...

  4. WPF 微信 MVVM

    公司的同事离职了,接下来的日子可能会忙碌,能完善DEMO的时间也会少了,因此,把做的简易DEMO整体先记录一下,等后续不断的完善. 参考两位大神的日志:WEB版微信协议部分功能分析.[完全开源]微信客 ...

  5. ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式

    由于ASP.NET Core应用是一个同时处理多个请求的服务器应用,所以在处理某个请求过程中抛出的异常并不会导致整个应用的终止.出于安全方面的考量,为了避免敏感信息的外泄,客户端在默认的情况下并不会得 ...

  6. vmware上网的方式

    vmware上网设置 vmware虚拟机上网设置 我的一些心得,如下: 如何使vmware虚拟机中的操作系统能够上网? 第一种情况: 主机使用PPPOE拨号上网 方法一:NAT方式 1.先关闭虚拟机中 ...

  7. ASP.NET Core 中文文档目录

    翻译计划 五月中旬 .NET Core RC2 如期发布,我们遂决定翻译 ASP.NET Core 文档.我们在 何镇汐先生. 悲梦先生. 张仁建先生和 雷欧纳德先生的群中发布了翻译计划招募信息,并召 ...

  8. JSP 标准标签库(JSTL)

    JSP 标准标签库(JSTL) JSP标准标签库(JSTL)是一个JSP标签集合,它封装了JSP应用的通用核心功能. JSTL支持通用的.结构化的任务,比如迭代,条件判断,XML文档操作,国际化标签, ...

  9. springmvc SSM 多数据源 shiro redis 后台框架 整合

    A集成代码生成器 [正反双向(单表.主表.明细表.树形表,开发利器)+快速构建表单 下载地址    ; freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本,处理类 ...

  10. form表单验证-Javascript

    Form表单验证: js基础考试内容,form表单验证,正则表达式,blur事件,自动获取数组,以及css布局样式,动态清除等.完整代码如下: <!DOCTYPE html PUBLIC &qu ...