赖勇浩(http://laiyonghao.com)
注:本文根据2011年12月4日我在上海PyConChina上的报告的录像整理而来,有较多口语,但废话不多。
原录像:http://e.gensee.com/v_3df867_14
(前面约四分半钟的暖场与自我介绍,略)今天的主题演讲其实受到两个人的很大的启发,一个是洪强宁洪教授,他在2010的时候做过一个叫《Python于web2.0网站的应用》,大家可以看到我今天这个标题就是直接从他那里借鉴过来的,连微创新都没有。这个slide是很好的,所以我有给出网址,大家可以去看一下。然后还有沈崴,沈崴在2010年的时候也写了一个slide,就是《Python编程艺术》。这个slide对我的Python代码的风格影响非常大,他主要是讲到了应用Python时应该遵循怎样的编程哲学,也就是说怎样更加Pythonic。这两个slide我曾经在珠三角技术沙龙的某次活动上说是我在2010年遇到的最好的两个幻灯片。
今天我以我经历的项目为蓝本,向大家介绍一下webgame服务器端开发这一块的技术和工具。大部分内容都是我在过去或现在的项目中使用的,有小部分内容是我以后才会在项目中用的,大家可以只当我是在做一个介绍,不要当我在做一个推广。如果你选择了这些技术,我不负责任,风险自担。所以这是一个源于项目、高于项目的(分享),因为我在做项目的过程当中会做一些思考和实验,然后这些思考和实验的结果就反映在这个演讲里面。
(开始项目介绍)嗯,这是刚才跟大家提到过的《天下盛境》,它是一个横版卷轴的动作类网页游戏,可以看到它的地图是从左到右移动的,可以在主城里交友、跟网友互动,或是在副本打怪。它现在host在0505u.com这个运营平台,服务器端完全使用Python开发,没有一行C代码,从去年(2010年)的8月份开发到现在(2011年12月初)。这是我最近做的一个项目,因为演讲主题会讲到前做的一些东西,所以再介绍另一个项目——web版的棋牌。它其实就是QQ游戏的一个仿制品,在我自己的网站上有提供给大家访问,它的服务器端也是完全用Python来开发的,时间大概是用了两三个月的时间来开发,然后后面有一些零星的维护。它是一个半成品,不是一个可以商用的成品。
接下来从库,也就是library的角度来介绍一下相关的开发。首先,对于Python在项目中的“位置”大家是怎么看的呢?游戏里面,经常用到脚本语言,比如最常用的一个脚本语言LUA,一般来说网游会用C++写一个host,由它调用多个LUA脚本来完成一个项目(的业务逻辑)。这就是LUA项目常见的(脚本)存在形式,而Python的话稍有不同,Python本身就是主体来的,有一些C/C++写的扩展来解决某些特定的问题,也有一些用Python来写的业务逻辑,通常Python的网游就是这样的一个结构(见下图)。
大型的Python项目大家看看是不是这样的:首先有一个入口的主文件main.py,然后有几个业务逻辑的文件(file1.py、file2.py),然后同层有一些自制或公司用的库(lib1/lib2/lib3),大家的项目是否都是这样的结构?
这样做是不对的。但是我之前经历的几个项目、包括我之前看到过的几个项目,都是这样做。然后我认为,大中型的Python项目的结构应该是这样的:
它就是一个入口文件,然后写了一些业务逻辑(file1.py/file2.py),就是这样。大家可能会觉得有一些奇怪,那不是更小了吗?是的,其实我主张lib要放在site-packages里面,也就是做的时候,库就是库,你要分开,不要跟业务逻辑混在一起。但是大家觉得有必要搞那么复杂吗?用刚才的方式也赚到钱了呀。项目也上线了。其实呢,这么做是有一些好处的,而且最重要的是它不复杂。
其实就只是写一个setup.py,就是用distutils写一个setup.py然后你再把它打包、安装过去就可以了。setup.py有两个比较关键的地方,第一个就是如何避免手写setup.py,第二个就是怎么建立命名空间包。所谓命名空间包就就是类似这种先有一个abu的前缀,后面才是rpc的包名,这样我们就可以建立专门用来做数据库的abu.db,包括我们自己的业务逻辑,比如abu.qipai。这个在zope项目是比较常见的。为了构建这样的结构,我给大家介绍一个东西——paster。因为它已经放上pypi所以大家可以使用pip/easy_install来安装它。它提供了创建项目、安装、测试、部署和运行的全栈式的支持。
大家可以帮看一下它的帮助,它后面可以加很多命令,比如创建项目、运行项目,还有产生配置文件之类的。有很多项目都使用它来构建自己的功能,比如说像pylons、turbogears、zopeskel等。如果要通过paster来运行服务的话,像今天大妈(ZoomQuiet)说到supervisor是吧,其实是我比较鄙视的一个东西来的,它不好用,可以试一下这个,另外我也比较推崇start-stop-daemon,无论如何,我觉得不需要再手写守护进程,没有必要。通过pastedeploy可以把自己的应用以守护进程的方式或其它方式运行起来。以上是对paster的简单介绍,接下来看一下它的基本用法。
要创建一个应用或一个库,首先要有一个模板。可以通过(create子命令的--list-template参数来查看当前环境可用的模板,比如在这里有一安装就有的basic_package,然后可以用-t参数指定模板,后续可跟项目名,即可创建(项目)包。paster会询问一些问题,比如版本,只需要填入或采用默认值即可,等询问完成,就获得了setup.py和相应的目录结构及相关文件了,马上即可使用,无需手写,因为setup.py里的setup函数调用有许多参数,而且这些参数还支持多种形式,要了解清楚也是非常困难的事情(,所以能不手写就不手写吧)。以上讲述的是如何避免手写setup.py,接下来聊一下如何创建命名空间包。创建命名空间包,可以先通过pypi安装pbp.skels。pbp.skels带有许多模板,可以加速创建命名空间包之类的应用,节省宝贵时间。安装以后,可以看到多了一个pbp_package:
然后可以通过-t参数指定使用这个模板来创建带点的命名空间包了。通过命名空间,可以有效地把自己的代码与别人的代码分隔开来。接下来是一个深入的主题,我不在此展开,有兴趣的朋友可以去读一下这篇wiki(http://lucasmanual.com/mywiki/PythonPaste),它讲述的是怎么样针对paster编写自己的横板、扩展它的命令,因为paster甚至可以让你自己添加扩展自己的命令。
(再回到setup.py上来),通过它可以做到项目生命周期的全系列支持。比如在开发时使用develop子命名,可以避免每一次改动都要install一次。还可以用test进行测试,bdist/sdist打发布包,register在pypiserver注册,用upload把发布包上传到pypiserver等。
所以通过这些工具的支持,大家可以很方便地把代码以库的形式分隔开来,放到库应该在的地方,而不是跟业务逻辑代码混在一起,这也有利于在产生服务器上部署代码。甚至可以自建cheeseshop,也就是pypiserver,可以建立一个公司内部使用cheeseshop,就能方便同事使用你的项目。特别是像我前东家网易这样的大公司,有时候想推广一些东西给同事用,同事说我很难用上你的东西啊,比如要穿越内网隔离之类的很麻烦,那就可以通过自建cheeseshop来解决。
(还有一个最佳实践就是)每一次开发软件包的时候,都应该有一个干净的、纯洁的环境。virtualenv可以帮助大家建立一个纯净的环境。在项目发布的时候,不要使用系统的那个python环境,而是应该针对每一个项目建立相应的virtualenv的目录,用virtualenv里的python来运行它。去年洪教授已经在它的幻灯片里介绍过了,大家可以找来看一下,他的幻灯片写的非常详尽、严谨,而且把一个pythoner应该要了解的东西他基本都有介绍到,我从他那里学习到很多。
接下来讲一下插件,首先,插件跟库有什么不同呢?为了讲好这个话题,我曾特意搜索了一下,结果看到stackoverflow上有一个很好的解释(http://stackoverflow.com/a/2792342):插件扩展了大性应用的能力;而库则是一系列的子程序或class来帮助你的开发。所以库和插件是两个有较大区别的概念,所以我今天是分开来介绍的。(举个生活中的例子),伞就是一个插件,当手握一把伞的时候,人就有了“防雨”的能力了。(回到软件开发中),我们以棋牌项目为例,如图,大家可以看到有多种游戏在其中:
有斗地主、五子棋和象棋等,这些可以看作是(软件的)“功能”。接下来转到后台看一下,可能会稍有不同的感觉:
这个棋牌的项目是每一张桌子是一条独立的进程来运行的,比如我和另一个人下棋,那们就会有一条单独的进程为服务我们。进程以desk/main.py作为入口点,它后面可以跟不同的参数,如果跟的是xiangqi,那么它就会加载象棋业务逻辑的插件,成为一个象棋服务器;参数是doudizhu,则会加载斗地主的插件,成为一个斗地主服务器。这就给开发业务逻辑程序员提供了很好的扩展性,而且可以匹配权限隔离,也可以通过封装降低业务逻辑的开发技术,可以让相对初级的程序去开发业务逻辑,甚至干脆外包也不会暴露太多细节。以这个棋牌项目为例,开发业务逻辑(游戏玩法)的人不需要了解网络编辑,不需要了解数据库,不需要了解多线程,因为它开发的时候不需要调用原始的网络、数据库、多线程的接口,因为它们都是host提供的功能,他们只是实现host定义好的接口(或协议)。这其中也是通过setuptools来做的:
大家看这个doudizhu目录,它里面有个setup.py,还有一个很重要的是game_impl.py文件。在game_impl.py文件中,它实现了业务逻辑,也就是实现了host定义的接口。接下来我们先看看setup.py文件,大家可以看到其中最重的是这两行:可以看到定义了一个qipaionweb.games的节,节里面有个叫doudizhu的配置项,它的值是doudizhu.game_impl:GameImpl。这个值是一个class,它也可以是一个function。
接下来讲一下如何加载插件,大家可以看以下代码:
大家可以看到get_game_impl_class函数接受一个game_name参数,这个game_name参数其实就是前面讲到的命令行传入的参数,比如xiangqi、doudizhu之类的。然后通过pkg_resources.load_entry_point把setup.py里通过entry_points参数定义的内容取出来,在这里也就是classGameImpl啦,所以接下来把它实例化后就可用了。在host中,我们除却定义接口,还做了一些业务封装和通用功能,比如与上一级进程(也就是房间进程通信),比如每一个小游戏都有的踢人,统一管理业务相关的计时器等。游戏的实现首先是根据定义实现接口,也就是实现业务逻辑。业务程序员不接触网络、不接触数据库、面对的也是单线程的编程环境,可以大大地降低开发难度。对于插件的话题,大家还可以参考一下trac的组件架构,喜欢OOD的朋友应该能从中借鉴不少东西,我自己也从中学习到许多,比如它的组件管理器、组件和扩展点,以及接口的声明等。trac的架构与我刚才讲的稍有不同,我讲的是一个很简单的版本,因为棋牌并不需要像trac项目那么高的扩展性。
这是trac文档里的两张图,可以看到trac.core里面有一个组件管理器,它对应很多个组件,而组件就有很多个扩展点,每一个扩展点都会实现某一个接口,大概就是这样的架构。它的wiki里面有详细的文档,大家可以在线访问。插件机制不只有这一种,甚至可以自己设计和实现,比如今天上午演讲的limodou写的ulipad里面就自己实现了一套插件机制。它是一套借助mixin来实现的插件机制。
游戏(服务器)是一件CPU密集、I/O密集的应用……(待续)