升级到 MySQL 8.4,MySQL 启动报错:io_setup() failed with EAGAIN

news/2024/7/4 8:44:03/文章来源:https://www.cnblogs.com/ivictor/p/18277923

问题

最近碰到一个 case,一台主机上,部署了多个实例。之前使用的是 MySQL 8.0,启动时没有任何问题。但升级到 MySQL 8.4 后,部分实例在启动时出现了以下错误。

[Warning] [MY-012582] [InnoDB] io_setup() failed with EAGAIN. Will make 5 attempts before giving up.
[Warning] [MY-012583] [InnoDB] io_setup() attempt 1.
[Warning] [MY-012583] [InnoDB] io_setup() attempt 2.
[Warning] [MY-012583] [InnoDB] io_setup() attempt 3.
[Warning] [MY-012583] [InnoDB] io_setup() attempt 4.
[Warning] [MY-012583] [InnoDB] io_setup() attempt 5.
[ERROR] [MY-012584] [InnoDB] io_setup() failed with EAGAIN after 5 attempts.
[ERROR] [MY-012954] [InnoDB] Cannot initialize AIO sub-system
[ERROR] [MY-012930] [InnoDB] Plugin initialization aborted with error Generic error.
[ERROR] [MY-010334] [Server] Failed to initialize DD Storage Engine
[ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
[ERROR] [MY-010119] [Server] Aborting
[System] [MY-010910] [Server] /usr/local/mysql/bin/mysqld: Shutdown complete (mysqld 8.4.0)  MySQL Community Server - GPL.
[System] [MY-015016] [Server] MySQL Server - end.

下面我们来分析下这个报错的具体原因及解决方法。

定位过程

首先搜索下这个报错是在哪个文件产生的。

# grep "io_setup() failed" -r /usr/src/mysql-8.4.0
/usr/src/mysql-8.4.0/storage/innobase/os/os0file.cc:          ib::warn(ER_IB_MSG_757) << "io_setup() failed with EAGAIN."
/usr/src/mysql-8.4.0/storage/innobase/os/os0file.cc:            << "io_setup() failed with EAGAIN after "

接着分析该文件中产生报错的具体函数。

// storage/innobase/os/os0file.cc
bool AIO::linux_create_io_ctx(ulint max_events, io_context_t *io_ctx) {
  ssize_t n_retries = 0;
  for (;;) {
    memset(io_ctx, 0x0, sizeof(*io_ctx));
    int ret = io_setup(max_events, io_ctx);

    if (ret == 0) {
      /* Success. Return now. */
      return (true);
    }
    switch (ret) {
      case -EAGAIN:
        if (n_retries == 0) {
          /* First time around. */
          ib::warn(ER_IB_MSG_757) << "io_setup() failed with EAGAIN."
                                     " Will make "
                                  << OS_AIO_IO_SETUP_RETRY_ATTEMPTS
                                  << " attempts before giving up.";
        }
        if (n_retries < OS_AIO_IO_SETUP_RETRY_ATTEMPTS) {
          ++n_retries;
          ib::warn(ER_IB_MSG_758) << "io_setup() attempt " << n_retries << ".";
          std::this_thread::sleep_for(OS_AIO_IO_SETUP_RETRY_SLEEP);
          continue;
        }

        /* Have tried enough. Better call it a day. */
        ib::error(ER_IB_MSG_759)
            << "io_setup() failed with EAGAIN after "
            << OS_AIO_IO_SETUP_RETRY_ATTEMPTS << " attempts.";
        break;
        ...
    }
    ib::info(ER_IB_MSG_762) << "You can disable Linux Native AIO by"
                               " setting innodb_use_native_aio = 0 in my.cnf";

    break;
  }
  return (false);
}

可以看到,错误信息主要是在执行io_setup,产生 EAGAIN 错误时打印的。

函数中的io_setup是一个 Linux 系统调用,用于初始化一个异步 I/O (AIO) 上下文(context)。异步 I/O(AIO)允许程序在发出 I/O 操作请求后继续执行其他工作,而不是等待操作完成。io_setup是 Linux 内核提供的异步 I/O 接口,通常用于高性能应用程序和数据库系统,以实现非阻塞 I/O 操作。max_events 指定了这个异步 I/O 上下文可以处理的最大并发 I/O 请求数。io_setup执行成功时会返回 0,失败时则返回 -1,并通过 errno 表示具体错误。

当返回的错误是 EAGAIN 时,则意味着指定的 max_events 超过了系统允许的最大异步 I/O (AIO) 事件数。

系统允许创建的最大异步 I/O 事件数是在/proc/sys/fs/aio-max-nr中定义的,默认值跟系统有关,通常是 65536。

所以,解决方法找到了,直接调整/proc/sys/fs/aio-max-nr的值即可。

# echo 1048576 > /proc/sys/fs/aio-max-nr

注意这种只是临时修改,系统重启就会失效。如果要永久修改,需调整 /etc/sysctl.conf。

# vim /etc/sysctl.conf
fs.aio-max-nr=1048576
# sysctl -p

问题解决了,接下来我们分析下同一台主机,为什么之前的 MySQL 8.0 没问题,升级到 MySQL 8.4 就报错了呢?

这个时候,就需要分析函数中 max_events 的生成逻辑了。

堆栈信息

下面是AIO::linux_create_io_ctx函数被调用的堆栈信息。

#0  AIO::linux_create_io_ctx (max_events=256, io_ctx=0x7fffe02d1500)
    at /usr/src/mysql-8.4.0/storage/innobase/os/os0file.cc:2559
#1  0x0000000004db1649 in AIO::init_linux_native_aio (this=0x7fffe02d1d70)
    at /usr/src/mysql-8.4.0/storage/innobase/os/os0file.cc:6139
#2  0x0000000004db16ed in AIO::init (this=0x7fffe02d1d70)
    at /usr/src/mysql-8.4.0/storage/innobase/os/os0file.cc:6167
#3  0x0000000004db1826 in AIO::create (id=LATCH_ID_OS_AIO_IBUF_MUTEX, n=256, n_segments=1)
    at /usr/src/mysql-8.4.0/storage/innobase/os/os0file.cc:6200
#4  0x0000000004db1a2b in AIO::start (n_per_seg=256, n_readers=64, n_writers=4)
    at /usr/src/mysql-8.4.0/storage/innobase/os/os0file.cc:6254
#5  0x0000000004db261a in os_aio_init (n_readers=64, n_writers=4)
    at /usr/src/mysql-8.4.0/storage/innobase/os/os0file.cc:6514
#6  0x0000000004ee6b4d in srv_start (create_new_db=false)
    at /usr/src/mysql-8.4.0/storage/innobase/srv/srv0start.cc:1743
#7  0x0000000004bdfc92 in innobase_init_files (dict_init_mode=DICT_INIT_CHECK_FILES, tablespaces=0x7fffe77bf720)
    at /usr/src/mysql-8.4.0/storage/innobase/handler/ha_innodb.cc:5744
#8  0x0000000004bf0e22 in innobase_ddse_dict_init (dict_init_mode=DICT_INIT_CHECK_FILES, tables=0x7fffe77bf740, 
    tablespaces=0x7fffe77bf720) at /usr/src/mysql-8.4.0/storage/innobase/handler/ha_innodb.cc:13133
#9  0x00000000049153b8 in dd::bootstrap::DDSE_dict_init (thd=0xb7ce6b0, dict_init_mode=DICT_INIT_CHECK_FILES, 
    version=80300) at /usr/src/mysql-8.4.0/sql/dd/impl/bootstrap/bootstrapper.cc:746
#10 0x0000000004916195 in dd::bootstrap::restart_dictionary (thd=0xb7ce6b0)
    at /usr/src/mysql-8.4.0/sql/dd/impl/bootstrap/bootstrapper.cc:907
#11 0x0000000003814e54 in bootstrap::handle_bootstrap (arg=0x7fffffffcf20)
    at /usr/src/mysql-8.4.0/sql/bootstrap.cc:340
#12 0x0000000005792a92 in pfs_spawn_thread (arg=0xb7ece50) at /usr/src/mysql-8.4.0/storage/perfschema/pfs.cc:3051
#13 0x00007ffff7bc6ea5 in start_thread () from /lib64/libpthread.so.0
#14 0x00007ffff5ff0b0d in clone () from /lib64/libc.so.6

堆栈中的重点是 #6 的srv_start函数,这个函数会调用os_aio_init来初始化异步 I/O 系统。

// storage/innobase/srv/srv0start.cc
dberr_t srv_start(bool create_new_db) {
  ...
  if (!os_aio_init(srv_n_read_io_threads, srv_n_write_io_threads)) {
    ib::error(ER_IB_MSG_1129);

    return (srv_init_abort(DB_ERROR));
  }
...

调用 os_aio_init 时,会传递两个参数:srv_n_read_io_threads 和 srv_n_write_io_threads。这两个参数实际上对应的就是 MySQL 中的 innodb_read_io_threads 和 innodb_write_io_threads,这两个参数分别用来表示 InnoDB 中用于读操作、写操作的 I/O 线程数。

如果初始化失败,会打印ER_IB_MSG_1129错误。

ER_IB_MSG_1129是一个预定义的错误代码,对应的错误信息是在share/messages_to_error_log.txt中定义的。

ER_IB_MSG_1129
  eng "Cannot initialize AIO sub-system"

所以,错误日志中看到的[ERROR] [MY-012954] [InnoDB] Cannot initialize AIO sub-system其实就是在这里打印的。

有的童鞋可能猜到了,异步 I/O 系统初始化失败与 innodb_read_io_threads 和 innodb_write_io_threads 的设置有关,事实也确实如此。

下面,我们分析下 MySQL 启动过程中需要初始化多少个异步 I/O 请求。

MySQL 启动过程中需要初始化多少个异步 I/O 请求?

异步 I/O 的初始化主要是在AIO::linux_create_io_ctx中进行的,接下来,我们分析下AIO::linux_create_io_ctx的调用场景:

场景1:AIO::is_linux_native_aio_supported

该函数用来判断系统是否支持 AIO。

bool AIO::is_linux_native_aio_supported() {
  ...
  if (!linux_create_io_ctx(1, &io_ctx)) {
    return (false);
  }
  ...
}

这里只会初始化 1 个异步 I/O 请求。

场景2:AIO::init_linux_native_aio

该函数是用来初始化 Linux 原生异步 I/O 的。

dberr_t AIO::init_linux_native_aio() {
  ...
  ulint max_events = slots_per_segment();
  for (ulint i = 0; i < m_n_segments; ++i, ++ctx) {
    if (!linux_create_io_ctx(max_events, ctx)) {
      return (DB_IO_ERROR);
    }
  }
  return (DB_SUCCESS);
}

函数中的 m_n_segments 是需要创建的异步 I/O (AIO) 上下文的数量,max_events 是每个异步 I/O (AIO) 上下文支持的最大并发 I/O 请求数。所以,这个函数会初始化 m_n_segments * max_events 个异步 I/O 请求。

在 MySQL 的启动过程中,AIO::is_linux_native_aio_supported 只被调用一次,而 AIO::init_linux_native_aio 则会被调用三次,分别用于 insert buffer 线程、读线程和写线程的初始化。

这两个函数都是在AIO::start中调用的。

// storage/innobase/os/os0file.cc
bool AIO::start(ulint n_per_seg, ulint n_readers, ulint n_writers) {
#if defined(LINUX_NATIVE_AIO)
  /* Check if native aio is supported on this system and tmpfs */
  if (srv_use_native_aio && !is_linux_native_aio_supported()) {
    ib::warn(ER_IB_MSG_829) << "Linux Native AIO disabled.";
    srv_use_native_aio = false;
  }
#endif /* LINUX_NATIVE_AIO */
  ...
  if (0 < n_extra) {
    ...
    s_ibuf = create(LATCH_ID_OS_AIO_IBUF_MUTEX, n_per_seg, 1);
   ...
  }
  ...
  s_reads =
      create(LATCH_ID_OS_AIO_READ_MUTEX, n_readers * n_per_seg, n_readers);
  ...
  s_writes =
      create(LATCH_ID_OS_AIO_WRITE_MUTEX, n_writers * n_per_seg, n_writers);

  ...
  return true;
}

函数中的 n_per_seg 实际上就是 max_events。

n_per_seg 等于 8 * OS_AIO_N_PENDING_IOS_PER_THREAD,因为 OS_AIO_N_PENDING_IOS_PER_THREAD 是个常量,值为 32,所以 n_per_seg 等于 256。

AIO::init_linux_native_aio 中的 m_n_segments 实际上表示的是线程的数量:对于 insert buffer 线程,线程数为 1;对于读操作线程,线程数为 n_readers;对于写操作线程,线程数为 n_writers。

怎么知道 m_n_segments 就是线程的数量?

关键是在创建 AIO 对象时,会调用 AIO 的构造函数,而构造函数中的 m_slots 又决定了 max_events 的值。

AIO *AIO::create(latch_id_t id, ulint n, ulint n_segments) {
  ...
  AIO *array =
      ut::new_withkey<AIO>(UT_NEW_THIS_FILE_PSI_KEY, id, n, n_segments);
  ...
}

AIO::AIO(latch_id_t id, ulint n, ulint segments)
    : m_slots(n),
      m_n_segments(segments),
...
  
[[nodiscard]] ulint slots_per_segment() const {
  return (m_slots.size() / m_n_segments);
}

以读线程为例,AIO::create中的 n 等于 n_readers * n_per_seg,n_segments 等于 n_readers。

在初始化 AIO 对象时,n_readers * n_per_seg 将赋值给 m_slots,n_readers 将赋值给 m_n_segments。

所以AIO::init_linux_native_aio中的 max_events = slots_per_segment() = m_slots.size() / m_n_segments = n_readers * n_per_seg / n_readers = n_per_seg。

计算公式

基于上面的分析,我们可以推论出 MySQL 在启动过程中需要初始化的异步 I/O 请求数的计算公式。

(1 + innodb_read_io_threads + innodb_write_io_threads) * 256 + 1

最后一个 1 是判断系统是否支持 AIO。

验证

下面通过一个具体的案例来验证下上面的计算公式是否正确。

首先通过/proc/sys/fs/aio-nr查看当前系统中已分配的异步 I/O 请求的数量。

# cat /proc/sys/fs/aio-nr
4866

接着,启动一个 MySQL 8.4 实例,启动命令中显式设置 innodb_read_io_threads 和 innodb_write_io_threads。

# /usr/local/mysql8.4/bin/mysqld --defaults-file=/etc/my_3308.cnf --innodb-read-io-threads=64 --innodb-write-io-threads=4 &

实例启动后,再次查看/proc/sys/fs/aio-nr

# cat /proc/sys/fs/aio-nr
22531

两个数之间的差值是 17665。

按照之前的公式计算,也是 17665,完全吻合。

(1 + 64 + 4) * 256 + 1 = 17665

为什么 MySQL 8.4 启动会报错呢?

因为 innodb_read_io_threads 的默认值在 MySQL 8.4 中发生了变化。

在 MySQL 8.4 之前,innodb_read_io_threads 默认为 4,而在 MySQL 8.4 中,innodb_read_io_threads 默认等于主机逻辑 CPU 的一半,最小是 4,最大是 64。

static MYSQL_SYSVAR_ULONG(
    read_io_threads, srv_n_read_io_threads,
    PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
    "Number of background read I/O threads in InnoDB.", nullptr, nullptr,
    std::clamp(std::thread::hardware_concurrency() / 2, 4U, 64U), 1, 64, 0);

不巧,问题 case 主机的逻辑 CPU 是 128 核,所以就导致了 innodb_read_io_threads 等于 64。

这就意味着,在/proc/sys/fs/aio-max-nr等于 65536(默认值)的情况下,该主机上只能启动 3(65536/17665) 个 MySQL 8.4 实例。

结论

  1. MySQL 在启动时,如果出现io_setup() failed with EAGAIN错误,可适当增加/proc/sys/fs/aio-max-nr的值。
  2. MySQL 在启动过程中需要初始化的异步 I/O 请求数等于(1 + innodb_read_io_threads + innodb_write_io_threads) * 256 + 1
  3. innodb_read_io_threads 的默认值在 MySQL 8.4 中发生了变化,建议在配置文件中显式指定。

参考资料

io_setup(2) — Linux manual page: https://www.man7.org/linux/man-pages/man2/io_setup.2.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/735777.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Apache DolphinScheduler社区又一PMC获推选通过!

PROFILE姓名:程鑫 公司:阿里云 职位:开发工程师 Github ID: rickchengx 从事领域:大数据调度系统开发 兴趣爱好:健身推举理由 他于2022年8月2日开始了他的DolphinScheduler之旅,在社区工作了将近两年,并于2023年5月12日成为Committer。成为Committer后的一年里,他继续保…

BOSHIDA 探讨DC/AC电源模块为绿色能源应用提供可靠的转换解决方案

BOSHIDA 探讨DC/AC电源模块为绿色能源应用提供可靠的转换解决方案 DC/AC电源模块是一种能够将直流电源转换为交流电源的装置。随着绿色能源的不断发展和应用,DC/AC电源模块在可再生能源、电动车辆、太阳能发电等领域中扮演着重要的角色。本文将着重探讨DC/AC电源模块为绿色能源…

Centos7 安装Rabbitmq3.9.11

安装erlang 安装依赖包yum -y install gcc glibc-devel make ncurses-devel openssl-devel xmlto perl wget gtk2-devel binutils-devel下载wget https://github.com/erlang/otp/releases/download/OTP-24.1.7/otp_src_24.1.7.tar.gz解压tar -zxvf otp_src_24.1.7.tar.gz转移到…

mysql数据库简介

一、数据库介绍 1.数据库基本概念 数据(Data) 描述事物的符号记录 包括数字,文字、图形、图像、声音、档案记录等 以“记录”形式按统一的格式进行存储 表 将不同的记录组织在一起 用来存储具体数据 数据库 表的集合,是存储数据的仓库 以一定的组织方式存储的相互有关的数据…

mac 电脑查看已安装的谷歌插件

1、打开谷歌浏览器 地址栏上面输入  chrome://version/ 2、找到 个人资料路径: /Users/admin/Library/Application Support/Google/Chrome/Default3、打开个新窗口 地址了上输入:个人资料路径 ,并找到 Extensions/ 文件夹 4、里面都是已安装的谷歌插件, 想要哪个插件…

Nordic nRF Connect SDK(NCS) VS Code 安装记录

1.Nordic SDK Nordic有2套并存的SDK:老的nRF5 SDK和新的nRF Connect SDK(NCS),两套SDK相互独立,大家选择其中一套进行开发即可。 一般而言,如果你选择的芯片是nRF51或者nRF52系列,那么推荐使用nRF5 SDK。 如果你选择的是Nordic最新产品系列,比如nRF53或者nRF9160,那么…

浅谈 K8s Service 网络机制

浅谈 K8s Service 网络机制 云原生运维圈 2024-07-01 12:03 上海 1人听过以下文章来源于腾讯云原生 ,作者王成腾讯云原生. 云原生技术交流阵地,汇聚云原生最新技术资讯、文章、活动,以及云原生产品及用户最佳实践内容。王成,腾讯云研发工程师,Kubernetes member,从事数据…

全新升级!中央集中式架构功能测试为新车型保驾护航

目前,文中所述功能测试新方案均已应用于国内多款新架构车型的研发,得到了广泛认可。 “软件定义汽车”新时代下,整车电气电气架构向中央-区域集中式发展已成为行业共识,车型架构的变革带来更复杂的整车功能定义、更多的新技术的应用(如SOA服务化、智能配电等)和更…

江门MES制造执行系统:助力工厂实现智能化管理

江门MES制造执行系统(MES)在工厂实现智能化管理方面发挥着重要作用,以下是它的一些助力方面: 实时监控与控制:江门MES系统可以实时监控生产过程中的各个环节,包括设备状态、生产进度、质量指标等,帮助工厂管理人员及时了解生产情况并做出相应的调整和控制。生产计划与排程…

搭建微信小程序

在开发小程序之前,您需要先注册微信小程序。进入小程序页面,单击前往注册,根据指引填写信息和提交相应的资料,点击注册,完成账号申请。使用申请的微信公众平台账号登录小程序后台,单击开发管理> 开发设置,可以看到小程序的AppID,请记录AppID,后续操作中需要使用。 …

《从零开始学Python》(第二版) PDF读书分享

Python 是一种面向对象、解释型计算机程序设计语言,由 Guido van Rossum 于 1989 年底发明,第一个公开发行版发行于 1991 年。Python 语法简洁而清晰,具有丰富和强大的类库。它常被昵称为胶水语言,能够把用其他语言制作的各种模块(尤其是 C/C++)很轻松地联结在一起。 Pyt…

记一次 .NET某网络边缘计算系统 卡死分析

一:背景 1. 讲故事 早就听说过有什么 网络边缘计算,这次还真给遇到了,有点意思,问了下 chatgpt 这是干嘛的 ?网络边缘计算是一种计算模型,它将计算能力和数据存储位置从传统的集中式数据中心向网络边缘的用户设备、传感器和其他物联网设备移动。这种模型的目的是在接近数…

Cannot load from short array because sun.awt.FontConfiguration.head is null

新服务器,部署项目,使用easyExcel功能导出文件时,报错提示:Cannot load from short array because "sun.awt.FontConfiguration.head" is null,可以看到是字体文件配置引发的空指针异常; 解决方法:登录服务器,执行命令 yum install fontconfigfc-cache --for…

heap

堆块: chunk 堆是以一个个的堆块构成的,这些堆块就叫chunk chunk的大小是8字节对齐,但是一个堆块的具体大小是16字节对齐的,比如一个堆块只能是 0x40,0x50,0x60 不会是0x48这样的数据 其中一个堆块的header头部字节占16字节大小,也就是0x10字节 64位程序下的最小长度是3…

固件的提取

固件提取的三类方法:直接从官网上找到目标型号的设备固件下载 使用Telnet或者ssh从目标设备中获取固件 从开发板中的flash芯片中或者通过uart和jtag调试接口将固件提取下来JTAG(Joint Test Action Group),是一种用于测试和调试电子设备的技术标准。它使用4线或5线接口,其中…

转:在Linux上运行WinForm

C#winform软件实现一次编译,跨平台windows和linux、mac兼容运行,兼容Visual Studio原生界面Form表单开发 - 亲善美 - 博客园 (cnblogs.com)一、背景: 微软的.net core开发工具,目前来看,winform界面软件还没有打算要支持linux系统下运行的意思,要想让c#桌面软件在linux系…

固件的烧录以及部分PCB基础

固件 固件的基础定义: 固件(firmware)一般存储于设备中的电可擦除只读存储器(允许用户通过特定的电子方式复写存储内容,在【工作情况下是只读的,并且关闭电源仍存储数据)EEPROM(Electrically Erasable Programmable ROM)或FLASH芯片中,一般可由用户通过特定的刷新程序进…

Golang:go-querystring将struct编码为URL查询参数的库

Golang:go-querystring将struct编码为URL查询参数的库 原创 吃个大西瓜 Coding Big Tree 2024-05-09 08:30 北京go-querystring is a Go library for encoding structs into URL query parameters.译文:go-querystring 将struct编码为URL查询参数的Golang库文档https://pkg.g…

C#之缓存

原文链接:https://zhuanlan.zhihu.com/p/657458522 缓存指在中间层中存储数据的行为,该行为可使后续数据检索更快。 从概念上讲,缓存是一种性能优化策略和设计考虑因素。 缓存可以显著提高应用性能,方法是提高不常更改(或检索成本高)的数据的就绪性 既然缓存是是一种性能…

有关paddleocr在pyinstall中打包问题的解决方案

借鉴网址python解决paddleocr打包问题_pyinstaller怎么解决paddleocr中的动态导入-CSDN博客 在打包时我使用的spec文件如下:(需要将pathe和binaries换为自己的paddleocr路径) block_cipher = Nonea = Analysis([main.py], pathex=[E:\\PyEnviroment\\Lib\\site-packages…