zoukankan      html  css  js  c++  java
  • [七夕特供版]:流年不利啊,才处理了线程死亡案件,这次更猛,进程连续死了好几个

    前言

    前两天发了一篇,关于线程神秘死亡的,过程也诸多波折,也很有意思。

    结果就在昨天,又遇到一起进程死亡案件,容我给大家细细道来。

    我们有一台专门定制的,供市场人员进行产品展示和推销的pc(配置是挺不错,英特尔i7 * 8核,32g内存);这是一台pc,装的win10系统,市场人员要展示的时候,就打开浏览器或者客户端进行演示即可。

    那后台服务怎么部署呢?我们是采用了Vmvare虚拟机的方式;pc开机自动启动Vmvare,自动启动Vmvare中的虚拟机;虚拟机启动时,通过systemd来启动我们的n个基础服务(redis、rabbitmq、mysql这类),和几个微服务,为了简单,都是做的单机部署。

    微服务也不是很多,整体采用了spring cloud架构,一个eureka注册中心,一个用户管理中心,再加上1个业务微服务(之前的演示比较简单,只演示核心功能,所以只需部署核心服务即可)。

    我们怎么做的呢,我们在/usr/lib/systemd/system下有个service:bootstrap.service。

    [Unit]
    Description=start all cad service
    After=network.target redis.service fdfs_storaged.service fdfs_trackerd.service mysqld.service nginx.service rabbitmq-server.service
    
    [Service]
    // 1
    ExecStart=/bin/bash -x /home/CAD_OneKeyDeploy/boot.sh
    Type=forking
    
    [Install]
    WantedBy=multi-user.target
    

    同时对这个service,设置成了开机启动。

    systemctl enable bootstrap.service
    

    这个开机脚本中,1处会去执行一个shell脚本,我们看看这个脚本的内容:

    #!/bin/bash
    
    
    echo "start to boot service"
    // 启动注册中心
    /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart eureka
    // 启动用户管理
    /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart scm
    // 启动核心业务服务
    /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart cad
    
    netstat -nltp
    
    jps -mlVv | grep -v "jps"
    
    
    echo "boot service finished"
    

    这个脚本的逻辑很简单,就是执行去startup.sh来顺序启动几个服务,startup.sh的逻辑也比较简单,就是根据参数来重启对应的服务,重启的逻辑就是,先停止服务,再启动服务,启动后,我们会调用如下url来检测服务是否启动成功:

    // eureka服务对应的检测url
    curl -s localhost:8761/actuator/health
    

    这个url,是spring boot的actuator自带的,如果服务正常,那么结果会是这样的:

    [root@localhost ~]#curl -s localhost:8761/actuator/health
    {"status":"UP"}
    

    简单来说,我们重启服务的逻辑很简单:

    1. 杀掉服务
    2. 启动服务
    3. 60s内每s循环检测url,查看状态是否为UP;如果超过60s,则打印错误日志后退出本shell

    之前,这个脚本一直运行得挺好的,这次演示,说要加一些演示功能,于是我们更新了版本,也在boot.sh里多加了2个要启动的服务。

    #!/bin/bash
    
    echo "start to boot service"
    
    /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart eureka
    /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart scm
    
    /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart cad
    // 这下边,加了两个服务
    /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart ccm
    /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart pas
    
    netstat -nltp
    
    jps -mlVv | grep -v "jps"
    
    
    echo "boot service finished"
    
    

    可以看到,我们加了两个服务,ccm和pas,同时,scm、cad都更新了版本。

    然后问题就来了,有一次我开机自启动虚拟机之后,就没管了;其他组的同学,也部署了他们的服务在他们自己的虚拟机上,要和我们联动,跟我反馈说,我们的服务挂了。

    我就上去看,一看发现eureka都没有,然后赶时间嘛,直接进行了手工重启,这个问题也就过了。

    结果前两天,另一位同事,负责上面新增的那个pas服务的,跟我说,也遇到了这个问题,他的描述更细致一点:

    虚拟机开启后,一开始用ps -ef都能看到启动的进程,比如eureka,用户中心慢慢都启动起来了;但是,再过一阵,再执行ps -ef,发现一个java进程都没有了。
    最终的结果就是,一个进程都没有启动起来。
    

    问题就是这么个情况了,这个问题还是比较严重,万一市场人员拿着去演示的时候,出这个事情,那问题就大发了!

    手里的bug它也不香了,还是优先处理这个bug吧!

    排查过程

    因为之前遇到过,因为第三方服务的ip地址发生了变化,我们这边没及时修改,导致服务启动失败的案例,我们优先排查了我们用户中心服务的日志。

    发现确实又出现了第三方服务的ip发生变化了,我们这边没及时改,导致服务启动的时候报错了。

    但是,仔细看了代码后,发现异常是捕获了的,并不会导致用户中心启动失败。

    用户中心的日志里显示如下:

    08-24 16:02:12.517 [main] INFO  [] ScmApplication
                    - 
    	Active Profile: dev
    	swagger-ui: 	http://127.0.0.1:9080/scm
    	---------------------------------------------------------- [ScmApplication.java:58]
    08-24 16:02:28.913 [Thread-10] INFO  [] o.s.c.n.e.serviceregistry.EurekaServiceRegistry
                    - Unregistering application SCM with eureka with status DOWN [EurekaServiceRegistry.java:65]
    08-24 16:02:28.914 [Thread-10] WARN  [] com.netflix.discovery.DiscoveryClient
                    - Saw local status change event StatusChangeEvent [timestamp=1598256148914, current=DOWN, previous=UP] [DiscoveryClient.java:1321]
    

    请注意看第一行日志的时间,是16:02:12.517,此时,打印的这行业务日志是代表我们已经成功启动了。

    再看第二行日志,是16:02:28.913,是Thread-10这个线程打印的,意思是说,向eureka取消注册,修改状态为down。

    这中间过了16s,到底是为什么就需要向eureka把服务取消注册了呢?

    查看另外的核心业务服务的日志,竟然发现:感觉日志没打印到日志文件,只有零星的几行日志。

    我现在也没想明白这个日志为啥会这样。

    但是,我们还是有其他线索,启动失败后,我们查看

    systemctl status bootstrap.service,没发现什么大问题,就是说失败了,因为没截图,我这里只展示模拟的demo的结果图:

    对于运维半桶水的我,也没看出什么线索,于是又去看journalctl日志。

    journalctl _SYSTEMD_UNIT=bootstrap.service
    

    journalctl日志分析

    由于我们尝试过多次重启,可能上文中的日志时间和下面的不是配套的,但不影响具体问题的分析哈。

    -- Logs begin at Mon 2020-08-24 16:39:30 CST, end at Tue 2020-08-25 10:40:56 CST. --
    // 1
    Aug 24 16:40:05 localhost.localdomain bash[14360]: + echo 'start to boot service'
    Aug 24 16:40:05 localhost.localdomain bash[14360]: start to boot service
    Aug 24 16:40:05 localhost.localdomain bash[14360]: + /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart eureka
    Aug 24 16:40:05 localhost.localdomain bash[14360]: +++ dirname /home/CAD_OneKeyDeploy/startup.sh
    Aug 24 16:40:05 localhost.localdomain bash[14360]: ++ cd /home/CAD_OneKeyDeploy
    ...
    

    执行上述的命令后,拿到的结果就是上面这样的,1处,第一行日志的时间为:

    start   16:40:05
    
    Aug 24 16:40:05 localhost.localdomain bash[14360]: + /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart eureka
    
    

    从上面的这一行可以看出,正在开始启动eureka。

    因为我已经把日志文件取回来了,我在本地搜索了一下关键字:startup.sh restart

    结果如下:

    Aug 24 16:40:05 localhost.localdomain bash[14360]: + /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart eureka
    Aug 24 16:40:30 localhost.localdomain bash[14360]: + /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart scm
    Aug 24 16:41:25 localhost.localdomain bash[14360]: + /bin/bash -x /home/CAD_OneKeyDeploy/startup.sh restart cad
    

    可以梳理出来:

    16:40:05   eureka 开始启动
    16:40:30   用户中心 开始启动
    16:41:25   业务系统cad  开始启动
    

    ok,现在是41:25的时候,开始启动业务系统了,日志也打印得很正常,直到:

    可以看到,到41点35的时候,就已经开始看不懂了。

    再到下边的日志,

    这边就更晕了,eureka都关闭了?

    柳暗花明

    和同事盯着这个日志看了半天,也没看出个花来,后边来了点灵感,看着那个时间,感觉有点巧合。

    再来看看,开始时间:

    start           16:40:05
    出现异常的时间   16:41:35
    

    算了下,好像刚好是90s?相差一个整数,看起来很像是一个配置项。

    于是就开始在搜索引擎里查找超时之类的关键字,然后就真的找到了这么一个配置。

    先是找到了一个默认配置项:

    DefaultTimeoutStartSec=, DefaultTimeoutStopSec=, DefaultRestartSec=
    设置启动/停止一个单元所允许的最大时长, 以及在重启一个单元时,停止与启动之间的间隔时长。 若仅设置一个整数而没有单位,那么单位是秒。 也可以在整数后面加上时间单位后缀: "ms"(毫秒), "s"(秒), "min"(分钟), "h"(小时), "d"(天), "w"(周) 。 DefaultTimeoutStartSec= 与 DefaultTimeoutStopSec= 的默认值都是 90s , 而 DefaultRestartSec= 的默认值是 100ms 。 对于 Type=oneshot 类型的 service 单元, 这些选项没有意义(相当于全部被禁用)。 对于其他类型的 service 单元,可以在单元文件中设置 TimeoutStartSec=, TimeoutStopSec=, RestartSec= 以覆盖此处设置的默认值 (参见 systemd.service(5))。 对于其他非 service 类型的单元, DefaultTimeoutStartSec= 是 TimeoutSec= 的默认值。
    

    引用自:http://www.jinbuguo.com/systemd/systemd-system.conf.html

    接下来,我找到了英文文档:

    man 5 systemd-system.conf
    
    DefaultTimeoutStartSec=, DefaultTimeoutStopSec=, DefaultRestartSec=
               Configures the default timeouts for starting and stopping of units, as well as the default time to sleep between automatic restarts of units, as configured per-unit in TimeoutStartSec=, TimeoutStopSec= and RestartSec=
               (for services, see systemd.service(5) for details on the per-unit settings). For non-service units, DefaultTimeoutStartSec= sets the default TimeoutSec= value.
    

    既然都有默认值了,那肯定可以针对每个service单独配置了,然后就又找到了如下选项:

    man 5 systemd.service
    
    TimeoutStartSec=
               Configures the time to wait for start-up. If a daemon service does not signal start-up completion within the configured time, the service will be considered failed and will be shut down again. Takes a unit-less value in  seconds, or a time span value such as "5min 20s". Pass "0" to disable the timeout logic. Defaults to DefaultTimeoutStartSec= from the manager configuration file, except when Type=oneshot is used, in which case the  timeout is disabled by default (see systemd-system.conf(5)).
    
    

    从这段话里,我们就能找到这个问题的答案了:

    TimeoutStartSec
    配置服务启动的最长等待时间。如果一个后台服务没有在指定时间内通知systemd,告知我们启动完成的话,service会被认为是失败了,将会被关闭。
    单位为s,也可以手动指定单位,如"5min 20s"。
    设置为0,则可以禁止超时的处理逻辑,相当于永不超时。默认值为DefaultTimeoutStartSec。
    

    解决很简单,把这个时间设置得很长就行了。

    结论

    问题的原因就是这样了,因为我们的脚本的执行时间,超过了90s,虽然此时已经启动了eureka,用户中心,但是在执行业务微服务的过程中,超过了90s,导致systemd又主动关闭了eureka和用户中心服务。

    而我们再细看systemctl status 时,终于知道什么叫timeout了。

    终于懂了,timeout,现在,看着这个词,又有了一点新的理解。

    demo复现

    我这边实际弄了个demo,复现了一下。

    test.service

    
    [Unit]
    Description=start test service
    After=network.target
    
    [Service]
    ExecStart=/bin/bash -x /root/boot.sh
    Type=forking
    TimeoutStartSec=20s
    
    [Install]
    WantedBy=multi-user.target
    

    这个service会启动一个shell,boot.sh。注意,超时为20s。

    boot.sh

    #!/bin/bash
    
    echo "start to listen on port 9999"
    
    nc -l 9999  &
    
    echo "sleep 30s"
    
    sleep 30
    
    echo "sleep over"
    

    nc -l 9999 &会启动一个进程,监听9999端口。

    然后我们的脚本,睡眠了30s,这会导致超时。

    超时后,按理来说,就会导致我们启动的监听9999端口的进程被杀掉。

    测试开始

    shell1上,启动service:

    [root@localhost ~]# systemctl restart test.service
    

    shell2上,查看9999端口的进程是否启动:

    [root@localhost system]#  netstat -ntlp|grep 9999
    tcp        0      0 0.0.0.0:9999            0.0.0.0:*               LISTEN      18957/nc            
    tcp6       0      0 :::9999                 :::*                    LISTEN      18957/nc           
    

    可以发现,已经启动了。然后我们拿到这里的18957这个pid。

    开启shell3,使用strace监听18957这个进程的signal相关的活动,因为,按理来说,systemd超时后,会给18957发送 sigterm 这个信号量。

    [root@localhost ~]# strace -e trace=signal -p 18957
    strace: Process 18957 attached
    --- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=1, si_uid=0} ---
    +++ killed by SIGTERM +++
    

    从上面看到,等待了一阵时间后,18957收到了systemd发来的SIGTERM信号。

    然后我们再切回shell1,看看结果:

    [root@localhost ~]# systemctl restart test.service
    Job for test.service failed because a timeout was exceeded. See "systemctl status test.service" and "journalctl -xe" for details.
    

    查看下status,test.service已经失败了,原因是超时:

    然后shell2中,再去查看9999端口的进程是否还在:

    ok,进程已死。

    总结

    好了,本案到这里就算结了,其实根本原因在于,毕竟是个半桶水的运维,而不是运维大佬,linux里面,还是很高深的,只能慢慢学了。

    ok,我是逐日,一个混迹成都的Java后端程序猿,欢迎大家勾兑。

  • 相关阅读:
    Moq4在.NET3.5和.NET4版本之间的差异
    TDD中的迭代
    洛谷 3413 萌数
    割点(tarjan)
    hdu-4507 吉哥系列故事——恨7不成妻
    hdu-3709 Balanced Number
    poj-3252 Round Numbers
    hdu 1007 Quoit Design 分治求最近点对
    LA 3905 Meteor 扫描线
    uva 11464
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/13560087.html
Copyright © 2011-2022 走看看