【原創】modb 功能設計之「支持對sql語句的相關日誌記錄」


      在《   modb 開發之需求和整體設計   》中,第三個要實現的功能點就是    支持對 sql 語句的相關日誌記錄」。下面就講解下設計這個功能的。

【需求分析】

終於到了處理 sql 日誌的階段了,萬里長征重點的關鍵一步。 須要考慮解決的問題點以下:
  • 在哪一個模塊上作 sql 日誌記錄
  • 都要記錄哪些信息才能作到跨機房數據同步時,具備可查詢、可分析、可監控的目的
  • sql 日誌記錄的模式或者說頻率
針對 MoDB 要作跨機房數據的同步這個功能,那麼能夠對 sql 語句進行記錄的「地方」有:
  • modb 應用中
  • Atlas 應用中
其中 Atlas 目前已支持 sql 日誌的記錄,格式以下:
[11/25/2013 14:58:54] C:172.16.80.111 S:127.0.0.1 OK 0.155 "SET NAMES utf8"
      其中所涵蓋的內容包括:時間戳、源和目的 ip 地址、查詢對應的應答狀態信息、查詢耗時,以及查詢語句自己。
      Atlas 會對全部經由 Atlas 發往 MySQL 服務器的類型爲 COM_QUERY 的查詢按照上述形式進行記錄。 而 modb 中對 sql 的日誌記錄須要本身實現。 從整體設計上講,訪問 Atlas (訪問 MySQL 數據庫)的入口有兩處:一個是經過 modb 進行訪問;另外一個是各類業務應用程序直接訪問。 而只有經由 modb 訪問 Atlas 的數據庫查詢動做,纔是跨機房同步所須要處理的內容,同時 Atlas 自己記錄的 sql 查詢操做比較全面,很大一部分咱們實際上是不須要關心的。綜上所述,必須在 modb 上實現 sql 日誌的記錄。

至於須要記錄的日誌內容,應該包括但不限於下面幾點:
  • 日誌記錄的時間戳
  • 日誌的「流向」(從哪裏來,到哪裏去)
  • sql 語句自己
  • sql 語句的執行狀況(分紅:直接在 MySQL 上執行成功後在 modb 上記錄;經過 modb 向 MySQL 發送執行命令後記錄)
承載 sql 語句的載體 以 JSON 數據結構保存相關信息,最終做爲 rabbitmq 的消息發送接收。

日誌記錄的模式
  • 每條日誌都執行打開文件,寫日誌,關閉文件的動做
  • 僅在應用初始化時打開文件,在須要記錄日誌時寫,在應用退出時關閉文件。經過 fflush 控制刷盤頻率

【JSON 庫選擇】

下面,能夠談談 JSON 解析的問題了。

      JSON 格式自己不復雜,經過官網上的描述至多 10 分鐘就能夠基本瞭解清楚。一個值得思考的問題是,是否須要支持相似於 SAX(Simple API for XML)的流式解析方式。對於 modb 應用來說,是不須要支持的。另一個問題是,JSON 官網上提供的了那麼多開源的庫,選擇什麼樣的纔是適合個人?這個就須要親身實踐了。因此我實踐了以下幾個開源庫:

====

-- rui_maciel/mjson --
該庫能夠很方便的集成到其餘項目中,支持跨平臺;
該庫支持 SAX-like 解析;支持從文本文件中按行獲取數據進行解析;
支持 UTF-8;
支持 pretty 格式和 raw 格式的 json 數據相互轉換;

一句話總結:
針對 json 數據中特定節點數據的搜索功能基本不可用(這個比較噁心)


-- william/libjson --
一句話總結:
      庫自己支持的功能絕對有亮點,但因爲原做者對 C99 標準貫徹的很是堅定,因此將上述代碼移植到不支持 C99 標準的 VS 上有必定困難。


--vincenthz/libjson --
可中斷的解析器:按字節處理 或者 按 string 塊處理。
沒有對象模型的限定:可經過簡單回調方式方便地集成到任何模型中。
代碼量很小。
速度快。
JSON全特定支持。
無本地語言轉換:字符編碼處理由用戶進行。
支持對json數據解析深度的進行控制。
支持對待處理數據大小的限制。
(可選)支持YAML/python註釋和C註釋。

一句話總結:
      沒有搜索接口,故意把字符串內容留給用戶本身處理。


-- json-parser --
一句話總結:
      沒有搜索接口,沒有 UTF-8 處理。


-- Jansson --
提供簡單直觀的 API 以及數據模型
全面的文檔
無第三方庫依賴
對 Unicode 的徹底支持(UTF-8 等)
完整的測試集
以 MIT 許可證發佈

一句話總結:
      跨平臺支持良好,提供了完整的測試集,各類搜索方式都支持,總之,該有的都有了,不錯。

====

      選定了使用 jansson 庫,接下來就該定義待處理的 json 數據結構了。本來我覺得這個應該很容易定,其實仍是有點搞頭的,請看下面:

【JSON 數據結構定義】

可供選擇的數據結構以下:

1. sql 的 value 以 string 的形式包含單條待執行語句。
      這種形式的的問題是任何 sql 動做都對應產生一條 rabbitmq 消息,因此總的消息量會增長,好處是不須要 modb 去作複雜業務處理,即不用考慮當前 sql 是做用於哪一個庫,由於切換庫的動做也會以 sql 的形式經過 json 數據結構以 rabbitmq 消息進行發送。

缺點:rabbitmq 消息量變大;業務側須要將 sql 逐條發送;
優勢:modb 邏輯處理簡單(如日誌記錄等)。

形式一:
{
    "src" : "172.16.80.111",
    "key" : "172.16.80.123",
    "app" : "Movision",
    "state" : "transfer",
    "sql" : "set names utf8"
}
形式二:針對這種形式須要在鏈接時設置好 CLIENT_MULTI_STATEMENTS ,而且須要客戶端實現多結果集處理。
{
    "src" : "172.16.80.111",
    "key" : "172.16.80.123",
    "app" : "Movision",
    "state" : "transfer",
    "sql" : "set names utf8;show databases"
}

2. sql 的 value 以 array 的形式包含多條待執行語句。
這種形式其實和上面形式大致相同(尤爲和形式二)。

缺點:同上
優勢:同上

形式:
{
    "src" : "172.16.80.111",
    "key" : "172.16.80.123",
    "app" : "Movision",
    "state" : "notify",
    "sql" : [
        "set names utf8",
        "show databases",
        "use mysql"
    ]
}

3. sql 的 value 以 object 的形式包含多條待執行語句。
      這種形式爲上層業務提供了靈活的操做方式,即容許在一條 rabbitmq 消息中同時對多個數據庫中的數據進行操做。缺點是增長了 modb 的邏輯處理複雜度(須要作額外的字符集設置、數據庫切換等動做,而且日誌記錄也更復雜)。另外也對 json 解析庫提供了更好的要求(好比相同的 key 與不一樣的 value 的映射)。

缺點:讓 modb 須要處理各類複雜的狀況。
優勢:爲上層業務提供了靈活性。

形式一:
{
    "src" : "172.16.80.111",
    "key" : "172.16.80.123",
    "app" : "Movison",
    "state" : "notify",
    "sql" : {
        "default" : "show databases", 
        "default" : "use test", 
        "test" : "show tables", 
    }
}
形式二:
{
    "src" : "172.16.80.111",
    "key" : "172.16.80.123",
    "app" : "moooofly",
    "state" : "notify",
    "sql" : [
        {
            "dbname" : "", 
            "sqlstr" : "show databases"
        },
        {
            "dbname" : "", 
            "sqlstr" : "use test"
        },
        {
            "dbname" : "test", 
            "sqlstr" : "show tables"
         },
    ]
}
      綜上,考慮到 modb 須要同步 sql 語句是比較單一的數據 insert、update 和 delete ,應該不會有多數據庫同時操做的必要。因此,只須要支持「sql 的 value 以 string 的形式包含單條待執行語句」這類就能夠了。


【json 消息中字段的含義】
  • src 字段表示當前消息的來源地址;
  • key 字段表示 routing_key 和 binding_key ,根據具體業務場景進行區別對待;
  • app 字段表示當前消息來源於何種應用;
  • state 字段用於標識消息該如何被處理,該字段具備兩種值:"transfer" 和 "notify" 。業務模塊老是使用 "transfer" 狀態告之 modb 進行跨機房同步,但收到 rabbitmq 消息時不需關心該值;
  • sql 字段用於標識當前傳輸的 sql 語句。

【遇到的問題】
      最初在 modb 上實現 MySQL 數據庫訪問時,僅支持簡單 sql 的處理,後續開發過程當中,有 java 業務開發人員說基於其使用的  sdk 作業務實現時,最經常使用的方式是使用 prepared statement ,而且其使用的 bind 參數的類型大多數狀況都 是自適應的,不指定具體類型。但 C api 中卻沒有相應的接口實現自適應功能,因此在 C api 中必須按照下面的方式進行設置。
memset(ps_params, 0, sizeof (ps_params));

  /* - v0 -- INT */

  ps_params[0].buffer_type= MYSQL_TYPE_LONG;
  ps_params[0].buffer= (char *) &int_data[0];
  ps_params[0].length= 0;
  ps_params[0].is_null= 0;

  /* - v_str_1 -- CHAR(32) */

  ps_params[1].buffer_type= MYSQL_TYPE_STRING;
  ps_params[1].buffer= (char *) str_data[0];
  ps_params[1].buffer_length= WL4435_STRING_SIZE;
  ps_params[1].length= &str_length;
  ps_params[1].is_null= 0;

  /* - v_dbl_1 -- DOUBLE */

  ps_params[2].buffer_type= MYSQL_TYPE_DOUBLE;
  ps_params[2].buffer= (char *) &dbl_data[0];
  ps_params[2].length= 0;
  ps_params[2].is_null= 0;

  /* - v_dec_1 -- DECIMAL */

  ps_params[3].buffer_type= MYSQL_TYPE_NEWDECIMAL;
  ps_params[3].buffer= (char *) dec_data[0];
  ps_params[3].buffer_length= WL4435_STRING_SIZE;
  ps_params[3].length= 0;
  ps_params[3].is_null= 0;

  /* - v_dec_2 -- DECIMAL */

  ps_params[8].buffer_type= MYSQL_TYPE_DECIMAL;
  ps_params[8].buffer= (char *) dec_data[0];
  ps_params[8].buffer_length= WL4435_STRING_SIZE;
  ps_params[8].length= 0;
  ps_params[8].is_null= 0;
      這樣就存在了一個問題,當 java 客戶端經過本身的 sdk 採用 prepared statement 方式更新數據庫後,再將相應的 sql 語句和參數以 rabbitmq 消息的形式發送給 modb 後,以後 modb 再更新本地數據庫,此時沒法知道應該設置爲什麼種參數類型,只能根據值進行猜想。這就有可能致使錯誤發生。

一種可選的補救措施:
{
    "src" : "172.16.80.111",	
    "key" : "pc_1",
    "app" : "Ejabberd",
    "state" : "transfer",
    "sql" : "insert into users values(?,?,?)",
    "sql-args" : [1, 2, "abc"]
}


=== 未完待續 ===
相關文章
相關標籤/搜索