在服务端开发过程中,一般会使用MySQL等关系型数据库作为最终的存储引擎,Redis其实也可以作为一种键值对型的数据库,但在一些实际场景中,特别是关系型结构并不适合使用Redis直接作为数据库。这俩家伙简直可以用“男女搭配,干活不累”来形容,搭配起来使用才能事半功倍。本篇我们就这两者如何合理搭配以及他们之间数据如何进行同步展开。

一般地,Redis可以用来作为MySQL的缓存层。为什么MySQL最好有缓存层呢?想象一下这样的场景:在一个多人在线的游戏里,排行榜、好友关系、队列等直接关系数据的情景下,如果直接和MySQL正面交手,大量的数据请求可能会让MySQL疲惫不堪,甚至过量的请求将会击穿数据库,导致整个数据服务中断,数据库性能的瓶颈将掣肘业务的开发;那么如果通过Redis来做数据缓存,将大大减小查询数据的压力。在这种架子里,当我们在业务层有数据查询需求时,先到Redis缓存中查询,如果查不到,再到MySQL数据库中查询,同时将查到的数据更新到Redis里;当我们在业务层有修改插入数据需求时,直接向MySQL发起请求,同时更新Redis缓存。

在上面这种架子中,有一个关键点,就是MySQL的CRUD发生后自动地更新到Redis里,这需要通过MySQL UDF来实现。具体来说,我们把更新Redis的逻辑放到MySQL中去做,即定义一个触发器Trigger,监听CRUD这些操作,当操作发生后,调用对应的UDF函数,远程写回Redis,所以业务逻辑只需要负责更新MySQL就行了,剩下的交给MySQL UDF去完成。

一. 什么是UDF

UDF,是User Defined Function的缩写,用户定义函数。MySQL支持函数,也支持自定义的函数。UDF比存储方法有更高的执行效率,并且支持聚集函数。

UDF定义了5个API:xxx_init()、xxx_deinit()、xxx()、xxx_add()、xxx_clear()。官方文档(http://dev.mysql.com/doc/refman/5.7/en/adding-udf.html)给出了这些API的说明。相关的结构体定义在mysql_com.h里,它又被mysql.h包含,使用时只需#include<mysql.h>即可。他们之间的关系和执行顺序可以以下图来表示:

1. xxx()

这是主函数,5个函数至少需要xxx(),对MySQL操作的结果在此返回。函数的声明如下:

char *xxx(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error);

long long xxx(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);

double xxx(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);

SQL的类型和C/C++类型的映射:

SQL Type C/C++ Type
STRING char *
INTEGER long long
REAL double

2. xxx_init()

xxx()主函数的初始化,如果定义了,则用来检查传入xxx()的参数数量、类型、分配内存空间等初始化操作。函数的声明如下:

my_bool xxx_init(UDF_INIT *initid, UDF_ARGS *args, char *message);

3. xxx_deinit()

xxx()主函数的反初始化,如果定义了,则用来释放初始化时分配的内存空间。函数的声明如下:

void xxx_deinit(UDF_INIT *initid);

4. xxx_add()

在聚合UDF中反复调用,将参数加入聚合参数中。函数的声明如下:

void xxx_add(UDF_INIT *initid, UDF_ARGS *args, char *is_null,char *error);

5. xxx_clear()

在聚合UDF中反复调用,重置聚合参数,为下一行数据的操作做准备。函数的声明如下:

void xxx_clear(UDF_INIT *initid, char *is_null, char *error);

二. UDF函数的基本使用

在此之前,需要先安装mysql的开发包:

[root@localhost zhxilin]# yum install mysql-devel -y

我们定义一个最简单的UDF主函数:

 /*simple.cpp*/
#include <mysql.h> extern "C" long long simple_add(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
{
int a = *((long long *)args->args[]);
int b = *((long long *)args->args[]);
return a + b;
} extern "C" my_bool simple_add_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
return ;
}

由于mysql提供的接口是C实现的,我们在C++中使用时需要添加:

extern "C" { ... }

接下来编译成动态库.so:

[zhxilin@localhost mysql-redis-test]$ g++ -shared -fPIC -I /usr/include/mysql -o simple_add.so simple.cpp

-shared 表示编译和链接时使用的是全局共享的类库;

-fPIC编译器输出位置无关的目标代码,适用于动态库;

-I /usr/include/mysql 指明包含的头文件mysql.h所在的位置。

编译出simple_add.so后用root拷贝到/usr/lib64/mysql/plugin下:

[root@localhost mysql-redis-test]# cp simple_add.so /usr/lib64/mysql/plugin/

紧接着可以在MySQL中创建函数执行了。登录MySQL,创建关联函数:

mysql> CREATE FUNCTION simple_add RETURNS INTEGER SONAME 'simple_add.so';
Query OK, 0 rows affected (0.04 sec)

测试UDF函数:

mysql> select simple_add(10, 5);
+-------------------+
| simple_add(10, 5) |
+-------------------+
| 15 |
+-------------------+
1 row in set (0.00 sec)

可以看到,UDF正确执行了加法。

创建UDF函数的语法是 CREATE FUNCTION xxx RETURNS [INTEGER/STRING/REAL] SONAME '[so name]';

删除UDF函数的语法是 DROP FUNCTION simple_add;

mysql> DROP FUNCTION simple_add;
Query OK, 0 rows affected (0.03 sec)

三. 在UDF中访问Redis

跟上述做法一样,只需在UDF里调用Redis提供的接口函数。Redis官方给出了Redis C++ Client (https://github.com/mrpi/redis-cplusplus-client),封装了Redis的基本操作。

源码是依赖boost,需要先安装boost:

[root@localhost dev]# yum install boost boost-devel

然后下载redis cpp client源码:

[root@localhost dev]# git clone https://github.com/mrpi/redis-cplusplus-client

使用时需要把redisclient.h、anet.h、fmacros.h、anet.c 这4个文件考到目录下,开始编写关于Redis的UDF。我们定义了redis_hset作为主函数,连接Redis并调用hset插入哈希表,redis_hset_init作为初始化,检查参数个数和类型。

 /* test.cpp */
#include <stdio.h>
#include <mysql.h>
#include "redisclient.h"
using namespace boost;
using namespace std; static redis::client *m_client = NULL; extern "C" char *redis_hset(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error) {
try {
// 连接Redis
if(NULL == m_client) {
const char* c_host = getenv("REDIS_HOST");
string host = "127.0.0.1";
if(c_host) {
host = c_host;
}
m_client = new redis::client(host);
} if(!(args->args && args->args[] && args->args[] && args->args[])) {
*is_null = ;
return result;
} // 调用hset插入一个哈希表
if(m_client->hset(args->args[], args->args[], args->args[])) {
return result;
} else {
*error = ;
return result;
}
} catch (const redis::redis_error& e) {
return result;
}
} extern "C" my_bool redis_hset_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
if ( != args->arg_count) {
// hset(key, field, value) 需要三个参数
strncpy(message, "Please input 3 args for: hset('key', 'field', 'value');", MYSQL_ERRMSG_SIZE);
return -;
}
if (args->arg_type[] != STRING_RESULT ||
args->arg_type[] != STRING_RESULT ||
args->arg_type[] != STRING_RESULT) {
// 检查参数类型
strncpy(message, "Args type error: hset('key', 'field', 'value');", MYSQL_ERRMSG_SIZE);
return -;
} args->arg_type[] = STRING_RESULT;
args->arg_type[] = STRING_RESULT;
args->arg_type[] = STRING_RESULT; initid->ptr = NULL;
return ;
}

编译链接:

[zhxilin@localhost mysql-redis-test]$ g++ -shared -fPIC -I /usr/include/mysql -lboost_serialization -lboost_system -lboost_thread -o libmyredis.so anet.c test.cpp

编译时需要加上-lboost_serialization -lboost_system -lboost_thread, 表示需要链接三个动态库:libboost_serialization.so、libboost_system.so、libboost_thread.so,否则在运行时会报缺少函数定义的错误。

编译出libmyredis.so之后,将其拷贝到mysql的插件目录下并提权:

[root@localhost mysql-redis-test]# cp libmyredis.so /usr/lib64/mysql/plugin/ & chmod  /usr/lib64/mysql/plugin/libmyredis.so 

完成之后登录MySQL,创建关联函数测试一下:

mysql> DROP FUNCTION IF EXISTS `redis_hset`;
Query OK, 0 rows affected (0.16 sec) mysql> CREATE FUNCTION redis_hset RETURNS STRING SONAME 'libmyredis.so';
Query OK, 0 rows affected (0.02 sec)

先删除老的UDF,注意函数名加反引号(``)。调用UDF测试,返回0,执行成功:

mysql> SELECT redis_hset('zhxilin', 'id', '');
+-----------------------------------------+
| redis_hset('zhxilin', 'id', '') |
+-----------------------------------------+
| 0 |
+-----------------------------------------+
1 row in set (0.00 sec)

打开redis-cli,查看结果:

127.0.0.1:6379> HGETALL zhxilin
1) "id"
2) "09388334"

四. 通过MySQL触发器刷新Redis

在上一节的基础上,我们想让MySQL在增删改查的时候自动调用UDF,还需要借助MySQL触发器。触发器可以监听INSERT、UPDATE、DELETE等基本操作。在MySQL中,创建触发器的基本语法如下:

CREATE TRIGGER trigger_name
trigger_time
trigger_event ON table_name
FOR EACH ROW
trigger_statement

trigger_time表示触发时机,值为AFTERBEFORE

trigger_event表示触发的事件,值为INSERTUPDATEDELETE等;

trigger_statement表示触发器的程序体,可以是一句SQL语句或者调用UDF。

在trigger_statement中,如果有多条SQL语句,需要用BEGIN...END包含起来:

BEGIN
[statement_list]
END

由于MySQL默认的结束分隔符是分号(;),如果我们在BEGIN...END中出现了分号,将被标记成结束,此时没法完成触发器的定义。有一个办法,可以调用DELIMITER命令来暂时修改结束分隔符,用完再改会分号即可。比如改成$:

mysql> DELIMITER $

我们开始定义一个触发器,监听对Student表的插入操作,Student表在上一篇文章中创建的,可以查看上一篇文章。

mysql > DELIMITER $
> CREATE TRIGGER tg_student
> AFTER INSERT on Student
> FOR EACH ROW
> BEGIN
> SET @id = (SELECT redis_hset(CONCAT('stu_', new.Sid), 'id', CAST(new.Sid AS CHAR(8))));
> SET @name = (SELECT redis_hset(CONCAT('stu_', new.Sid), 'name', CAST(new.Sname AS CHAR(20))));
> Set @age = (SELECT redis_hset(CONCAT('stu_', new.Sid), 'age', CAST(new.Sage AS CHAR)));
> Set @gender = (SELECT redis_hset(CONCAT('stu_', new.Sid), 'gender', CAST(new.Sgen AS CHAR)));
> Set @dept = (SELECT redis_hset(CONCAT('stu_', new.Sid), 'department', CAST(new.Sdept AS CHAR(10))));
> END $

创建完触发器可以通过show查看,或者drop删除:

mysql> SHOW TRIGGERS;
mysql> DROP TRIGGER tg_student;

接下来我们调用一句插入语句,然后观察Redis和MySQL数据的变化:

mysql> INSERT INTO Student VALUES('', 'Rose', 19, 'F', 'SS3-205');
Query OK, 1 row affected (0.27 sec)

MySQL的结果:

mysql> SELECT * FROM Student;
+----------+---------+------+------+---------+
| Sid | Sname | Sage | Sgen | Sdept |
+----------+---------+------+------+---------+
| 09388123 | Lucy | 18 | F | AS2-123 |
| 09388165 | Rose | 19 | F | SS3-205 |
| 09388308 | zhsuiy | 19 | F | MD8-208 |
| 09388318 | daemon | 18 | M | ZS4-630 |
| 09388321 | David | 20 | M | ZS4-731 |
| 09388334 | zhxilin | 20 | M | ZS4-722 |
+----------+---------+------+------+---------+
6 rows in set (0.00 sec)

Redis的结果:

127.0.0.1:6379> HGETALL stu_09388165
1) "id"
2) "09388165"
3) "name"
4) "Rose"
5) "age"
6) "19"
7) "gender"
8) "F"
9) "department"
10) "SS3-205"

以上结果表明,当MySQL插入数据时,通过触发器调用UDF,实现了自动刷新Redis的数据。另外,调用MySQL插入的命令,可以通过C++实现,进而就实现了在C++的业务逻辑里,只需调用MySQL++的接口就能实现MySQL数据库和Redis缓存的更新,这部分内容在上一篇文章已经介绍过了。

总结

通过实践,能体会到MySQL和Redis是多么相亲相爱吧!^_^

本篇文章讲了从最基础的UDF开始,再到通过UDF连接Redis插入数据,再进一步介绍通过MySQL Trigger自动更新Redis数据的整个思路,实现了一个目标,即只在业务代码中更新MySQL数据库,进而Redis能够自动同步刷新。

MySQL对UDF函数和触发器的支持,使得实现Redis数据和MySQL自动同步成了可能。当然UDF毕竟是通过插件的形式运行在MySQL中的,并没有过多的安全干预,一旦插件发生致命性崩溃,有可能MySQL也会挂,所以在编写UDF的时候需要非常谨慎!

【菜鸟玩Linux开发】通过MySQL自动同步刷新Redis的更多相关文章

  1. 通过MySql自动同步刷新redis

    在服务端开发过程中,一般会使用MySQL等关系型数据库作为最终的存储引擎,Redis其实也可以作为一种键值对型的数据库,但在一些实际场景中,特别是关系型结构并不适合使用Redis直接作为数据库.这俩家 ...

  2. 转载:【菜鸟玩Linux开发】通过MySQL自动同步刷新Redis

    转载: http://www.cnblogs.com/zhxilin/archive/2016/09/30/5923671.html

  3. 【菜鸟玩Linux开发】在C++里操作MySQL

    MySQL是一个的开源关系型数据库,对于服务端开发来说是一个优秀的选择.本篇内容将介绍如何在C++程序里操作MySQL数据库. ———————————————————————————————————— ...

  4. 【菜鸟玩Linux开发】在Linux中使用VS Code编译调试C++项目

    最近项目需求,需要在Linux下开发C++相关项目,经过一番摸索,简单总结了一下如何通过VS Code进行编译调试的一些注意事项. 关于VS Code在Linux下的安装这里就不提了,不管是CentO ...

  5. 【菜鸟玩Linux开发】Redis安装和自启动配置

    Redis是一个C实现的基于内存.可持久化的键值对数据库,在分布式服务中常作为缓存服务.本篇将介绍在CentOS下如何从零开始安装到配置启动服务. 一. 安装Redis Redis的安装其实相当简单, ...

  6. 通过mysql自动同步redis

    在服务端开发过程中,一般会使用MySQL等关系型数据库作为最终的存储引擎,Redis其实也可以作为一种键值对型的数据库,但在一些实际场景中,特别是关系型结构并不适合使用Redis直接作为数据库.这俩家 ...

  7. 在linux下实现mysql自动备份数据

    使用的系统为CentOS,mysql版本为5.6 备份功能主要利用以下功能实现: mysql命令中的mysqldump命令 linux下脚本编写 linux下crontab定时任务的使用 首先确定你要 ...

  8. linux系统中mysql自动备份脚本

    mysql数据库中存储着网站最核心最宝贵的数据,如果因为不可预测的原因导致数据损坏或丢失,对一个网站的打击是毁灭性的,一次又一次的教训提醒着我们一定要做好备份,但是手工备份确实比较麻烦,每天都要手工操 ...

  9. Linux 下的 mysql 自动备份

    Linux 下实现自动备份,主要就是编写好执行备份的 shell script( *.sh )文件,设好权限(可读,可执行).然后利用 Linux 定时任务 crontab 来执行备份脚本就可以了.以 ...

随机推荐

  1. myeclipse中导入js报如下错误Syntax error on token "Invalid Regular Expression Options", no accurate correc

    今天在使用bootstrap的时候引入的js文件出现错误Syntax error on token "Invalid Regular Expression Options", no ...

  2. 使用PowerShell找出具体某个站点所使用的模板(Web Template)名称?

    $web = get-spweb –identity http://servername/sites/site/web #得到站点的对象 $web.WebTemplate #得到WebTemplate ...

  3. iOS在线更新framework,使用NSBundle动态读取

    官方文档:https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/LoadingCode/Tasks/Loadin ...

  4. Nova PhoneGap框架 第七章 设备事件处理

    我们的框架包含了几种设备事件的处理,目的是为了让我们的程序员更容易的完成代码.这些事件包括:回退键(Android)和横竖屏切换事件. 7.1 Android回退键 首先来说说回退键的事件处理.当用户 ...

  5. 从Facebook跑来阿里的赵海平大叔,你要干啥?

    赵海平在今年三月份来到阿里,听毕玄(他现任主管)说去年五六月份就跟赵海平聊上了.有人问:为啥 BAT 三大巨头,你看中了阿里巴巴?在今天现场达一千多人的分享中赵海平给出了回复:“因为百度和腾讯没找我呗 ...

  6. ASP.NET MVC学前篇之Lambda表达式、依赖倒置

    ASP.NET MVC学前篇之Lambda表达式.依赖倒置 前言 随着上篇文章的阅读,可能有的朋友会有疑问,比如(A.Method(xxx=>xx>yy);)类似于这样的函数调用语句,里面 ...

  7. ASP.NET MVC 从零开始 - 自动化部署(其一)

    本文是从我的 github 博客 http://lxconan.github.io 导入的. 这是这个系列的第四篇了,前三篇请参见: ASP.NET MVC 从零开始 – Create and Run ...

  8. 【VC++技术杂谈008】使用zlib解压zip压缩文件

    最近因为项目的需要,要对zip压缩文件进行批量解压.在网上查阅了相关的资料后,最终使用zlib开源库实现了该功能.本文将对zlib开源库进行简单介绍,并给出一个使用zlib开源库对zip压缩文件进行解 ...

  9. C语言 · 动态数组的使用

    从键盘读入n个整数,使用动态数组存储所读入的整数,并计算它们的和与平均值分别输出.要求尽可能使用函数实现程序代码.平均值为小数的只保留其整数部分. 样例输入: 5 3 4 0 0 2样例输出:9 1样 ...

  10. Html5绘制时钟

    最近在对Html5比较感兴趣,就用空闲时间做一些小例子进行练习,今天绘制一个走动的时钟,具体如下图所示: 具体思路在上图已有说明,代码如下: <script type="text/ja ...