Jun 24 2016
经过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出来。
Jun 21 2016
https://emptysqua.re/blog/motor-internals-how-i-asynchronized-a-synchronous-library/
这篇文章是Motor的作者介绍Motor如何通过Greenlet来实现PyMongo在Tornado中异步调用的原理,总结来说就一下几点。
- 使用Torando的IOStream包装socket以实现异步调度
- 把IOStream的读写操作放在greenlet中运行,并注册一个switch到当前greenlet的callback到IOStream的Futrue中
- 在发生读写操作是switch到当前greenlet的父greenlet继续执行,挂起当前greenlet
- 在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的异步库。
Jun 16 2016
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,所以不能单独拿出来使用。有几个需要理解的概念:
- Future对象
用来保存异步获取到的结果,并在set_reslut的时候调用callback方法,把对应的callback方法放到ioloop的callback列表中等待下一次ioloop循环再执行
- 装饰器coroutine
在这个装饰器中实现了协程的调度,通过不断的调用next函数来不断获取Future对象,然后每次拿到Future对象在add_callback到ioloop上,等到Future被set_reslut后再次next,直到生成器中抛出Return的异常。
具体的实现过程不是很好描述,调度过程比较复杂,还是看看参考文章大牛的解析吧。
Jun 14 2016
准备用Tornado + greenlet + Django ORM搭一个框架,大体上有个思路,在开始前再次阅读下Tornado的代码。目的是在学习Torndao使用的同时,了解下原理,以便在使用过程中少踩点坑。
准备
学习IO多路复用: epoll
http://scotdoyle.com/python-epoll-howto.html
https://fukun.org/archives/10051470.html
Reactor模型
http://blog.csdn.net/u013074465/article/details/46276967
去年大概这个时候也硬着头皮读过Tornado的代码,当时没有经验,还不知道编程模型,在程序的理解上都是顺序执行的思路,所以看到Tornado的代码只会觉得特么的牛B,各种类,各种方法的调来调去。现在理解了Reactor模型以后,再回过头看Tornado,就比较容易理解了,所以在阅读代码前如果能在构架上先理解,读起来会快很多。
Tornado的代码基于2.0版本,最新的4.3版本读起来比较绕,抽象的更加细化,阅读代码的目的在于学习编程思路,不求新。
Jun 2 2016
前一篇Django+Elasticsearch实现搜索功能已经实现了搜索的基本功能,但是其实还是有一些错误,这里先纠正一下。
- 以为通过elsticstack实现了默认的分词器
通过查看elasticsearch types的mapping发现string类型的根本就没有加载ik analyzer
- 拼音分词方式设置错误
拼音分词的尝试过程中,终于实现了全拼分词
环境搭建
Elasticsearch安装拼音分词:
http://my.oschina.net/UpBoy/blog/625014?fromerr=mRvT8rzk
Django依赖安装:
django-haystack==2.5.dev1
之前会使用elasticstack与haystack2.4配合,发现问题比较多,所以删掉elasticstack,升级haystack为github上的最新版本