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的开发包:

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

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

  1. 1 /*simple.cpp*/
  2. 2 #include <mysql.h>
  3. 3
  4. 4 extern "C" long long simple_add(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
  5. 5 {
  6. 6 int a = *((long long *)args->args[0]);
  7. 7 int b = *((long long *)args->args[1]);
  8. 8 return a + b;
  9. 9 }
  10. 10
  11. 11 extern "C" my_bool simple_add_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
  12. 12 {
  13. 13 return 0;
  14. 14 }

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

  1. extern "C" { ... }

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

  1. [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下:

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

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

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

测试UDF函数:

  1. mysql> select simple_add(10, 5);
  2. +-------------------+
  3. | simple_add(10, 5) |
  4. +-------------------+
  5. | 15 |
  6. +-------------------+
  7. 1 row in set (0.00 sec)

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

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

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

  1. mysql> DROP FUNCTION simple_add;
  2. 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:

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

然后下载redis cpp client源码:

  1. [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. 1 /* test.cpp */
  2. 2 #include <stdio.h>
  3. 3 #include <mysql.h>
  4. 4 #include "redisclient.h"
  5. 5 using namespace boost;
  6. 6 using namespace std;
  7. 7
  8. 8 static redis::client *m_client = NULL;
  9. 9
  10. 10 extern "C" char *redis_hset(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error) {
  11. 11 try {
  12. 12 // 连接Redis
  13. 13 if(NULL == m_client) {
  14. 14 const char* c_host = getenv("REDIS_HOST");
  15. 15 string host = "127.0.0.1";
  16. 16 if(c_host) {
  17. 17 host = c_host;
  18. 18 }
  19. 19 m_client = new redis::client(host);
  20. 20 }
  21. 21
  22. 22 if(!(args->args && args->args[0] && args->args[1] && args->args[2])) {
  23. 23 *is_null = 1;
  24. 24 return result;
  25. 25 }
  26. 26
  27. 27 // 调用hset插入一个哈希表
  28. 28 if(m_client->hset(args->args[0], args->args[1], args->args[2])) {
  29. 29 return result;
  30. 30 } else {
  31. 31 *error = 1;
  32. 32 return result;
  33. 33 }
  34. 34 } catch (const redis::redis_error& e) {
  35. 35 return result;
  36. 36 }
  37. 37 }
  38. 38
  39. 39 extern "C" my_bool redis_hset_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
  40. 40 if (3 != args->arg_count) {
  41. 41 // hset(key, field, value) 需要三个参数
  42. 42 strncpy(message, "Please input 3 args for: hset('key', 'field', 'value');", MYSQL_ERRMSG_SIZE);
  43. 43 return -1;
  44. 44 }
  45. 45 if (args->arg_type[0] != STRING_RESULT ||
  46. 46 args->arg_type[1] != STRING_RESULT ||
  47. 47 args->arg_type[2] != STRING_RESULT) {
  48. 48 // 检查参数类型
  49. 49 strncpy(message, "Args type error: hset('key', 'field', 'value');", MYSQL_ERRMSG_SIZE);
  50. 50 return -1;
  51. 51 }
  52. 52
  53. 53 args->arg_type[0] = STRING_RESULT;
  54. 54 args->arg_type[1] = STRING_RESULT;
  55. 55 args->arg_type[2] = STRING_RESULT;
  56. 56
  57. 57 initid->ptr = NULL;
  58. 58 return 0;
  59. 59 }

编译链接:

  1. [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的插件目录下并提权:

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

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

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

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

  1. mysql> SELECT redis_hset('zhxilin', 'id', '09388334');
  2. +-----------------------------------------+
  3. | redis_hset('zhxilin', 'id', '09388334') |
  4. +-----------------------------------------+
  5. | 0 |
  6. +-----------------------------------------+
  7. 1 row in set (0.00 sec)

打开redis-cli,查看结果:

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

四. 通过MySQL触发器刷新Redis

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

  1. CREATE TRIGGER trigger_name
  2. trigger_time
  3. trigger_event ON table_name
  4. FOR EACH ROW
  5. trigger_statement

trigger_time表示触发时机,值为AFTERBEFORE

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

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

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

  1. BEGIN
  2. [statement_list]
  3. END

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

  1. mysql> DELIMITER $

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

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

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

  1. mysql> SHOW TRIGGERS;
  1. mysql> DROP TRIGGER tg_student;

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

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

MySQL的结果:

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

Redis的结果:

  1. 127.0.0.1:6379> HGETALL stu_09388165
  2. 1) "id"
  3. 2) "09388165"
  4. 3) "name"
  5. 4) "Rose"
  6. 5) "age"
  7. 6) "19"
  8. 7) "gender"
  9. 8) "F"
  10. 9) "department"
  11. 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的更多相关文章

  1. Redis和MySQL数据同步及Redis使用场景

    1.同步MySQL数据到Redis (1) 在redis数据库设置缓存时间,当该条数据缓存时间过期之后自动释放,去数据库进行重新查询,但这样的话,我们放在缓存中的数据对数据的一致性要求不是很高才能放入 ...

  2. 几篇关于MySQL数据同步到Elasticsearch的文章---第一篇:Debezium实现Mysql到Elasticsearch高效实时同步

    文章转载自: https://mp.weixin.qq.com/s?__biz=MzI2NDY1MTA3OQ==&mid=2247484358&idx=1&sn=3a78347 ...

  3. Mysql数据同步Elasticsearch方案总结

    Mysql数据同步Elasticsearch方案总结 https://my.oschina.net/u/4000872/blog/2252620

  4. 快速同步mysql数据到redis中

    MYSQL快速同步数据到Redis 举例场景:存储游戏玩家的任务数据,游戏服务器启动时将mysql中玩家的数据同步到redis中. 从MySQL中将数据导入到Redis的Hash结构中.当然,最直接的 ...

  5. 利用gearman同步mysql数据到redis

    一.Gearman 1.Gearman是一个分发任务的程序框架. 2.体系:a.client:发送一个jobb.server:找到合适的worker,把job交给该workerc.worker:处理j ...

  6. 几篇关于MySQL数据同步到Elasticsearch的文章---第三篇:logstash_output_kafka:Mysql同步Kafka深入详解

    文章转载自: https://mp.weixin.qq.com/s?__biz=MzI2NDY1MTA3OQ==&mid=2247484411&idx=1&sn=1f5a371 ...

  7. ElasticSearch5+logstash的logstash-input-jdbc实现mysql数据同步

    在实现的路上遇到了各种坑,再次验证官方文档只能产考不能全信! ElasticSearch安装就不说了上一篇有说! 安装logstash 官方:https://www.elastic.co/guide/ ...

  8. Memcached与MySQL数据同步

    1.介绍 在生产环境中,我们经常使用MySQL作为应用的数据库.但是随着用户的增多数据量的增大,我们将会自然而然的选择Memcached作为缓存数据库,从而减小MySQL的压力.但是memcached ...

  9. 通过mysql自动同步redis

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

随机推荐

  1. Oracle 11gR2 rac 的各项服务说明

       安装结束后,会产生一些后台进程来确保集群正常工作并能够与外部通讯.其中的一些有序linux平台的要求需要以root用户权限来启动.比如,网络配置的改动就需要更高的权限.其他后台进程将以grid软 ...

  2. h5 轻应用

    http://www.html5tricks.com/ http://open.weibo.com/wiki/%E8%BD%BB%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91 ...

  3. C#实现不安装Oracle客户端访问远程服务器数据

    概述: C#通过使用ADO的方式在未安装Oracle数据库的前提下,客户端程序远程访问服务器,会出现:“System.Data.OracleClient 需要 Oracle 客户端软件 8.1.7 或 ...

  4. linux 标准I/O (一)

    在前面<UNIX环境高级编程----文件描述符浅析>一文中所讲的I/O函数都是针对文件描述符.而对于标准I/O库,它们的操作都是围绕流来进行的.当用标准I/O库打开或创建一个文件时,我们已 ...

  5. MFC的组合框(ComboBox)控件切换下拉样式

    由于课题的需求需要做MFC串口程序,看了百度下载的串口助手的界面风格,发现这个设计很好 波特率的组合框只给出了5个可选数值,然后第6个选项是Custom,即手动输入. 实际上DCB结构的BaudRat ...

  6. 关于android im

    从各种pack中看到 环信 easemob.com  300万用户以下免费 org.jivesoftware.smackopenfireappkefu等开源im

  7. MongoDB整库备份+整库导入

    备份前检查: [root@Load29 tmp]# mongo localhost: MongoDB shell version: connecting to: localhost:/test Ser ...

  8. xsl如何实现递归复制?

    <xsl:template match="*" mode="addSeatSelectionToAirProduct"> <xsl:eleme ...

  9. Game of War - Fire Age 有何特别之处?

    作者:福克斯007 链接:https://www.zhihu.com/question/21611550/answer/52458767来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转 ...

  10. pandas的set_index和reset_index方法

    import pandas as pd data = pd.DataFrame(np.arange(1,10).reshape(3,3),index=["a","b&qu ...