在 libevent 中使用 MariaDB(MySQL)

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

閱讀本文以前,做者假設讀者已經有了 libevent 的相關知識。若是沒有的話,能夠參見個人系列文章:mysql

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


基本流程

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

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

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


流程狀態圖

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

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


詳細流程

Connect 階段

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

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 國際許可協議 進行許可。

本文地址:http://www.javashuo.com/article/p-cqunwlwf-cr.html
原文發佈於:https://cloud.tencent.com/developer/article/1346966,也是本人的專欄。

相關文章
相關標籤/搜索