升級到 MySQL 8.4,MySQL 啟動報錯:io_setup() failed with EAGAIN

来源:https://www.cnblogs.com/ivictor/p/18277923
-Advertisement-
Play Games

問題 最近碰到一個 case,一臺主機上,部署了多個實例。之前使用的是 MySQL 8.0,啟動時沒有任何問題。但升級到 MySQL 8.4 後,部分實例在啟動時出現了以下錯誤。 [Warning] [MY-012582] [InnoDB] io_setup() failed with EAGAIN ...


問題

最近碰到一個 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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄TTL、RS232、RS485三種協議介紹TTL(Transistor-Transistor Logic)串口通信協議工作原理:TTL電平標準:特點:優勢:缺點:TTL協議混淆點澄清:擴展學習:COMS技術CMOS技術的工作方式:主要特點:CMOS技術的局限性:RS232協議工作原理:特點:優勢 ...
  • 設備採用晶元:STM32F407ZET6 4個LED燈,網路標號分別為LED0 ,LED1,FSMC D10,FSMC D11。對應的引腳號分別為PF9,PF10,PE12,PE13。 GPIO外設基本概念 General-Purpose Input Output,通用型輸入輸出的,也簡稱I/O口, ...
  • 目錄操作系統,啟動!大致過程重要程式bootsect.ssetup.shead.s 操作系統,啟動! 大致過程 ​ 電腦的工作方式是取指執行,而執行其的前提是記憶體中有代碼。操作系統剛開始並不是在記憶體中,而是在磁碟上,因此第一步需要將其以一定的方式從磁碟讀入記憶體。 (1)x86PC剛開機時CPU處於 ...
  • 在 CentOS 上安裝 Git 可以通過以下幾個步驟來完成: 1. 使用 YUM 安裝 Git(一般這種情況已經可以滿足) 這是最簡單的方法,使用 CentOS 自帶的 YUM 包管理器。 更新 YUM 包索引: sudo yum update 安裝 Git: sudo yum install g ...
  • 表格示意: 標準 邏輯電平0 邏輯電平1 是否全雙工 抗干擾能力 TTL 輸出低電平<0.4V, 輸入低電平<=0.8V 輸出高電平>2.4V,輸入高電平>=2.0V 全雙工 差 RS232 +3~+15V -3~-15V 全雙工 強 RS485 +2V~+6V - 6V~- 2V 半雙工 很強 電 ...
  • 寫這個方法是因為需要向一臺沒有外網的伺服器上安裝gcc,各種百度找到了相關依賴、依賴的依賴。。。。。。的rpm包,林林總總近100個rpm,拷貝到目標伺服器上安裝的時候發現這些rpm包的安裝順序完全靠猜測,安裝就報:有依賴需要提前安裝。這時候感謝百度大模型文心一言的幫助,找到了可以使用本地倉庫來管理 ...
  • 切換分支的時候,需要更新所有的子模塊,可以編寫 Shell 代碼簡化這一過程。 本教程適用於 mac 系統,終端使用 zsh。 ...
  • 在這個數據驅動的大模型時代,數據集成的作用和意義愈發重要。數據不僅僅是信息的載體,更是推動企業決策和創新的關鍵因素。作為全球最流行的批流一體數據集成工具,WhaleTunnel隨著WhaleStudio 2.6版本正式發佈,帶來了多項功能增強和新特性,性能大幅提升,連接器和功能方面也有大量更新。 上 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...