mysql UDF介紹


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 *message) { if (3 != args->arg_count || args->arg_type[0] != STRING_RESULT || args->arg_type[1] != STRING_RESULT || args->arg_type[2] != STRING_RESULT){ // hset(key, field, value) 需要三個參數 strncpy(message, "please input 3 args and must be string, such as: hset('key', 'feild', 'value');", MYSQL_ERRMSG_SIZE); return -1; } args->arg_type[0] = STRING_RESULT; args->arg_type[1] = STRING_RESULT; args->arg_type[2] = STRING_RESULT; initid->ptr = NULL; return 0; } /* // 測試 int main(){ char is_null; char message[128] = {0}; char result[128] = {0}; unsigned long length = 0; UDF_ARGS args; UDF_INIT initid; args.arg_count = 3; args.args = new char*[3]; args.args[0] = new char[16]; args.args[1] = new char[16]; args.args[2] = new char[16]; args.arg_type = new Item_result[3]; strcpy(args.args[0], "mykey"); strcpy(args.args[1], "myfeild"); strcpy(args.args[2], "myvalue"); redis_hset_init(&initid, &args, message); redis_hset(&initid, &args, result, &length, &is_null, message); printf("%s\n", result); if(args.arg_type) delete args.arg_type; if(args.args) delete args.args; return 0; } */ 

請提前啟動redis服務器,我是在本機啟動的,所以地址就是localhost,端口不寫默認就是6379。編譯和拷貝

[root@centos6 ~]# g++ -shared -fPIC -I /usr/include/mysql -lboost_serialization -o myredis.so anet.c test.cpp [root@centos6 ~]# rm -f /usr/lib64/mysql/plugin/myredis.so && cp myredis.so /usr/lib64/mysql/plugin/ && chmod 777 /usr/lib64/mysql/plugin/myredis.so 

登錄mysql客戶端,執行

mysql> DROP FUNCTION IF EXISTS `redis_hset`; create function redis_hset returns string soname 'myredis.so'; Query OK, 0 rows affected (0.01 sec) Query OK, 0 rows affected (0.00 sec) mysql> select * from mysql.func; +------------+-----+------------+----------+ | name | ret | dl | type | +------------+-----+------------+----------+ | redis_hset | 0 | myredis.so | function | +------------+-----+------------+----------+ 

安裝完成,現在測試。在mysql中執行

mysql> select redis_hset('Jack', 'id', '101'); +---------------------------------+ | redis_hset('User', 'id', '101') | +---------------------------------+ | 0 | +---------------------------------+ 

返回字符"0",說明調用成功。到redis上看看結果

127.0.0.1:6379> hgetall User 1) "id" 2) "101" 

數據正確,mysql的DUF和redis通信成功,並且正確的修改數據。

2.用觸發器實現動態更新redis緩存

這一步的思路,就是在mysql中創建一個觸發器,監聽表的insert/update/delete等操作,有數據更新的時候調用上一步的UDF函數刷新信息到redis緩存中去。

先准備數據庫表和觸發器

CREATE TABLE `tb_user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL, PRIMARY KEY (`id`) ); 

觸發器

delimiter $
create trigger tg_user
after insert on tb_user
for each row begin set 

這里觸發器監控的是insert,當插入新數據的時候,會把用戶id和用戶名刷新到redis緩存中去。也可以針對update和delete做觸發,篇幅有限,就不列出來了。

測試

mysql> insert into test.tb_user(`username`) values('Jack'); Query OK, 1 row affected (0.01 sec) mysql> insert into test.tb_user(`username`) values('Lucy'); Query OK, 1 row affected (0.01 sec) 

插入了兩條數據,查看redis的反應

127.0.0.1:6379> keys user_* 1) "user_1" 2) "user_2" 127.0.0.1:6379> hgetall user_1 1) "id" 2) "1" 3) "username" 4) "Jack" 127.0.0.1:6379> hgetall user_2 1) "id" 2) "2" 3) "username" 4) "Lucy" 

可以看到user_1和user_2兩條key都插入了,每條key的內容也和我們在mysql中插入的一致。至此,我們的目的,“數據庫更新,自動刷新到緩存”就實現了,妥妥的!

總結

以上的思路我已經講的很清楚了,我只寫了一個簡單的示例,使用的是redis的hset命令。這里我們找一個現成的同步mysql到redis的工具,很全面。從 http://pan.baidu.com/s/1qW9DHYc 下載mysql_udf_redis.tar.bz2,基本滿足常用的redis操作命令。



作者:Liberalman
鏈接:https://www.jianshu.com/p/4381a38403a1
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM