【编者按】新婚现场给服务器扩容 , 下班路上修Bug……对于程序员来说 , 这样的日常并不陌生 。 在新年第一天 , 国外一名叫Ali Josie 的软件工程师、信息安全爱好者就经历了找Bug、复现、修复这样的事情 , 并且还发表了一篇《This Is Why Our 3000 Apache Servers Went Down On The First Day of 2022》 , 具体是怎么一回事呢?作者 |Ali Josie 译者| 弯月
出品 | CSDN(ID:CSDNnews)
新年第一天 , 又恰逢周六 , 早上醒来却看到一堆整个基础设施挂掉的警报!我的一位同事就遭遇了这样的真实 , 他当时的心情可想而知 。
大清早
首先 , 最重要的是恢复服务 , 把服务宕机的影响降到最低 , 我们重启了所有Apache服务器 , 还好没有任何问题 。 接下来就要找出宕机的原因了 。 为什么所有服务器都在新年第一天宕机?这肯定不是偶然吧?
我们看到每台服务器上都记录了如下日志:
思考
回到错误本身 。我们每天早上都会做一次日志轮转 , 这样每天都用新的日志 。 因此要重启服务器 。 似乎Apache已经成功重启 , 但由于libgomp错误又宕机了 。
在网上搜索到的大量结果中寻找答案无异于大海捞针 , 于是我们开始阅读libgomp的源代码 , 看看究竟发生了什么 。 首先 , libgomp是什么?根据其主页的描述:
- “GOMP项目是C、C++和Fortran编译器OpenMP的一个实现……GOMP能简化所有GNU系统上的并行编程 。 ”
搜索了一下源代码 ,我们发现错误消息的唯一出处是这里:
所以显然 , 它在试图创建一个线程键 , 但出错了 。 检查pthread_key_create的手册:
- “pthread_key_create会创建用于线程专有的数据键 , 可在进程的所有线程中使用 。 pthread_key_create提供的键值是不透明的对象 , 用于定位线程专有的数据 。 虽然不同线程可以使用相同的键名称 , 通过pthread_setspecific绑定到键的值是按照线程维护的 , 在线程的整个生命周期都有效 。 ”
“pthread_key_create函数会在下述情况失败:
- 系统资源不足 , 无法创建另一个线程特定数据键 , 或每个进程的键总数达到了 PTHREAD_KEYS_MAX 上限 。
- 内存不足 , 无法创建键 。 ”

文章图片
所以说 , key只是一个0~1024之间(不含1024)的数字 , 赋给pthread_key_create的调用者 。 这些键由一个简单的CAS负责赋值 , 因此肯定有某个地方释放这些键 。 似乎我们找到了问题 。 我们只需要增大PTHREAD_KEYS_MAX 。 但是 , 这个值是常量 。 我们甚至找到了一个帖子 , 要求增加PTHREAD_KEYS_MAX:
- “pthread_key_create会拒绝超过 PTHREAD_KEYS_MAX pthread_key_t的创建请求 。 我遇到的问题是在NetBSD上Apache无法与多种模块一起工作 , 因为这个值太低了 。 时间长了 , 服务器就会陷入无法提供服务的状态 。 ”
我们开始调查为何重新加载Apache会进入libgomp的这段代码 。 所以显然 , 重载Apache会导致mod_php调用一个名为Imagick的模块 。 Imagick是什么?它是一个使用ImageMagick库来创建和修改图片的PHP扩展 。
怀疑
似乎关闭Imagick就可以避免使用libgomp , 这样就不会遇到最大线程数的问题了 。 而且只需要设置一个环境变量即可 。 似乎这个方案非常安全 , 但我们依然有一个最大的疑问:
- 为什么会在1月1日发生?而且这么大的范围 , 真的是偶然吗?
- 为什么用了这么多年都没事儿?会不会因为是某个更新的原因?
但这些都没有任何帮助 。 我们甚至怀疑过Apache的最大uptime 。
最后我们检查了Imagick的更新日志 , 发现了这个:
“多个修改来减少GOMP段错误的发生 , 包括:
- 在关闭过程中 , 如果可能 , 则调用omp_pause_resource_all
- 增加了 `imagick.shutdown_sleep_count` (默认10)和`imagick.set_single_thread`(默认On) 。两者都可以减少关闭时的段错误 。”
灵光乍现
在搜索了更多奇怪的东西后 , 我们想看看一月份有没有人遇到这个问题 。

文章图片
第一篇文章正是解开这一切的钥匙!

文章图片
突然想到……要是线程键从来没有被释放 , 会怎样?有可能吗?因为从部署依赖就从来没有发生过这个问题……所以我们重新计算了一下 , 1024个键 , 如果每天早上重新加载 , 就需要两年零10个月才会超过1024次重新加载 。 如果过去1024天内每天早上都分配一个线程键 , 而这个键从未被释放的话……
终于看到了一丝曙光 。 我们终于找到了重现该问题的方法 。 我们做了一个测试环境 , 用同样的服务器配置 , 然后简单地运行这个脚本 。
Apache在重新加载了1024次以后 , libgomp就报错了 。 现在所有问题都得到了解答 。
来看看能否通过增加环境变量MAGICK_THREAD_LIMIT(新版Imagick是OMP_THREAD_LIMIT) 。 很不幸 , 问题依旧 。 所以下一步就是更新Imagick版本到一个修正了该问题的版本(v3.5.0+) 。 很幸运 , 更新之后重新加载数千次都不会出问题 。
检查
还有个未解决的问题:新版Imagick有没有删除这个键?为了解答这个疑问 , 我们使用了一个工具:ltrace这个工具可以截获并记录程序运行的特定命令 。 我们首先在旧版本的Imagick(v.3.4.4)的服务器上运行ltrace:
-L告诉ltrace忽略默认的过滤器 , 以降低噪声 。
-c会在末尾汇总所有结果 。 而 /usr/sbin/apache2 -k graceful相当于systemctlreload apache 。
结果并没有出乎意料:
然后在新版(v3.6.0)上运行同样的命令:
总结
终于解决了 , 但是为什么这么长时间都没有重启过?我们决定不再在这个问题上浪费时间了 , 因为“如果排除一切不可能的选项 , 那么剩下来的那个无论多么不可思议 , 都是真相 。 ”
解决这个问题后感觉挺奇怪 。 虽然解决问题感觉挺自豪 , 但世界上还有很多长时间运行的服务器不知道在什么时候就会遇到这个问题 。
《 新程序员003 》正式上市 ,50余位技术专家共同创作 , 云原生和数字化的开发者们的一本技术精选图书 。 内容既有发展趋势及方法论结构 , 华为、阿里、字节跳动、网易、快手、微软、亚马逊、英特尔、西门子、施耐德等30多家知名公司云原生和数字化一手实战经验!
? 雷军再失猛将 , 小米12号创始员工李伟星离职 , 曾一起喝粥创立小米;英特尔54亿美元收购Tower半导体|极客头条
?50年前 , Hello World发明者第一次提交的Go代码长这样……
【OpenMP|新年第一天,3000台Apache服务器宕机】? 从1750亿到1.6万亿 , 人工智能未来:除了大模型 , 还有什么?
推荐阅读
- csdn|[译]新年第一天,3000台Apache服务器宕机
- 八仙|开学第一天,豪贤路小学的学生头戴自制“老虎帽”走秀
- 中国经营报|新能源汽车迎新年开门红 销量增速反超燃油车
- Google|关于NFT、元宇宙,YouTube首席产品官谈了新年计划
- Web|关于NFT、元宇宙,YouTube首席产品官谈了新年计划
- 频段|辣榜 | 京东新年开门红:小米AX3000路由器295元
- 设计|新年开工新装备,这六款“香到爆”华为笔记本,助你成为职场达人
- 新华社|空间站“神剧”|对联、福字、布老虎,空间站里欢欢喜喜迎新年
- 应用|6个必备效率App 帮助你实现新年目标
- 非法|初七上班第一天 山东表彰了这些优秀企业家