PyCon 作为全球 Python 爱好者的盛会已在世界各地多个城市举办过活动,本月 3 号、4 号, PyCon 首次登陆中国,由 GTUG (Google Technology User Group)、华蟒用户组CPyUG (China Python User Group)和 TopGeek 主办,多位重量级嘉宾一同为国内的 Python 爱好者带来了一场饕餮盛宴。
在主持人简单介绍大会的主办方、赞助商及志愿者之后,就开始了大会第一天的议程。陈世欣在暖场致辞中从“快、容、易、多、变、全”等多方面介绍了 Python,并引用了 Python 社区的名言:
Life is short (You need python)
人生苦短、我用 Python
随后,播放了几段由未能到场的 Python 大牛为大会录制的视频,包括 Python 创始人 Guido van Rossum,目前在新加坡的上海 Python 用户组发起人侃嘉 KJ,还有台湾中蟒项目的Glaze。Guido 在视频中显得格外高兴,很高兴在中国能有这么多人使用Python,如今的Python 已经进入Web、社交和移动领域,能够处理TB 级别的数据,它还特别提到了国内的豆瓣网,希望明年也能继续在中国举办PyCon。Guido 在 Python 官方网站的主页上也增加了本次 PyCon 的信息:
First China PyCon
The first PyCon in China will be held in Shanghai December 3-4.
语言与框架
eurasia 作者,国内 Python 界的大侠沈崴首先登场,在《Python ,通向未来之路》中主要回顾了一下历史,他在 2000 年左右通过 boost.python 了解了 Python,之后便爱上了 Python,很喜欢去 dohao 论坛,还研读了《The Zope Book》(中文版标题《Zope 指南》)。当Python 从1.x 升级到2.0 时,他认为这是一个飞跃,而到了2.2 时就感觉非常完善了,但随后的每一次小版本升级都能带来不少惊喜。到了3.0 时代,兼容性问题尤为突出, Plone 就是兼容性问题之集大成者。也有 PyPy 这样的非标准实现,为提升性能提供了一种可能。
简单对比 Ruby 和 Python 之后,他觉得两者相似,并打趣到如果一门脚本语言加了大括号,可以解决一部份人语言里要有大括号和 end 的需求,也算是造福人类了。Python 诞生自 1990 年,由于发展得非常成熟,很早便步入中年危机,无事可做,开始折腾,进入 Web 领域,有重量级的 Plone 把该考虑的问题都考虑了,什么都可以生成,但深度定制能力不够,也有轻量级的组合 CherryPy + SQLObject + Kid ,还有后来大红大紫的 Django 、 pyramid 和国人 Limodou 开发的 Uliweb 。
沈崴指出今后异步将成为趋势,先后涌现了 Stackless Python 、 gevent 和 greenlet 等框架;面对多核时代的到来,最终会去掉 GIL,方向就是 Stackless+JIT-GIL,在新的 PyPy 中已经实现了 Stackless+JIT,去除 GIL 指日可待。
不管用的人多,还是少,Python 都是一门好的语言。
作为国内研究 Python 框架最多的人, Uliweb 的开发者李迎辉(Limodou)对框架有自己的理解,他在《Web 框架开发思考与实践 ― Uliweb》中就指出框架的设计更多的是和人本身有关,框架与库不同,框架提供了一个骨架,用户的主动性较少,而库则是用户主动调用的。Web 框架就是专注于Web 领域的框架,它是一种实践,其中固化了一些可以复用的内容。Web 框架与Web 应用的差异在于两者的目标、应用范围不同,前者提供通用解决方案,后者则是为了解决个性化业务问题。常见的轻量级框架有 web.py 、 Bottle 和 Flask ,中量级框架有 Django 、 web2py 、 Uliweb 和 pyramid ,重量级的则是 Zope 。
面对框架,不同的人有不同的态度,可以完全接受,也可以自己做,或者先选一种使用,然后自己修改,亦或者做补丁和扩展等。不同的框架本身也有不同的理念,至于为什么会有全功能框架,则是因为问题域本身复杂多变,框架需要能解决大部分需求,自然会变得功能完整,但需要注意的是功能多并不一定就是复杂,全功能往往是一种缺省的实现。
说到如何学习一个框架,Limodou 建议要把握核心,有精力的可以读下源代码,了解其设计理念,分析不同框架的相同点,比较不同点,并不断实践。
Uliweb 目前版本 0.0.1,被网友戏称为是史上最低的版本,Limodou 谦虚地表示目前他还想更多地完善框架,等过阵子才会考虑提升版本号。Uliweb 的目标是成为全功能 Web 框架,原则上要使用简单,重点在复用和配置化,要方便扩展和替换,支持标准的部署方式。框架采用了 Model-View-Template 模式,提供各种常见功能,自带命令行工具。其中最主要的外部核心组件是 Werkzeug 和 SQLAlchemy ,最小的开发单位是 App,提供了 App 的依赖定义,这么做的优点是复用比较简单,还有很多特性有待使用者自己发现。
在开发 Uliweb 的过程中,Limodou 也总结出了自己的体会:
用到了一些比较底层的技术;造了不少轮子,真正了解了不少实现细节;框架不是一成不变的,要学会吸收;坚持很重要!
来自 Google 的侯雍容为大家带来了《用coro-thread 技术来扩展Web 应用程序栈》,作为使用纯Python 的公司,Slide(后被Google 收购)从mod_python 起步,随后慢慢转移到了Coroutine 的平台之上。讲到多线程,由于Python 的线程是真实的操作系统线程,由系统管理,再加之GIL 的原因,导致Python 多线程并不理想。有两种解决方案,一是 Stackless Python ,其中去除了一些限制,自己实现了线程调度;二是 Coroutine,可以使用由 Stackless Python 派生出来的 greenlet ,他们也开源了一个基于 greenlet 的工具包 gogreen ,其中包含 corohttpd、MySQL 模块,实现了线程调度。
同样来自 Google 的叶剑烨介绍了他们的Schemaless 数据存储系统,类似于NoSQL 数据库,数据存储于MySQL 之中,完全由Python 实现。之所以要自己实现这套系统,完全是因为当时主流的NoSQL 数据库产品都还没有进入人们的实现,没什么好的选择,加之希望能将数据集中存储于MySQL 里,还要能支持事务。该数据库的数据结构比较简单,采用了树的结构,每个节点都是树(tree),节点靠边(edge)连接,每个节点都有自己的属性(properties),靠两张表来存储——GraphNode、GraphEdge, 只有6 个API。属性是Python 的dict,序列化由 wirebin (C 扩展)实现。
当数据量和访问量上升到一定程度后,可以根据节点 ID 的高 8 位进行分片(最多支持 256 个分片),根据节点 ID 的低 8 为进行负载均衡。数据的访问会被负载到 Data Access Server 上,其中使用了多个 coro-threads 线程(可以简单地理解为非常轻量级的 Python 线程,当不是 CPU 密集型应用时,不用担心程序会失去控制权)的 Python 进程,这一部分主要基于 gogreen 实现。此外,还有一套 LRU 算法的缓存,L1 存储节点与边,L2 则是数据,通过 wireibn 序列化并用 zlib 压缩,当服务器退出时,缓存还会被持久化到磁盘上,这样就不需要预热缓存了。
目前,一台服务器可以同时跑 32 个服务器实例(同 CPU 核数),每个实例 128 个 Worker,每个 DB 建 16 个连接,可并行处理 4096 个请求,平均响应时间 1.38ms,平均缓存命中率 99.72%。折算下来,每个 CPU 每秒响应 2000 个请求,每台服务器每秒响应 64000 个请求。
始于 2009 年的 SAE 继 PHP 之后,即将推出 Python 支持,PyCon 的每位与会者都拿到了 SAE Python 的邀请码。SAE 团队的陈正在《SAE(Sina AppEngine) 的 Python 版实现》中详细介绍了 SAE 中对 Python 的一些使用细节,例如禁用了 os.fork、os.system、os.exec 和 os.popen,文件系统分为只读和可写目录。SAE 选择修改 CPython 代码来实现沙箱拦截,去除了其中进程、线程相关的宏定义,禁止使用动态扩展,网络上做了白名单及访问频率控制。未来的 SAE 还会加入 Git 支持、保护模式下的交互式调试器,还要内嵌 pypi,另外也可能会增加一些类似认证、记账这样的基础设施。
应用与实践
豆瓣作为国内最大的Python 应用,给了国内的Python 开发者巨大的信心,在豆瓣里他们用Python 做几乎所有的事情。洪强宁详细介绍了Python 在豆瓣的应用。
最早的豆瓣就完全使用Python 开发,后台索引基于twisted,GUI 使用Quixote,还用到了cElementTree,数据库是MySQL。所以豆瓣天生就有着Python 的文化。在豆瓣,开发环境正逐步从Subversion 向 mercurial 迁移,还使用了 Python 编写的 trac 和 IRC,之所以选择 IRC 是因为 IRC 有很大的灵活性,可以编写 Bot 自动化很多东西。
前端方面,豆瓣使用 Mako 模板引擎来制作模板,还自己开发了一些东西来自动处理静态文件,比如自动生成静态文件 URL、渲染模板时动态 inline 文件等,另外还计划引入 pyScss 。移动开发方面,豆瓣基于 gevent 、 bottle 和 APNSWrapper 自己开发了一套 APNS Agent。在产品开发上,他们还在使用很老的 Quixote 1.2,虽然古老却很简单、快速、稳定,尤其是 traversal based 特性,让他们能在每个级别加入特定的逻辑,更容易在目录级别进行控制;另外也大量使用了 Python 的一些语言特性,例如 Decorators、Generators、元编程;还开源了 OneRing ,可以用 Web 技术来开发桌面应用。技术支持方面,豆瓣基于 django-piston 、 sleepy.mongoose 开发了 Restful MongoDB Service,提供商业合作方的数据存储接口。QA 方面,通过 pylint 进行静态代码检查、SQL 注入漏洞检查、XSS 漏洞检查;还基于 Django 开发了一套上线管理工具,可以自动打 tag,确认测试结果,发送上线报告。
豆瓣自己实现了一个 MySQL 客户端,使用了部分 C 代码,只支持 select,返回一个迭代器,只有数据被访问时才生成 Python 对象。算法层面,用 C++ 实现算法,boost.py 进行数据的加载和初始化,两者结合提供高效灵活的服务。平台部分,豆瓣实现了一个性能剖析工具,可以看到请求中各部分的性能开销;基于 thrift 实现了一套 RPC 服务框架 DoubanService,用 PasteScript 实现代码生成,还将 Twitter 的 thrift_client 移植到了 Python 上,提供负载均衡和 failover;分布式计算框架部分,也是豆瓣自己实现的 Dpark,相当于支持迭代式计算的 Hadoop,由 Spark 演变而来,基于 mesos 实现资源调度;此外,在豆瓣内部还有一套 Douban App Engine,定位为内部的 PaaS 平台,用 virtualenv 做依赖关系处理, gunicorn 和 gevent 实现服务器,内嵌了一些最佳实践。
洪强宁表示豆瓣是一个多语言环境,大部分是 Python,在某些场景内也会使用 C、C++、Go、R、Object-C、Java、C#等各种语言。
Zope 中文社区创始人潘俊勇在《易度 PaaS 云开发平台技术内幕》中为大家回顾了 Zope 的历史,总结了 Zope 风光不再的原因,指出框架应该渐进地演变,必须快速适应外界变化,而且得要有简化问题的能力,互联网已经开始影响企业软件了。易度作为一个 PaaS 平台,主要思路如下:
- 捡回 Zope 的浏览器开发
- 增加应用软件包管理
- 分离软件和数据
- 开放平台所有接口
- 更好的支持调试
- 支持线下开发
易度希望打造一个类似 Force.com 的企业应用 PaaS,提供组织架构、人员、权限、表单、流程等功能,在浏览器中就能进行开发、绘制流程图。索引采用 xapian,zodb 充当数据库,消息日志存储于 MySQL 中,Redis 提供队列、缓存、Session 等服务,BlueBream/Pyramid 的应用服务器置于 Nginx 之后。易度提供了一个安全运行沙箱,使用 RestrictedPython 进行限制恶意代码的运行。演讲结束时,潘俊勇再次推荐大家要去学习 Pyramid。
在《系统工程师的非专业课——大规模视频网站的计费与流量管理》中,来自土豆网的黄冬为大家深入讲解了网络带宽、流量与计费相关的知识与“潜规则”。首先,黄冬指明带宽指的是 bps(Bits Per Second),而非常用下载软件中出现的 Bps(Bytes Per Second),而在运营商处,1Kbps=1000bps,而非 1024bps。一般在计算时,一周前的数据按 30 分钟汇总,2 个月后按照 2 小时汇总,到 2 年后每个数据点就是 1 天;常见的 5 分钟对齐指的是 5 分钟采集一次数据,8:02 发出的包算在 8:05 的点里。
常用的带宽峰值计算方法有包月包端口和峰值计费法(按月计费,每天得到一个峰值点,月度第 n 最高峰值计算),商业 CDN 一般采用 95 计费法(按月计费,每 5 分钟得到一个峰值点,月底第 95% 个最高峰值计费)。带宽数据汇总可以多端口各取峰值,也可以多端口合计峰值,后者总是小于等于前者,计费周期也有自然月与自定义周期之分。在实际环境中的测试显示,通常的带宽损耗率为 3% 左右,不要盲目相信国内的 TCP 整形加速说的能从 80% 提高到 90%。
在付费时,要进行财务审计,并且具备真实性、完整性和合法性,结算审计时相同的算法必须得出相同的结果、整体核算差异应该低于 3%。他还提出了一些关键事项,例如带宽采集间隔(5 分钟采集)、带宽计算方法(5/30/120 分钟均值)、峰值计算方法(第 n 峰值 /95 计费),尤其是在选择峰值流量计费时,第 1 峰值和第 4 峰值计费几乎没有差别,但是第 4 峰值和 95 计费的差异基本固定在 3% 左右,根据统计来看,国内的网民在每天晚上 9 点、10 点,周六及周日上网比较频繁,峰值可能出现在这些时间。
在用户体验上,必须要能进行度量,一般遵循 8 秒生死线原则,其中首屏的展示时间最为重要。例如,响应时间的度量就有以下几种方法:
- 在服务器端,可以使用 Apache 2.x 的 %D(单位:微秒)和 Nginx 的 $request_time
- 在客户浏览器上,可以使用 JavaScript(腾讯就大量使用这种方式)、Flash(统计下载速度)和一些浏览器自带特性
- 在通信过程中,也可以使用代理服务器和网络交换机来进行统计
应用层负载调度可以通过 Dispatcher 来实现,客户端访问 Dispatcher,要么 302 转发给具体的服务器,要么返回一组可用服务器列表,由客户端自己调用。要分析带宽究竟被谁占用了,可以进行流量分析,根据服务器列表、HTTP Log 和 IP 库推算出 IDC、区域、时段和流量。
黄冬将自己的演讲总结为:用尺度量用户体验、用计费去优化成本、将来源匹配向最合适的服务点、事后度量、事后审计,要用数据说话!演讲最后还做了精彩演示,通过 Python 程序,结合标准输入输出,分析了一些日志文件。在 QA 环节,有人问及土豆网如何抵御 DDOS 攻击,黄冬自信地回答这其实就是一场资源的比拼,相信国内没有人能够 DDOS 掉土豆网。
Python 中文社区创始人周琦(Zoom.Quiet)为大家带来了精彩的《基于 MQ 解耦应用开发》。之所以要引入 MQ 是因为碰到过太多复杂的东西,其根源在于业务复杂,也有来自于设计的压力,系统就“被开发、被耦合”了,导致系统代码蔓生。要解决这些问题,就要有勇气去抵抗业务的乱入和乱变。周琦提出的对策基于以下理论假设:
伸缩性与分层无关;事务序列化的范畴与伸缩性有关;绝大部分应用至少使用 1 次;确保了幂等性,就能很简单地使用消息来协调
在这一理论前提下,就有了 MOP(Message-Oriented Programming),常见的邮件提醒功能就是最好的例子,增加一个 MQ 层,避免发送邮件时的阻塞。在实现上,业务原子化是前提,分为几个层级:
- 安全且幂等——读取
- 安全不幂等——查询 / 修订
- 幂等不安全——创建
- 不安全幂等——删除(最好不要用 MQ 来解偶)
最好先识别出前 3 种,然后引入 MQ 来对这些进行支持。此处的 M 不仅仅是消息,可以是各种东西。MQ 中的 Queue 要有很强的迸发能力(一下子会进入很多请求),内容要有原子性,健康程度可自查(可回收)。
Celery 是一个纯 Python 实现的分布式任务队列,有多种后端可供选择,能与多种框架结合使用,通过 gevent 和 Eventlet 实现多进程并发,但配置比较复杂,类似的还有 Perl 写的 Gearman(基于 RPC 的远程分布式任务调度框架)。而 MongoDB 则是作为 MQ 后端的好选择,拥有内存映射机制 mmap,内置 Capped Collection 定长集,有很多文档级的原子操作,支持 Javascript。虽然不能直接作为 MQ 使用,但有纯 Python 编写的 Karait ,它采用了 MongoDB 作为后端,提供跨语言支持,拥有众多特性。
目前的 MQ 缺少工作状态的监控,数据传输时缺少活动序列管理,灾难响应时缺少灾难迁移,还缺少实时统计。采用了 MQ 后,实现 KISS 时可以选择最土的方案,成对维护生产者和消费者,系统里内置测试,要守住 Master(MQ 就是单点,但单点不是问题,单点 HOLD 不住才是问题),还要时常重构。
游戏中的 Python
要问 Python 有多简单?来自网易的林伟(skywind)在《Python 游戏开发探索与发现》中告诉大家 Python 已经足够简化到让策划人员也能写代码,用 Python 来描述一个游戏的场景。国内 WebGame 已经超过国外,在网易大量游戏项目用 Python 相关内容开发。他就网络游戏开发做了展开讨论,首先纠正了一些常见的误区,例如,游戏开发不等于网络编程,不要过分追求单机速度,通常瓶颈在网络 IO,广播才是最致命的影响游戏在线人数的问题。Python 2.7 的异步模型(epoll、kevent)在内网环境中进行 4-5 万人的连接管理不成问题,每秒可以响应 40-50k 的请求。他曾在某项目中将 30% 用 C 写的代码,用 Python 重写,其代码量下降到了 10%,且问题更少。他建议:
- 尽量使用非阻塞方式
- 尽量使用特定平台提供的异步事件模型
- 量大时考虑压缩数据
- 出现峰值时尽量用缓存平滑
- 尽量把任务从多线程移到多进程中
- 优化内存管理方式
- 优化缓存效率
- 减少查找次数
- 尽可能减少系统调用
- 尽量使用二进制协议(如果使用 protobuf 进行序列化,一定选择 C 实现的版本)
林伟回顾了网游的一些网络模型,最经典的就是始于 1979 年的早期 MUDOS,采用单机模型,单进程单线程,用文件来存储信息,但这种模型在用户数达到一定数量后就会产生问题,磁盘压力太大;随后是中期的分离化模型,将数据库分离出来,4、5 年前的大多数分区网游、RPG 和休闲游戏大多采用这一模型;再进一步就是引入模块化,在数据库前增加一个前置,由它来负责缓存、抽象数据访问以及总体控制;还有一些其他的网络模型,例如路由模型、状态模型、分布式对象模型等等。他还介绍了一种模型,在模块化基础上,又增加了更多的层次和模块,虽然功能强大,但过于复杂。
多一台机器,复杂度往往成几何倍数增长!先考虑简单的模型,等人数上千上万,有需要时才考虑重构。
Python 之父 Guido 也指出过:
一个难以向人阐述的实现,往往是一个坏的实现。
普通的聊天服务器,通常可以让 Session 服务器连接 Mirror,如果是同一 Session 内的可以直接聊天,不同 Session 则由 Mirror 转发。战网服务器的实现比较复杂,基于 P2P 实现,需要有 Session 服务器、房间服务器、登录交易,网关等等,存在一些安全性问题,需要要引入一套仲裁机制。P2P 应该尽量穿透,在无法获取外网 IP 时可以由服务器转发,一般会有 70% 的连通率,30% 要靠服务器转发,具体的 P2P 实现可以参考 pyp2p 。
第二天下午的两场主题演讲更是全与 WebGame 有关,先是珠三角技术沙龙带头人之一的赖勇浩带来的《Python 之于Webgame 的应用》, 他先推荐了两个自己比较喜欢的演讲内容,一个是洪强宁的《Python 于Web 2.0 网站的应用》,另一个则是沈葳的《Python 编程艺术》,两人恰好都是大会第一天的演讲嘉宾。赖勇浩在演讲中介绍了Webgame 服务器端的技术与一些工具。
在库的方面,他建议库应该与业务逻辑分开存放,lib 就放在site-packages 中;不要手写配置脚本setup.py,尽量使用工具生成, PasteScript 提供了创建、安装、测试、部署、运行等众多功能;可以使用 pbp.skels 来生成众多代码,比如生成命名空间包;在部署时,一定要有一个纯净的 Python 环境,这时可以使用 virtualenv 。
插件方面,主要是善用 setuptools ,以棋牌游戏为例,游戏插件可以分为接口与实现两部分,接口中主要是房间进程交互、通用功能(例如踢人)、计时器管理、定义接口,实现中主要是实现接口、实现业务逻辑,实现部分可以不接触网络编程,不接触数据库。对插件感兴趣的还可以学习一下 Trac Component Architecture 。
游戏和 I/O 有着密不可分的关系,Node.js 的作者说过:
I/O needs to be done differently.
他也同意这一观点,只是不太认同 Node.js 的实现方式,Node.js 的改变太多,他认为接口应该尽可能与以前一样,但是底层的实现方式则应该与 Node.js 类似。从目前来看,Coroutine 将成为趋势,可以考虑使用 gevent ,它在 libevent 之上通过 greenlet 提供了一套 API。另外也可以参考沈葳的 eurasia 。
其他方面,赖勇浩建议在通信时使用二进制协议,比如 Google 的 protobuf ,他们自己开发了一个名为 abu.RPC 的通信框架,使用了 protobuf、libevent 和 greenlet,因此可以实现更小的数据量、更快的传输和同步 API,支持并行管线和双向调用特性。受到 Falcon 语言(这是一门开源的多范式语言)的启发,开发了 python-message ,实现了进程内的发布订阅,应用于任务、邮件、好友等子系统内。此外,还做了些小组件,比如 absolute32 来实现一些标准库的封装。目前,他所开发的纯 Python 游戏服务器,单台 8 核 8G 内存,可以承受最高 1500 人同时在线。
另一位演讲者王健在《中型角色扮演网络游戏服务器的 Python 应用》中同样分享了自己在游戏服务器开发方面的经验,他分析了网游服务器的主要性能瓶颈,基本就是磁盘 I/O 和网络 I/O(Webgame 中还有特殊的战斗回放数据存储需求),而且 Python 无法很好地使用多核 CPU。他在实现时使用的架构与林伟在第一天的演讲中介绍的架构类似,由 Flash 实现客户端,通过网关层(处理网络信息收发与压缩)与游戏服务器(负责游戏主要业务逻辑,读数据库并在启动时全部缓存)交互,在游戏服务器与 MySQL 数据库中有一个 DB 前置,负责处理数据的串行写入,MySQL 数据库使用 MyISAM 引擎,不支持事务,效率更高;还有一套管理界面,客户端使用 LAMP 实现,连接 eurasia 实现的管理服务器,再由该服务器访问游戏服务器和 MySQL 数据库。令人意想不到的是这么多的服务都部署在同一台服务器上,采用多进程部署,Socket 通讯的方式,大大提升了服务器的利用率,发挥了多核的性能,另外还是用了 Psyco 和 PyPy 大大优化了脚本解释的性能,可谓是充分挖掘了单机的性能。
纵览两天的主题演讲,多位嘉宾都提到了 Python 在多核环境下的多线程问题,认为 Coroutine 将是发展大方向,Python 的多线程弱点可以通过多进程和 Coroutine 来解决,而在解释器方面,性能也在逐步提升,而且还有 PyPy 这样的非标准实现可供选择。多个演讲主题都与游戏服务器有关,这从另一个侧面反映了 Python 在游戏领域的应用已经比较普及,让 Python 在科学计算以外的游戏领域中占据了一席之地。
大会还有一个快速演讲环节,也有不少有趣的内容,没有参加本届 PyCon 的同学可以访问大会官网下载讲义,也可以在此观看大会录像。本次大会十分火爆,到场300 人,场外有400 多人观看现场直播,期待明年的PyCon 大会能有更多精彩内容奉献给大家。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。
评论