V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
sujin190
V2EX  ›  Python

说个最近遇到的 tornado 的小坑吧

  •  
  •   sujin190 ·
    snower · 2016-03-09 22:06:42 +08:00 · 10773 次点击
    这是一个创建于 2974 天前的主题,其中的信息可能已经有所发展或是发生改变。
    事情是这样的,服务端使用 tornado 提供 api 接口给客户端使用,存储使用 mysql ,使用 tormysql 连接 MySQL 数据库查询,前天下午时服务器网络突然断了, 15 分钟后回复,可是 api 接口还是无法使用,查看服务器状态,网络正常,负载正常,服务正常,于是去查看 nginx 日志,发现 99%的请求都 504 超时了,查看 mysql 服务器, cpu 负载接近 100%,命令行进 mysql show processlist 查看,将近 400 个查询正在运行,大量处于 statistics 状态,每个查询时间明显高了不少,但也还是有查询正常返回了,并未超过请求超时时间,为什么 nginx 显示几乎所有请求都超时了呢?
    想不明白于是果断重启试试,重启不一会 504 超时又慢慢上升,不一会几乎所有的请求均超时无返回,如此数次还是无法正常启动,最后不得不关掉大部分借口再慢慢放开服务才起来,为此导致借口听着服务达数小时。
    事后对 mysql 负载虽然满了,查询很慢却还是有返回,但请求几乎都超时甚是不接,于是仔细查看日志,终于发现原来网络异常后,客户端一直在重试,网络正常后,瞬间过来的并发高了十几倍,由于 tornado 异步 IO 高并发的特性,并未出现访问拒绝,而是接受了所有的请求,并都产生了 mysql 查询发往数据库,所有 mysql 瞬间并发查询十分高导致负载上升,每个查询耗时高达数秒,同时 tornado 在使用 tormysql 查询超过最大连接数后并进入队列等待,慢慢的,队列等待时间超过请求超时时间, nginx 返回 504 超时,但在队列中的查询还是继续发往 mysql ,因为客户端为收到正常返回,所以再次发起重试,又再次增加了查询队列的长度,最终所有请求在队列中等待 mysql 查询的时间都超过了请求超时时间,所以也就出现了 mysql 负载很高但几乎所有请求都超时的情况。
    tornado 使用异步 io 确实实现了超高并发的支持,但也正是因为如此,但其依赖的后端服务超过负载后缺少快速失败的特性,其能同时接受大量请求的特性会使得在等待后端服务过程中出现循环超时,最终出现服务不可用情况,
    第 1 条附言  ·  2016-03-11 21:01:39 +08:00
    又再次对 mysql 进行了测试,原来是有一个查询 join 了三张表, 400 并发导致直接耗尽了 cpu ,单表有索引查询还是过万的,不过重点还是 tornado 在使用异步 mysql 驱动过程中对突然升高的并发超过 mysql 负载时缺少快速失败机制
    36 条回复    2016-03-10 14:41:18 +08:00
    lecher
        1
    lecher  
       2016-03-09 22:27:27 +08:00 via Android
    这个故障挺有价值的, tornado 爆掉了 MySQL 的查询性能。

    TorMySQL 的并发是不是开的也高了点,失去平缓峰值的作用。导致峰值直接击穿 MySQL 。
    cevincheung
        2
    cevincheung  
       2016-03-09 22:37:08 +08:00
    中间挂个中间件咧~~~atlas
    sujin190
        3
    sujin190  
    OP
       2016-03-09 22:40:17 +08:00
    @lecher 是的,以前一直想高并发高并发的,没想过超过 mysql 查询数后的快速失败策略,学习到了,现在果断调低了连接池最大连接数, TorMySQL 应该有个队列等待超时策略才是
    sujin190
        4
    sujin190  
    OP
       2016-03-09 22:43:25 +08:00
    @cevincheung 像 php 来说,每个进程同一时刻只能接受一个请求,所以一般来说 mysql 并发查询不会太高,同时过高的并发过来会直接访问拒绝,但 tornado 不会,所以像 php 之类的来说是不会出现循环等待的,挂个中间件估计不能解决问题啊
    cevincheung
        5
    cevincheung  
       2016-03-09 22:53:55 +08:00
    那就换 postgresql 试试~~~
    pynix
        6
    pynix  
       2016-03-09 23:07:11 +08:00
    需要缓存来降低 MySQL 查询密度
    sujin190
        7
    sujin190  
    OP
       2016-03-09 23:15:33 +08:00
    @cevincheung 其实我瞬间并发超过了单机 mysql 的负载,加机器才是王道。。
    sujin190
        8
    sujin190  
    OP
       2016-03-09 23:17:09 +08:00
    @pynix 正有此意
    cevincheung
        9
    cevincheung  
       2016-03-09 23:20:03 +08:00
    @sujin190 试试单机 pgsql 抗
    skydiver
        10
    skydiver  
       2016-03-09 23:51:04 +08:00 via iPad
    mysql 连接数设置低一点,多了自动拒绝连接。
    另外客户端的重试策略也需要调整
    calease
        11
    calease  
       2016-03-09 23:53:23 +08:00 via iPhone
    中间加一个 redis 应该就没问题了吧。
    虽然高峰时第一次会 timeout ,
    但第二次就去缓存拿了,
    不会导致反复 retry 。
    还真没试过 tornado 直接连接 MySQL.
    lecher
        12
    lecher  
       2016-03-09 23:57:17 +08:00 via Android
    好奇到底是多大的并发量击穿了 MySQL 。
    有没有使用 Redis memcached 之类的缓存热数据?
    MySQL 如果不是没有建索引或特别复杂的联表查询,一秒跑几千个查询也是绰绰有余的。
    glasslion
        13
    glasslion  
       2016-03-10 00:22:50 +08:00
    @cevincheung pg 连接数高了也一般是用 pgbouncer , pgpool 抗吧
    alexapollo
        14
    alexapollo  
       2016-03-10 00:30:40 +08:00
    前端尽量少重试,防止雪崩
    scys
        15
    scys  
       2016-03-10 00:35:08 +08:00
    这种情况我倒是遇到过很多次,现在换用了 aiomysql 。
    对池管理方便比较简单,最大限制就行,反正并发量用更加简单的 iptables rate limit -_- ... 解决
    客户端等得起。
    cevincheung
        16
    cevincheung  
       2016-03-10 01:06:59 +08:00
    @glasslion
    中间多个中间件应该不会有突发的连接而且应该不会让数据库负载达到峰值,起码不会再出现这种情况吧?又不实际的执行什么东西。(我这么觉得)
    GTim
        17
    GTim  
       2016-03-10 07:04:38 +08:00
    好奇多大的并发量,不然讨论没意义啊
    sujin190
        18
    sujin190  
    OP
       2016-03-10 09:30:36 +08:00
    @cevincheung 正常其实压力不大,高峰突然停机重启才会出现这种情况
    sujin190
        19
    sujin190  
    OP
       2016-03-10 09:31:08 +08:00
    @skydiver 恩,以前没考虑到这种情况
    sujin190
        20
    sujin190  
    OP
       2016-03-10 09:32:00 +08:00
    @calease 因为平时压力很小,所以暂时还没走缓存,没想到突然网络异常重启就跪了
    sujin190
        21
    sujin190  
    OP
       2016-03-10 09:36:15 +08:00
    @lecher 并不高, 400 个 mysql 并发查询,有 join ,平时压力并不高所以。。
    sujin190
        22
    sujin190  
    OP
       2016-03-10 09:41:45 +08:00
    @scys aiomysql 遇到的问题还是一样的啊,并发高了之后, tornado 不会拒绝请求,当超过连接池最大链接数后,大量的请求都会阻塞在从连接池获取连接那,最后几乎所以得请求等待连接的时间就超过了请求超时时间, nginx 超时断开连接但 tornado 查询数据库请求却并未取消,客户端如果有重试的话,等待从连接池或取连接的请求会越来越多,最后几乎所有请求都会超时的,除非 tornado 内存爆了,否则是不会出现访问拒绝的
    ethego
        23
    ethego  
       2016-03-10 09:51:51 +08:00
    来几台主从,读写分离
    AlexaZhou
        24
    AlexaZhou  
       2016-03-10 10:27:30 +08:00
    支持,分析的很到位

    有没有一种办法可以在 Nginx 返回 504 时,通知 Tornado 把正在进行的查询取消掉呢
    zeayes
        25
    zeayes  
       2016-03-10 10:58:55 +08:00
    可以通过 ioloop 里面的_handlers 数量做下简单的过载保护。
    tabris17
        26
    tabris17  
       2016-03-10 11:16:05 +08:00
    所以要用连接池中间件啊,另外这也属于配置不当吧
    micyng
        27
    micyng  
       2016-03-10 12:23:31 +08:00 via iPhone
    tornado 本来就不合适做这种同步 io 的事情
    原理都不清楚就断言是坑
    sujin190
        28
    sujin190  
    OP
       2016-03-10 12:32:02 +08:00
    @AlexaZhou 这显然做不到吧,可以取消查询,但调用栈取消不掉啊
    sujin190
        29
    sujin190  
    OP
       2016-03-10 12:32:34 +08:00
    @micyng 我就没说同步 io 。。。
    sujin190
        30
    sujin190  
    OP
       2016-03-10 12:34:01 +08:00
    @tabris17 算是吧,哈哈,现在果断调小了最大连接数
    sujin190
        31
    sujin190  
    OP
       2016-03-10 12:34:22 +08:00
    @ethego 平时流量并不高,所以应该暂时不用
    Ge4Los
        32
    Ge4Los  
       2016-03-10 12:35:23 +08:00
    @micyng 确实不算坑啊。
    后端挂数据库查询阻塞, tornado 不就都阻塞了。
    lecher
        33
    lecher  
       2016-03-10 12:54:15 +08:00 via Android
    400 并发查询繁忙这个事情算是开发的时候数据管理的坑了,互联网项目为了性能,通常是在数据库存冗余数据,交给数据库的语句最好都是单表查询。
    要完全填掉这个坑,估计要改一下数据库的字段和查询语句,尽可能让执行的 SQL 语句实现单表查询,简单的 join 宁愿分成两步先取 ID 再去另一个表作 where in 。把数据库的处理提高一个量级,能支持到 4000 每秒,就没有这些事了。
    临时解决方案也得是加个内存缓存,先把复杂查询的结果都缓存一下。数据库做读写分离将数据库拆成主从结构也可以提高几倍查询性能。
    应急就是加配置加机器了,但是 SQL 处理上不去是硬伤。
    sujin190
        34
    sujin190  
    OP
       2016-03-10 13:02:00 +08:00
    @Ge4Los 我的问题正是 tornado 没有阻塞才出了这个故障。。
    sujin190
        35
    sujin190  
    OP
       2016-03-10 13:10:37 +08:00
    @lecher 恩,但同时不管提高 mysql 查询性能到多少,总有可能瞬间并发超过的可能,所以应用层能提供一个当严重超负载时快速失败的策略还是必须的
    zhicheng
        36
    zhicheng  
       2016-03-10 14:41:18 +08:00
    跟 tornado 没什么关系。跟不阻塞更没有关系了,有关系的是你的客户端实现。

    “网络异常后,客户端一直在重试,网络正常后,瞬间过来的并发高了十几倍”

    阻塞模型雪崩得更快,如果不能保证客户端的实现,那至少在 nginx 上开一个 rate limit 的插件。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2185 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 10:41 · PVG 18:41 · LAX 03:41 · JFK 06:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.