python线上技术

编程入门 行业动态 更新时间:2024-10-09 13:27:26

python<a href=https://www.elefans.com/category/jswz/34/1770445.html style=线上技术"/>

python线上技术

关于解决python线上问题的几种有效技术

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

背景

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

热更新(hotfix)

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

2

3defnot_has_a(x):

4returnhasattr(x,'a')

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

2

3defnot_has_a(x)

4returnnothasattr(x,'a')

5

6setattr(test,'not_has_a',not_has_a)

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

2compiled_code=compile(hotfix_content,'hotfix','exec')

3import__main__

4execcompiled_codein__main__.__dict__

日志系统(logging)

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

2logger=logging.getLogger(moduleName)

3logger.setLevel(logging.DEBUG)

4ch=logging.StreamHandler()

5ch.setLevel(logging.DEBUG)

6formatter=logging.Formatter(

7"%(asctime)s - %(name)s - %(levelname)s - %(message)s")

8ch.setFormatter(formatter)

9logger.addHandler(ch)

10returnlogger

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

2try:

31/0

4except:

5importtraceback

6logger.error(traceback.format_exc())

7logger.info('info')

8logger.debug('debug')

9logger.warning('warning')

10logger.error('error')

11logger.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。1classA(object):

2def__init__(self):

3self.other=None

4

5defset_other(self,other):

6self.other=other

7

8classB(object):

9def__init__(self,other):

10self.other=other

11

12if__name__=='__main__':

13gc.disable()

14foriinxrange(10000):

15a=A()

16b=B(a)

17a.set_other(b)

18printobjgraph.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线上技术

本文发布于:2024-02-06 06:53:40,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1747008.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:线上   技术   python

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!