celery踩坑记

作者: __XY__ | 来源:发表于2018-07-30 14:33 被阅读0次

    0 概述

    以前所接触业务从未使用过celery,都是采用类似redis中做一个队列来处理异步任务。
    但是这样的缺点是显而易见的,比如debug过程不够直观,每次都要紧盯着redis中的内容,尤其格式还是bytes。
    另外一个明显的缺点就是只有一个队列,串行处理起来太慢。
    所以这次项目启动,涉及多个异步任务,比如邮件,短信,提币等等,决定使用celery。
    然而没有想到的是,在测试阶段都没啥bug,但是在正式服务器上,却时不时报错。
    鉴于这种报错从未在测试阶段发现,且严重影响了业务逻辑,一直搞得压力很大。

    1 获取日志

    第一步,把正式服务器上的celery日志脱到本地去分析。

    • 1 发现bug出现的时间不稳定,有时候不会报错。
    • 2 报错的bug出现时,一般都是流量高峰。
    • 3 报错bug请求前一秒内基本都有同样请求。
    • 4 ps: 之前定位bug的时候思路都被带到了报错信息上面,结果花了很多时间,问题还是没有解决。
      直到发现只有在流量高峰的时候才会出现bug。

    2 本地稳定复现。

    既然高流量才能激发bug,索性来个for循环个100次,去调用celery异步任务A。
    终于bug能稳定复现了,但是报错原因不统一,存在多个,同时跟正式服务器日志的报错日志匹配上了。
    error1

     [2018-07-24 10:48:43,833: WARNING/ForkPoolWorker-3] (sqlalchemy.exc.InvalidRequestError) Can't reconnect until invalid transaction is rolled back [SQL: 'SELECT token_s      ys_model.id AS token_sys_model_id, token_sys_model.gas_price_gwei AS token_sys_model_gas_price_gwei, token_sys_model.withdraw_eth_fee AS token_sys_model_withdraw_eth_f      ee, token_sys_model.is_deleted AS token_sys_model_is_deleted, token_sys_model.dt_create AS token_sys_model_dt_create \nFROM token_sys_model ORDER BY token_sys_model.id       DESC \n LIMIT %(param_1)s'] [parameters: [{}]]
    

    error2

     7 [2018-07-24 10:28:43,705: WARNING/ForkPoolWorker-3] (pymysql.err.OperationalError) (2006, "MySQL server has gone away (TimeoutError(110, 'Connection timed out'))") [SQ      L: 'SELECT token_sys_model.id AS token_sys_model_id, token_sys_model.gas_price_gwei AS token_sys_model_gas_price_gwei, token_sys_model.withdraw_eth_fee AS token_sys_mo      del_withdraw_eth_fee, token_sys_model.is_deleted AS token_sys_model_is_deleted, token_sys_model.dt_create AS token_sys_model_dt_create \nFROM token_sys_model ORDER BY       token_sys_model.id DESC \n LIMIT %(param_1)s'] [parameters: {'param_1': 1}] (Background on this error at: http://sqlalche.me/e/e3q8)
    

    3 bug初探

    目前所有的报错信息都指向了与数据库的连接上面。
    那就从连接层面入手:
    首先启动celery,不加任何额外参数,数据库执行 show process list ,发现只有一个连接。
    观察这个链接,排除超时的原因(如何排除可以查看我的另外一篇文章)

    然后去分析celery的并发模型,发现一个concurrency 参数。
    默认个数为cpu核数,默认并发模型为prefork,也就是多线程or多进程(这块不太明白)。
    目前端倪已初现,很有可能是因为 并行程序共用数据库连接

    4 stack over flow 确认

    搜索关键词为celery connection error的帖子,很多都没有大的用处,都是在说参数什么的。
    直到看到了这篇帖子

    https://stackoverflow.com/questions/19300644/resourceclosederror-with-mysql-sqlalchemy-and-celery
    

    这个帖子虽然还没有正式回答,但是目前来看,他的问题与我的log是最接近的。
    同时在comments部分,楼主指出使用-c 1 指定一个线程去执行,并不会触发bug。
    果然,在本地测试-c 1 参数也没有出现bug。
    至此。bug已确认。

    5 学习

    官方文档明确指出:

    "The Session object is entirely designed to be used in a non-concurrent fashion,
    which in terms of multithreading means "only in one thread at a time" ..
    some process needs to be in place such that mutltiple calls across many threads don’t actually get a handle to the same session. 
    We call this notion thread local storage."
    

    即,session对象只能在一个线程中使用,不能多个线程同时使用同一个session
    ,如果非要公用session,那么可以使用ScopedSession

    The ScopedSession object by default uses [threading.local()] as storage, 
    so that a single Session is maintained for all who call upon the ScopedSession registry, 
    but only within the scope of a single thread.
    Callers who call upon the registry in a different thread get a Session instance that is local to that other thread.
    Using this technique, the ScopedSession provides a quick and relatively simple way of providing a single, 
    global object in an application that is safe to be called upon from multiple threads.
    

    至此,bug得到官方文档的确认。

    6 解决方式:

    如果能确定了bug,解决方式就是再简单不过了

    • 1 用-c1参数来指定只有一个线程,考虑并发,可以启动4个worker处理。
    • 2 异步中的session对象全部替换为ScopedSession来使用。

    7 反思

    • 1 测试阶段不够完善,没有对高并发情况下进行测试
    • 2 对于系统的监控不够及时,时候发现在测试阶段偶尔会出现此bug,但是因为没有注意到,并且急于上线,
    • 3所有异步任务因为本来就是长耗时的操作,更需要进行并发测试
    • 4 log日志监控增加了sentry来监控
    • 5 发现问题也不要死盯着报错信息去解决。
    • 6 直觉有时候很重要。

    相关文章

      网友评论

        本文标题:celery踩坑记

        本文链接:https://www.haomeiwen.com/subject/ayzxvftx.html