通過UDF使mysql主動刷新redis緩存

UDF是mysql的一個拓展接口析孽,UDF(Userdefined function)用戶自定義函數(shù)勿她。在什么地方使用這個功能呢术奖,試想有如下場景:

你的網(wǎng)站使用mysql作為最終數(shù)據(jù)落地的存儲引擎腰奋,而redis作為緩存以減小查詢請求穿透到mysql的數(shù)量,可以極大的降低數(shù)據(jù)庫性能瓶頸帶來的整個網(wǎng)站對外服務(wù)的卡頓阳谍、不可用等情況蛀柴。這種方式的架構(gòu),當(dāng)有查詢請求的時候矫夯,我們可以在業(yè)務(wù)邏輯層控制鸽疾,先從緩存中查詢,無命中的情況下训貌,再到數(shù)據(jù)庫中查詢制肮,同時緩存到redis中;當(dāng)有修改請求的時候递沪,我們可以先修改數(shù)據(jù)庫豺鼻,然后刪除或更新緩存。

以上方式是我們業(yè)務(wù)量不大款慨,開發(fā)簡單的儒飒,少橫向擴(kuò)展的情況下做的。當(dāng)開發(fā)復(fù)雜度隨著業(yè)務(wù)量并發(fā)增大檩奠,呈現(xiàn)橫向擴(kuò)展和垂直方向上螺旋迭代上升趨勢的時候约素,邏輯復(fù)雜度直線上升。還采用在業(yè)務(wù)邏輯層做緩存控制將變得很復(fù)雜笆凌,運(yùn)維上也容易出錯。

這個時候士葫,如果能將緩存邏輯和業(yè)務(wù)邏輯分離乞而,緩存層對業(yè)務(wù)邏輯提供服務(wù)透明,業(yè)務(wù)邏輯不用關(guān)心緩存邏輯慢显,緩存邏輯也不用隨業(yè)務(wù)變化而改動爪模,互相做自己的事情欠啤,這樣高內(nèi)聚低耦合可以極大的增加擴(kuò)展性和健壯性,也是我們做架構(gòu)應(yīng)該努力發(fā)展的方向屋灌。

那么具體來說洁段,我的這里要做的事情,其實就是把緩存更新的邏輯共郭,放到mysql中去做祠丝。寫一個trigger觸發(fā)器監(jiān)控insert/update/delete這些修改數(shù)據(jù)的操作,當(dāng)有修改操作的時候除嘹,調(diào)用對應(yīng)的自定義UDF函數(shù)來遠(yuǎn)程回寫redis緩存写半,而我們在業(yè)務(wù)邏輯層則只管更新數(shù)據(jù)就行了,緩存更新的操作都放給以上的緩存層邏輯來完成尉咕。

當(dāng)然叠蝇,以上操作也可以反方向來,先寫redis年缎,然后由redis同步到mysql去悔捶。兩種方式各有利弊,看你的具體場景如何選擇了单芜,這里不討論蜕该。我們此處的目的是使用mysql的udf函數(shù)更新數(shù)據(jù)到redis中。

開發(fā)環(huán)境

操作系統(tǒng):centos 6.4 server缓溅,內(nèi)核2.6.32-358.18.1.el6.x86_64

編譯器:gcc 4.4.7

數(shù)據(jù)庫:mysql server 5.1.73

mysql服務(wù)器之前已經(jīng)安裝了蛇损,現(xiàn)在我們要安裝mysql開發(fā)包

yum install mysql-devel -y

一、UDF函數(shù)入門

首先學(xué)習(xí)下UDF函數(shù)的使用方法坛怪。我們自定義一個函數(shù)文件淤齐,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/

如果你不知道插件的路徑更啄,執(zhí)行

mysql> show variables like '%plugin%';
+---------------+-------------------------+
| Variable_name | Value                   |
+---------------+-------------------------+
| plugin_dir    | /usr/lib64/mysql/plugin |
+---------------+-------------------------+
1 row in set (0.00 sec)

登錄mysql,創(chuàng)建函數(shù)關(guān)聯(lián)

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函數(shù)生效了,輸出結(jié)果為 1 + 2 = 3.

如果要刪除UDF函數(shù)

mysql> drop function testadd;
Query OK, 0 rows affected (0.01 sec)

然后刪除插件目錄下的.so文件

[root@centos6 ~]# rm -f /usr/lib64/mysql/plugin/test_add.so

二怪嫌、結(jié)合redis做緩存更新

1.在DUF中訪問redis

更新redis的原理其實和上面示例一樣的义锥,只是要在UDF中調(diào)用redis的api函數(shù)。在此之前岩灭,請先下載api源碼
git clone https://github.com/mrpi/redis-cplusplus-client
這是redis官網(wǎng)給出的c++訪問redis的客戶端api代碼拌倍,依賴于boost,先安裝

[root@centos6 ~]# yum install boost boost-devel

然后

[root@centos6 ~]# cd redis-cplusplus-client

當(dāng)要調(diào)用該庫的時候,把如下幾個文件拷貝過去一起編譯就可以了
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"); // 獲取操作系統(tǒng)變量
        string host = "localhost";
        if(c_host)
            host = c_host;
        _client = new redis::client(host);
    }
}

// 調(diào)用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) 需要三個參數(shù)
        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;
}
*/

請?zhí)崆皢觬edis服務(wù)器泡孩,我是在本機(jī)啟動的,所以地址就是localhost寺谤,端口不寫默認(rèn)就是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客戶端,執(zhí)行

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 |
+------------+-----+------------+----------+

安裝完成矗漾,現(xiàn)在測試锈候。在mysql中執(zhí)行

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

返回字符"0",說明調(diào)用成功敞贡。到redis上看看結(jié)果

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

數(shù)據(jù)正確泵琳,mysql的DUF和redis通信成功,并且正確的修改數(shù)據(jù)誊役。

2.用觸發(fā)器實現(xiàn)動態(tài)更新redis緩存

這一步的思路获列,就是在mysql中創(chuàng)建一個觸發(fā)器,監(jiān)聽表的insert/update/delete等操作蛔垢,有數(shù)據(jù)更新的時候調(diào)用上一步的UDF函數(shù)刷新信息到redis緩存中去击孩。

先準(zhǔn)備數(shù)據(jù)庫表和觸發(fā)器

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

觸發(fā)器

delimiter $
create trigger tg_user
after insert on tb_user
for each row 
begin
    set @id = (select redis_hset(CONCAT('user_', new.id), 'id', CAST(new.id AS CHAR)));
    set @username = (select redis_hset(CONCAT('user_', new.id), 'username', new.username));
end $

這里觸發(fā)器監(jiān)控的是insert,當(dāng)插入新數(shù)據(jù)的時候鹏漆,會把用戶id和用戶名刷新到redis緩存中去巩梢。也可以針對update和delete做觸發(fā),篇幅有限艺玲,就不列出來了括蝠。

測試

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)

插入了兩條數(shù)據(jù),查看redis的反應(yīng)

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的內(nèi)容也和我們在mysql中插入的一致忌警。至此,我們的目的秒梳,“數(shù)據(jù)庫更新法绵,自動刷新到緩存”就實現(xiàn)了,妥妥的酪碘!

總結(jié)

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


創(chuàng)建于 2016-08-23 杭州,更新于 2016-08-23 杭州

該文章在以下平臺同步

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扶关,一起剝皮案震驚了整個濱河市阴汇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌节槐,老刑警劉巖搀庶,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異铜异,居然都是意外死亡哥倔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門揍庄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咆蒿,“玉大人,你說我怎么就攤上這事蚂子∥植猓” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵食茎,是天一觀的道長蒂破。 經(jīng)常有香客問我,道長别渔,這世上最難降的妖魔是什么附迷? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮哎媚,結(jié)果婚禮上喇伯,老公的妹妹穿的比我還像新娘。我一直安慰自己抄伍,他們只是感情好艘刚,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著截珍,像睡著了一般攀甚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岗喉,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天秋度,我揣著相機(jī)與錄音,去河邊找鬼钱床。 笑死荚斯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播事期,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼滥壕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了兽泣?” 一聲冷哼從身側(cè)響起绎橘,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唠倦,沒想到半個月后称鳞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稠鼻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年冈止,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片候齿。...
    茶點(diǎn)故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡熙暴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出毛肋,到底是詐尸還是另有隱情怨咪,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布润匙,位于F島的核電站诗眨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏孕讳。R本人自食惡果不足惜匠楚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厂财。 院中可真熱鬧芋簿,春花似錦、人聲如沸璃饱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荚恶。三九已至撩穿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谒撼,已是汗流浹背食寡。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留廓潜,地道東北人抵皱。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓善榛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親呻畸。 傳聞我的和親對象是個殘疾皇子移盆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)伤为,斷路器味滞,智...
    卡卡羅2017閱讀 134,693評論 18 139
  • 轉(zhuǎn)載地址:http://gnucto.blog.51cto.com/3391516/998509 Redis與Me...
    Ddaidai閱讀 21,453評論 0 82
  • 道藝合一:追尋生命根本意義,實踐生活美學(xué)之 在自己的寧靜中擊鼓 ——臺灣藝文團(tuán)體“優(yōu)人神鼓” 優(yōu)钮呀,在中國傳統(tǒng)戲曲中...
    海藍(lán)堡堡主閱讀 550評論 0 18
  • 因為各種原因今天工作效率很低,明天也被安排了昨凡。 要做合群的事爽醋,然后堅持獨(dú)行的信念。
    武允兒閱讀 271評論 0 0
  • 我素來是個好靜的人,喜歡獨(dú)處哪痰,喜歡大自然遂赠。 走在鄉(xiāng)間的田野里,很多時候晌杰,四周一片寂靜跷睦。天地間仿佛只有我一人。 我家...
    一世福緣閱讀 2,500評論 155 211