跬步 On Coding

greentor填坑记

经过2周的学习开发,Tornado + Django ORM的环境搭好了,这阶段的学习告一段落,虽然这个环境是一个玩具环境,没有经过生产的检验,但是在搭环境的过程中学习了Tornado,greenlet,Django数据库相关的姿势,感觉还是有不少提升。

在这2天的调试中,暴露出了2个比较严重的问题,记录下填坑过程。

1. 线程安全

WSGI服务器在接受到新的http请求时会开一个新线程来调用application进行处理,Django ORM在有数据库查询的时候,会在当前线程中创建一个新的数据库连接并保存到线程local空间中,在同一个线程中的连接是可以被复用的。不同的线程持有不同的连接,这样就保证Django ORM是线程安全的。

Tornado是单线程的,在Tornado中使用Django ORM无论处理多少请求,都会用同一个保存在当前local()中的连接,这样就必然会产生连接使用的冲突。比如同时并发的2个请求,第一个请求关闭了连接,第二个请求还在继续使用这个连接就会抛出异常。

在greentor的配合下,Tornado涉及数据库连接的请求都运行在greenlet中,如果有一个greenlet local来对每个请求的数据库连接进行隔离,就能避免线程安全问题,在这里的greenlet协程完全可以类比为线程。然而greenlet并没有local,那我们就造一个local出来。

greentor Tornado异步方案

https://emptysqua.re/blog/motor-internals-how-i-asynchronized-a-synchronous-library/

这篇文章是Motor的作者介绍Motor如何通过Greenlet来实现PyMongo在Tornado中异步调用的原理,总结来说就一下几点。

  1. 使用Torando的IOStream包装socket以实现异步调度
  2. 把IOStream的读写操作放在greenlet中运行,并注册一个switch到当前greenlet的callback到IOStream的Futrue中
  3. 在发生读写操作是switch到当前greenlet的父greenlet继续执行,挂起当前greenlet
  4. 在IOStream的读写操作完成后调用callback switch到挂起的子greenlet中继续执行
def tornado_motor_sock_method(method):
    coro = gen.coroutine(method)

    @functools.wraps(method)
    def wrapped(self, *args, **kwargs):
        #当前greenlet是一个子greenlet
        child_gr = greenlet.getcurrent()
        #获取当前greenlet的父greenlet,即之前代码提到过的asynchronize所在的greenlet
        main = child_gr.parent

        def callback(future):
            if future.exc_info():
                child_gr.throw(*future.exc_info())
            elif future.exception():
                child_gr.throw(future.exception())
            else:
                #当future的结果到达,切换回挂起的子greenlet
                child_gr.switch(future.result())

        #保证callback在当前greenlet的父greenlet中运行
        self.io_loop.add_future(coro(self, *args, **kwargs), callback)
        #return这句会暂时挂起当前greenlet,将控制权切换回父greenlet,
        #在上面的callback执行时,才会切换回当前greenlet,return语句返回
        return main.switch()
    return wrapped

使用greenlet的好处是我们可以通过这个挂起,唤醒的过程来中断当前的同步代码,而不需要用Tornado自己实现协程,每次都要yield出来,然后回调。通过使用greenlet可以很方便的把同步的网络IO库修改为支持Tornado的异步库。

Python协程

https://zh.wikipedia.org/zh-cn/%E5%8D%8F%E7%A8%8B

协程可以理解为线程中的微线程,通过手动挂起函数的执行状态,在合适的时机再次激活继续运行,而不需要上下文切换。所以在python中使用协程会比线程性能更好。

Tornado协程

http://blog.csdn.net/wyx819/article/details/45420017

上面有大牛分析的Tornado的线程实现,依赖与Tornado的IOLoop,所以不能单独拿出来使用。有几个需要理解的概念:

  1. Future对象 用来保存异步获取到的结果,并在set_reslut的时候调用callback方法,把对应的callback方法放到ioloop的callback列表中等待下一次ioloop循环再执行
  2. 装饰器coroutine 在这个装饰器中实现了协程的调度,通过不断的调用next函数来不断获取Future对象,然后每次拿到Future对象在add_callback到ioloop上,等到Future被set_reslut后再次next,直到生成器中抛出Return的异常。

具体的实现过程不是很好描述,调度过程比较复杂,还是看看参考文章大牛的解析吧。

Tornado代码阅读笔记 IOLoop

准备用Tornado + greenlet + Django ORM搭一个框架,大体上有个思路,在开始前再次阅读下Tornado的代码。目的是在学习Torndao使用的同时,了解下原理,以便在使用过程中少踩点坑。

准备

  1. 学习IO多路复用: epoll
    http://scotdoyle.com/python-epoll-howto.html
    https://fukun.org/archives/10051470.html

  2. Reactor模型
    http://blog.csdn.net/u013074465/article/details/46276967

去年大概这个时候也硬着头皮读过Tornado的代码,当时没有经验,还不知道编程模型,在程序的理解上都是顺序执行的思路,所以看到Tornado的代码只会觉得特么的牛B,各种类,各种方法的调来调去。现在理解了Reactor模型以后,再回过头看Tornado,就比较容易理解了,所以在阅读代码前如果能在构架上先理解,读起来会快很多。

Tornado的代码基于2.0版本,最新的4.3版本读起来比较绕,抽象的更加细化,阅读代码的目的在于学习编程思路,不求新。

haystack-Elasticsearch实现拼音搜索

前一篇Django+Elasticsearch实现搜索功能已经实现了搜索的基本功能,但是其实还是有一些错误,这里先纠正一下。

  1. 以为通过elsticstack实现了默认的分词器 通过查看elasticsearch types的mapping发现string类型的根本就没有加载ik analyzer
  2. 拼音分词方式设置错误 拼音分词的尝试过程中,终于实现了全拼分词

环境搭建

Elasticsearch安装拼音分词:

http://my.oschina.net/UpBoy/blog/625014?fromerr=mRvT8rzk

Django依赖安装:

django-haystack==2.5.dev1

之前会使用elasticstack与haystack2.4配合,发现问题比较多,所以删掉elasticstack,升级haystack为github上的最新版本