在服务端开发过程中,一般会使用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. SQLServer中的数据库备份和还原

    更多资源:http://denghejun.github.io 备份 SQLServer中的备份,这里是T-SQL的用法,具体示例代码如下,使用也相对简单,其中TestDatabase 是指所需备份的 ...

  2. 安卓刷机--fastboot线刷

    首先需要下载fastboot.exe,copy到system32文件夹下. 对于安卓系统的智能手机,同时按住开机键和音量减键,或手机连上电脑,输入adb reboot bootloader进入fast ...

  3. 避免Castle Windsor引起的内存泄露

    原文地址: http://nexussharp.wordpress.com/2012/04/21/castle-windsor-avoid-memory-leaks-by-learning-the-u ...

  4. Hadoop学习笔记—20.网站日志分析项目案例(二)数据清洗

    网站日志分析项目案例(一)项目介绍:http://www.cnblogs.com/edisonchou/p/4449082.html 网站日志分析项目案例(二)数据清洗:当前页面 网站日志分析项目案例 ...

  5. 从零3D基础入门XNA 4.0(2)——模型和BasicEffect

    [题外话] 上一篇文章介绍了3D开发基础与XNA开发程序的整体结构,以及使用Model类的Draw方法将模型绘制到屏幕上.本文接着上一篇文章继续,介绍XNA中模型的结构.BasicEffect的使用以 ...

  6. 【转】Spark常见问题汇总

    原文地址:https://my.oschina.net/tearsky/blog/629201 摘要: 1.Operation category READ is not supported in st ...

  7. Atitit 深入理解耦合Coupling的原理与attilax总结

    Atitit 深入理解耦合Coupling的原理与attilax总结     耦合是指两个或两个以上的电路元件或电网络等的输入与输出之间存在紧密配合与相互影响,并通过相互作用从一侧向另一侧传输能量的现 ...

  8. 深入学习jQuery选择器系列第六篇——过滤选择器之状态选择器

    × 目录 [1]焦点状态 [2]哈希状态 [3]动画状态[4]显隐状态 前面的话 过滤选择器的内容非常多,本文介绍过滤选择器的最后一部分——状态选择器 焦点状态 :focus :focus选择器选择当 ...

  9. Android之登录那点事

    随着互联网的高速发展,一个应用为了保护用户的隐私,通常会通过设置用户名+密码的验证方式保证用户隐私的相对安全,我知道一般网站的登录验证,通常会设置一个二维码,通过验证二维码,防止恶意软件通过机械程序, ...

  10. .NET足球赛事资料数据库平台SmartLottery开源发布——全球足球联赛应有尽有

            本博客所有文章分类的总目录:[总目录]本博客博文总目录-实时更新 开源C#彩票数据资料库系列文章总目录:[目录]C#搭建足球赛事资料库与预测平台与彩票数据分析目录 前2个月,我的系列文 ...