MiscItems/2008-07-04

GAE和水木

prate <[email protected]>
reply-to        [email protected]
to      python-cn`CPyUG`华蟒用户组 <[email protected]>
date    Fri, Jul 4, 2008 at 16:27

subject ::[CPyUG:58081] 【原创】我的GAE使用体会(主要有关Datastore和Urlfetch)

命题

的用户名和标题等信息,然后在这基础上搜索自己感兴趣的信息。泡水木的人一定知道这种搜索器的意义,比如搜某个ID在什么版上出没,以及搜索标题中含有 某个关键字的文章,水木自己的所谓令狐冲搜索太弱了,几乎什么都查不到。GAE数据库不是号称用什么big table么,我估计做这个肯定没问题。

由于这个应用本身并不复杂,主要就是整过来社区的网页然后存上,再做个搜索页面就完了,所以我连template都没怎么用

思量

首先是获取网页,GAE对urllib进行了限制,所以只能用它提供的urlfetch,实际上和urllib里的urlopen基本上一样,提取过来 之后,我用的SGMLParser提取有用信息。 urlopen就是有一个问题会时不时地出现DownloadError这种错误,比较讨厌,不过无大碍。

然后就是datastore了,由于GAE就给了500M,而水木近700个版面,每个版面上千上万的帖子,所以怎么精简地存储就成了我最关心的问题。 水木每一条帖子不可能把全文存上,顶多存一个标题,通过观察水木帖子的标题我们可以发现大部分都是RE文,所以存的时候只要存楼主的文章标题就行了。开 始我想用的是GAE的那个entity relationship来表达文章的这种附属关系, http: //code.google.com/appengine/articles/modeling.html ,结果这么编了,实际上并没我预想的那样效果好,7500条帖子花了3M的空间, 通过dataviewer我分析原因是ReferenceProperty是以地址的形式存储的,而GAE这个地址非常的长,因此一点都不省空间。 而且entity relationship必然要用一些datastore写语句(put),也就降低了运行速度。后来我就没用这个 relationship,不过我认为在提取合集文章中的那些用户时,这还是有用的。后来,我沿用省略RE文标题的思路换了个简单办法,近两万的文章才 花掉4M的空间。

由于GAE只能对HTTP请求作反应,所以只能把提取文章和存储做成requesthandler,通过我客户端的小程序不断发http请求,从而激发 这个handler爬水木。所以得到一个请求能执行多少东西就成了我关心的问题。

运行

我设计的程序对数据库的操作大致如下图

GQL
|
|
---yes---
|           |
no          put
|
GQL
|
---yes---
|           |
|          put
|           |
---------
|
put

执行三十次

将近60次读操作,60次写操作。在本地调试的时候,不知道为什么,程序不把我这个GQL的信息放到index.yaml里,上传之后,也不报错,但是 当存了近一万条帖子之后,开始频繁报deadlinetimeout了,从logging来看是停在读那里了。后来我手工把上图的读操作信息加到 index.yaml里,上传,等到显示变成serving之后,就不发生deadlinetimeout了。可见index对于读操作的提速显著。

这就是我的一些体会,欢迎各位交流

什么才是更好的WEB框架

v cc <[email protected]>
reply-to        [email protected]
to      [email protected]
date    Fri, Jul 4, 2008 at 23:38

subject:: [CPyUG:58154] 设计的威力 - 什么才是更好的WEB框架

引言

最近看多了这样的言论:用一个最基本的,再自己用最好的Class来组合扩展,就是一个更好的框架,什么MVC、ORM、Cache、I18N等等,都是很容易加的,..... 深不以为然。

为了避免不必要的误解,首先定义下什么是"更好"的WEB框架。顾名思义,更好就是比现有所有框架都要好。它不是指你在XXX上加了一个功能比XXX好就是更好,也不是你喜欢它就是更好。也许某个框架更适合你,但不能称它是更好的框架。

OK,言归正传。说把一大堆Class,哪怕它们单个都是"最好"的class,拼凑在一起就比一个从一开始就非常完整、精妙设计的框架更好听起来实在有点离谱。任何一个稍微有点软件工程概念的人都知道这是不可能的。

分解Trac

不服?好,我们用事实说话。MVC、ORM、Cache、I180N很容易加吗?非也。最近把trac应用到公司的开发管理中,大名鼎鼎的trac啊,trac的开发团队还开发着一大堆好的不得了的东东啊,例如genshi模板系统,厉害得不得了。虽然他们还开发了一个专门做I18N的Babel,然而很不幸,trac的i18就有问题,莫名其妙的问题,i18n不完整。最重要的ticket这个页面居然有些字段不能i18n!!! 为什么?这就是一个开始没做过完整设计而不断的打补丁来实现新功能的结果。

trac组合了sqlalchemy这个目前看来也许是"最好"的Python ORM,然而很不幸,sqlalchemy却不合用,因为它不能为定义的字段定义一个label来你在页面显示的时候用。这也不能怪sqlalchemy,因为它不是为web框架设计的,它不需要加这些多余的东西,加了这些东西它反而不是一个干净的ORM了。看看Django的设计: class MyModel(models.Model):

用于页面显示时的字段名,做i18n多容易实现啊!从这里就看出设计的威力出来了吧!

我是怎样解决trac的i18n问题的?说起来也挺恶心,我在ticket的模板里这样干: 先定义一个函数:

    <py:def function="ZhFieldName(name)">
        <py:choose test="">
          <py:when test="name == 'type'">类型</py:when>
   <py:when test="name == 'milestone'">里程碑</py:when>
   <py:when test="name == 'priority'">优先级</py:when>
   <py:when test="name == 'keywords'">关键字</py:when>
   <py:when test="name == 'version'">版本</py:when>
   <py:when test="name == 'component'">组件</py:when>
   <py:when test="name == 'cc'">抄送</py:when>
   <py:when test="name == 'description'">描述</py:when>
   <py:when test="name == 'summary'">总结</py:when>
   <py:when test="name == 'owner'">属主</py:when>
   <py:when test="name == 'status'">状态</py:when>
   <py:otherwise>${name}</py:otherwise>
 </py:choose>
    </py:def> 

再在取field.name时这样干:

${ZhFieldName(field_name)}
 
ticket的form是这样的:
        <fieldset id="properties"
                  py:if="'TICKET_CHGPROP' in perm(ticket.resource) or
                         (not ticket.exists and 'TICKET_CREATE' in perm)"
                  py:with="fields = [f for f in fields if not f.skip]">
          <legend>${ticket.exists and 'Change ' or ''}Properties</legend>
          <table>
            <tr>
              <th><label for="field-summary">Summary:</label></th>
              <td class="fullrow" colspan="3">
                <input type="text" id="field-summary" name="field_summary"
                       value="$ticket.summary" size="70" />
              </td>
            </tr>
            <py:if test="'TICKET_ADMIN' in perm(ticket.resource)">
              <tr>
                <th><label for="field-reporter">Reporter:</label></th>
                <td class="fullrow" colspan="3">
                  <input type="text" id="field-reporter" name="field_reporter"
                         value="${ticket.reporter}" size="70" />
                </td>
              </tr>
            </py:if>
            <py:if test="'TICKET_EDIT_DESCRIPTION' in perm(ticket.resource) or not ticket.exists">
              <tr>
                <th><label for="field-description">Description:</label></th>
                <td class="fullrow" colspan="3">
                  <textarea id="field-description" name="field_description"
                            class="wikitext" rows="10" cols="68"
                            py:content="ticket.description"></textarea>
                </td>
              </tr>
            </py:if>
            <tr py:for="row in group(fields, 2, lambda f: f.type != 'textarea')"
                py:with="fullrow = len(row) == 1">
              <py:for each="idx, field in enumerate(row)">
                <th class="col${idx + 1}" py:if="idx == 0 or not fullrow">
                  <label for="field-${field.name}" py:if="field"
                         py:strip="field.type == 'radio'">
    ${ZhFieldName(field.edit_label or field.label or field.name)}:
                         </label>
                </th>
                <td class="col${idx + 1}" py:if="idx == 0 or not fullrow"
                    colspan="${fullrow and 3 or None}">
                  <py:choose test="field.type" py:if="field">
                    <select py:when="'select'" id="field-${field.name}" name="field_${field.name}">
                      <option py:if="field.optional"></option>
                      <option py:for="option in field.options"
                              selected="${ticket[field.name] == option or None}"
                              py:content="option"></option>
                      <optgroup py:for="optgroup in field.optgroups"
                                label="${optgroup.label}">
                        <option py:for="option in optgroup.options"
                                selected="${ticket[field.name] == option or None}"
                                py:content="option"></option>
                      </optgroup>
                    </select>
                    <textarea py:when="'textarea'" id="field-${field.name}" name="field_${field.name}"
                              cols="${field.width}" rows="${field.height}"
                              py:content="ticket[field.name]"></textarea>
                    <span py:when="'checkbox'">
                      <input type="checkbox" id="field-${field.name}" name="field_${field.name}"
                             checked="${ticket[field.name] == '1' and 'checked' or None}" value="1" />
                      <input type="hidden" name="field_checkbox_${field.name}" value="1" />
                    </span>
                    <label py:when="'radio'"
                           py:for="idx, option in enumerate(field.options)">
                      <input type="radio" name="field_${field.name}" value="${option}"
                             checked="${ticket[field.name] == option or None}" />
                      ${option}
                    </label>
                    <py:otherwise><!--! Text input fields -->
                      <py:choose>
                        <span py:when="field.cc_entry"><!--! Special case for Cc: field -->
                          <em>${field.cc_entry}</em>
                          <input type="checkbox" id="field-cc" name="cc_update"
                            title="This checkbox allows you to add or remove yourself from the CC list."
                            checked="${field.cc_update}" />
                        </span>
                        <!--! Cc: when TICKET_EDIT_CC is allowed -->
                        <span py:when="field.name == 'cc'">
                          <input  type="text" id="field-${field.name}"
                            title="Space or comma delimited email addresses and usernames are accepted."
                            name="field_${field.name}" value="${ticket[field.name]}" />
                        </span>
                        <!--! All the other text input fields -->
                        <input py:otherwise="" type="text" id="field-${field.name}"
                          name="field_${field.name}" value="${ticket[field.name]}" />
                      </py:choose>
                    </py:otherwise>
                  </py:choose>
                </td>
              </py:for>
            </tr>
          </table>
        </fieldset>

这是不是更好了?我感觉一点都不好。这就是大名鼎鼎的genshi模板系统,有人认为它是最好的模板系统。可是写的template真的好难读啊,就连我这么有经验的开发人员看着都觉得头大。这也不能怪genshi,它只是一个模板语言啊,更高层的Form之类的设计不是它应该干的活啊。如果用django的newforms来实现,那可得多省心啊。

瞧我这补丁打的,这还算简单了,至少在模板里就搞定,不用去碰它的代码。但是如果做过好的i18n的设计,根本就不会碰到这个问题;如果对HTML表现层做过好的设计,也绝对不用写这样难看懂的template。

结论

我最近还干过一件事,花了一个下午改django-pyodbc的backend到django最新开发版。django的数据库层已经做过重构,清晰了许多,让我也没费太大力的就完成了,这就是出色设计所带来的威力!

什么是更好的WEB框架,我相信每个人心里都有数了。让我们更靠谱一点,谦虚一点,好好吸收优秀设计的营养,说不定哪一天,我们也可以设计出"更好"的框架来! _

vcc _ 没有最好,只有更好!


反馈

MiscItems/2008-07-04 (last edited 2009-12-25 07:16:25 by localhost)