新年第一天,3000台Apache服务器宕机

新年第一天,3000台Apache服务器宕机作者 AliJosie 译者 弯月出品 CSDN ID CSDNnews 新年第一天 又恰逢周六 早上醒来却看到一堆整个基础设施挂掉的警报 我的一位同事就遭遇了这样的真实 他当时的心情可想而知 大清早首先 最重要的是恢复服务 把服务宕机的影响降到最低 我们重启了所有 Apache 服务器 还好没有任何问题 接下来就要找出宕机的原因了 为什么所有服务器都在新年第一天宕机 这肯定不是偶然吧 我们看到每台服务器上都记录了如下日志 AH00171 Gracefulrest

【编者按】新婚现场给服务器扩容,下班路上修Bug……对于程序员来说,这样的日常并不陌生。在新年第一天,国外一名叫Ali Josie 的软件工程师、信息安全爱好者就经历了找Bug、复现、修复这样的事情,并且还发表了一篇《This Is Why Our 3000 Apache Servers Went Down On The First Day of 2022》,具体是怎么一回事呢?

新年第一天,又恰逢周六,早上醒来却看到一堆整个基础设施挂掉的警报!我的一位同事就遭遇了这样的真实,他当时的心情可想而知。

大清早

首先,最重要的是恢复服务,把服务宕机的影响降到最低,我们重启了所有Apache服务器,还好没有任何问题。接下来就要找出宕机的原因了。为什么所有服务器都在新年第一天宕机?这肯定不是偶然吧?

我们看到每台服务器上都记录了如下日志:

AH00171: Graceful restart requested, doing restart libgomp: could not create thread pool destructor. 

libgomp是什么?我们先上网查了一下这个错误。ServerFault上有人问过这个问题,但没人回答,至少没有我们能用的东西。不过这个问题有点奇怪,因为提问者说他的服务器每隔24~36小时就会发生一次。

思考

回到错误本身。我们每天早上都会做一次日志轮转,这样每天都用新的日志。因此要重启服务器。似乎Apache已经成功重启,但由于libgomp错误又宕机了。

在网上搜索到的大量结果中寻找答案无异于大海捞针,于是我们开始阅读libgomp的源代码,看看究竟发生了什么。首先,libgomp是什么?根据其主页的描述:

  • “GOMP项目是C、C++和Fortran编译器OpenMP的一个实现……GOMP能简化所有GNU系统上的并行编程。”

所以它是OpenMP的实现。它怎么会出问题?

  • “pthread_key_create会创建用于线程专有的数据键,可在进程的所有线程中使用。pthread_key_create()提供的键值是不透明的对象,用于定位线程专有的数据。虽然不同线程可以使用相同的键名称,通过pthread_setspecific()绑定到键的值是按照线程维护的,在线程的整个生命周期都有效。”

有意思!那返回值是什么?

“pthread_key_create()函数会在下述情况失败:

  • 系统资源不足,无法创建另一个线程特定数据键,或每个进程的键总数达到了 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 HTTP的和libgomp的代码,但似乎一切都很正常,至少我们没发现任何问题。问题也无法重现,很快这个问题就会变成未解之谜。我们搜索了许多无关的关键字,甚至找到了一些关于“2038年问题”的帖子。

但这些都没有任何帮助。我们甚至怀疑过Apache的最大uptime。

最后我们检查了Imagick的更新日志,发现了这个:

“多个修改来减少GOMP段错误的发生,包括:

  • 在关闭过程中,如果可能,则调用omp_pause_resource_all
  • 增加了 imagick.shutdown_sleep_count (默认10)和imagick.set_single_thread(默认On)。两者都可以减少关闭时的段错误。”

这符合我们的猜测:将Imagick的最大线程数设置为1就能解决问题。但并没有解答有关时间的最大疑问。

灵光乍现

在搜索了更多奇怪的东西后,我们想看看一月份有没有人遇到这个问题。

在这里插入图片描述
第一篇文章正是解开这一切的钥匙!
在这里插入图片描述
突然想到……要是线程键从来没有被释放,会怎样?有可能吗?因为从部署依赖就从来没有发生过这个问题……所以我们重新计算了一下,1024个键,如果每天早上重新加载,就需要两年零10个月才会超过1024次重新加载。如果过去1024天内每天早上都分配一个线程键,而这个键从未被释放的话……






终于看到了一丝曙光。我们终于找到了重现该问题的方法。我们做了一个测试环境,用同样的服务器配置,然后简单地运行这个脚本。

 for i in seq{ 
   1..1100}; do sudo systemctl reload apache2;done 

重新加载apache2 1100次(多了76次作为冗余)。然后果然问题出现了!

Apache在重新加载了1024次以后,libgomp就报错了。现在所有问题都得到了解答。

来看看能否通过增加环境变量MAGICK_THREAD_LIMIT(新版Imagick是OMP_THREAD_LIMIT)。很不幸,问题依旧。所以下一步就是更新Imagick版本到一个修正了该问题的版本(v3.5.0+)。很幸运,更新之后重新加载数千次都不会出问题。

检查

还有个未解决的问题:新版Imagick有没有删除这个键?为了解答这个疑问,我们使用了一个工具:ltrace这个工具可以截获并记录程序运行的特定命令。我们首先在旧版本的Imagick(v.3.4.4)的服务器上运行ltrace:

ltrace -xpthread_key_*@libpthread.so.0 -L -c /usr/sbin/apache2 -k graceful 
  • -x是特定库中的函数的搜索字符串,此处为 libpthrad.so.0中的 pthrad_key_create 和 pthread_key_delete。
  • -L告诉ltrace忽略默认的过滤器,以降低噪声。
  • -c会在末尾汇总所有结果。而 /usr/sbin/apache2 -k graceful相当于systemctlreload apache。

结果并没有出乎意料:

% time seconds usecs/call calls function ------ ----------- ----------- ----------------------------- 100.00 0.000157 157 1 pthread_key_create ------ ----------- ----------- ----------------------------- 100.00 0.000157 1total 

3.4.4版只调用了pthread_key_create而没有删除!

然后在新版(v3.6.0)上运行同样的命令:

 % time seconds usecs/call calls function ------ ----------- ----------- ------------------------------ ------ ----------- ----------- ------------------------------ 100.00 0.000000 0 total 

看来,新版都没有使用多线程,因此完全没有创建键。

总结

终于解决了,但是为什么这么长时间都没有重启过?我们决定不再在这个问题上浪费时间了,因为“如果排除一切不可能的选项,那么剩下来的那个无论多么不可思议,都是真相。”

解决这个问题后感觉挺奇怪。虽然解决问题感觉挺自豪,但世界上还有很多长时间运行的服务器不知道在什么时候就会遇到这个问题。

原文链接:https://alijosie.medium.com/this-is-why-our-3000-apache-servers-went-down-on-the-first-day-of-2022-3cc5e

本文为 CSDN 翻译,转载请注明来源出处。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/207676.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午1:20
下一篇 2026年3月19日 下午1:20


相关推荐

  • pycharm安装后无法运行_pycharm代码运行不了

    pycharm安装后无法运行_pycharm代码运行不了问题:pycharm安装后不能执行python脚本的问题原因:pycharm没有设置解析器解决方法:打开pycharm->File->Settings->ProjectInterpreter->设置未你的python路径,我的是:C:\Python27\python.exe,你们根据各自python安装路径修改一下即可

    2022年8月26日
    12
  • 初探架构之美_结构优化设计

    初探架构之美_结构优化设计中国科学技术大学软件学院 王松 原创作品版权所有转载请注明出处本科时就听说过《架构之美》这本书,但一直觉得会很深奥而没敢去看。这次课外阅读书籍中再次出现这本书,于是下定决心拜读一下这本著作。敲了几年代码,总觉得代码比较实际,架构比较空洞。“虚幻”的架构往往让人摸不着头脑,因为架构难以落在纸上,人们谈起架构时又总是以一种只可意会不可言传的姿态。美丽的架构无法定义,可它却一定是自然的、

    2025年8月11日
    4
  • 西门子plc16进制转10进制_16进制字符串转数字

    西门子plc16进制转10进制_16进制字符串转数字二进制在C#中无法直接表示,我们一般用0和1的字符串来表示一个数的二进制形式。比如4的二进制为“100”。下面介绍C#里面用于进制转换的方法。十进制转换为二进制(int–>string)System.Convert.ToString(d,2);//d为int类型以4为例,输出为100十六进制转换为二进制(int–>string)System.Conver…

    2025年6月5日
    4
  • 孙鑫VC视频教程笔记——Lesson2

    孙鑫VC视频教程笔记——Lesson21.函数重载发生在一个类里的,而函数的覆盖是发生在两个类之间(比如父类和子类)2.如果在基类中有一个函数是虚函数,子类有调用子类的,子类没有调用父类的。3.纯虚函数类似于JAVA中的接口类,必须被实现了才能创建对象。4.引用相当于给变量起别名,它必须在声明时就初始化。tip:按F2可以重命名文件5.在包含头文件时,使用“”和6.预编译指令符#ifndef#define#endif可以用于防止重

    2022年5月16日
    45
  • struts2拦截器详解_拦截和修改tcp数据

    struts2拦截器详解_拦截和修改tcp数据Struts2中的拦截器和servelt中的过滤器是非常的相似的。如果学过过滤器的话,肯定能够感觉的到,尽管有些微的不同。可是struts2的拦截器到底如何使用呢,为什么会有这些配置呢?接下来一一来看。 过滤器和拦截器是非常相似的,过滤器publicinterfaceFilter接口里面有三个方法: init(FilterConfigfilterConfig),des

    2026年4月16日
    5
  • python判断linux中文件是否存在_Python判断文件是否存在的三种方法

    python判断linux中文件是否存在_Python判断文件是否存在的三种方法通常在读写文件之前,需要判断文件或目录是否存在,不然某些处理方法可能会使程序出错。所以最好在做任何操作之前,先判断文件是否存在。这里将介绍三种判断文件或文件夹是否存在的方法,分别使用os模块、Try语句、pathlib模块。1.使用os模块os模块中的os.path.exists()方法用于检验文件是否存在。判断文件是否存在importosos.path.exists(test_file.txt…

    2022年6月29日
    120

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号