<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>开源小站 &#187; Python</title>
	<atom:link href="http://www.litrin.net/tag/python/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.litrin.net</link>
	<description>It is Cool to OpenSource</description>
	<lastBuildDate>Fri, 03 Feb 2012 04:33:02 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Tornado的进程级别缓存用法</title>
		<link>http://www.litrin.net/2012/02/03/tornado%e7%9a%84%e8%bf%9b%e7%a8%8b%e7%ba%a7%e5%88%ab%e7%bc%93%e5%ad%98%e7%94%a8%e6%b3%95/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=tornado%25e7%259a%2584%25e8%25bf%259b%25e7%25a8%258b%25e7%25ba%25a7%25e5%2588%25ab%25e7%25bc%2593%25e5%25ad%2598%25e7%2594%25a8%25e6%25b3%2595</link>
		<comments>http://www.litrin.net/2012/02/03/tornado%e7%9a%84%e8%bf%9b%e7%a8%8b%e7%ba%a7%e5%88%ab%e7%bc%93%e5%ad%98%e7%94%a8%e6%b3%95/#comments</comments>
		<pubDate>Fri, 03 Feb 2012 03:20:48 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[www]]></category>
		<category><![CDATA[nosql]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://www.litrin.net/?p=1665</guid>
		<description><![CDATA[之前一直在跟朋友调试一个基于tornado框架的长连接HTTP服务，并不断的优化尽量以最少的资源换取最大的连接数。我的想法是通过定期轮询一个Redis键，当键值发生改变后推送信息给客户端，并同时回写改变数据到redis以便下次比对。这种方法中规中矩，但显然每次变动都会牵扯两次Redis读写，网络开销的代价很大。即便采用了Redis pipline连接池，受通讯时间的影响，性能只能说“可以接受”而已。瓶颈在数据存储的级别。 记得在PHP开发过程中，经常会用到实例级别或者线程级别的变量缓存，带来的性能提升是巨大的。但对于tornado来说，由于每次访问都会实例化tornado.web.RequestHandler，不能满足要求，只能考虑在主线程上进行变量缓存。一旦成功将有可能直接抛弃redis。 种种原因，不公开最终实现的代码，这里只用一个页面计数器来模拟。通过一个页面http://localhost/get获得主页http://localhost/的Page View。 import tornado.httpserver import tornado.ioloop import tornado.options import tornado.httpclient import tornado.web import sys, os from tornado.options import define, options ##### class PageCounter(object): count = 0 #### #PageCounter = 0 这种采用全局变量的方法被证实无法实现。 class MainPage(tornado.web.RequestHandler): def get(self): PageCounter.count += 1 self.finish('Hello World!') class GetPageView(tornado.web.RequestHandler): def get(self): sPageView = str(PageCounter.count) self.write(sPageView) def main(port): if os.fork() [...]]]></description>
			<content:encoded><![CDATA[<p>之前一直在跟朋友调试一个<a href="http://www.litrin.net/2011/10/18/%E9%9D%9E%E9%98%BB%E5%A1%9E%E7%9A%84python-web%E6%A1%86%E6%9E%B6tornado/" target="_blank">基于tornado框架的长连接HTTP服务</a>，并不断的优化尽量以最少的资源换取最大的连接数。我的想法是通过定期轮询一个Redis键，当键值发生改变后推送信息给客户端，并同时回写改变数据到redis以便下次比对。这种方法中规中矩，但显然每次变动都会牵扯两次Redis读写，网络开销的代价很大。即便采用了Redis pipline连接池，受通讯时间的影响，性能只能说“可以接受”而已。瓶颈在数据存储的级别。</p>
<p>记得在PHP开发过程中，经常会用到实例级别或者线程级别的变量缓存，带来的性能提升是巨大的。但对于tornado来说，由于每次访问都会实例化tornado.web.RequestHandler，不能满足要求，只能考虑在主线程上进行变量缓存。一旦成功将有可能直接抛弃redis。</p>
<p><span id="more-1665"></span></p>
<p>种种原因，不公开最终实现的代码，这里只用一个页面计数器来模拟。通过一个页面http://localhost/get获得主页http://localhost/的Page View。</p>
<pre class="py" name=code>
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.httpclient
import tornado.web
import sys, os
from tornado.options import define, options

#####
class PageCounter(object):
    count = 0
####

#PageCounter = 0 这种采用全局变量的方法被证实无法实现。

class MainPage(tornado.web.RequestHandler):

    def get(self):
        PageCounter.count += 1
        self.finish('Hello World!')

class GetPageView(tornado.web.RequestHandler):
    def get(self):
        sPageView = str(PageCounter.count)
        self.write(sPageView)

def main(port):
    if os.fork() != 0:
        exit()
    define("port", default=port, help="run on the given port", type=int)
    application = tornado.web.Application([
        ("/", MainPage),
        ("/get", GetPageView),
    ])
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":

    if len (sys.argv) &gt; 1:
        port = sys.argv[1]
    else:
        port = 80
    main(port)</pre>
<p>这里要说的是object基类的PageCounter方法，可以定义一种数据结构以及对应的操作方法。当然这段代码只是Demo，写得不规范。由于PageCount是全局的，跟随进程实例化，所以可以很灵活在各个实例之间传递数据。</p>
<p><a href="http://www.litrin.net/2012/02/03/tornado%e7%9a%84%e8%bf%9b%e7%a8%8b%e7%ba%a7%e5%88%ab%e7%bc%93%e5%ad%98%e7%94%a8%e6%b3%95/homepage/" rel="attachment wp-att-1668"><img class="aligncenter size-medium wp-image-1668" title="homepage" src="http://www.litrin.net/wp-content/uploads/2012/02/homepage-300x218.png" alt="" width="300" height="218" /></a></p>
<p><a href="http://www.litrin.net/2012/02/03/tornado%e7%9a%84%e8%bf%9b%e7%a8%8b%e7%ba%a7%e5%88%ab%e7%bc%93%e5%ad%98%e7%94%a8%e6%b3%95/pageview/" rel="attachment wp-att-1669"><img class="aligncenter size-medium wp-image-1669" title="pageView" src="http://www.litrin.net/wp-content/uploads/2012/02/pageView-300x218.png" alt="" width="300" height="218" /></a></p>
<p>&nbsp;</p>
<p>类似这种方法，我们的项目性能提升了至少5倍。但这样实现的结果也非常明显，首先不能持久化，一旦进程终止，数据就被清空；其次是进程隔离，这种方法共享数据只能工作在tornado的一个进程中，对于习惯于多进程并行的Tornado部署会有问题，我们最终的做法是做唯一哈希；最后要注意的是内存释放的问题，数据保存在内存中，如果是大量的数据通过dict/list做映射的话需要进行定期清理，当然通过这种方法可以变态的做一个基于HTTP协议的Key-value数据库。</p>
<p># ab -n10000 -c1000 http://localhost/<br />
This is ApacheBench, Version 2.3 < $Revision: 655654 $><br />
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/<br />
Licensed to The Apache Software Foundation, http://www.apache.org/</p>
<p>Benchmarking localhost (be patient)<br />
Completed 1000 requests<br />
Completed 2000 requests<br />
Completed 3000 requests<br />
Completed 4000 requests<br />
Completed 5000 requests<br />
Completed 6000 requests<br />
Completed 7000 requests<br />
Completed 8000 requests<br />
Completed 9000 requests<br />
Completed 10000 requests<br />
Finished 10000 requests</p>
<p>Server Software:        TornadoServer/2.1.1<br />
Server Hostname:        localhost<br />
Server Port:            80</p>
<p>Document Path:          /<br />
Document Length:        12 bytes</p>
<p>Concurrency Level:      1000<br />
Time taken for tests:   8.921 seconds<br />
Complete requests:      10000<br />
Failed requests:        0<br />
Write errors:           0<br />
Total transferred:      1700000 bytes<br />
HTML transferred:       120000 bytes<br />
Requests per second:    1120.89 [#/sec] (mean)<br />
Time per request:       892.148 [ms] (mean)<br />
Time per request:       0.892 [ms] (mean, across all concurrent requests)<br />
Transfer rate:          186.09 [Kbytes/sec] received</p>
<p>Connection Times (ms)<br />
              min  mean[+/-sd] median   max<br />
Connect:        0  230 792.8      0    3012<br />
Processing:    14  134  73.9    114    1878<br />
Waiting:       14  134  73.9    114    1878<br />
Total:         86  364 815.1    114    3818</p>
<p>Percentage of the requests served within a certain time (ms)<br />
  50%    114<br />
  66%    115<br />
  75%    121<br />
  80%    141<br />
  90%    378<br />
  95%   3135<br />
  98%   3176<br />
  99%   3223<br />
 100%   3818 (longest request)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2012/02/03/tornado%e7%9a%84%e8%bf%9b%e7%a8%8b%e7%ba%a7%e5%88%ab%e7%bc%93%e5%ad%98%e7%94%a8%e6%b3%95/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ctypes实现“C重构”</title>
		<link>http://www.litrin.net/2012/01/04/ctypes%e5%ae%9e%e7%8e%b0c%e9%87%8d%e6%9e%84/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=ctypes%25e5%25ae%259e%25e7%258e%25b0c%25e9%2587%258d%25e6%259e%2584</link>
		<comments>http://www.litrin.net/2012/01/04/ctypes%e5%ae%9e%e7%8e%b0c%e9%87%8d%e6%9e%84/#comments</comments>
		<pubDate>Wed, 04 Jan 2012 08:59:46 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[开源7788]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://www.litrin.net/?p=1618</guid>
		<description><![CDATA[现阶段很多网站都是基于非编译的语言，例如Php，Python语言完成。这样做的效果是开发效率很高，同时开发人员也很容易就位。当网站访问量达到一个数量级之后，就会遇到传说中的C10K问题，借助解释型语言的性能劣势，整个网站会到达一种“无处修改，无处优化，无处升级”的怪圈。这时候往往就会需要采用更加底层的C去重构部分或整个项目，“C重构”由此而来。当然，技术的进化也很迅速，FaceBook开源了hiphop这个PHP2C的工具，可以部分自动化的实现“C重构”。 一直把Python当成胶水语言来用，这次就接着这个话题，说个Python C重构的例子： 首先是python原码：(很眼熟吗？) #!/usr/bin/env python import time lGoal = {} def main(i): # if lGoal.has_key(i): # return lGoal[i] if i &#60; 1: return long (0) elif i &#60; 2: return long(1) return main(i-1) + main(i-2) - main(i-5) def getList(iMax): for i in range(1, iMax+1): iGoal = main(i) lGoal[i] = iGoal return lGoal #i = [...]]]></description>
			<content:encoded><![CDATA[<p>现阶段很多网站都是基于非编译的语言，例如Php，Python语言完成。这样做的效果是开发效率很高，同时开发人员也很容易就位。当网站访问量达到一个数量级之后，就会遇到传说中的C10K问题，借助解释型语言的性能劣势，整个网站会到达一种“无处修改，无处优化，无处升级”的怪圈。这时候往往就会需要采用更加底层的C去重构部分或整个项目，“C重构”由此而来。当然，技术的进化也很迅速，FaceBook开源了hiphop这个PHP2C的工具，可以部分自动化的实现“C重构”。</p>
<p>一直把Python当成胶水语言来用，这次就接着这个话题，说个Python C重构的例子：</p>
<p><span id="more-1618"></span></p>
<p>首先是python原码：(<a title="PHP的普青、文青和2B青" href="http://www.litrin.net/2011/11/05/php%e7%9a%84%e6%99%ae%e9%9d%92%e3%80%81%e6%96%87%e9%9d%92%e5%92%8c2b%e9%9d%92/" target="_blank">很眼熟吗？</a>)</p>
<pre name=code class=python>#!/usr/bin/env python

import time

lGoal = {}

def main(i):
#    if lGoal.has_key(i):
#        return lGoal[i]

    if i &lt; 1:
        return long (0)
    elif i &lt; 2:
        return long(1)
    return main(i-1) + main(i-2) - main(i-5)

def getList(iMax):
    for i in range(1, iMax+1):
        iGoal = main(i)
        lGoal[i] = iGoal

    return lGoal

#i = int(raw_input())
i = 30
timeStart = time.time()
getList(i)

print "Cost: %s" % (time.time() - timeStart)</pre>
<p>看看它的耗时：<br />
Cost: 21.9739699364</p>
<p>首先对于这种多重循环和调用，绝对是C的强项，理论上Python 2 C 之后会有很大的提升空间。重构的思路是用Python的ctypes模块调用基于C的lib库，这样可以在保留大部分Python代码的基础上完成重构，而且重构之后的程序仍然还是Python的。<br />
开始C重构vi LoadTest.c：</p>
<pre name=code class=cpp>#include &lt;stdio.h&gt;
/*
int main()
{
    int i, goal;
    for (i=1; i &lt;= 30; i++)
    {
        goal = C_Time_Cost(i);
        printf ("%d, %d \n", i, goal);

    }

   return 0;
}
*/
int C_Time_Cost(int a)
{
    if (a &lt; 1)
    {
        return 0;
    }
    if (a &lt; 2 )
    {
        return 1;
    }

    return C_Time_Cost( a - 1 ) + C_Time_Cost( a - 2 ) - C_Time_Cost( a - 5 ) ;
}</pre>
<p>编译到.o文件：<br />
gcc -c LoadTest.c -fPIC</p>
<p>编译成为Lib<br />
gcc &#8211;shared -o libLoadTest.so LoadTest.o<br />
cp libLoadTest.so /lib64 &amp;&amp; ldconfig //这一点很重要，否则会报找不到lib文件。</p>
<p>修改python:</p>
<pre name=code class=python>#!/usr/bin/env python

import time
from ctypes import *
lGoal = {}

def main(i):
    cdll.LoadLibrary("libLoadTest.so")
    libc = CDLL("libLoadTest.so")
    goal = libc.C_Time_Cost(c_int(i))
    return goal

def getList(iMax):
    for i in range(1, iMax+1):
        iGoal = main(i)
        lGoal[i] = iGoal

    return lGoal

#i = int(raw_input())
i = 30
timeStart = time.time()
getList(i)

print "Cost: %s" % (time.time() - timeStart)</pre>
<p>执行时间：<br />
Cost: 0.280420064926</p>
<p>对比之前的21.9739699364 性能提升78倍！不过需要注意，ctypes模块的加载本身很耗资源，使用不当会起到反效果，例如将i=30改为i=10的情况下，python: 0.000591039657593，而Ctypes则需要0.00165605545044，反而落后3倍的时间。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2012/01/04/ctypes%e5%ae%9e%e7%8e%b0c%e9%87%8d%e6%9e%84/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>非阻塞的Python web框架tornado</title>
		<link>http://www.litrin.net/2011/10/18/%e9%9d%9e%e9%98%bb%e5%a1%9e%e7%9a%84python-web%e6%a1%86%e6%9e%b6tornado/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e9%259d%259e%25e9%2598%25bb%25e5%25a1%259e%25e7%259a%2584python-web%25e6%25a1%2586%25e6%259e%25b6tornado</link>
		<comments>http://www.litrin.net/2011/10/18/%e9%9d%9e%e9%98%bb%e5%a1%9e%e7%9a%84python-web%e6%a1%86%e6%9e%b6tornado/#comments</comments>
		<pubDate>Tue, 18 Oct 2011 08:10:12 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[www]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[服务器]]></category>

		<guid isPermaLink="false">http://www.litrin.net/?p=1581</guid>
		<description><![CDATA[公司项目中需要使用长链接方式的获取后端数据库——主要是Redis的实时数据。 由于项目本身是PHP的初次看到这个项目，首先想到的是Apache + mod_php的方式，配合php的ob_start()方式直接调用，就如同我之前的一篇东西所说的那样。可问题不这么简单： 系统是nginx + php-fpm方式，php-fpm“hold不住”过多的Http请求，而nginx需要调整响应时间。 用户数量很多，Apache的消耗很大。本身功能点很小，实现成本不合算。 说到并发，Apache采用的方式是大量的fork进程，通过“人多力量大”的方式应对多个请求，这样的基于进程（线程）模型的并发，一旦调用sleep，进程只是休眠而已，仍然占用着内存，仍然需要进程调度，资源始终得不到释放，资源自然无法得到控制。这些年nginx的流行大多都是因为nginx采用的epoll方式有效的解决这个问题——直接挂掉进程，然后再指定时间内重新启用。这就是传说中的“非阻塞”(asynchronous IO  AIO)。 多并发、轻量级应用，我首先想到的是Python，加上“非阻塞”关键字，得到的结果就是Tornado。 tornado原本是FriendFeed引擎的开源版本，本身就是为解决“C10K”问题而生的。 稍微研究了一下，coding也很简便： #!/usr/bin/env python #By Litrin J #http://www.litrin.net/ import tornado.httpserver import tornado.ioloop import tornado.options import tornado.web import time, redis, hashlib,random from tornado.options import define, options define("port", default=8888, help="run on the given port", type=int) class LongPolling(tornado.web.RequestHandler): minWaitTime = 15 maxWaitTime = 900 RedisHost = [...]]]></description>
			<content:encoded><![CDATA[<p>公司项目中需要使用长链接方式的获取后端数据库——主要是Redis的实时数据。</p>
<p>由于项目本身是PHP的初次看到这个项目，首先想到的是Apache + mod_php的方式，配合php的ob_start()方式直接调用，就如同我之前的<a title="重新认识下PHP的输出" href="http://www.litrin.net/2011/01/16/%e9%87%8d%e6%96%b0%e8%ae%a4%e8%af%86%e4%b8%8bphp%e7%9a%84%e8%be%93%e5%87%ba/">一篇东西</a>所说的那样。可问题不这么简单：</p>
<ol>
<li>系统是nginx + php-fpm方式，php-fpm“hold不住”过多的Http请求，而nginx需要调整响应时间。</li>
<li>用户数量很多，Apache的消耗很大。本身功能点很小，实现成本不合算。<span id="more-1581"></span></li>
</ol>
<p>说到并发，Apache采用的方式是大量的fork进程，通过“人多力量大”的方式应对多个请求，这样的基于进程（线程）模型的并发，一旦调用sleep，进程只是休眠而已，仍然占用着内存，仍然需要进程调度，资源始终得不到释放，资源自然无法得到控制。这些年nginx的流行大多都是因为nginx采用的epoll方式有效的解决这个问题——直接挂掉进程，然后再指定时间内重新启用。这就是传说中的“非阻塞”(asynchronous IO  AIO)。</p>
<p>多并发、轻量级应用，我首先想到的是Python，加上“非阻塞”关键字，得到的结果就是<a rel="nofollow" target="_blank" href="http://www.tornadoweb.org/">Tornado</a>。</p>
<p>tornado原本是FriendFeed引擎的开源版本，本身就是为解决“C10K”问题而生的。</p>
<p>稍微研究了一下，coding也很简便：</p>
<pre class="py" name=code>#!/usr/bin/env python
#By Litrin J
#http://www.litrin.net/
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import time, redis, hashlib,random
from tornado.options import define, options

define("port", default=8888, help="run on the given port", type=int)

class LongPolling(tornado.web.RequestHandler):

    minWaitTime = 15
    maxWaitTime = 900

    RedisHost   = "172.18.194.98"
    RedisPrefix = "Q/MSG%s"

    SignSalt    = "salt"

    StopCode    = "{'stop':1}"

    @tornado.web.asynchronous
    def get(self):
        sUid        = self.get_argument("uid", None)
        sSign       = self.get_argument("sign", None)
        sJsonCall   = self.get_argument("jsonCallback", None)

        if (sUid is None or sSign is None
                or self.checkSign(sUid, sSign) == False):
            raise tornado.web.HTTPError(404)
            self.clear()

        else:
            self.doLongPolling(callback=self.onWaitting)

    def checkSign(self, sUid, sSign):
        return True

    def doLongPolling(self, callback):
        #Check if the client close the connection
        if self.request.connection.stream.closed():
            self.clear()

        sKey        =   self.RedisPrefix % self.uid
        res         =   redis.Redis(self.RedisHost)
        sMessage    =   res.rpop(sKey)
        del res #close the redis at 1st time

        callback(sMessage)

    def onWaitting(self, sMessage):
        if (sMessage is not None):
            self.onRespones(sMessage)
        else:
            iNextPollingTime = time.time() + self.minWaitTime
            if self.minWaitTime &lt; self.maxWaitTime:
                self.minWaitTime *= 2
                #Can't useing time.sleep for no-bloking mode
                tornado.ioloop.IOLoop.instance().add_timeout(
                iNextPollingTime ,
                lambda: self.doLongPolling(callback=self.onWaitting)
            )
            else:
                self.onRespones(self.StopCode)

    def onRespones(self, sMessage):
        sJsonCall   = self.get_argument("jsonCallback", None)
        if sJsonCall is not None:
            sMessage= sJsonCall + "( " + sMessage.decode('utf-8') + ")"

        self.write(sMessage)
        self.finish()

def main():
    tornado.options.parse_command_line()
    application = tornado.web.Application([
        (r"/", LongPolling),
    ])
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()</pre>
<p>一旦建立http连接，系统会每隔一段时间去轮询用户的队列中是否有数据，有数据时才向客户端发送请求，否则就是“长时间小菊花”状态。很简单，仅仅用了不到100行代码的样子。当然，为了安全因素和节省资源，代码里添加了很多限制性的操作，否则可能会更简洁高效。</p>
<p>需要注意的是，tornado的理念就是建立在“非阻塞”基础上的，你当然可以选择time.sleep(n)，但传统上的time.sleep(n)方式的休眠会导致IO阻塞，故只能采用tornado.ioloop.IOLoop.instance().add_timeout 的方法回调来回调去，将下次启动的时间转给系统。话说回来，这是我第一次在Java或JS以外使用回调函数……</p>
<p>初步测试了一下，借助Redis本身就很强的并发能力在台式机上可以每秒钟完成4K次请求以上，并可以保持同时4K以上的访问不掉线，此时的CPU load仍然保持在1以下，单是测试的结果就足以让Apache+Php组合汗颜。</p>
<p>部署的时候采用了单IP多端口方式，服务器有4个核心，决定开4个端口对应，分别是8885~8888，修改</p>
<pre>define("port", default=8888, help="run on the given port", type=int)</pre>
<p>即可</p>
<p>前端nginx用负责负载分发：</p>
<pre>upstream backend {
        server 127.0.0.1:8888;
        server 127.0.0.1:8887;
        server 127.0.0.1:8886;
        server 127.0.0.1:8885;
}
 server{
        listen  80;
        server_name message.test.com;
        keepalive_timeout 65;    #
        proxy_read_timeout 2000; #
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;

    location / {
        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;

        proxy_pass  http://backend;
        }
}</pre>
<p>需要注意的是，这样的httpSocket方式很可能被内核认为是SYN攻击，而且对于大量的keep alive而言，很可能超过核心限制，还要修改sysctl参数。</p>
<pre>#系统最大打开文件数
fs.file-max = 201510

#TCP连接的最长时间
net.ipv4.tcp_keepalive_time = 1800
#出现问题的尝试次数
net.ipv4.tcp_keepalive_probes = 15
#检查是否连接的等待时间
net.ipv4.tcp_keepalive_intvl = 60
#不进行SYN cookies防御
net.ipv4.tcp_syncookies = 0</pre>
<pre></pre>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2011/10/18/%e9%9d%9e%e9%98%bb%e5%a1%9e%e7%9a%84python-web%e6%a1%86%e6%9e%b6tornado/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>posix的特性——fork</title>
		<link>http://www.litrin.net/2011/08/31/posix%e7%9a%84%e7%89%b9%e6%80%a7%e2%80%94%e2%80%94fork/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=posix%25e7%259a%2584%25e7%2589%25b9%25e6%2580%25a7%25e2%2580%2594%25e2%2580%2594fork</link>
		<comments>http://www.litrin.net/2011/08/31/posix%e7%9a%84%e7%89%b9%e6%80%a7%e2%80%94%e2%80%94fork/#comments</comments>
		<pubDate>Wed, 31 Aug 2011 05:28:42 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://www.litrin.net/?p=1541</guid>
		<description><![CDATA[记得之前曾经在一段Python中介绍过python的mulitprocess模块在windows的不兼容。其中提及了linux/Unix特有的fork方式，而windows无法实现导致的死循环。当时只是提到了一下，重点不同的关系，没有深究。 fork，顾名思义就是一把叉子，或者专业一点叫做复制叉。作为sys/type.h的一个函数，系统在每次调用fork()之后，将会以此为分叉，对进程本身进行复制，新的进程和旧进程有近乎完全一致的cpu时间和内存寄存器。 这本身并不重要，重要的是对于fork()函数的返回而言，父进程返回的是子进程的pid，而子进程返回的是0，当然还有一个-1的返回表示fork()的失败。对与C大致如此： #include &#60;unistd.h&#62;; #include &#60;sys/types.h&#62;; main () { pid_t pid; pid=fork(); if (pid &#60; 0) printf("error in fork!"); else { if (pid == 0) { printf("i am the child process, my process id is %d\n",getpid()); exit(); } else { printf("i am the parent process, my process id is %d\n",getpid()); exit(); } } } [...]]]></description>
			<content:encoded><![CDATA[<p>记得之前曾经在一段Python中介绍过<a title="python multiprocessing的问题" href="http://www.litrin.net/2010/06/23/python-multiprocessing%e7%9a%84%e9%97%ae%e9%a2%98/">python的mulitprocess模块在windows的不兼容</a>。其中提及了linux/Unix特有的fork方式，而windows无法实现导致的死循环。当时只是提到了一下，重点不同的关系，没有深究。</p>
<p>fork，顾名思义就是一把叉子，或者专业一点叫做复制叉。作为sys/type.h的一个函数，系统在每次调用fork()之后，将会以此为分叉，对进程本身进行复制，新的进程和旧进程有近乎完全一致的cpu时间和内存寄存器。</p>
<p><span id="more-1541"></span></p>
<p><img src="http://www.litrin.net/wp-content/uploads/2011/08/linux_fork.gif" alt="fork模型" title="fork模型" width="246" height="152" class="aligncenter size-full wp-image-1545" /></p>
<p>这本身并不重要，重要的是对于fork()函数的返回而言，父进程返回的是子进程的pid，而子进程返回的是0，当然还有一个-1的返回表示fork()的失败。对与C大致如此：</p>
<pre>#include &lt;unistd.h&gt;;
#include &lt;sys/types.h&gt;;

main ()
{
        pid_t pid;
        pid=fork();

        if (pid &lt; 0)
                printf("error in fork!");
        else
        {
			if (pid == 0)
                        {
                                 printf("i am the child process, my process id is %d\n",getpid());
		                 exit();
                        }
			else
                        {
                                printf("i am the parent process, my process id is %d\n",getpid());
				exit();
                         }
         }
}</pre>
<p>执行后，父进程和子进程分别将自己的Pid打印出来。</p>
<p>既然如此，对于我常用的php来说，可以同样做到后端执刑一些耗时的操作，从而达到异步的效果：</p>
<pre name=code class="php">function mulitProcess()/*{{{*/
{
        if (function_exists(pcntl_fork))
        {
            $pid = pcntl_fork();

		    if ( $pid === -1 )
		    {
		    	return false;
		    }
		    elseif ( $pid )
		    {
                        usleep(10);
                        exit();
                    }
                    else
                    {
                         doBackground();
                    }
        }
        // windows system or php not support PCNTL libs
        else
        {
			doBackground();
        }
}</pre>
<p>对于很多守护进程来说，脱离shell（即“脱壳”）正是利用了这种方式进行进程复制。对与PHP来说需要进行2次fork之后配合一个死循环即可,当然为了防止死循环真的“死掉”需要sleep()函数来进行休眠：</p>
<pre name=code class="php">function daemonize() /*{{{*/
{
    $pid    =   pcntl_fork();

    if (-1 == $pid)
    {
        return false;
    }
    elseif ($pid)  //parent process
    {
        usleep(500);
        exit();
    }
    $sid    =   posix_setsid();

    if (!$sid)
    {
        return false;
    }

    $pid    =   pcntl_fork();

    if ( -1 == $pid )
    {
        return false;
    }
    elseif ($pid)
    {
        usleep(500);
        exit(0);
    }

    // deny process display and io
    if (defined('STDIN'))
            fclose(STDIN);

    if (defined('STDOUT'))
            fclose(STDOUT);

    if (defined('STDERR'))
            fclose(STDERR);
    while(true)
            doSomeThing();

}/*}}}*/</pre>
<p>需要注意的是，windows没有fork()，php直接屏蔽了PCNL函数库，而python相对没有一棒子打死。Python采用的方式等于在命令行重新调用一次脚本，如果直接将没有考虑到这个问题的python脚本直接移植windows的话进程数量将会爆炸式增长，很快就会导致资源耗尽。由于我在windows上大多只是进行测试而已，多进程带来的性能优势不重要，习惯上我的方法就是使用threading模块替换掉mulitprocessing模块。写个示例：</p>
<pre name=code class="python">
try: #if os.name == 'posix':
    from multiprocessing import Process, Queue
except: #else:
    from threading import Thread as Process
    from Queue import Queue
</pre>
<p><img src="http://www.litrin.net/wp-content/uploads/2011/08/windows_fork_sim-300x125.gif" alt="windows模拟fork模型" title="悲催的windows模拟fork模型" width="300" height="125" class="aligncenter size-medium wp-image-1546" /></p>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2011/08/31/posix%e7%9a%84%e7%89%b9%e6%80%a7%e2%80%94%e2%80%94fork/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ubuntu上Coreseek+php的安装</title>
		<link>http://www.litrin.net/2011/06/16/ubuntu%e4%b8%8acoreseekphp%e7%9a%84%e5%ae%89%e8%a3%85/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=ubuntu%25e4%25b8%258acoreseekphp%25e7%259a%2584%25e5%25ae%2589%25e8%25a3%2585</link>
		<comments>http://www.litrin.net/2011/06/16/ubuntu%e4%b8%8acoreseekphp%e7%9a%84%e5%ae%89%e8%a3%85/#comments</comments>
		<pubDate>Thu, 16 Jun 2011 02:23:30 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[数据库应用]]></category>
		<category><![CDATA[DataBase]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[ubuntu]]></category>
		<category><![CDATA[www]]></category>
		<category><![CDATA[服务器]]></category>

		<guid isPermaLink="false">http://www.litrin.net/?p=1489</guid>
		<description><![CDATA[Coreseek是一个基于sphinx引擎，支持与mmseg中文分词模块合作完成中文的全文搜索引擎。相对sql这类操作，Coreseek负载可谓是微不足道。当然类似的索引服务器还有给予Java的solr等。我选择coreseek的主要原因之一是他可以通过配置后可以与现有的mysql客户端兼容，并可以直接嵌入到mysql中成为mysql的引擎之一。 首先，下载安装包，我选择的是最新的stable版， 不过不客气的说，即便是coreseek的stable版本，不论是从稳定性、兼容性还是灵活性上都不能算是完善，至少无法跟apache这类经典应用相提并论。 安装ubuntu开发包： apt-get install make gcc g++ automake libtool mysql-client libmysqlclient15-dev libxml2-dev libexpat1-dev   解压： tar xzvf coreseek-3.2.14.tar.gz cd coreseek-3.2.14 应该看到有mmseg csft testpack 至少3个目录 安装mmseg中文分词模块 cd mmseg-3.2.14 ./bootstrap ./configure &#8211;prefix=/usr/local/mmseg3 #不喜欢这个官方目录配置，更换目录后配置无法生效郁闷了很久，稍后再说 make make install cd ../testpack /usr/local/mmseg3/bin/mmseg -d /usr/local/mmseg3/etc var/test/test.xml #测试结果，请确保终端可以看到utf8中文，应该可以看到分词后的结果 安装Python包 虽然coreseek支持直接的mysql连接，但考虑到分词、分表的情况，这种设置不是很灵活。我的方案是通过python脚本的预处理，将mysql的数据整理后传输到coreseek建立索引。这样还有一个优势就是“万能数据源”——memcache、redis这类几乎不指望coreseek官方支持的妖异存储方案，只要有python的api，均可以索引。 官方的安装手册中介绍的python接口是基于ActivePython 替代系统python语言的方式进行的。ActivePython属于第三方封包，远没有系统自带python来的方便，刚开始用系统python测试了数次，均不成功，后来根据出错信息，找到了原因。 apt-get install python-dev python-sqlite python-mysqldb python-memcache 安装coreseek cd ../csft-3.2.14 [...]]]></description>
			<content:encoded><![CDATA[<p>Coreseek是一个基于sphinx引擎，支持与mmseg中文分词模块合作完成中文的全文搜索引擎。相对sql这类操作，Coreseek负载可谓是微不足道。当然类似的索引服务器还有给予Java的solr等。我选择coreseek的主要原因之一是他可以通过配置后可以与现有的mysql客户端兼容，并可以直接嵌入到mysql中成为mysql的引擎之一。</p>
<p>首先，下载安装包，我选择的是<a rel="nofollow" target="_blank" href="http://www.coreseek.cn/uploads/csft/3.2/coreseek-3.2.14.tar.gz">最新的stable版</a>， 不过不客气的说，即便是coreseek的stable版本，不论是从稳定性、兼容性还是灵活性上都不能算是完善，至少无法跟apache这类经典应用相提并论。</p>
<p><span id="more-1489"></span><br />
安装ubuntu开发包：<br />
apt-get install make gcc g++ automake libtool mysql-client libmysqlclient15-dev libxml2-dev libexpat1-dev</p>
<p><strong> </strong><br />
解压：<br />
tar xzvf coreseek-3.2.14.tar.gz<br />
cd coreseek-3.2.14</p>
<p>应该看到有mmseg csft testpack 至少3个目录</p>
<p><strong>安装mmseg中文分词模块</strong></p>
<p>cd mmseg-3.2.14<br />
./bootstrap<br />
./configure &#8211;prefix=/usr/local/mmseg3 #不喜欢这个官方目录配置，更换目录后配置无法生效郁闷了很久，稍后再说<br />
make<br />
make install<br />
cd ../testpack<br />
/usr/local/mmseg3/bin/mmseg -d /usr/local/mmseg3/etc var/test/test.xml #测试结果，请确保终端可以看到utf8中文，应该可以看到分词后的结果</p>
<p><strong>安装Python包</strong></p>
<p>虽然coreseek支持直接的mysql连接，但考虑到分词、分表的情况，这种设置不是很灵活。我的方案是通过python脚本的预处理，将mysql的数据整理后传输到coreseek建立索引。这样还有一个优势就是“万能数据源”——memcache、redis这类几乎不指望coreseek官方支持的妖异存储方案，只要有python的api，均可以索引。</p>
<p>官方的安装手册中介绍的python接口是基于<a rel="nofollow" target="_blank" href="http://www.activestate.com/activepython" target="_blank">ActivePython</a> 替代系统python语言的方式进行的。ActivePython属于第三方封包，远没有系统自带python来的方便，刚开始用系统python测试了数次，均不成功，后来根据出错信息，找到了原因。</p>
<p>apt-get install python-dev python-sqlite python-mysqldb python-memcache</p>
<p><strong>安装coreseek</strong></p>
<p>cd ../csft-3.2.14<br />
./buildconf.sh<br />
./configure &#8211;prefix=/usr/local/coreseek &#8211;without-unixodbc &#8211;with-mmseg &#8211;with-mmseg-includes=/usr/local/mmseg3/include/mmseg/ &#8211;with-mmseg-libs=/usr/local/mmseg3/lib/ &#8211;with-mysql &#8211;with-python<br />
make<br />
make install</p>
<p>cd ../testpack<br />
/usr/local/coreseek/bin/indexer -c etc/csft.conf &#8211;all<br />
/usr/local/coreseek/bin/search -c etc/csft.conf 网络搜索 #没什么问题了</p>
<p><strong>安装libsphinxclient</strong></p>
<p>coreseek官方教程中建议php使用直接include一个php文件进行操作，事实上php有独立的sphinx模块可以直接操作coreseek (coreseek就是sphinx！)已经进入了php的官方函数库，而且效率的提升不是一点点！但php模块依赖于libsphinxclient包。</p>
<p>cd ../csft-3.2.14/api/libsphinxclient<br />
./configure<br />
make<br />
make install<br />
ldconfig</p>
<p><strong>安装php-sphinx支持 </strong></p>
<p>apt-get install php5-dev<br />
wget http://pecl.php.net/get/sphinx-1.1.0.tgz<br />
tar vzxf  sphinx-1.1.0.tgz<br />
cd  sphinx-1.1.0<br />
phpize<br />
./configure<br />
make<br />
make install</p>
<p>确信成功后修改php.ini，还是遵从ubuntu的配置规则<br />
echo “extension=sphinx.so”  &gt; /etc/php5/conf.d/sphinx.ini</p>
<p>如果使用apache或者fast-cgi的话，请重起。察看phpinfo() 如下则安装成功。</p>
<h2><a name="module_sphinx">sphinx</a></h2>
<table border="0" cellpadding="3" width="600">
<tbody>
<tr>
<th>sphinx support</th>
<th>enabled</th>
</tr>
<tr>
<th>Version</th>
<th>1.1.0</th>
</tr>
<tr>
<th>Revision</th>
<th>$Revision: 303462 $</th>
</tr>
</tbody>
</table>
<p> </p>
<p><strong>配置coreseek</strong></p>
<p>说容易也容易，说麻烦也麻烦的工作，照贴一份吧前面说过，我用的是python数据源</p>
<p>/usr/local/coreseek/etc/coreseek.conf</p>
<pre>python
{
           path = /usr/local/coreseek/DBSource #python 数据源脚本的存放路径
}
source Blog #索引库1 “Blog”
{
          type = python #类型为python
          name = Blog.MainSource #调用python的class
}
index Blog
{
    source          = Blog #数据源名称
    path            = /data/sphinx/Blog #数据文件存放路径
    docinfo         = extern
    mlock           = 0
    morphology      = none
    min_word_len    = 1
    html_strip      = 0
   charset_dictpath = /usr/local/mmseg3/etc/ #mmseg中文分词库的位置，本文开头时我说不能修改mmseg的安装路径就错在这里
   charset_type        = zh_cn.utf-8
}
source UserInfo #索引库2 “UserInfo” 其余同上
{
    type = python
    name = UserInfo.MainSource
}
index UserInfo
{
     source          = UserInfo
     path            = /data/sphinx/UserInfo
     docinfo         = extern
     mlock           = 0
    morphology      = none
    min_word_len    = 1
    html_strip      = 0
    charset_dictpath = /usr/local/mmseg3/etc/
    charset_type        = zh_cn.utf-8
}

searchd #服务器配置
{
    listen          =   9312  #显而易见，默认的监听端口
    listen          =   172.18.196.90:3306:mysql41 #兼容mysql方式的监听，我们配置了php-sphinx，故除了命令行方式外意义不大，可以关闭
    pid_file        =   /var/run/coreseek.pid
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2011/06/16/ubuntu%e4%b8%8acoreseekphp%e7%9a%84%e5%ae%89%e8%a3%85/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>写在zipsite1.0发布之后</title>
		<link>http://www.litrin.net/2011/03/27/%e5%86%99%e5%9c%a8zipsite1-0%e5%8f%91%e5%b8%83%e4%b9%8b%e5%90%8e/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e5%2586%2599%25e5%259c%25a8zipsite1-0%25e5%258f%2591%25e5%25b8%2583%25e4%25b9%258b%25e5%2590%258e</link>
		<comments>http://www.litrin.net/2011/03/27/%e5%86%99%e5%9c%a8zipsite1-0%e5%8f%91%e5%b8%83%e4%b9%8b%e5%90%8e/#comments</comments>
		<pubDate>Sun, 27 Mar 2011 13:51:45 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[www]]></category>
		<category><![CDATA[站长的blog]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[GAE]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://www.litrin.net/?p=1418</guid>
		<description><![CDATA[首先，大肆宣扬一番：今天zipsite正式发布了第一个里程碑式版本 Ver1.0 Release！ 话说去年11月份，刚接到一个项目，要在Android平台上开发一个基于手机通讯录的应用。众所周知的原因，Android开发者网站http://developer.android.com/ 在国内无法访问。一开始，我直接下载了本地版本的html全站镜像文件。作为一开始了解这个平台，读读SDK搭建方法一点问题也没有。后来对照几个example进行开发联系，发觉很多类型不知道何去何从，用了本地搜索发觉也是摸不着头脑。于是有了一个想法：为何不把手册放在网上，借助google来索引整个网站。 于是我马上切换到Geeker模式，浏览手册的目录，发觉所有的html都是很规范的相对路径，完全可以直接放在网上。而且主目录下已经有了部分GAE的文件，似乎可以很方便的上传到GAE。尝试了几次，大失所望，原来单是文件数目就有接近3000，超出了GAE 1000个文件的限制很多。 切换回程序员模式。想到之前曾经写过一个系统底层的Python脚本，为了方便翻阅归档的文件，通过python对于zip良好的支持和多线程操作，一次性diff多个版本的区别。用这种方式可以解决GAE的一系列限制，上传网站。 很快，最基本的代码完成了，网站也上传成功，大部分情况工作的也不错。于是有了想要把这个项目分享的想法。 既然要开源，只是基本的功能实现是不够的，而且代码也不够清晰。就算原理再简单，也应该给人清新的感觉，于是先重写了原型代码。想了很久有什么功能需要再添加，后来想到了GAE的CPU Time其实也是有限制的，zip解压又是CPU耗费型的。又通过GAE的Data Store和Memcache做了双重缓冲。大致上有点意思了，于是申请了google code的空间，通过SVN放出了第一个版本ver0.1作为测试。最后，为了显得有点可扩展性，写了一个接口，可以支持插件，其实主要是纯文本和HTML，最初的想法只是看到Android手册中的HTML有太多的空格和换行，很浪费带宽而已。 话说当时正巧我的广告帐户里面攒到了$80多之后，很久没有动静。始终无法达到最低的汇款金额$100。要知道，$80比$0还要折磨人。切换到商人模式，复杂化了插件，让他可以自动的制定位置插入广告。又复杂了一步。 本来这个项目只是为了想要借助google的本地索引来完成一些事情，谁知道上线很久以后，Google仍不能完成全站的收录。SEO吧。又写了sitemap的支持功能到项目中，发布了Ver0.2版本。 剩下的任务就是不断的修正bug而已。春节前几天，我觉得整个项目该有的功能都差不多了，于是这才开始整理feature——生命周期完全颠倒了。也正是这个原因，回头看看之前的代码，已经乱的差不多了。特别是插件的接口我觉得真的很垃圾，况且即便是程序员，也没有几个人愿意通过大量的正则表达式来实现功能，于是索性取消了。 为了给这个项目制作一个像样点的文档，我特地在Mac app store里面买了一套iwork，花了差不多$90大洋。呵呵，也算是开发费用的一部分吧。 然后就是Ver1.0 RC 版本，虽然这个项目没有什么人关注，但很庆幸，有几个人特地找到我，愿意参加测试，在此感谢下！ 今天终于咬牙发布了最终的Release版本。希望不要马上推出个什么紧急补丁之类的东西。 说到下一个版本，我觉得应该遵守GAE的习惯，采用整数版本号来表示Release版本，小数表示tag或者snap版本。2版本应该增加一个管理员界面，在后台上多做些文章；重新启用插件部分，当然表达方式越简单越好。Zipsite项目的最终目标是成为一个运行在GAE沙盒内部的一个快速、傻瓜化的网站搭建系统。]]></description>
			<content:encoded><![CDATA[<p>首先，大肆宣扬一番：今天<a rel="nofollow" target="_blank" title="ZipSite project" href="http://code.google.com/p/zipsite" target="_blank">zipsite</a>正式发布了第一个里程碑式版本 Ver1.0 Release！</p>
<p>话说去年11月份，刚接到一个项目，要在Android平台上开发一个基于手机通讯录的应用。众所周知的原因，Android开发者网站<a rel="nofollow" target="_blank" href="http://developer.android.com/">http://developer.android.com/</a> 在国内无法访问。一开始，我直接下载了本地版本的html全站镜像文件。作为一开始了解这个平台，读读SDK搭建方法一点问题也没有。后来对照几个example进行开发联系，发觉很多类型不知道何去何从，用了本地搜索发觉也是摸不着头脑。于是有了一个想法：为何不把手册放在网上，借助google来索引整个网站。</p>
<p><span id="more-1418"></span></p>
<p>于是我马上切换到Geeker模式，浏览手册的目录，发觉所有的html都是很规范的相对路径，完全可以直接放在网上。而且主目录下已经有了部分GAE的文件，似乎可以很方便的上传到GAE。尝试了几次，大失所望，原来单是文件数目就有接近3000，超出了GAE 1000个文件的限制很多。</p>
<p>切换回程序员模式。想到之前曾经写过一个系统底层的Python脚本，为了方便翻阅归档的文件，通过python对于zip良好的支持和多线程操作，一次性diff多个版本的区别。用这种方式可以解决GAE的一系列限制，上传网站。<br />
很快，最<a rel="nofollow" target="_blank" title="突破GAE文件数量的限制" href="http://www.litrin.net/2010/11/26/%e7%aa%81%e7%a0%b4gae%e6%96%87%e4%bb%b6%e6%95%b0%e9%87%8f%e7%9a%84%e9%99%90%e5%88%b6/" target="_blank">基本的代码</a>完成了，<a title="Android开发者手册" href="http://android-sdk.appspot.com/" target="_blank">网站也上传成功</a>，大部分情况工作的也不错。于是有了想要把这个项目分享的想法。<br />
既然要开源，只是基本的功能实现是不够的，而且代码也不够清晰。就算原理再简单，也应该给人清新的感觉，于是先重写了原型代码。想了很久有什么功能需要再添加，后来想到了GAE的CPU Time其实也是有限制的，zip解压又是CPU耗费型的。又通过GAE的Data Store和Memcache做了双重缓冲。大致上有点意思了，于是申请了google code的空间，通过SVN放出了第一个版本ver0.1作为测试。最后，为了显得有点可扩展性，写了一个接口，可以支持插件，其实主要是纯文本和HTML，最初的想法只是看到Android手册中的HTML有太多的空格和换行，很浪费带宽而已。</p>
<p>话说当时正巧我的广告帐户里面攒到了$80多之后，很久没有动静。始终无法达到最低的汇款金额$100。要知道，$80比$0还要折磨人。切换到商人模式，复杂化了插件，让他可以自动的制定位置插入广告。又复杂了一步。</p>
<p>本来这个项目只是为了想要借助google的本地索引来完成一些事情，谁知道上线很久以后，Google仍不能完成全站的收录。SEO吧。又写了sitemap的支持功能到项目中，发布了Ver0.2版本。</p>
<p>剩下的任务就是不断的修正bug而已。春节前几天，我觉得整个项目该有的功能都差不多了，于是这才开始整理feature——生命周期完全颠倒了。也正是这个原因，回头看看之前的代码，已经乱的差不多了。特别是插件的接口我觉得真的很垃圾，况且即便是程序员，也没有几个人愿意通过大量的正则表达式来实现功能，于是索性取消了。</p>
<p>为了给这个项目制作一个像样点的文档，我特地在Mac app store里面买了一套iwork，花了差不多$90大洋。呵呵，也算是开发费用的一部分吧。</p>
<p>然后就是Ver1.0 RC 版本，虽然这个项目没有什么人关注，但很庆幸，有几个人特地找到我，愿意参加测试，在此感谢下！</p>
<p>今天终于咬牙发布了最终的Release版本。希望不要马上推出个什么紧急补丁之类的东西。</p>
<p>说到下一个版本，我觉得应该遵守GAE的习惯，采用整数版本号来表示Release版本，小数表示tag或者snap版本。2版本应该增加一个管理员界面，在后台上多做些文章；重新启用插件部分，当然表达方式越简单越好。Zipsite项目的最终目标是成为一个运行在GAE沙盒内部的一个快速、傻瓜化的网站搭建系统。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2011/03/27/%e5%86%99%e5%9c%a8zipsite1-0%e5%8f%91%e5%b8%83%e4%b9%8b%e5%90%8e/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>说说Python的垃圾回收机制</title>
		<link>http://www.litrin.net/2011/02/08/%e8%af%b4%e8%af%b4python%e7%9a%84%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6%e6%9c%ba%e5%88%b6/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e8%25af%25b4%25e8%25af%25b4python%25e7%259a%2584%25e5%259e%2583%25e5%259c%25be%25e5%259b%259e%25e6%2594%25b6%25e6%259c%25ba%25e5%2588%25b6</link>
		<comments>http://www.litrin.net/2011/02/08/%e8%af%b4%e8%af%b4python%e7%9a%84%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6%e6%9c%ba%e5%88%b6/#comments</comments>
		<pubDate>Tue, 08 Feb 2011 05:27:01 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[硬件相关]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://www.litrin.net/?p=1393</guid>
		<description><![CDATA[作为Java和python这类相对设计比较完善的解释型语言而言，总有很严密的垃圾回收机制用以防止资源被浪费甚至内存溢出之类的问题。起先我始终认为这会大大提升系统的性能，然而这次碰上了悖论。 刚过完春节，春节前，考虑到会有很多人发送贺卡，邮件系统的压力剧增。同时春节期间无人值守，没有办法人为干预很多。于是我打包了之前的一段python程序用来在节日期间发送大量的邮件贺卡。 初次测试的过程中，由于仅仅只是测试下性能如何，没有“特别规矩”没有关闭定义的类，于是代码大致如此： for mailBody in mailList:         mail = SendMail()         mail.MailServer['UserName'] = username         mail.MailServer['Password'] = password          mail.MailServer['Host'] = host         mail.MailServer['port'] = port         mail.SenderName = senderName         mail.Charset = 'UTF-8'         mail.AddMeta('Precedence', 'bulk')         delFromDb(mailBody[3])           try :             mail.Send(From = smtpFrom,               Address = mailBody[0],               Subject = mailBody[1], [...]]]></description>
			<content:encoded><![CDATA[<p>作为Java和python这类相对设计比较完善的解释型语言而言，总有很严密的垃圾回收机制用以防止资源被浪费甚至内存溢出之类的问题。起先我始终认为这会大大提升系统的性能，然而这次碰上了悖论。<br />
刚过完春节，春节前，考虑到会有很多人发送贺卡，邮件系统的压力剧增。同时春节期间无人值守，没有办法人为干预很多。于是我打包了<a href="http://www.litrin.net/2010/06/30/%E9%87%8D%E6%96%B0%E5%B0%81%E5%8C%85%E7%9A%84python-smtplib/" target="_blank">之前的一段python程序</a>用来在节日期间发送大量的邮件贺卡。</p>
<p><span id="more-1393"></span><br />
初次测试的过程中，由于仅仅只是测试下性能如何，没有“特别规矩”没有关闭定义的类，于是代码大致如此：</p>
<pre name=code class=python>
for mailBody in mailList:

        mail = SendMail()
        mail.MailServer['UserName'] = username
        mail.MailServer['Password'] = password 
        mail.MailServer['Host'] = host
        mail.MailServer['port'] = port

        mail.SenderName = senderName
        mail.Charset = 'UTF-8'

        mail.AddMeta('Precedence', 'bulk')

        delFromDb(mailBody[3])  

        try :
            mail.Send(From = smtpFrom,
              Address = mailBody[0],
              Subject = mailBody[1],
              Body = mailBody[2])           

            logging.info('Mail send to: ' + mailBody[0] + " with subject is " + mailBody[1] + " By thread #" + str(thread))

        except:
            logging.error('Error send to: ' + mailBody[0] + " with subject is " + mailBody[1] +"By thread #" + str(thread))</pre>
<p>初版测试了一下，每小时的发送量保守达到2w左右。已经达到了要求，可以正式部署。<br />
考虑到了生产环境中的服务器负载很高，于是对python的代码提出了更高的要求，在上述代码的最后加上了一句：<br />
mail = None<br />
结束发送循环以后立即释放 SendMail()这个类占据的资源。这是一个程序员都应该遵守的好习惯。部署好了以后，再次测试，每小时仅能发送不足7000。性能仅有之前的30%。<br />
多次拉锯测试，只要加上那句mail=None，性能就会下降。所有的性能缺陷都指向了这句“好习惯”。没什么说的，直接在头部加上</p>
<pre name=code class=python>
import gc
gc.disable()
</pre>
<p>加不加那句已经没有实质性的影响了。</p>
<p>Python不同于Java的垃圾回收机制，Java机制是只有等到资源，特别是内存资源紧张的情况下才会执行垃圾回收机制。而Python则是忠实于C++系的逻辑，只要资源没有被引用就回自动被回收机制无情的回收。<br />
在这个例子中，由于Sendmail的类相对比较复杂的操作，每次构建和解构的过程都需要花费较长时间。而且由于Sendmail是主循环，占用了大量的内存，每次垃圾的回收系统都会重新整理内存碎片，如果每次都为释放内存而手工解构的话性能反而会大大降低。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2011/02/08/%e8%af%b4%e8%af%b4python%e7%9a%84%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6%e6%9c%ba%e5%88%b6/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从GAE看网站架构趋势</title>
		<link>http://www.litrin.net/2010/12/21/%e4%bb%8egae%e7%9c%8b%e7%bd%91%e7%ab%99%e6%9e%b6%e6%9e%84%e8%b6%8b%e5%8a%bf/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e4%25bb%258egae%25e7%259c%258b%25e7%25bd%2591%25e7%25ab%2599%25e6%259e%25b6%25e6%259e%2584%25e8%25b6%258b%25e5%258a%25bf</link>
		<comments>http://www.litrin.net/2010/12/21/%e4%bb%8egae%e7%9c%8b%e7%bd%91%e7%ab%99%e6%9e%b6%e6%9e%84%e8%b6%8b%e5%8a%bf/#comments</comments>
		<pubDate>Tue, 21 Dec 2010 01:59:53 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[数据库应用]]></category>
		<category><![CDATA[硬件相关]]></category>
		<category><![CDATA[站长的blog]]></category>
		<category><![CDATA[网络和安全]]></category>
		<category><![CDATA[GAE]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[业界话题]]></category>
		<category><![CDATA[服务器]]></category>

		<guid isPermaLink="false">http://www.litrin.net/?p=1372</guid>
		<description><![CDATA[在Google App Engine做了几个东西。从当初觉得这个平台的迥然不同，直到现在逐步了解了这个平台的逻辑架构。作为Google的一款成功的作品，相信GAE的很多东西将会陆陆续续被模仿和抄袭，直到真正影响到我们。本文就以我个人的理解，谈谈GAE将会给今后的网站架构带来哪些革命性的东西。 首当其冲的应该算是云技术，但这个技术总体上属于物理层的技术，况且云已经成为公认的技术趋势。这里主要是谈下网站的架构，云技术相对关系较为偏离，我也就不再探讨了。 BigTable数据库 一开始接触下来的GAE，最感觉不适应的就非它的“数据存储”技术。通常用惯了SQL，用了GAE觉得数据设计上很要有很大的转变。 GAE的“数据库”感觉就是一个巨大的List，每一条记录都是key-value的对应关系。当然起初是无法满足很多传统上的数据结构。不过如果你能够在数据结构设计上打破SQL的限制，通过极端灵活的Key-value, key-value, key-value，你就能发觉这样的框架事实上比传统的SQL更适合网站。 说mysql比oracle在网站实现上的优势并非是因为它功能的强大，而是它速度快，结构简单。尽管有人戏称mysql“裸奔”，但无可否认的就是这种“裸奔”的效果带来的mysql的成功。GAE的数据库比mysql还要“裸奔”，但更快的响应将会是SQL永远无法企及的。FaceBook之前也抛弃了SQL亦证明了这一点。 此外整站之间的数据复制也算是革新之一。 多通道的融合 GAE整合了mail，xmpp，跨域验证等一系列的通道也算是特色之一，想必很多人会认为google这是在捆绑自己的产品。作为之前的门户网站的特色，每家人都会有一个自己的邮箱，一个自己的聊天工具，以及一系列自己的产品捆绑在内。GAE的创新在于用户只需要一个自己的google帐户就可以实现捆绑所有的gae服务。这是一个真正意义上的以用户为本。 Xmpp的捆绑还意味着EDM之后，将来IM营销或者类似的IM推送将会逐步成为一个产业。或者更进一步，IM机器人或者类似的技术将会一定程度上取代现有人人对话模式。当然，这需要一段时间，但至少个人认为目前大量的taobao网店需要这种技术^_^。 精巧的运维界面 相信GAE的后他大家也会印象深刻，能对每一个模块进行精确的监控，已经让运维的工作从翻阅日志的文本化提升到了图形化。粒度也更为精准，如果能配合Analytics之类的数据，完全可以实现智能化的运维。 RSS XML SOAP 网址抓取&#8230; 其实这也不算是什么革新，Web2.0时代的开始就是以此为标志的。但事实上单单从开发库中各个库的名称都叫API来看，就可以明白，其实整个GAE就是构建在这些基础之上的。SOAP成为了各个模块之间的粘合剂。 这个带来的优势就是，无需关心这个库那个库——好像本站技术性文档的大部分都是解决的“库问题”。各自统一接口，加速了开发的进程。]]></description>
			<content:encoded><![CDATA[<p>在Google App Engine做了几个东西。从当初觉得这个平台的迥然不同，直到现在逐步了解了这个平台的逻辑架构。作为Google的一款成功的作品，相信GAE的很多东西将会陆陆续续被模仿和抄袭，直到真正影响到我们。本文就以我个人的理解，谈谈GAE将会给今后的网站架构带来哪些革命性的东西。</p>
<p>首当其冲的应该算是云技术，但这个技术总体上属于物理层的技术，况且云已经成为公认的技术趋势。这里主要是谈下网站的架构，云技术相对关系较为偏离，我也就不再探讨了。</p>
<p><span id="more-1372"></span></p>
<p><strong>BigTable数据库</strong></p>
<p>一开始接触下来的GAE，最感觉不适应的就非它的“数据存储”技术。通常用惯了SQL，用了GAE觉得数据设计上很要有很大的转变。<br />
GAE的“数据库”感觉就是一个巨大的List，每一条记录都是key-value的对应关系。当然起初是无法满足很多传统上的数据结构。不过如果你能够在数据结构设计上打破SQL的限制，通过极端灵活的Key-value, key-value, key-value，你就能发觉这样的框架事实上比传统的SQL更适合网站。<br />
说mysql比oracle在网站实现上的优势并非是因为它功能的强大，而是它速度快，结构简单。尽管有人戏称mysql“裸奔”，但无可否认的就是这种“裸奔”的效果带来的mysql的成功。GAE的数据库比mysql还要“裸奔”，但更快的响应将会是SQL永远无法企及的。FaceBook之前也抛弃了SQL亦证明了这一点。</p>
<p>此外整站之间的数据复制也算是革新之一。</p>
<p><strong>多通道的融合</strong></p>
<p>GAE整合了mail，xmpp，跨域验证等一系列的通道也算是特色之一，想必很多人会认为google这是在捆绑自己的产品。作为之前的门户网站的特色，每家人都会有一个自己的邮箱，一个自己的聊天工具，以及一系列自己的产品捆绑在内。GAE的创新在于用户只需要一个自己的google帐户就可以实现捆绑所有的gae服务。这是一个真正意义上的以用户为本。<br />
Xmpp的捆绑还意味着EDM之后，将来IM营销或者类似的IM推送将会逐步成为一个产业。或者更进一步，IM机器人或者类似的技术将会一定程度上取代现有人人对话模式。当然，这需要一段时间，但至少个人认为目前大量的taobao网店需要这种技术^_^。</p>
<p><strong>精巧的运维界面</strong></p>
<p>相信GAE的后他大家也会印象深刻，能对每一个模块进行精确的监控，已经让运维的工作从翻阅日志的文本化提升到了图形化。粒度也更为精准，如果能配合Analytics之类的数据，完全可以实现智能化的运维。</p>
<p><strong>RSS XML </strong><strong>SOAP </strong>网址抓取&#8230;</p>
<p>其实这也不算是什么革新，Web2.0时代的开始就是以此为标志的。但事实上单单从开发库中各个库的名称都叫API来看，就可以明白，其实整个GAE就是构建在这些基础之上的。SOAP成为了各个模块之间的粘合剂。<br />
这个带来的优势就是，无需关心这个库那个库——好像<a href="http://www.litrin.net/" target="_blank">本站</a>技术性文档的大部分都是解决的“库问题”。各自统一接口，加速了开发的进程。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2010/12/21/%e4%bb%8egae%e7%9c%8b%e7%bd%91%e7%ab%99%e6%9e%b6%e6%9e%84%e8%b6%8b%e5%8a%bf/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Gtalk通知机器人脚本</title>
		<link>http://www.litrin.net/2010/12/02/gtalk%e9%80%9a%e7%9f%a5%e6%9c%ba%e5%99%a8%e4%ba%ba%e8%84%9a%e6%9c%ac/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=gtalk%25e9%2580%259a%25e7%259f%25a5%25e6%259c%25ba%25e5%2599%25a8%25e4%25ba%25ba%25e8%2584%259a%25e6%259c%25ac</link>
		<comments>http://www.litrin.net/2010/12/02/gtalk%e9%80%9a%e7%9f%a5%e6%9c%ba%e5%99%a8%e4%ba%ba%e8%84%9a%e6%9c%ac/#comments</comments>
		<pubDate>Thu, 02 Dec 2010 06:08:53 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[www]]></category>
		<category><![CDATA[网络和安全]]></category>
		<category><![CDATA[GAE]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://www.litrin.net/?p=1356</guid>
		<description><![CDATA[服务器每天都有这样那样的信息需要实时推送。最初一直是Mail，最容易实现，成本也低。后来有了短信接口，就有了相对实时但要花钱的方式推送。 知道GAE可以直接跟gtalk挂钩推送消息，可能一直挂Gtalk的人远没有挂QQ和MSN的多（密之声：Gtalk又没有星星和钻石，挂也没劲），但有了Android + 3G的实时在线，这种方式却可以直接替代SMS这类花钱的服务，何乐不为？ 大致上完成了原型，制作了一个基于Python的命令行程序，不敢独享，分享给大家。希望大家能够一起加入这个项目完善它。 在此之前，请现将 server-say @@@ appspot.com （你懂的！）加为好友 #!/usr/bin/python # -*- coding:utf-8 -*- import sys import re import urllib class ServerSaid: ApiURL = 'http://server-say.appspot.com/api' # #请到http://server-said.appspot.com/注册您的Ip地址。没有注册的Ip每小时最多10条消息。 # Account = '' MessageBody = '' DefaultEncode = 'utf-8' def SendMessage(self): cleanMessage = self.MessageBody.encode(self.DefaultEncode) if (self.checkEmail(self.Account) and self.checkMessage(cleanMessage)): cleanMessage = self.MessageBody.encode('utf-8') url = self.ApiURL + '?account='+ [...]]]></description>
			<content:encoded><![CDATA[<p>服务器每天都有这样那样的信息需要实时推送。最初一直是Mail，最容易实现，成本也低。后来有了短信接口，就有了相对实时但要花钱的方式推送。</p>
<p>知道GAE可以直接跟gtalk挂钩推送消息，可能一直挂Gtalk的人远没有挂QQ和MSN的多（密之声：Gtalk又没有星星和钻石，挂也没劲），但有了Android + 3G的实时在线，这种方式却可以直接替代SMS这类花钱的服务，何乐不为？</p>
<p>大致上完成了原型，制作了一个基于Python的命令行程序，不敢独享，分享给大家。希望大家能够一起加入这个项目完善它。<br />
在此之前，请现将 server-say @@@ appspot.com （你懂的！）加为好友</p>
<p><span id="more-1356"></span></p>
<pre class="python" name='code'>
#!/usr/bin/python
# -*- coding:utf-8 -*-
import sys
import re
import urllib

class ServerSaid:

	ApiURL = 'http://server-say.appspot.com/api'
	#
	#请到http://server-said.appspot.com/注册您的Ip地址。没有注册的Ip每小时最多10条消息。
	#
	Account = ''
	MessageBody = ''
	DefaultEncode = 'utf-8'

	def SendMessage(self):
		cleanMessage = self.MessageBody.encode(self.DefaultEncode)
		if (self.checkEmail(self.Account) and self.checkMessage(cleanMessage)):
			cleanMessage = self.MessageBody.encode('utf-8')
			url = self.ApiURL + '?account='+ urllib.quote(self.Account) +'&#038;message=' + urllib.quote(cleanMessage)
		#	print url
			query = urllib.urlopen(url)
			if ( query.read() == '200' ):
				print 'Message be sent!\\n'

			else:
				print self.Error(0)
		else :
			print self.Error(1)

	def Error(self, code=0):
		return  'Message can\\'t be send! \\n'

	def checkEmail(self, Email):
		RegexString = r'^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w{2,5}$'

		return re.match(RegexString, Email)

	def checkMessage(self, Message):

		RegexString = r'^.{1,140}$'
		return re.match(RegexString, Message)

if __name__ == '__main__':

	if (len(sys.argv)>1):
		handler = ServerSaid()
		handler.Account = sys.argv[1]

		Message = sys.argv[2]

		handler.MessageBody = Message.decode(handler.DefaultEncode)

		handler.SendMessage()
	else:
		print 'serversaid.py YOUR_GTALK MESSAGE '
</pre>
<p>这里为了防止消息的滥发，使用前最好先到 <a rel="nofollow" target="_blank" href="http://server-say.appspot.com/">http://server-say.appspot.com/ </a>注册一下。没有注册的IP每小时仅能发送10条消息，我想大部分情况下也够用了。注册了的用户可以无限制的发送消息了，除非我在GAE注册的那张信用卡里的钱花光——我会哭的。</p>
<p>啥也不说了，PHP接口也搞定了！</p>
<pre name='code' class='php'>
< ?php
/*
 *    Gtalk 通告机器人 Ver 0.1
 *
 *    Litrin Jiang 2010/12/02
 *
 * --------------------------------
 *  1. 2010/12: v0.1 原型实现
 *
 */

Class Gtalk{

	private $APIUrl = 'Http://server-say.appspot.com/api?';
	//Api 的地址，不要修改

	public $mothod = 'get';
	//默认的提交方式，现阶段仅支持get
	public $account = '';
	//接收人的Gtalk账户
	public $messageBody = '';
	//消息主体，最多支持140个字符

	public $Charset = 'UTF-8';

	public function Send($account=null, $messageBody=null){
		if (is_array($account)){
			foreach ($account as $key) {
				$this->Send($key);
			}
		}

		if ($account === null){
			$account = $this.account;
		}
		$this->CheckAccount($account);

		if($messageBody === null){
			$messageBody = $this->messageBody;
		}
		if ($this->Charset != 'UTF-8'){
			$messageBody = iconv("UTF-8", $this->Charset.'//IGNORE', $messageBody);
		}
		$this->CheckMessage($messageBody);

		$url = $this->APIUrl .'account=' . urlencode($account) . '&#038;message=' .  urlencode($messageBody);

		$query = file($url);
		/*
		if($query == '200'){
			return true;
		}else{
			return false;
		}*/

	}

	private function CheckAccount($account){
		$regexString = '^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w{2,5}$';
		echo ereg($regexString, $account);
		if (ereg($regexString, $account) != true){
			#throw new Exception("Value not a Email account! ");
		}

	}

	private function CheckMessage($messageBody){
		$regexString = '^.{1,140}';
		if (ereg($regexString, $messageBody) != true){
			#throw new Exception("Message not a allowed format! ");
		}
	}
}

?>
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2010/12/02/gtalk%e9%80%9a%e7%9f%a5%e6%9c%ba%e5%99%a8%e4%ba%ba%e8%84%9a%e6%9c%ac/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>突破GAE文件数量的限制</title>
		<link>http://www.litrin.net/2010/11/26/%e7%aa%81%e7%a0%b4gae%e6%96%87%e4%bb%b6%e6%95%b0%e9%87%8f%e7%9a%84%e9%99%90%e5%88%b6/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e7%25aa%2581%25e7%25a0%25b4gae%25e6%2596%2587%25e4%25bb%25b6%25e6%2595%25b0%25e9%2587%258f%25e7%259a%2584%25e9%2599%2590%25e5%2588%25b6</link>
		<comments>http://www.litrin.net/2010/11/26/%e7%aa%81%e7%a0%b4gae%e6%96%87%e4%bb%b6%e6%95%b0%e9%87%8f%e7%9a%84%e9%99%90%e5%88%b6/#comments</comments>
		<pubDate>Fri, 26 Nov 2010 08:50:21 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[www]]></category>
		<category><![CDATA[GAE]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://www.litrin.net/?p=1343</guid>
		<description><![CDATA[这些天有个项目是需要一部分Android开发。本想去官网看文档，众所周知的原因，官网无法连接。下载了本地的文件，由于是做的整站镜像，搜索功能无法正常使用，这对于经常要使用此功能的我来说很是麻烦。于是想到了把文件上传到GAE，再利用Google的本地功能来实现。 想的很简单，写好了app.yaml就要上传。谁知道试了几次也不成功。帮助手册上说的很明确： 限制 值 请求大小 10 兆字节 响应大小 10 兆字节 请求持续时间 30 秒 同时动态请求 30 * 应用程序文件的最大数目 1,000 静态文件的最大数目 1,000 应用程序文件的最大大小 10 兆字节 静态文件的最大大小 10 兆字节 所有应用程序和静态文件的最大总大小 150 兆字节 看了看要上传的文件夹，5000多个文件，小气的google根本满足不了我。 想到Python的灵活性，在手册中翻了到了zipfile类库。有门！就上传zip文档，边解压边浏览。Google要用时间来换空间那就累死他的CPU 写个GAE应用loadzip.py： #!/bin/env python ## ## This is a project for Google App Engine ## that support create a webisite by ZIP packages! ## ## [...]]]></description>
			<content:encoded><![CDATA[<p>这些天有个项目是需要一部分Android开发。本想去官网看文档，众所周知的原因，官网无法连接。下载了本地的文件，由于是做的整站镜像，搜索功能无法正常使用，这对于经常要使用此功能的我来说很是麻烦。于是想到了把文件上传到GAE，再利用Google的本地功能来实现。 想的很简单，写好了app.yaml就要上传。谁知道试了几次也不成功。<a rel="nofollow" target="_blank" href="http://code.google.com/intl/zh-CN/appengine/docs/python/runtime.html" target="_blank">帮助手册</a>上说的很明确：</p>
<table>
<tbody>
<tr>
<th>限制</th>
<th>值</th>
</tr>
<tr>
<td>请求大小</td>
<td>10 兆字节</td>
</tr>
<tr>
<td>响应大小</td>
<td>10 兆字节</td>
</tr>
<tr>
<td>请求持续时间</td>
<td>30 秒</td>
</tr>
<tr>
<td>同时动态请求</td>
<td>30 *</td>
</tr>
<tr>
<td>应用程序文件的最大数目</td>
<td><span style="color: #ff0000;">1,000</span></td>
</tr>
<tr>
<td>静态文件的最大数目</td>
<td><span style="color: #ff0000;">1,000</span></td>
</tr>
<tr>
<td>应用程序文件的最大大小</td>
<td>10 兆字节</td>
</tr>
<tr>
<td>静态文件的最大大小</td>
<td>10 兆字节</td>
</tr>
<tr>
<td>所有应用程序和静态文件的最大总大小</td>
<td>150 兆字节</td>
</tr>
</tbody>
</table>
<p><span id="more-1343"></span>看了看要上传的文件夹，5000多个文件，小气的google根本满足不了我。 想到Python的灵活性，在手册中翻了到了zipfile类库。有门！就上传zip文档，边解压边浏览。Google要用时间来换空间那就累死他的CPU <img src='http://www.litrin.net/wp-includes/images/smilies/icon_evil.gif' alt=':evil:' class='wp-smiley' />  写个GAE应用loadzip.py：</p>
<pre class="python" name='code'>#!/bin/env python
##
##	This is a project for Google App Engine
##		that support create a webisite by ZIP packages!
##
##	By Litrin J. 2010/11
##	Website: www.litrin.net
##	Example: android-sdk.appspot.com
##

import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.api import memcache
from zipfile import ZipFile
import os
import logging
import mimetypes
import re

class MainHandler(webapp.RequestHandler):
	'''
This is a project for Google App Engine that support create a webisite by ZIP packages!
	'''
	URL = ''
	CACHEDTIME = 60*60*24*30
	#setting memcaching time (seconds)

	def get(self):

		self.URL=self.request.path

		if ( self.URL[-1:] == '/'):
		#Get the defaule file for the path
			self.URL+='index.html'

		sRealFileName = os.getcwd() + self.URL

		if (os.path.exists(sRealFileName)):
		#If the file not be ziped, read it drictory
			fNoZipedFile = open(sRealFileName,'r')
			Entry = fNoZipedFile.read()
			fNoZipedFile.close()

		else:
			Entry = self.loadFile()

		self.buildMineType()
		#Set the Mine type in header

		if (self.response.headers['Content-Type'] == 'text/html'):

			Entry = self.regex(Entry)

		self.response.out.write(Entry)
		#Response building finished!

	def loadZipFile(self):
	#Load the file from zip files. This is the core function!
		lFilename = self.URL.split('/')

		iPathLevel = 1
		#Loop count, The dir layers
		bLoaded = False
		#Sucessful marks

		while(iPathLevel &lt; = len(lFilename)):
		#Get the zip file name and the filename from the URL, support muli-layer
			sFilePath = os.getcwd()
			sFileName = ''
			iElementCount = 1

			for sElement in lFilename:
			#Set or reset the Zip filename
				if ( iElementCount &lt;= iPathLevel):
					sFilePath += sElement + '/'

				if ( iElementCount &gt;= iPathLevel ):
					sFileName += sElement + '/'

				iElementCount += 1

			sFileName = sFileName[0:-1]
			sZipFilename = sFilePath[0:-1] + '.zip'

			if (os.path.exists(sZipFilename)):
			#Found the Zip file
				ZipFileHandle = ZipFile(sZipFilename)
				Entry = ZipFileHandle.read(sFileName)
				bLoaded = True
				ZipFileHandle.close()

				if (Entry is not None):
					logging.info(sFileName + " in " + sZipFilename + " Loaded!")
					return Entry
					break

			iPathLevel +=1

		if (bLoaded==False):
		#Can't file the file in Zip packages
			logging.error('No found ' + self.request.path + '' +sZipFilename+' !')
			self.error(404)

			return None

	def loadFile(self):
	#Load in from memcache if cached
		Entry = self.loadFromMemcach()

		if Entry is None:
		#If not cached, cache it
			Entry = self.loadZipFile()
			if(Entry is not None):
				self.writeToMemcache(Entry)

		return Entry

	def loadFromMemcach(self):
		memcachkey = self.URL
		#The cache key is URL

		Entry = memcache.get(memcachkey)
		if Entry is not None:
			return Entry

		else:
			return None

	def writeToMemcache(self, data):
		memcachkey = self.URL
		#set the URL as the key
		memcache.add(memcachkey, data, self.CACHEDTIME)

		logging.info(memcachkey + ' cached!')
		return True

	def buildMineType(self):
	#Building the MineType in Http header
		sFilename = os.path.basename(self.URL)
		lFileName = sFilename.split(".")
		sExFilename = lFileName.pop()
		#Get the file ex-filename

		mimetypes.init()
		sMineType = mimetypes.types_map['.'+sExFilename]

		if (sMineType == ''):
		#Others can't be idented, set to HTML
			sMineType = 'text/html'

		self.response.headers['Content-Type'] = sMineType
		#Send Content-type

	def regex(self, Entry):

		lRegGroup=(
			('\n+', '\n'),
			#
			('\t+', '\t'),
			#
		)

		for sRegCell in lRegGroup:
			(sSource, sTarget) = sRegCell
			rInfo = re.compile(sSource)
			Entry = rInfo.sub(sTarget, Entry)

		return Entry

def main():
	application = webapp.WSGIApplication([('.*', MainHandler)], debug=True)
	wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
	main()</pre>
<p>比较简单，我也没有写太多注释。考虑到Google一直被这么搞，挂掉也不好，于是启用了memcache。 重写app.yaml</p>
<pre>application: android-sdk
version: 1
runtime: python
api_version: 1

handlers:

- url: .*
 script: zipload.py

#- url: /
# static_files: index.html
# upload: index.html</pre>
<p>准备就绪，把手册中每一个子目录压缩成zip，极限大小也无所谓，然后把手册根目录下的所有文件和这两个文件放在同一目录下。上传至GAE便可。 捎带说下，由于GAE还有对文件大小的限制。用此方法还无法完全解决Adroid手册上传的问题，后来我用了分级的目录结构搞定的，原理上跟上面是一致的，在此不再累述了。 把我的Android手册网站共享给大家 <a rel="nofollow" target="_blank" href="http://android-sdk.appspot.com/" target="_blank">http://android-sdk.appspot.com/index.html</a> PS:</p>
<ol>
<li>Linux下的Idle工具真烂，远不及以及够烂的windows版。好在SPE这个工具很好用。</li>
<li>GAE的sdk for linux没有桌面工具，只能用命令行方式，在此代表Linux用户表示抗议！</li>
</ol>
<p style="text-align: center;"><span style="font-size: large;"><span style="color: #ff6600;">本项目的后续版本已经通过google code开源，敬请访问：</span></span></p>
<p style="text-align: center;"><a rel="nofollow" target="_blank" href="http://code.google.com/p/zipsite/"><span style="font-size: large;"><span style="color: #ff6600;">http://code.google.com/p/zipsite/</span></span></a><span style="font-size: large;"><span style="color: #ff6600;"> </span></span></p>
<p style="text-align: center;"><span style="font-size: large;"><span style="color: #ff6600;">获取最新版本！</span></span></p>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2010/11/26/%e7%aa%81%e7%a0%b4gae%e6%96%87%e4%bb%b6%e6%95%b0%e9%87%8f%e7%9a%84%e9%99%90%e5%88%b6/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>重新封包的python smtplib</title>
		<link>http://www.litrin.net/2010/06/30/%e9%87%8d%e6%96%b0%e5%b0%81%e5%8c%85%e7%9a%84python-smtplib/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e9%2587%258d%25e6%2596%25b0%25e5%25b0%2581%25e5%258c%2585%25e7%259a%2584python-smtplib</link>
		<comments>http://www.litrin.net/2010/06/30/%e9%87%8d%e6%96%b0%e5%b0%81%e5%8c%85%e7%9a%84python-smtplib/#comments</comments>
		<pubDate>Wed, 30 Jun 2010 02:25:01 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://www.litrin.net/?p=1269</guid>
		<description><![CDATA[这些天做了一个小的系统：将Oracle中的数据导出然后通过邮件直接发送给用户。 oracle部分主要通过sqlplus的报表格式导出成csv。邮件部分主要通过python读取csv然后再转译成html格式发送。 原理上很简单，到头来python的smtplib调试了半天才ok，为了方便，重新封了一下包，基本上的用途都在里面了。分享给各位！ 提醒各位：不要用它来收发垃圾邮件！ ## ##****************************************************************************** ##   NAME:       SendMail ##   PURPOSE:    ## ##   REVISIONS: ##   Ver        Date        Author           Description ##   ---------  ----------  ---------------  ------------------------------------ ##   1.0        2010/06/28  Litrin J.        1. Created this Script. ## ##   NOTES: ## ##      Object Name:     SendMail.py ##      Sysdate:         2010/06/28 ##      ##      Username:         Litrin Jiang ##      Website:          litrin.net ## ##****************************************************************************** ## [...]]]></description>
			<content:encoded><![CDATA[<p>这些天做了一个小的系统：将Oracle中的数据导出然后通过邮件直接发送给用户。<br />
oracle部分主要通过sqlplus的报表格式导出成csv。邮件部分主要通过python读取csv然后再转译成html格式发送。</p>
<p>原理上很简单，到头来python的smtplib调试了半天才ok，为了方便，重新封了一下包，基本上的用途都在里面了。分享给各位！<br />
<span style="color: #ff0000;">提醒各位：不要用它来收发垃圾邮件！</span></p>
<p><span id="more-1269"></span></p>
<pre name=code class=python>
##
##******************************************************************************
##   NAME:       SendMail
##   PURPOSE:   
##
##   REVISIONS:
##   Ver        Date        Author           Description
##   ---------  ----------  ---------------  ------------------------------------
##   1.0        2010/06/28  Litrin J.        1. Created this Script.
##
##   NOTES:
##
##      Object Name:     SendMail.py
##      Sysdate:         2010/06/28
##     
##      Username:         Litrin Jiang
##      Website:          litrin.net
##
##******************************************************************************
##

import sys
import smtplib
import email
import re

class SendMail:
   
    MailServer = {}
    MailServer['UserName'] = ''            
    MailServer['Password'] = ''                 #SMTP密码
    MailServer['Host'] = ''
    MailServer['SSL'] = False
    MailServer['Port'] = 0

    Subtype = 'html'
    Charset = 'gb2312'

    SenderName = ''
    ReciverName = ''   
    Notification = False

    __aRealAddressList = []
   
    def __init__ (self, From = '', Address='', Subject='', Body='', CCAddress = '', BCCAddress = ''):

        if ((From + Address + Subject + Body) != ''):

            self.Send(self, From, Address, Subject, Body, CCAddress = '')
       

    def Send(self, From, Address, Subject, Body, CCAddress = '', BCCAddress = ''):

        self.Email = email.MIMEText.MIMEText (Body,_subtype=self.Subtype, _charset=self.Charset)

        self.Email['From'] = self.AddressList(From, self.SenderName, False)
                   
        self.Email['To'] = self.AddressList(Address)

        if CCAddress != '':
            self.Email['Cc'] = self.AddressList(CCAddress)

        if BCCAddress != '':
            self.AddressList(BCCAddress)
           
        self.Email['Subject'] = self.StrEncode(Subject)
       
        self.Email['X-Maile'] = 'Python Script by Litrin.net'
        self.Email['X-Python-Version'] = str(sys.platform) + str(sys.version_info)
       
        if (self.Notification) :
            self.Email['Disposition-Notification-To'] = From

 
        self.__ServerSending()

    def StrEncode(self, String, Charset = ''):
        if Charset == '':
            Charset = self.Charset
           
        return str(email.Header.Header (String, Charset))

    def AddressList(self, oAddress, ReciverName = '', SendingList = True):

        if isinstance(oAddress, dict):
            To = ''
            for dAddress in oAddress :
                To += self.AddressList(oAddress[dAddress],dAddress )  + ','
            return To
       
        elif isinstance(oAddress, list):
            To = ''
            for sAddress in oAddress :
                To += self.AddressList (sAddress, '') + ','
            return To
       
        else:
           
            try :
                self.CheckEmail(oAddress)
            except:
                print oAddress + 'is not a Email address!'
                exit()
               
            if ReciverName == '':
                ReciverName = self.ReciverName     
           
            if ReciverName != '':
                To = self.StrEncode(ReciverName) + '&lt;' + oAddress + '&gt;'
            else:
                To = oAddress
               
            if SendingList :
                self.__aRealAddressList.append(oAddress)               

            return To
   

    def CheckEmail(self, sEmailAddress):
        sRegex = r'^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$'
        return re.match(sRegex, sEmailAddress)
   
    def __ServerSending(self):

        if (self.MailServer['SSL'] == False):
          
            server = smtplib.SMTP(self.MailServer['Host'],port = self.MailServer['Port'])
        else:
            server = smtplib.SMTP_SSL(self.MailServer['Host'], port = self.MailServer['Port'])

          
        server.login(self.MailServer['UserName'], self.MailServer['Password'])
       
        for sAddress in self.__aRealAddressList:           
           
            server.sendmail(self.Email['From'], sAddress, self.Email.as_string())
           
        server.quit()

if __name__ == '__main__':
    mail = SendMail()
    mail.MailServer['UserName'] = ''            
    mail.MailServer['Password'] = ''                 #SMTP密码
    mail.MailServer['Host'] = ''
    #mail.MailServer['SSL'] = True
    mail.SenderName = 'Litrin J.'
   

    mail.Send(From = <a rel="nofollow" target="_blank" href="mailto:'abc@123.com'">'abc@123.com'</a>,
              Address = <a rel="nofollow" target="_blank" href="mailto:'abc@71inc.com'">'abc@71inc.com'</a>,
              Subject = 'Subject:test',
              Body = '&lt;b&gt;This is a test mail!&lt;/b&gt;')
 </pre>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2010/06/30/%e9%87%8d%e6%96%b0%e5%b0%81%e5%8c%85%e7%9a%84python-smtplib/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>python multiprocessing的问题</title>
		<link>http://www.litrin.net/2010/06/23/python-multiprocessing%e7%9a%84%e9%97%ae%e9%a2%98/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=python-multiprocessing%25e7%259a%2584%25e9%2597%25ae%25e9%25a2%2598</link>
		<comments>http://www.litrin.net/2010/06/23/python-multiprocessing%e7%9a%84%e9%97%ae%e9%a2%98/#comments</comments>
		<pubDate>Wed, 23 Jun 2010 09:30:55 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[硬件相关]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://www.litrin.net/?p=1260</guid>
		<description><![CDATA[multiprocessing的是Python2.6中新加入的模块，旨在用类似threading调用tread(线程)的方式使用process(进程)。 服务器中经常需要对大规模的数据进行压缩，传统使用单进程操作不足以体现8核CPU并发的威力。于是写了一个脚本用于多进程压缩。然而在windows的主机上进行调试，全都是死循环，以至于机器都无法进行响应。导入Linux主机，测试却通过。对脚本进行了精简如下： 出错的代码： from multiprocessing import Process #from threading import Thread def work(a): print "This is : " + str(a) + '\n' exit x = 2 while(x&#62;1): p = Process(target=work, args=(x,)) p.start() x -= 1 类似的调用方法，换成threading库是正确的 #from multiprocessing import Process from threading import Thread def work(a): print "This is : " + str(a) + '\n' [...]]]></description>
			<content:encoded><![CDATA[<p>multiprocessing的是Python2.6中新加入的模块，旨在用类似threading调用tread(线程)的方式使用process(进程)。</p>
<p>服务器中经常需要对大规模的数据进行压缩，传统使用单进程操作不足以体现8核CPU并发的威力。于是写了一个脚本用于多进程压缩。然而在windows的主机上进行调试，全都是死循环，以至于机器都无法进行响应。导入Linux主机，测试却通过。对脚本进行了精简如下：</p>
<p><span id="more-1260"></span><br />
出错的代码：</p>
<pre name=code class="python">
from multiprocessing  import Process
#from threading  import Thread

def work(a):
    print "This is : " + str(a) + '\n'
    exit

x = 2

while(x&gt;1):
    p = Process(target=work, args=(x,))
    p.start()
    x -= 1</pre>
<p>类似的调用方法，换成threading库是正确的</p>
<pre name=code class="python">
#from multiprocessing  import Process
from threading  import Thread

def work(a):
    print "This is : " + str(a) + '\n'
    exit

x = 2

while(x&gt;1):
    thread = Thread(target=work, args=(x,))
    thread.start()
    x -= 1</pre>
<p>翻阅了<a rel="nofollow" target="_blank" href="http://docs.python.org/library/multiprocessing.html#windows">官方文档</a>，找到了问题所在：</p>
<p>*Nix平台对于multiprocessing 的实现是基于C库中的fork()，所有子进程与父进程的数据是完全相同，可以说是父进程的完全克隆。<br />
而对于windows，由于windows对于进程的实现方式不同，没有fork()函数，multiprocessing 的调用只能是对于该脚本的重新调用，难怪会出现死循环的问题。</p>
<p>改良后的代码：</p>
<pre name=code class="python">
from multiprocessing  import Process
#from threading  import Thread

def work(a):
    print "This is : " + str(a) + '\n'
    exit

if __name__ == '__main__' :
    x = 2

    while (x&gt;1):
        p= Process(target=work, args=(x,))
        p.start()
        x -= 1</pre>
<p>对于multiprocessing 和 Threading的区别：</p>
<p>Threading的操作只能和父进程在同一个物理CPU上执行。但由于不需要底层的调用，大多数简单的操作效率很高。<br />
multiprocessing的操作可以在多个CPU上执行，但耗费的内存资源也远高于thread方式，适合于集中运算或大并发的状态。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2010/06/23/python-multiprocessing%e7%9a%84%e9%97%ae%e9%a2%98/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>GAE的数据导入问题</title>
		<link>http://www.litrin.net/2010/05/26/gae%e7%9a%84%e6%95%b0%e6%8d%ae%e5%af%bc%e5%85%a5%e9%97%ae%e9%a2%98/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=gae%25e7%259a%2584%25e6%2595%25b0%25e6%258d%25ae%25e5%25af%25bc%25e5%2585%25a5%25e9%2597%25ae%25e9%25a2%2598</link>
		<comments>http://www.litrin.net/2010/05/26/gae%e7%9a%84%e6%95%b0%e6%8d%ae%e5%af%bc%e5%85%a5%e9%97%ae%e9%a2%98/#comments</comments>
		<pubDate>Wed, 26 May 2010 01:58:28 +0000</pubDate>
		<dc:creator>Litrin</dc:creator>
				<category><![CDATA[www]]></category>
		<category><![CDATA[GAE]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[服务器]]></category>

		<guid isPermaLink="false">/?p=1242</guid>
		<description><![CDATA[又是关于Google App Engine的问题。 根据GAE的官方文档，向GAE的存储区导入数据，导入了数次均出现问题，翻来覆去的无法成功。 CVS结构示例：1300428,浙江省嘉兴市,联通130卡 （一个手机号码的对应归属地列表） configfile: from google.appengine.ext import db from google.appengine.tools.bulkloader  import Loader class MobilePhone(db.Model):   Number = db.StringProperty()   Area = db.StringProperty()   BrandType = db.StringProperty() class MPLoader(Loader):   def __init__(self):     Loader.__init__(self, 'MobilePhone',                     [('Number', unicode),                      ('Area', unicode),                      ('BrandType', unicode),                      ]) loaders = [MPLoader] 主要分析了一下，问题分为几类。 1.验证不能通过的问题： 这个问题很多时候是一个低级错误，之前我曾经犯过类似的错误。在app.yaml中的handlers handlers: [...]]]></description>
			<content:encoded><![CDATA[<p>又是关于Google App Engine的问题。<br />
根据<a rel="nofollow" target="_blank" href="http://code.google.com/intl/zh-CN/appengine/docs/python/tools/uploadingdata.html" target="_blank">GAE的官方文档</a>，向GAE的存储区导入数据，导入了数次均出现问题，翻来覆去的无法成功。</p>
<p>CVS结构示例：1300428,浙江省嘉兴市,联通130卡 （一个手机号码的对应归属地列表）</p>
<p>configfile:</p>
<pre class="py" name="code" >
from google.appengine.ext import db
from google.appengine.tools.bulkloader  import Loader

class MobilePhone(db.Model):
  Number = db.StringProperty()
  Area = db.StringProperty()
  BrandType = db.StringProperty()

class MPLoader(Loader):
  def __init__(self):

    Loader.__init__(self, 'MobilePhone',
                    [('Number', unicode),
                     ('Area', unicode),
                     ('BrandType', unicode),
                     ])

loaders = [MPLoader]</pre>
<p>主要分析了一下，问题分为几类。</p>
<p><span id="more-1242"></span></p>
<p>1.验证不能通过的问题：</p>
<p>这个问题很多时候是一个低级错误，之前我曾经犯过类似的错误。在app.yaml中的handlers</p>
<p>handlers:<br />
- url: /remote_api<br />
  script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py<br />
  login: admin<br />
- url: /*<br />
  script: main.py</p>
<p>这个/*的配置必须放在最后。否则所有的访问都会直接匹配main.py自然出现问题。</p>
<p>2.跟编码有关的错误：</p>
<p>这个错误是GAE SDK的一个bug，恐怕对于ASCII系统的用户没什么关系，对于我们UTF-8，GB2312，ASCII已经昏倒的用户来说，这是个严重的问题。</p>
<p>在GAE的安装目录下 google\appengine\tools\bulkloader.py文件中2521行</p>
<pre class="python" name="code">
    for (name, converter), val in zip(self.__properties, values):
      if converter is bool and val.lower() in ('0', 'false', 'no'):
        val = False
      properties[name] = converter(val)</pre>
<p>修改为：</p>
<pre class="python" name="code" >    for (name, converter), val in zip(self.__properties, values):
        if converter is bool and val.lower() in ('0', 'false', 'no'):
            val = False
        if isinstance(val,str) and not isinstance(val, unicode):
            val=unicode(val,'utf-8')
        properties[name] = converter(val)</pre>
<p>系统会将unicode类型的变量转换。</p>
<p>3.对于大容量数据的删除</p>
<p>GAE的限制，一次最多载入1000行的数据，每次最多删除500行数据，这对于像我制作的手机号码归属之类的大数据库是远远不够的，多次删除又会导致500报错，我的方法是使用task queue。<br />
Task Queue在官方的中文文档中没有提及，在英文文档中介绍的也不够详细。个人的理解就是让系统定期的去访问一个页面而已，我的例子：</p>
<pre class="python" name="code">from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext import db
from google.appengine.api.labs import taskqueue

class MobilePhone(db.Model):
    Number = db.StringProperty()
    Area = db.StringProperty()
    BrandType = db.StringProperty()

class Delete(webapp.RequestHandler):
    def get(self):
        q = db.GqlQuery("SELECT * FROM MobilePhone limit 100")
        results = q.fetch(100)
        db.delete(results)
        q = db.GqlQuery("SELECT * FROM MobilePhone limit 100")
        if (q.count() &gt; 0):
            self.addqueue()

        self.response.out.write('Hello world!')

    def addqueue(self):
        taskurl = 'http://ip-and-mp.appspot.com/'
        taskqueue.add( url='/del', params=dict(url=taskurl))
        self.response.out.write('added!')</pre>
<p>每次删除100行，使用默认的default queue，即每秒一次。</p>
<p>4.关于命令行的问题</p>
<p>我按照中文文档的办法始终有问题，添加了&#8211;email=xxxx &#8211;passin的参数才成功。手册已经远远落后了。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.litrin.net/2010/05/26/gae%e7%9a%84%e6%95%b0%e6%8d%ae%e5%af%bc%e5%85%a5%e9%97%ae%e9%a2%98/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

