惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
T
Threatpost
P
Palo Alto Networks Blog
NISL@THU
NISL@THU
O
OpenAI News
Project Zero
Project Zero
G
GRAHAM CLULEY
P
Privacy International News Feed
A
Arctic Wolf
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
M
MIT News - Artificial intelligence
T
Threat Research - Cisco Blogs
S
Security @ Cisco Blogs
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
D
Docker
aimingoo的专栏
aimingoo的专栏
博客园 - 【当耐特】
N
Netflix TechBlog - Medium
云风的 BLOG
云风的 BLOG
雷峰网
雷峰网
W
WeLiveSecurity
P
Proofpoint News Feed
腾讯CDC
Cloudbric
Cloudbric
S
Secure Thoughts
C
Check Point Blog
博客园 - Franky
T
The Exploit Database - CXSecurity.com
T
Troy Hunt's Blog
GbyAI
GbyAI
Security Archives - TechRepublic
Security Archives - TechRepublic
Application and Cybersecurity Blog
Application and Cybersecurity Blog
月光博客
月光博客
C
Cyber Attacks, Cyber Crime and Cyber Security
I
Intezer
TaoSecurity Blog
TaoSecurity Blog
L
Lohrmann on Cybersecurity
V
Visual Studio Blog
F
Fortinet All Blogs
博客园 - 叶小钗
C
CXSECURITY Database RSS Feed - CXSecurity.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Recorded Future
Recorded Future
C
Cisco Blogs
博客园 - 司徒正美
Stack Overflow Blog
Stack Overflow Blog
Y
Y Combinator Blog
Apple Machine Learning Research
Apple Machine Learning Research

卡瓦邦噶!

服务器高性能网络调优 | 卡瓦邦噶! 为何写作 | 卡瓦邦噶! 读《金阁寺》 | 卡瓦邦噶! 雨季又来 | 卡瓦邦噶! MTU Probe 引起的初始延迟 | 卡瓦邦噶! 3.5 秒的固定延迟问题 | 卡瓦邦噶! 学习网络的一点经验 | 卡瓦邦噶! ARP 问题诊断 | 卡瓦邦噶! 网络断断续续…… | 卡瓦邦噶! Piccolo P2P 镜像分发 | 卡瓦邦噶! 一起看电影 | 卡瓦邦噶! 《征服C指针》 | 卡瓦邦噶! 我的姥姥 | 卡瓦邦噶! Python的哲学 Python 3.5的新特性 学校不教的计算机课 垃圾回收(GC)的三种基本方式 在编程中体验纯粹的快乐 从《美丽新世界》谈自由 在快钱实习 迷人的嗓音和迷人的故事——《Sleepyhead》 Python 的十个自然语言处理工具 记一个愚蠢的bug 一年炉石传说的游戏体验 《以撒的结合:重生》网页版图鉴 分清 C++的指针、引用和数组 笑话三则 自由比皇帝更伟大——《悲惨世界》笔记 Git 10 周年访谈:Linus 讲述背后故事 用 0x3f3f3f3f 设定最大int值的优点 Joel给计算机系学生的建议 一个词法分析器的简单实现 怎样才算健康的生活方式 MacVim 配置攻略 学习培训课程的视频效果好吗? 语言的控制 可悲的大多数 CSS样式思维导图 2014年终总结 奥巴马成为首位写程序的美国总统 青岛老城区的下水道好在哪里? 做优秀 UI 的七个建议(第二部分) 选择爱情的骑士 Git简明教程 Java集合总览 读 《1984》 java问答:终极父类(六)——等待/唤醒和接口 Java 问答:终极父类(五)——toString() Hyperlapse快速视频背后的技术细节 平庸之恶 Java程序员须知的七个日志管理工具 苏州 逛书摊随想 关于考试作弊 你的工作不仅仅是编程 推荐在线学习Java的英文资源 关注女性命运——《千禧年三部曲》 用好你的幻灯片——《演说之禅》 使用ReentrantLock和Lambda表达式让同步更纯净 新手学编程,从哪里开始? 程序员都是工程师吗? 不要学习代码,要学会思考 Java 问答:终极父类(四)——hashCode() Java 问答:终极父类(三)——finalize()和 getClass() 程序员职业之路的选择 我是一名摄影家 写给何小树的城市指南 为什么一些语言会比别的快? Java的常见误区与细节 跟朋友在一起玩游戏 死神永生——读《三体》 五种类型的程序员 创业圣经——读《黑客与画家》 纪念加西亚·马尔克斯 Junit中处理异常的另一种方式:catch-exception Java8采用Martin Fowler的方法创建内部DSL Linux HotSopt虚拟机GC线程的CPU占用率 J2EE概念介绍 如何成为一名黑客 Java 问答:终极父类(二)——equals()方法 为什么我喜欢Java Java 问答:终极父类(一)——clone()方法 七个改变世界的Java项目 java中默认类型转换的小问题 传统与创新 欢迎来到互联网 莫言和马尔克斯——读《生死疲劳》 位运算的妙用 读《人为什么活着》 写博客教会我的事情 中国特色操作系统 2013年总结 《永不妥协》影评 恨不相逢未嫁时——《廊桥遗梦》影评 如何优雅地使用PPT 给明年依然年轻的我们 天才与柱子 黑客守则和黑客精神 Looking for Freedom——《被解救的姜戈》 简洁之道
我的线程池怎么没了?
laixintao · 2019-02-28 · via 卡瓦邦噶!

事情的背景是这样的:我们有一个系统 A,通过 HTTP 请求到系统 B,系统 B 处理这个请求需要很长时间,并且请求频率也不同,有时候请求多,有时候请求少,所以会先返回给 A “HTTP 200 OK”, 然后再在一个线程池中继续处理这个请求。B 是一个 uWSGI app,线程池也是在 uWSGI 中开的线程池。

我们发现有一些请求处理到一般就没了,没有处理完成。但是即没有返回值,也没有 Exception 在日志里面。有问题的任务日志大体是这样子的(日志的格式是时间、module 名称、进程 ID、线程ID):

Python

1

2

3

2019-02-26 14:41:33,023 app 19272 139889324566272 start task...

2019-02-26 14:41:33,025 app 19272 139889324566272 task log...

2019-02-26 14:42:30,815 app 18134 139889324566272 start task...

这个问题蛋疼的地方在于,没有 Exception 日志,线程就这么罢工了,转而去处理下一个任务了。而且出现的频率大约是 5 分钟一次,我们有一个 staging 环境,一天大约四五次。俗话说如果一个 BUG 能重现,那么就约等于解决了。而这个问题,是我想了好几天都不知道该怎么复现的。出现的时候转瞬即逝,没有留下一丝痕迹……

我们先后尝试了加上更多的 log(但是没有什么用,task 停止的地方很随意,几乎没有什么规律),试图找出这些有问题的 task 的规律(也没有什么规律)。之前听一哥们讲过用 settrace 打印出来所有的调用和执行日志,我写好了 settrace 的代码,打算要用这个东西了,但是试了一下打出来这个日志是在是太慢了,3s 处理完成的任务,开了 settrace 要 40s 才能完成。所以就暂时没有尝试这个方法,打算实在没什么办法了再考虑开这个日志。

今天看 log 的时候,发现线程 ID 虽然一样,但是进程 ID 已经变了。这个进程是 uWSGI 的进程,于是我怀疑是 uWSGI 销毁了这个进程,并且一并销毁了进程里面的线程池。我的理由是:因为 uWSGI 是一个 HTTP 服务器,它只关心我的所有的 HTTP 请求是否处理完了。我们的模型是先返回 HTTP Response,然后再在线程池处理。对于 uWSGI 来说,它看到已经返回 Response 了,就认为这个请求已经处理完了,我就可以关闭这个进程了。

那么 uWSGI 什么情况下会销毁并重启进程呢?我去翻了文档,发现有几个让 uWSGI respawn 的参数,其中有一个是:

max-requests
argument: required_argument

shortcut: -R

parser: uwsgi_opt_set_64bit

help: reload workers after the specified amount of managed requests

uWSGI 在一个 process 处理的请求书达到了 max-reqeusts 就会 respawn 这个 process。

我在本地尝试了一个,开一个 uWSGI 设置 –max-requests=10,发现确实请求数在 10 左右(不是精确的10,比如说下面的日志就是 14)的时候,这个进程会被 respawn,日志如下:

Python

1

2

3

4

5

6

7

8

9

[pid: 65656|app: 0|req: 11/11] 127.0.0.1 () {28 vars in 292 bytes} [Thu Feb 28 13:42:17 2019] GET / => generated 26 bytes in 1 msecs (HTTP/1.1 200) 4 headers in 114 bytes (2 switches on core 0)

[pid: 65656|app: 0|req: 12/12] 127.0.0.1 () {28 vars in 292 bytes} [Thu Feb 28 13:42:18 2019] GET / => generated 26 bytes in 1 msecs (HTTP/1.1 200) 4 headers in 114 bytes (1 switches on core 0)

[pid: 65656|app: 0|req: 13/13] 127.0.0.1 () {28 vars in 292 bytes} [Thu Feb 28 13:42:19 2019] GET / => generated 26 bytes in 1 msecs (HTTP/1.1 200) 4 headers in 114 bytes (2 switches on core 0)

[pid: 65656|app: 0|req: 14/14] 127.0.0.1 () {28 vars in 292 bytes} [Thu Feb 28 13:42:20 2019] GET / => generated 26 bytes in 1 msecs (HTTP/1.1 200) 4 headers in 114 bytes (2 switches on core 0)

...The work of process 65656 is done. Seeya!

worker 1 killed successfully (pid: 65656)

Respawned uWSGI worker 1 (new pid: 65840)

[DEBUG]2019-02-28 13:42:25,693  font_manager1465 Using fontManager instance from /Users/laixintao/.matplotlib/fontList.json

[pid: 65840|app: 0|req: 15/15] 127.0.0.1 () {28 vars in 292 bytes} [Thu Feb 28 13:42:25 2019] GET / => generated 26 bytes in 299 msecs (HTTP/1.1 200) 4 headers in 114 bytes (1 switches on core 0)

这个时候也确实留下了没有处理完的任务。

OK,这个问题找到了,那么之前的现象也都解释通了,线程不是自己退出的,是父进程被杀掉了,所以没有留下 traceback。

解决的办法

虽然文档和 uWSGI 代码中将 max-reqeusts 作为了一个 required arg,但是从代码的实现上看,如果设置为 0 的话,这一行 if 是永远不会执行的,也就是说 worker process 就不会被重启。于是我在 uWSGI 的配置文件中将 max-requests 选项给删掉了。临时解决了这个问题。

其实这并不是最好的方案,uWSGI 作为一个 HTTP 服务器,应该只用来处理 HTTP 请求。文档里这样说:

During the life-cycle of your webapp you will reload it hundreds of times.

You need reloading for code updates, you need reloading for changes in the uWSGI configuration, you need reloading to reset the state of your app.

Basically, reloading is one of the most simple, frequent and dangerous operation you do every time.

禁用 max-requests 虽然能保证正常情况下 process 不会被 respawn ,但是在不正常的情况,还是有可能出现的。

像是这种耗时的任务,应该通过中间件发往一个专门的 worker 来处理的,通过任务执行的框架来保证每一个任务都被执行到了。

即使不用消息,也可以用 uWSGI 的 spooler 来处理耗时的任务。

被浪费掉的时间

其实如果我早一点去看一下 uWSGI 的日志,应该是可以发现规律的,每 5000 个请求进程被杀一次,而这也正好是未处理完的任务出现的时间。但是我觉得 uWSGI 日志没有什么有用的信息,有 Nginx + django 日志就够了,所以不久前将 uWSGI 的日志 disable 了。现在看来这部分日志还是有用的,毕竟运行的 Nginx 和 uWSGI,这两个进程的运行状态很重要。至于 Django 的日志,属于业务的日志。

另外从日志可以看到,任务被中断之后,Process ID 已经不一样了,但是 Thread ID 一样,我误以为是线程一直存在,只不过线程自己出了问题。试图在线下重现的时候,我也一直用的 Django 的 runserver 模式,而忽略了服务器上跑的进程其实是 uWSGI 的事实。两个进程都不一样,根本不可能重现。

但是这个 BUG 的根本原因,我归咎于我没有仔细看 uWSGI 的文档。文档和外面的 example 几乎都带上了 max-requests 这个参数,于是我也在自己的配置文件里面写上了。但是我误以为它的意思是:uWSGI 等待队列的最大长度,而不是处理过这么多 requests 就重启。

mrluanma 说:

抄东西过来不看是很不好的习惯。我是会把每个选项是否需要都想清楚的。

uWSGI 文档中的 The Art of Graceful Reloading 说:

Finally: Do not blindly copy & paste!

Please, turn on your brain and try to adapt shown configs to your needs, or invent new ones.

Each app and system is different from the others.

Experiment before making a choice.

记住了。