转载:mysql数据同步redis
from: http://www.cnblogs.com/zhxilin/archive/2016/09/30/5923671.html
在服务端开发过程中,一般会使用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主函数:
1 /*simple.cpp*/
2 #include <mysql.h>
3
4 extern "C" long long simple_add(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
5 {
6 int a = *((long long *)args->args[0]);
7 int b = *((long long *)args->args[1]);
8 return a + b;
9 }
10
11 extern "C" my_bool simple_add_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
12 {
13 return 0;
14 }
由于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作为初始化,检查参数个数和类型。
1 /* test.cpp */
2 #include <stdio.h>
3 #include <mysql.h>
4 #include "redisclient.h"
5 using namespace boost;
6 using namespace std;
7
8 static redis::client *m_client = NULL;
9
10 extern "C" char *redis_hset(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error) {
11 try {
12 // 连接Redis
13 if(NULL == m_client) {
14 const char* c_host = getenv("REDIS_HOST");
15 string host = "127.0.0.1";
16 if(c_host) {
17 host = c_host;
18 }
19 m_client = new redis::client(host);
20 }
21
22 if(!(args->args && args->args[0] && args->args[1] && args->args[2])) {
23 *is_null = 1;
24 return result;
25 }
26
27 // 调用hset插入一个哈希表
28 if(m_client->hset(args->args[0], args->args[1], args->args[2])) {
29 return result;
30 } else {
31 *error = 1;
32 return result;
33 }
34 } catch (const redis::redis_error& e) {
35 return result;
36 }
37 }
38
39 extern "C" my_bool redis_hset_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
40 if (3 != args->arg_count) {
41 // hset(key, field, value) 需要三个参数
42 strncpy(message, "Please input 3 args for: hset('key', 'field', 'value');", MYSQL_ERRMSG_SIZE);
43 return -1;
44 }
45 if (args->arg_type[0] != STRING_RESULT ||
46 args->arg_type[1] != STRING_RESULT ||
47 args->arg_type[2] != STRING_RESULT) {
48 // 检查参数类型
49 strncpy(message, "Args type error: hset('key', 'field', 'value');", MYSQL_ERRMSG_SIZE);
50 return -1;
51 }
52
53 args->arg_type[0] = STRING_RESULT;
54 args->arg_type[1] = STRING_RESULT;
55 args->arg_type[2] = STRING_RESULT;
56
57 initid->ptr = NULL;
58 return 0;
59 }
编译链接:
[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 777 /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', '09388334');
+-----------------------------------------+
| redis_hset('zhxilin', 'id', '09388334') |
+-----------------------------------------+
| 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表示触发时机,值为AFTER或BEFORE;
trigger_event表示触发的事件,值为INSERT、UPDATE、DELETE等;
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('09388165', '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的时候需要非常谨慎!
转载:mysql数据同步redis的更多相关文章
- Redis和MySQL数据同步及Redis使用场景
1.同步MySQL数据到Redis (1) 在redis数据库设置缓存时间,当该条数据缓存时间过期之后自动释放,去数据库进行重新查询,但这样的话,我们放在缓存中的数据对数据的一致性要求不是很高才能放入 ...
- 几篇关于MySQL数据同步到Elasticsearch的文章---第一篇:Debezium实现Mysql到Elasticsearch高效实时同步
文章转载自: https://mp.weixin.qq.com/s?__biz=MzI2NDY1MTA3OQ==&mid=2247484358&idx=1&sn=3a78347 ...
- Mysql数据同步Elasticsearch方案总结
Mysql数据同步Elasticsearch方案总结 https://my.oschina.net/u/4000872/blog/2252620
- 快速同步mysql数据到redis中
MYSQL快速同步数据到Redis 举例场景:存储游戏玩家的任务数据,游戏服务器启动时将mysql中玩家的数据同步到redis中. 从MySQL中将数据导入到Redis的Hash结构中.当然,最直接的 ...
- 利用gearman同步mysql数据到redis
一.Gearman 1.Gearman是一个分发任务的程序框架. 2.体系:a.client:发送一个jobb.server:找到合适的worker,把job交给该workerc.worker:处理j ...
- 几篇关于MySQL数据同步到Elasticsearch的文章---第三篇:logstash_output_kafka:Mysql同步Kafka深入详解
文章转载自: https://mp.weixin.qq.com/s?__biz=MzI2NDY1MTA3OQ==&mid=2247484411&idx=1&sn=1f5a371 ...
- ElasticSearch5+logstash的logstash-input-jdbc实现mysql数据同步
在实现的路上遇到了各种坑,再次验证官方文档只能产考不能全信! ElasticSearch安装就不说了上一篇有说! 安装logstash 官方:https://www.elastic.co/guide/ ...
- Memcached与MySQL数据同步
1.介绍 在生产环境中,我们经常使用MySQL作为应用的数据库.但是随着用户的增多数据量的增大,我们将会自然而然的选择Memcached作为缓存数据库,从而减小MySQL的压力.但是memcached ...
- 通过mysql自动同步redis
在服务端开发过程中,一般会使用MySQL等关系型数据库作为最终的存储引擎,Redis其实也可以作为一种键值对型的数据库,但在一些实际场景中,特别是关系型结构并不适合使用Redis直接作为数据库.这俩家 ...
随机推荐
- PHP中开启gzip压缩的2种方法
网页开启gzip压缩以后,其体积可以减小20%~90%,可以节省下大量的带宽,从而减少页面响应时间,提高用户体验. php配置改法: 复制代码代码如下: zlib.output_compression ...
- LOJ 572 「LibreOJ Round #11」Misaka Network 与求和——min_25筛
题目:https://loj.ac/problem/572 莫比乌斯反演得 \( ans=\sum\limits_{D=1}^{n}\left\lfloor\frac{n}{D}\right\rflo ...
- Json之语法
JSON 文本格式在语法上与创建 JavaScript 对象的代码相同. 由于这种相似性,无需解析器,JavaScript 程序能够使用内建的 eval() 函数,用 JSON 数据来生成原生的 Ja ...
- 出现“安全时间戳无效,因为其创建时间(“2013-10-30T14:42:07.861Z”)是将来的时间。当前时间为“2013-10-30T14:36:23.988Z”,允许的时钟偏差是“00:05:00””的原因
具体原因是服务器的时间和本地的时间不能超过5分钟,超过5分钟了.只要修改你本地机器的时间,和服务器相差的时间不能超过5分钟,就可以了. 根本原因是windows 系统域认证要求的,所有都一样.
- javacv 340使用 人脸检测例子【转载】
Java下使用opencv进行人脸检测 工作需要,研究下人脸识别,发现opencv比较常用,尽管能检测人脸,但识别率不高,多数是用来获取摄像头的视频流的,提取里面的视频帧,实现人脸识别时通常会和其他框 ...
- vmware :Ubuntu 12.04添加新硬盘
http://blog.csdn.net/hanpengyu/article/details/7475645 一.VMware新增磁盘的设置步骤 (建议:在设置虚拟的时候,不要运行虚拟机的系统,不然添 ...
- Android自动化框架 模拟操作 模拟测试
转自:http://bbs2.c114.net/home.php?mod=space&uid=1025779&do=blog&id=5322 几种常见的Android自动化测试 ...
- man page ,info page 和/usr/share/doc/整理自鸟哥
- 比如查看date指令 - 命令:vbird@www ~]$ man date - 返回示例(部分):DATE(1) User Commands DATE(1) - 部分释义(注释2): ...
- EasyMall 项目记录-环境搭建
一.搭建项目运行的环境 (1)修改hosts文件 更改:C:\Windows\System32\drivers\etc目录下hosts文件 添加:127.0.0.1 www.easyma ...
- pandas的map函数与apply函数的区别
import pandas as pd import numpy as np df = pd.DataFrame(np.random.randn(4,3),columns=list("ABC ...