web高并发 gunicorn & gevent & flask & sqlalchemy & scoped_session

七月二日更新

    补充一把,这个问题之后还是有问题,gevent和pymysql兼容起来有问题,从官网找到了这段话


    幸好,我遇到的问题都有解决方案。。。


结论

    1. gunicorn的worker默认是同步工作模式,默认处理请求的子进程,也就是worker数目为1,因此,高并发的web场景,启动命令中使用-k gevent/eventlet/...来设置异步工作模式,使用-w worker_num来设置处理请求的子进程数目。我的启动命令最终是这样的:gunicorn --bind=0.0.0.0:8000 wsgi.application -k gevent -w 8。这样就是有八个以异步模式运行的worker来处理请求

    2. sqlalchemy的sessionmaker创建的session是线程不安全的,gunicorn的worker-class也就是-k参数开启的无非就是多线程或者协程,就会出现线程不安全导致的问题,要想线程安全,sqlalchemy提供了另外一个scoped_session的类,是线程安全的。关键代码如下:

        session = scoped_session(sessionmaker(bind=self.engine))


参考文档

    http://docs.gunicorn.org/en/stable/design.html#server-model

    https://docs.gunicorn.org/en/stable/settings.html?#workers

    https://docs.sqlalchemy.org/en/13/orm/contextual.html

    https://kknews.cc/other/mjmx2z9.html


事发经过

    事情是这样的,boss在我们页面上点了点,觉得网络拓扑的link加载慢,push开发同事去查原因,“跟大家调的都是一个方法,为什么就我的慢”,同事发出了哀嚎,可能开发阶段,效率问题不是致命的吧,转眼,距离事发已经过去了一周多,老板来催了,无解。然后我就一时脑热,接下了这个任务。

    因为应用是多服务的模式(算不上微服务),其中一个核心服务(称之为SA)调用了另外一个服务的api(称之为SB)。

    我,机(智)智(熄)的一匹,在SA里打印了调用SB api的那部分时间(end_time - start_time),在SB的api里打印了该api处理的时间,跑了几遍,把时间拿出来对比,嗯?事情好像不太对头,SA的时间十几秒,SB的时间不到一秒,这还了得。按理说两个的时间应该一样啊(抽自己一巴掌),想了好久,然后在SA的log里看到了下面这个log,

图 1

    一瞬间我看到了希望之光,原来两个时间的差都花在这块儿了,Starting new HTTP connection这句过了有19s才看到GET http://....这句,建立http连接花这么长时间?(后来发现这个urllib3的log你误导我啊)这说明人家SB api处理还是很快的,不过SA你发个请求为什么那么慢?总之,吭哧吭哧开查,google上没有大面积同问,这个问题不寻常?蹊跷,太蹊跷了,为什么建连接要这么久?tcp连接再慢,也不至于这样吧。代理去掉,没效果?感谢我机智(真机智)的同事灵光一现,说抓包吧!

    抓起!(为什么要抓包?可能是比较相信二进制数据吧(⊙.⊙))

图 2

    可以看到蓝色api在红色api返回后马上返回,而红色api耗时5s之久,这期间蓝色api请求一直在等它处理完成,不对,虽说现在我已经知道这个红色api就是要耗费5s,但是说好的并发呢?为什么阻塞了,至此问题进入正轨,查看了gunicorn进程,有两个(图3),奇怪,查gunicorn文档,gunicorn是基于pre-fork的工作模式, pre-fork是啥,就是master进程预先fork出子进程来处理请求。(⊙o⊙)?所以这两个进程有一个光动嘴不动手的?

图 3

    可算找着原因了,改,gunicorn启动命令加上worker参数,快!

    我们领导是个懂技术的,说你光加worker不行,咱这是高IO并发,你加进程有效,可是cpu就那几个核,来个一万请求,不一样得废吗,还得加worker-class。我知道http请求是IO密集型,我也知道IO密集的情况异步可以处理,可是就缺了那根弦想不到两者的关联【摊手】,领导厉害!

    加上了,跑起来了,速度快了!

    没等我高兴几分钟,问题来了【笑容逐渐呆滞

    log中显示sqlalchemy执行sql语句一直报错,系统几乎不能用了,继续折腾,肯定是改了启动命令导致哪里出了问题。以前也遇到过多线程导致sql cursor被多个线程调用导致的连接开关问题,所以第一反应就是-k gevent这个参数有猫腻,彼时,我对线程不安全这个概念的印象还模糊,更别提知道sqlalchemy中的session如何线程安全,感谢写下这篇文章的博主: https://kknews.cc/other/mjmx2z9.html ,我又找到了一线生机,把代码里的 session=sessionmaker() 改成了 session=scoped_session(sessionmaker()) ,竟然一切顺利!

    记录下这兵荒马乱的一天,我的正事儿工作还没做呢【哭泣】


    虽然总觉得自己是个菜鸡,但谁还不曾是个菜鸡呢,工程师小白,加油!

转载请标明出处?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容