当前位置: 首页 > 工具软件 > quixote > 使用案例 >

豆瓣动力核心——Quixote

苏彭薄
2023-12-01

**Quixot**e ['kwiksət; ki'həuti] 发音:七号题~
n. 堂吉诃德(西班牙作家塞万提斯所著《堂吉诃德》中的主人公)
本刊记者:那您能简要地介绍一下豆瓣的架构吗?

洪强宁:关于豆瓣的系统架构图,首先我们在Web server上做个划分,把网站内容分为动态内容和静态内容。在豆瓣上所有的html都是动态内容,图片都是静态内容。分成两个Web 服务可以做不同的调优。 对动态内容,我们用的是nginx和lighttpd的混合,nginx做负载的平衡,lighttpd通过 SCGi 与application server相连,application server是基于 quixote这个框架写的。

application server拿到用户的请求,分析用户的url,并且利用外部的资源,比如数据库,组合成一个html,返回。从数据库存取会比较慢,数据库有大量的IO,我们使用cache,我们使用的是Memcached,这是一个分布式的内存的cache,比如你可以用很多机器,每个机器有两个G的内存,我们自己开发了client端来使用它,另外如果用户有搜索请求,我们会用搜索引擎。Xapian是一个C++写的开源的搜索引擎,我们通过Web service去访问它。其他,我们还提供了另外的Web service接口响应用户的请求,比如要访问某个文件。spread是我们最近加了一部分,用户有的请求可以采用这样的异步服务。

数据库是这样的,两个MySQL做成一对,一个master ,一个 slave,根据应用划分,使得load不会太高。这个图上»¬的是两对,实际上有三对。还有一个slave,一方面作为备份,一方面用作数据挖掘,因为不能对线上的数据做直接操作。

对于静态部分,我们也是用nginx,你注意到豆瓣现在有日记的贴图功能系统,用户可能上传很多图片,我们采用的方案是用了mogile FS ,这是一个分布式的文件系统,同时可以做备份,保持高可用性,可以提高很大的IO。

关于application server,它都是用Python写的。我们是用的MVC方式,Controller我们用的是quixote ,它接受用户的请求,根据这个URL去找到Model的某个具体的函数来执行,它是一个dispatcher,当中会判断用户的权限等。然后再传给View,View根据模版进行渲染,形成网页。View的模版,我们以前是用的是PTL,PTL很高效,最近引用了mako,这是一个比较现代的开源的模版,用它写出的代码比较好维护,比PTL好维护一些.。同时,在使用mako的同时,我们的工程师做了很多加速的工作,现在mako的代码有很多是豆瓣的人写的。

你如果注意过Python的Web开发框架的话,你会发现Python的有三个比较著名的框架,Django,Pylons,TurboGears,Pylons默认的模版就是Mako。

下面的就是Model,业务模块,核心是类是User,因为Web2.0是以人为本,我们肯定会有一个User。只有人也做不了事情,还要有物。豆瓣的物,就是Subject,比如书,比如评论,比如小组等。

与数据库进行链接,我们一个很轻量级的与数据库进行链接,这也是一个开源项目,SQL Farm Manager。这个Web service,豆瓣中有很多用的都是Web service。


摘自《可爱的Python》,本文作者洪强宁,网名Qiangning Hong或hongqn,2002年开始接触Python,2004年起完全使用Python工作。豆瓣网(http://www.douban.com/)02号程序员,技术负责人。


缘起
Quixote**是由美国全国研究创新联合会(CNRI,Corporation for National Research Initiatives)的工程师A.M.Kuchling、Neil Schemenauer和Greg Ward开发的一个轻量级Web框架。**和几乎所有的开源项目一样,Quixote也是为了满足实际需要而出世的。

CNRI当时在进行一个名为MEMS Exchange的项目(http://www.mems-exchange.org/)。MEMS是微机电系统的缩写,制造一个MEMS设备往往需要多种制造设备,单个工厂可能无法提供所需的所有设备。因此,MEMS Exchange项目就是要整合起多家制造厂的资源,利用互联网派单和追踪制造过程,形成一个分布式的MEMS设备制造网络。

起初,他们做了一个Java版的客户端程序提供给用户,但他们发现,没有人愿意使用这个客户端程序,大家还是习惯性地用邮件发送加工过程。最终他们认识到,虽然客户端的表现力更强,功能也更完整,但相比起要下载一个庞大的程序,用户更加愿意使用他们每天面对的浏览器来做事情。于是,他们决定改到Web界面上来,要做一个Web应用。但是用Java的servlets开发Web应用是一件非常低效的事情,所以他们选择了Zope(和现在不同,在1999年,Python的Web应用框架没有什么选择的余地,基本上是Zope一家独大)。3个月的开发之后,他们得到了一个运转良好的系统。

然而,Zope带来的快乐并没有持续多长时间。几个月后,他们想提供更加复杂一点的界面,却发现用Zope写的代码难以维护和调试,在浏览器的文本编辑框里写代码也实在不是什么好的体验。由于当时除了Zope之外也没有什么别的Python Web框架,他们决定:自己写一个!在2000年,编写一个新的Web框架是类似于向风车挑战一样的事情,开发团队自嘲地用堂吉诃德的名字命名这个框架:Quixote。

特性介绍
(以下使用Quixote 1.2版本为例进行介绍)

Quixote有以下几个特点:

  1. 简单的URL分发规则

所有的Web框架要解决的第一件事情就是:当用户输入一个URL,应该调用哪段代码来响应用户需求呢?这就是URL分发。与Django之类的基于正则表达式匹配来实现URL分发的框架不同,Quixote是基于URL的目录结构进行逐层查找实现的。

一个Quixote应用对应着一个Python的名字空间(通常是一个包)。URL的每一级目录映射到一个子名字空间上(模块或者子包),URL的最末一级映射到上一级名字空间中的一个函数上。该函数返回一个字符串,就是返回给浏览器的页面。例如,如果你的应用是放在app这个包下的,那么http://www.example.com/hello/john 就对应app.hello.john(request)。末级目录则对应于_q_index函数,如http://www.example.com/hello/ 对应于app.hello._q_index(request)。就这么简单。

这种层级查找的结构还可以带来额外的好处:在目录层进行控制。在每一个目录层级进行查找之前,Quixote都会先运行当前名字空间下的_q_access(request)函数。比如你需要 http://www.example.com/settings/ 下的所有路径都只能由登录用户访问,那么只须要在 app.settings这个名字空间(app/settings.py,或者app/settings/init.py)中定义一个_q_access(request)函数,在其中检查当前用户的登录状态(cookie可以从request对象获得),如果发现是未登录状态,抛出一个quixote.errors.AccessError异常即可。

当Quixote执行代码时碰到异常,它会首先检查当前名字空间下有没有_q_exception_handler函数,如果有的话,则由这个函数处理异常,返回的字符串则为出错页面。如果没有定义这个函数,或者在执行它的时候又抛出了异常,则向上一级的名字空间查找_q_exception_handler函数,直至找到为止。所以,我们一般只须要在app/init.py中定义_q_exception_handler函数,就可以方便地实现定制出错页面了(当然你连这个也不定义也可以,Quixote提供了默认的出错页面)。

  1. 易于实现RESTful的URL
    刚才我们说 http://www.example.com/hello/john 对应app.hello.john(request),那除了john之外,还有成千上万的用户的名字怎么办?不能为每个用户都定义一个函数吧!传统的方法是把会变的部分用URL参数的形式传进来,比如http://www.example.com/ hello/?name=bob。但这种风格的URL既不好看,又不利于搜索引擎收录,要是都能像john一样直接成为URL的一部分多好啊!幸运的是,Quixote帮助你实现了这一点。

我们只须要在 app.hello 这个名字空间中定义一个_q_lookup(request, component)函数,当Quixote查不到bob这个名字时就会调用这个函数,把需要查找的名字bob传给component参数,用这个函数返回的结果作为找到的子名字空间或函数。在我们的例子里,代码就是:

1 # in app/hello/init.py

2 def _q_lookup(request, name):

3 def hello(request):

4 return say_hello(name)

5 return hello

这里我们返回了一个函数(也可以用定义了call方法的类实例代替),这个函数将被调用以生成HTML页面。

  1. 显式标记,拒绝魔术

Zen of Python中有一句“Explicit is better than implicit”,Quixote也是这个理念的贯彻者。所有可以通过URL访问到的函数和方法,必须在当级名字空间的_q_exports变量中列举出来(除了用_q_lookup实现的动态URL)。也就是说,要让 http://www.example.com/hello/bob 可以访问,必须在app/init.py中定义

1 _q_exports = [‘hello’]

  1. 非常类似Python的模板语言

呃,或者说,就是Python语言。为了让程序员不用再学习一门模板语言,而是直接使用已经掌握的Python语法写模板,Quixote的模板语言PTL(Python Template Language,以.ptl作为文件名后缀)的语法和Python一模一样(除了后面要说到的[html]标记),在执行了quixote.enable_ptl()之后,这些.ptl文件就可以像普通的.py文件一样作为Python模块import进来。

但是既然是模板语言,就会有一些专门为了生成字符串的语法糖。在函数定义时,加上一个 [html] 标签就可以改变这个函数的表现,使得每执行一条语句,运行的结果如果不是None的话,就会以字符串的形式添加到函数的返回值里,而无须再使用return语句了。假设hello.ptl的内容如下:

1 def say_hello [html] (name):

2 “Hello, “

3 “%s!” % name

我们来看看say_hello的返回值是什么:

1 >>> from quixote import enable_ptl

2 >>> enable_ptl() # 使得我们可以import ptl文件

3 >>> from hello import say_hello

4 >>> print say_hello(“Quixote”)

因为say_hello函数由两条语句组成,这两行语句的运行结果分别返回一个字符串,因此运行结果就是Hello, Quixote!。这个设定可以大大方便模板的编写。

  1. 内置的安全性支持

互联网上有很多寻找网站漏洞的攻击者,跨站脚本(XSS)是一个常见的攻击手段。这种攻击通常会在页面显示数据的地方插入一段恶意Javascript代码,当有人浏览这个页面的时候,就会运行这段代码。比如在say_hello这个例子中,恶意用户输入的名字(也就是name参数)是”,浏览这个页面的用户就会中招。幸好,PTL帮助我们解决了这个问题,它会自动将输入的数据进行HTML转义,就是用<、>等HTML实体替换掉危险的<、>等字符,这样输出的结果就是Hello, <script>alert(‘haha’)</script>,安全了。

PTL是怎么实现这一点的呢?实际上,打了[html]标记的PTL函数中的字符串常量(比如 “%s!”)并不是普通的str对象,而会自动转成quixote.html.htmltext对象,这个对象的接口和str几乎一样,但和普通的str对象做操作时(比如+、%等运算),会自动把str对象中的HTML特殊字符进行转义。

  1. 高效的模板

PTL的速度非常之快,在豆瓣,我们曾经拿它和号称最快的Python模板Mako进行过PK,PTL胜出。当然,这一部分是由于PTL的核心代码是用C写的(比如前面说的htmltext类),另一方面PTL的功能非常基本,完全依赖于Python语言自身的特性。

  1. 没有内置数据库支持
    Quixote专注于URL分发和模板系统,对于数据库没有提供直接支持。开发者可以根据自己的需要选择合适的数据库软件,如SQLAlchemy、SQLObject或者直接使用MySQLdb等。

快速起步
那我们来看一个完整的应用示例吧,就是我们前边举的例子:通过URL获得一个名字,在页面上显示对他的欢迎信息。

首先安装Quixote,在 http://quixote.ca/ 下载 Quixote 1.x版的最新版本(目前是1.2版)并安装。

1 # app/init.py

2

3 _q_exports = [‘hello’]

1 # app/hello/init.py

2

3 from app.hello.hello_ui import say_hello

4

5 _q_exports = []

6

7 def _q_index(request):

8 return say_hello(“everyone”)

9

10 def _q_lookup(request, name):

11 def hello(request):

12 return say_hello(name)

13 return hello

1 # app/hello/hello_ui.ptl

2

3 def say_hello [html] (name):

4 header(title=”Hello”)

5 “Hello, “

6 “”“%s!”“” % name

7 footer()

8

9 def header [html] (title):

10 “”“

   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

11

12

13 %s

14

15

16 “”” % title

17

18 def footer [html] ():

19 “”“

20

21 “”“

以上已经创建了整个Web应用。下面的问题是:怎么运行呢?

有好几个方式可以用来运行Quixote应用,比如mod_python、FastCGI和CGI等。在豆瓣我们使用的是SCGI(http://www.mems-exchange.org/software/scgi/,精巧地址: http://bit.ly/2GMFUb),这是Quixote的开发团队制作的一个简化版本的FastCGI,使用和Web服务器独立的进程运行Web应用。著名的轻量级Web服务器lighttpd (http://www.lighttpd.net/)直接内置支持SCGI,下面用lighttpd来运行hello程序。

首先安装scgi Python包,到 http://python.ca/scgi/ 下载最新版本并安装。

创建SCGI服务程序scgi-server.py:

1 #!/usr/bin/env python

2

3 from scgi.quixote_handler import QuixoteHandler, main

4 from quixote import enable_ptl

5

6 enable_ptl()

7

8 class MyHandler(QuixoteHandler):

9 root_namespace = ‘app’

10

11 if name == ‘main‘:

12 main(MyHandler)

这个程序运行时会在默认的4000端口上开启一个SCGI服务,端口号可以使用命令行参数 -p更改。更多控制参数请使用python scgi-server.py –help查看。

创建lighttpd的配置文件lighttpd.conf:

server.modules = (

“mod_scgi”,

)

server.document-root = “.”

server.port = 8080

scgi.server = ( “/” =>

( “localhost” => (

“host” => “127.0.0.1”,

“port” => 4000,

“check-local” => “disable”,

)

)

)

这个配置让lighttpd监听8080端口,把所有请求都转发给4000端口上的SCGI服务处理,再把处理结果发给用户。
运行SCGI服务和lighttpd(以GNU/Linux下为例):

$ python scgi-server.py

$ /usr/sbin/lighttpd -f lighttpd.conf

访问http://localhost:8080/hello/bob,就可以看到 “Hello, bob!”了

须注意的是,对代码进行修改后,要重启SCGI服务才能看到效果。我们可以做一个简单的重启SCGI服务的shell脚本,以简化操作:

!/bin/sh

PIDFILE=/var/tmp/quixote-scgi.pid # 默认的PID文件路径

kill cat $PIDFILE
sleep 1
python scgi-server.py

案例讲解
作为老牌的Web框架,Quixote已经被证明了它足够支撑起相当规模的网站。除了MEMS Exchange之外,LWN(Linux Weekly News, http://lwn.net/)也是使用Quixote搭建的。在国内,Quixote最著名的使用者就是豆瓣网(http://www.douban.com/)。

豆瓣网是一个致力于帮助用户发现自己可能感兴趣的书、电影、音乐、博客等信息的网站。2005年3月正式上线,当时Python社区的web框架屈指可数,Django、TurboGears等新兴框架还没出现,因此选择了Quixote作为整个网站的基础框架。至2008年10月,豆瓣网已经发展到200万注册用户,每日1000万次动态页面点击,但仍只需要两台Quixote应用服务器即可轻松负担,这充分说明了Quixote的性能和可扩展性。

小结
Quixote是一个轻量级的框架,简单、高效,代码也十分简洁易读。用豆瓣网创始人阿北的话来说:“用quixote的时候你的注意力大部分在要实现的内容上,framework不会拦在中间跳来跳去。”

但是,Quixote毕竟是2000年的框架,现在看来已经略显老态。比起Django、TurboGears这些后起之秀来,确实存在某些劣势。大略有以下几点:

1.调试开发不够方便,没有内置的调试用Web服务器;

2.修改代码后必须重启服务才能看到效果;

3.URL的末尾带和不带”/”会被解释到不同函数;

4.PTL模板适合Python程序员编写,却非常不适合美工独立编辑;

5.没有内置的WSGI支持;

6.没有内置的用户认证系统、数据库支持等。

 类似资料: