记一次MySQL5初始化被kill的问题排查 | 京东云技术团队

写在前面

由于测试环境JED申请比较繁琐,所以Eone提供了单机版Mysql供用户使用,近期Eone搭建Mysql5的时候发现莫名被kill了,容器规格是4C8G,磁盘30G

这不科学,之前都是可以的,镜像没变,配置没变,咋就不行了呢,一定不是我的问题,是机器的问题

问题排查

重现

通过多次搭建mysql5进行采样,发现并不是稳定复现,有一些容器是可以正常启动提供服务的,找到被mysql服务被kill的容器日志,发现是MySQL初始化被kill了,

/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --basedir=/usr/local/mysql --datadir=/export/data/mysql/data --user=admin --initialize-insecure

mysql配置文件:

[client]
socket=/tmp/mysql.sock

[mysqld]
basedir=/usr/local/mysql
datadir=/export/data/mysql/data
port=3306
server_id=1
socket=/tmp/mysql.sock
log-error=/tmp/mysqld.err
pid-file=/tmp/mysqld.pid

skip-host-cache
skip-name-resolve
skip-grant-tables

问题排查

初始化为什么被kill?

手动执行了下初始化的命令,发现直接被kill了

通过dmesg命令发现,貌似oom了

纳尼?怎么用了这么多内存?容器都没这么大内存诶

通过排查,发现MySQL有一个8G的匿名内存,这是哪来的???

将容器规格调整为32G内存,再次尝试,发现成功启动了,但是内存占用貌似不太正常

这不科学,正常的容器内存使用情况是这样的👇

MySQL配置不合理?

难道是因为mysql设置的大小不合理?默认不设置innodb_buffer_pool_size按理说应该是128M才对,抱着侥幸心理设置下试下

然而问题依旧,改了配置也没有效果,看来不是配置的问题

真是机器的问题?

对比了一下正常启动的机器,拿着ip找到运维,发现异常的机器是近期添加的云舰系统的机器,之前centos系统宿主机是正常的,新加的云舰系统宿主机都是异常的,看来真是机器的问题,这两台机器有啥差别呢,接下来运维同学开启了排查之路:

  • 首先,看看两台宿主机是不是物理CPU总数不一样

  • 再确认下CentOS是否开启了富容器功能

  • 对比了下两台宿主机的资源配置,发现好像是资源配置的问题

    正常的机器配置是这样的

    或许真的有可能,修改下open files限制再试下

    ulimit -n 1048576

    竟然可以了!!!MySQL正常提供服务了,真的是机器的问题

    image.png

    问题分析

    strace log分析

    28139 execve("/usr/local/mysql/bin/mysqld-debug", ["/usr/local/mysql/bin/mysqld-debu"..., "--initialize-insecure", "--basedir=/usr/local/mysql", "--datadir=/export/data/mysql/dat"..., "--user=admin"], 0x7ffe74bdcbe8 /* 294 vars */) = 0
    28139 brk(NULL)                         = 0x4b50000
    28139 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f93d3bf6000
    28139 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
    28139 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
    28139 fstat(3, {st_mode=S_IFREG|0644, st_size=16580, ...}) = 0
    28139 mmap(NULL, 16580, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f93d3bf1000
    28139 close(3)                          = 0
    28139 open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
    28139 read(3, "177ELF211

    可以看到最后用 mmap 一次分配了 16G 内存,然后就被杀了。

    mmap 前调用了 getrlimit , 猜测是 mysql 会根据系统资源限制来分配内存

    MySQL源码分析

    MySQL 源码中直接调用 getrlimit 的地方不多,排除了 ndb、innodb_memcached、libevent 之后,只有一处直接调用:

    static uint set_max_open_files(uint max_file_limit)
    {
      struct rlimit rlimit;
      uint old_cur;
      DBUG_ENTER("set_max_open_files");
      DBUG_PRINT("enter",("files: %u", max_file_limit));
    
      if (!getrlimit(RLIMIT_NOFILE,&rlimit))
      {
        old_cur= (uint) rlimit.rlim_cur;
        DBUG_PRINT("info", ("rlim_cur: %u  rlim_max: %u",
                            (uint) rlimit.rlim_cur,
                            (uint) rlimit.rlim_max));
        if (rlimit.rlim_cur == (rlim_t) RLIM_INFINITY)
          rlimit.rlim_cur = max_file_limit;
        if (rlimit.rlim_cur >= max_file_limit)
          DBUG_RETURN(rlimit.rlim_cur);                /* purecov: inspected */
        rlimit.rlim_cur= rlimit.rlim_max= max_file_limit;
        if (setrlimit(RLIMIT_NOFILE, &rlimit))
          max_file_limit= old_cur;                        /* Use original value */
        else
        {
          rlimit.rlim_cur= 0;                        /* Safety if next call fails */
          (void) getrlimit(RLIMIT_NOFILE,&rlimit);
          DBUG_PRINT("info", ("rlim_cur: %u", (uint) rlimit.rlim_cur));
          if (rlimit.rlim_cur)                        /* If call didn't fail */
            max_file_limit= (uint) rlimit.rlim_cur;
        }
      }
      DBUG_PRINT("exit",("max_file_limit: %u", max_file_limit));
      DBUG_RETURN(max_file_limit);
    

    其中逻辑是:如果系统的文件打开限制是 RLIM_INFINITY 或者比要设置的 max_file_limit 大,都返回系统的限制。

    这个函数也只被直接调用一次:

    uint my_set_max_open_files(uint files)
    {
      struct st_my_file_info *tmp;
      DBUG_ENTER("my_set_max_open_files");
      DBUG_PRINT("enter",("files: %u  my_file_limit: %u", files, my_file_limit));
    
      files+= MY_FILE_MIN;
      files= set_max_open_files(MY_MIN(files, OS_FILE_LIMIT)); // 获取最大打开文件数
      if (files <= MY_NFILE)
        DBUG_RETURN(files);
    
      // 分配内存
      if (!(tmp= (struct st_my_file_info*) my_malloc(key_memory_my_file_info,
                                                     sizeof(*tmp) * files,
                                                     MYF(MY_WME))))
        DBUG_RETURN(MY_NFILE);
    
      // 初始化
      /* Copy any initialized files */
      memcpy((char*) tmp, (char*) my_file_info,
             sizeof(*tmp) * MY_MIN(my_file_limit, files));
      memset((tmp + my_file_limit), 0,
            MY_MAX((int) (files - my_file_limit), 0) * sizeof(*tmp));
      my_free_open_file_info();                        /* Free if already allocated */
      my_file_info= tmp;
      my_file_limit= files;
      DBUG_PRINT("exit",("files: %u", files));
      DBUG_RETURN(files);
    }
    

    原来 MySQL5 会根据最大可打开文件数,提前为每个文件分配和初始化内存,在这个时候就可能分配过多内存,导致 OOM。MySQL8修复了这个问题。

    解决

    启动前设置ulimit

    ENTRYPOINT ["ulimit -n 1048576 && /home/admin/start.sh"]
    

    写在后面

    在linux中,open files设置过大会出现很多问题,应该合理控制open files数量

    参考文献

    learnku.com/articles/34…

    作者:京东零售 杨云龙

    来源:京东云开发者社区 转载请注明来源