赞同 2
分享

云服务器负载过高导致崩溃

简介:近期有一个线上小项目每隔一段时间被反应无法使用的情况,shell无法连接到云服务器,在云服务商监控数据中可以看到云服务器内部监控数据中断,而宿主机中的监控依然可以看到。
  2021.12.30
  Bug Man
  2
  94
  3.237.27.159
  中国.上海
 
 

面对这种情况,我的反应是只有强制重启服务器,连接上服务器之后再进行问题的排查。重启连接上服务器之后排查了很久都找不到问题在哪里,然后我决定给这台机器做一个监控,就用最简单的python脚本来获取服务器信息。因为我根据云服务后天的监控数据分析得出,服务器卡死的共通前兆就是内存使用率过高。

我们的服务器上是有celery的,可我们的服务都是在容器之中的,考虑到docker中获取宿主机使用参数不太靠谱我就在宿主机中使用crontab来起定时任务。先看一下我的python脚本代码把,我从运维朋友哪里得到查看CPU使用率top10、内存使用率top10的两条命令,用作获取机器使用率的工具。

import subprocess

from WeChat import WeChat


class MonitorServer:
    """ 监控服务器 """

    def get_memory_used_rate(self):
        """
        获取内存使用率
        `shell
            free
                          total        used        free      shared  buff/cache   available
            Mem:        4030596     1540296      122456       15912     2367844     2196720
            Swap:             0           0           0
        `
        """
        memory_total_cmd = """free | grep "Mem:" |awk '{print $2}'"""
        memory_available_cmd = """free | grep "Mem:" |awk '{print $7}'"""
        # 获取内存总数
        total_sub = subprocess.Popen(memory_total_cmd, shell=True, stdout=subprocess.PIPE)
        total_sub.wait()
        total = self.clear_shell_value(total_sub.stdout.read().decode("utf-8"))
        total = (float(total) if total.isdigit() else 0) / 1024 / 1024
        # 获取内存使用量
        available_sub = subprocess.Popen(memory_available_cmd, shell=True, stdout=subprocess.PIPE)
        available_sub.wait()
        available = self.clear_shell_value(available_sub.stdout.read().decode("utf-8"))
        available = (float(available) if available.isdigit() else 0) / 1024 / 1024
        used = (total - available)
        print("result:", total, used, used / total)
        return used / total

    def get_memory_used_top_10(self):
        """
        获取内存使用前十信息
        `shell
            USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
            root        2827  0.7  8.9 1663452 359960 ?      Sl   10:08   2:42 python3 manage.py celery worker -A PppsesDgBack.celery -l info
            ...
        `
        """
        memory_total_cmd = """ps auxw|head -1;ps auxw|sort -rn -k4|head -10"""
        # 获取内存总数
        total_sub = subprocess.Popen(memory_total_cmd, shell=True, stdout=subprocess.PIPE)
        total_sub.wait()
        result = total_sub.stdout.read().decode("utf-8")
        return result

    def get_cpu_used_top_10(self):
        """
        获取cpu使用前十信息
        `shell
            USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
            root        2827  0.7  8.9 1663452 359960 ?      Sl   10:08   2:42 python3 manage.py celery worker -A PppsesDgBack.celery -l info
            ...
        `
        """
        memory_total_cmd = """ps auxw|head -1;ps auxw|sort -rn -k3|head -10"""
        # 获取内存总数
        total_sub = subprocess.Popen(memory_total_cmd, shell=True, stdout=subprocess.PIPE)
        total_sub.wait()
        result = total_sub.stdout.read().decode("utf-8")
        return result

    def get_inner_ip(self):
        """
        获取本机ip
        `shell
            172.18.0.1
            172.17.16.11
        `
        """
        inner_ip_cmd = """
            /sbin/ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $2}'|tr -d 'addr:'
        """
        # 获取内存总数
        inner_ip_sub = subprocess.Popen(inner_ip_cmd, shell=True, stdout=subprocess.PIPE)
        inner_ip_sub.wait()
        result = inner_ip_sub.stdout.read().decode("utf-8")
        return result

    @staticmethod
    def clear_shell_value(target):
        return target.replace("\r\n", "").replace("\r", "").replace("\n", "")

    def memory_alarm(self, threshold_value=0.9):
        """
        内存告警阈
        @threshold_value :内存告警阈值
        """
        memory_used = self.get_memory_used_rate()
        if memory_used >= threshold_value:
            inner_ip = self.get_inner_ip()
            cpu_info = self.get_cpu_used_top_10()
            memory_info = self.get_memory_used_top_10()
            content = f"""内存使用率:{"{:.2f}".format(memory_used * 100)}%\n内网ip:\n{inner_ip}CPU使用信息:\n{cpu_info}"""
            self.alarm_notice(content=content)
            content = f"""内存使用率:{"{:.2f}".format(memory_used * 100)}%\n内网ip:\n{inner_ip}Memory使用信息:\n{memory_info}"""
            self.alarm_notice(content=content)
        return

    def alarm_notice(self, subject="服务告警:", content="忘记内容!"):
        """ 告警通知 """
        user = 'HuangJiaHui'
        # user = 'sorry|HuangJiaHui|MaZhenKai'
        wx = WeChat()
        wx.send_data(user, subject, content)
        return


if __name__ == '__main__':
    m_s = MonitorServer()
    m_s.memory_alarm(threshold_value=0.9)

上面的脚本的逻辑很清晰了,就是做到检查内存使用率,如果使用率达到某个阈值我就像企业微信发送告警信息。这里WeChat类可以参考我之前写过的一篇文章:用Python脚本给企业微信发送告警消息,这也是得益于之前在云中心工作的时候帮运维调试脚本的时候接触到了。

怎么获取数据服务器信息的问题得到了解决,那么接下来就考虑怎么用crontab定时来执行这个脚本。我们现在服务器上检查一下crontab服务的状态:systemctl status cron。确保服务启动之后我们就要开始编写crontab的表达式了,例如我想让我的定时任务每5分钟执行一次我可以这样写:*/5 * * * * /usr/bin/python3 /home/monitor.py。这里指的注意的是python一定要用绝对路径哦!下面我们就把这条表达式写入到corntab配置文件中,我们敲入命令:crontab -e。在最下面写入刚刚的表达式,像修改文件一样保存并退出。官方讲编写之后是不用重启进程的,但出于稳健的考虑还是重启一下:systemctl restart cron。之后我们就可以使用命令查看一些设置后了表达式:crontab -l,到此监控的脚本就已经部署好了,我们只需要等着服务器再次超载状态发来通知消息就知道是哪个程序在作妖了。

终于,内存负载超过90,并且有逐渐升高的趋势。这个时候我们查看发来的信息,首当其冲的居然是celery worker。其实我们目前并没有异步或者定时任务,但是我在settings.py之中设置了一个定时任务的空字典,如下。在搜索一番之后,我看有的网友说的是celery一直在fork新的进程而旧的进程又没有销毁掉,我们可以设置CELERYD_MAX_TASKS_PER_CHILD来限制每个woker最大执行任务的数量。但我最后直接将celery在supervisor中的托管任务给停掉了,这样解决了问题的根源,如果后期我们需要使用celery的时候记得做好限制和监控就可以了。

CELERYBEAT_SCHEDULE = {
    # "add": {
    #     "task": "PppsesDgBack.celery.add",  # 定时任务程序
    #     "schedule": crontab(minute='*/1'),  # 每分钟执行一次
    #     "args": (1, 2),  # 定时任务所需参数
    # },
}
CELERYD_MAX_TASKS_PER_CHILD = 3  # 每个worker最多执行3个任务就摧毁,避免内存泄漏