在 libevent 中使用 MariaDB(MySQL)

在之前我翻譯的官方文檔中提到了 MariaDB 提供了對異步 I/O 的支持。那篇文章是一個比較簡要的介紹。不過實際適配中,官方也提供了一個完整適配 libevent 的示例代碼。本文算是對我上述示例代碼的閱讀筆記吧。

閱讀本文之前,作者假設讀者已經有了 libevent 的相關知識。如果沒有的話,可以參見我的系列文章:

此外本文內容也適合其他的異步 I/O 庫,如:


基本流程

傳統的 MySQL client 在請求 DB 查詢的時候,API 調用流程爲:

mysql_real_connect()
mysql_real_query()
mysql_use_result()
mysql_fetch_row()
mysql_close()

不過,在異步 socket 模型中,根據官方介紹文檔中也提及了,對於會產生阻塞的函數調用 XXX,需要分開 XXX_start()XXX_stop() 進行調用。上述流程中,除了 mysql_use_result() 不是阻塞調用之外,其他的函數均需要如此區分。


流程狀態圖

異步服務器經常是以狀態圖模式進行設計開發的,官方 demo 是基於 libevent 設計的,也一樣。下面是簡化版的流程裝態圖(流程圖 + 狀態圖):

上圖主要是正常流程,異常流程暫未列出。實線表示該狀態的流轉需要經過異步 I/O 等待(libevent 調用 event_add())後才能獲取相應的狀態碼或返回值進行檢查後纔可以進行的狀態流轉,虛線表示在該狀態下即已有足夠的變量可進行狀態流轉。


詳細流程

Connect 階段

該階段包含三個狀態,其中兩個狀態分別是 mysql_real_connect_start()mysql_real_connect_cont() 函數的調用狀態。這兩個函數之間的流轉,後文 “阻塞函數改造” 小節中再做說明。

mysql_real_connect 系列函數返回 status == 0 之後,程序就可以流轉到該階段的第三個狀態,在代碼中的狀態碼是 9。這個狀態中,程序只進行異常判斷,如果正常,則流轉至下一流程 query 階段。如果在狀態 9 檢測到異常,程序中直接調用 exit() ,因此可以認爲這個狀態極少出錯。當然對於正式的程序,還是需要捕捉這個錯誤的。

Query 階段

該階段包含兩個狀態,分別是 mysql_real_query_start()mysql_real_query_cont() 函數的調用狀態。這兩個狀態的代碼就是非常典型的 _start + _cont 階段。後文將會說明相關內容。

另外,在 mysql_real_query_start() 處,還會檢查當前是否有新的查詢請求。如果沒有請求,則會直接進入 close 階段。這與普通的 MySQL 流程無異,因此不展開講。

Use Result 階段

這個階段調用的是 muysql_use_result() 函數。由於該函數不是阻塞函數,因此該階段只需要一個狀態,並且狀態的流轉不需要等待,直接流轉即可。

Fetch Row 階段

該階段向數據庫獲取結果的行,同樣有相應的 _start()_cont() 狀態,這兩個階段同樣後文再講述。在 _cont() 狀態中如果 status 值爲 0,則直接進入 39 狀態使用獲得的數據進行操作。

39 狀態中,如果數據未獲取完,則繼續回到該階段的 _start() 狀態;如果當前叉裙已經結束,則回到 query 階段。

Close 階段

如前文所述,該階段的入口是從 query 階段而來。和普通的 socket close 不同,MySQL client 的 close 操作是阻塞的,需要將這個階段的代碼改造成異步模式。和 query 階段類似,該階段只需要 _start()_cont() 兩個狀態即可

Exit 階段

這個階段其實不是 MySQL 的請求流程之一,而是整個應用程序的流程階段。在這個階段,應用程序需要調用其所使用的異步 I/O 框架的退出機制。對於 libevent,則是 event_loopbreak()


阻塞函數改造

狀態機函數

上文所提及的幾個階段中,有四個階段是對原有阻塞函數的改造,需要將阻塞函數分爲同名的 _start()_cont() 兩個函數。以 mysql_real_connect() 函數爲例,該函數需要改造爲 mysql_real_connect_start()mysql_real_connect_cont() 兩個函數。其中 _start 發起流程,而 _cont 表示 “continue”,則是處理異步 I/O 過程中的一些(不需要程序員關心)的中間狀態,同時判斷異步 I/O 是否已經完成。

這裏需要的兩個函數分別是:

// 僅聲明異步改造的關鍵變量

// _start 狀態
int status;
MYSQL mysql;
MYSQL *mysql_ret;
status = mysql_real_connect_start(&mysql_ret, host, user, passwd, db_name, port, unix_sock, 0);

// _cont 狀態
int status;
MYSQL mysql;
MYSQL *mysql_ret;
status = mysql_real_connect_cont(&mysql_ret, &MYSQL, _libevent_to_mysql_status(libevent_what));

// _libevent_to_mysql_status 轉換函數
static int _libevent_to_mysql_status(short event)
{
    int status= 0;
    if (event & EV_READ)
        status|= MYSQL_WAIT_READ;
    if (event & EV_WRITE)
        status|= MYSQL_WAIT_WRITE;
    if (event & EV_TIMEOUT)
        status|= MYSQL_WAIT_TIMEOUT;
    return status;
}

其中 start 函數的後七個參數,與原本 mysql_real_query 相同。而第一個參數 &mysql_ret ,則替代了原函數的返回值的作用。而 _start() 函數的返回值,則換成一個 int 類型的變量,用於適配異步 I/O。該 int 變量是一個位掩碼變量,與 libevent 事件回調函數中的 short what 變量的位掩碼一一對應(參見上文 _libevent_to_mysql_status() 函數,等同於官方 demo 中的 mysql_status() 函數)

狀態機流轉

狀態機中寫好了基本的調用函數之後,接下來就需要判斷狀態機的流轉條件了。參見下圖:

流轉條件集中針對兩個 “返回值” 的狀態進行流轉:

  • 異步 MySQL API 的 int 類型返回值 status:如果返回零,則表示當前操作正常完成,可走入下一步;如果非零,則表示下一步需要的事件掩碼,在 _cont() 函數上繼續等待
  • 原阻塞函數的返回值,也即異步 API 的第一個參數:處理方式以原阻塞式函數的處理方式相同。

轉換爲 libevent 掩碼

狀態流轉時,如果需要等待 I/O 操作,那麼需要使用異步 I/O 框架的事件函數進行操作。在 MySQL 異步 API 中,其狀態值與 libevent 的掩碼值是一一對應的。在前文 _libevent_to_mysql_status() 函數中已經體現了,對應關係如下:

類型

含義

MySQL 值或類型

libevent 值或類型

位掩碼

讀事件

MYSQL_WAIT_READ

EV_READ

位掩碼

寫事件

MYSQL_WAIT_WRITE

EV_WRITE

位掩碼

超時時間

MYSQL_WAIT_TIMEOUT

EV_TIMEOUT

變量

socket 文件描述符

mysql_get_socket(&mysql)

evutil_socket_t fd

變量

超時事件

mysql_get_timeout_value(&mysql)

struct timeval

有了上述對應關係,已經足以將 MySQL 的變量轉爲 event_set()event_add() 函數調用了。

這樣,一個完整的基於異步 I/O 框架的 MySQL client 過程,就建立起來了。


完整狀態圖

下面附上完整的狀態圖,能夠更加直觀地瀏覽整個異步狀態:


參考資料


本文章採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。

原文發佈於:https://cloud.tencent.com/developer/article/1346966

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章