UDF是mysql的一個拓展接口,UDF(Userdefined function)用戶自定義函數。在什么地方使用這個功能呢,試想有如下場景:
你的網站使用mysql作為最終數據落地的存儲引擎,而redis作為緩存以減小查詢請求穿透到mysql的數量,可以極大的降低數據庫性能瓶頸帶來的整個網站對外服務的卡頓、不可用等情況。這種方式的架構,當有查詢請求的時候,我們可以在業務邏輯層控制,先從緩存中查詢,無命中的情況下,再到數據庫中查詢,同時緩存到redis中;當有修改請求的時候,我們可以先修改數據庫,然后刪除或更新緩存。
以上方式是我們業務量不大,開發簡單的,少橫向擴展的情況下做的。當開發復雜度隨着業務量並發增大,呈現橫向擴展和垂直方向上螺旋迭代上升趨勢的時候,邏輯復雜度直線上升。還采用在業務邏輯層做緩存控制將變得很復雜,運維上也容易出錯。
這個時候,如果能將緩存邏輯和業務邏輯分離,緩存層對業務邏輯提供服務透明,業務邏輯不用關心緩存邏輯,緩存邏輯也不用隨業務變化而改動,互相做自己的事情,這樣高內聚低耦合可以極大的增加擴展性和健壯性,也是我們做架構應該努力發展的方向。
那么具體來說,我的這里要做的事情,其實就是把緩存更新的邏輯,放到mysql中去做。寫一個trigger觸發器監控insert/update/delete這些修改數據的操作,當有修改操作的時候,調用對應的自定義UDF函數來遠程回寫redis緩存,而我們在業務邏輯層則只管更新數據就行了,緩存更新的操作都放給以上的緩存層邏輯來完成。
當然,以上操作也可以反方向來,先寫redis,然后由redis同步到mysql去。兩種方式各有利弊,看你的具體場景如何選擇了,這里不討論。我們此處的目的是使用mysql的udf函數更新數據到redis中。
開發環境
操作系統:centos 6.4 server,內核2.6.32-358.18.1.el6.x86_64
編譯器:gcc 4.4.7
數據庫:mysql server 5.1.73
mysql服務器之前已經安裝了,現在我們要安裝mysql開發包
yum install mysql-devel -y
一、UDF函數入門
首先學習下UDF函數的使用方法。我們自定義一個函數文件,test_add.cpp如下
#include <mysql.h> extern "C" long long testadd(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error) { int a = *((long long *)args->args[0]); int b = *((long long *)args->args[1]); return a + b; } extern "C" my_bool testadd_init(UDF_INIT *initid, UDF_ARGS *args, char *message) { return 0; }
這是c++代碼,編譯
[root@centos6 ~]# g++ -shared -fPIC -I /usr/include/mysql -o test_add.so test_add.cpp
拷貝到mysql插件目錄下,要以root身份
[root@centos6 ~]# cp test_add.so /usr/lib64/mysql/plugin/
如果你不知道插件的路徑,執行
mysql> show variables like '%plugin%'; +---------------+-------------------------+ | Variable_name | Value | +---------------+-------------------------+ | plugin_dir | /usr/lib64/mysql/plugin | +---------------+-------------------------+ 1 row in set (0.00 sec)
登錄mysql,創建函數關聯
mysql> create function testadd returns integer soname 'test_add.so';
Query OK, 0 rows affected (0.00 sec)
至此,UDF就搞定了,接下來測試
mysql> select testadd(1,2); +--------------+ | testadd(1,2) | +--------------+ | 3 | +--------------+ 1 row in set (0.00 sec)
可以看到testadd函數生效了,輸出結果為 1 + 2 = 3.
如果要刪除UDF函數
mysql> drop function testadd; Query OK, 0 rows affected (0.01 sec)
然后刪除插件目錄下的.so文件
[root@centos6 ~]# rm -f /usr/lib64/mysql/plugin/test_add.so
二、結合redis做緩存更新
1.在DUF中訪問redis
更新redis的原理其實和上面示例一樣的,只是要在UDF中調用redis的api函數。在此之前,請先下載api源碼
git clone https://github.com/mrpi/redis-cplusplus-client
這是redis官網給出的c++訪問redis的客戶端api代碼,依賴於boost,先安裝
[root@centos6 ~]# yum install boost boost-devel
然后
[root@centos6 ~]# cd redis-cplusplus-client
當要調用該庫的時候,把如下幾個文件拷貝過去一起編譯就可以了
redisclient.h、anet.h、fmacros.h、anet.c。接下來看我的源碼test.cpp
#include <stdio.h> #include <mysql.h> #include "redisclient.h" using namespace boost; using namespace std; static redis::client *_client = NULL; // 初始化連接 void check_connection() { if(NULL == _client){ const char* c_host = getenv("REDIS_HOST"); // 獲取操作系統變量 string host = "localhost"; if(c_host) host = c_host; _client = new redis::client(host); } } // 調用redis的hset命令 extern "C" char *redis_hset(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error) { try{ check_connection(); if(!(args->args && args->args[0] && args->args[1] && args->args[2])){ *is_null = 1; *length = 8; snprintf(result, 8, "is null"); return result; } if(_client->hset(args->args[0], args->args[1], args->args[2])){ *length = 2; snprintf(result, 2, "0"); return result; } else { *error = 1; *length = 5; snprintf(result, 6, "error"); return result; } } catch (const redis::redis_error & e){ *length = ((std::string)e).length() + 1; snprintf(result, *length, "%s", (char*)e.what()); return result; } } /*資源分配*/ extern "C" my_bool redis_hset_init(UDF_INIT *initid, UDF_ARGS *args, char