
通过mysql自动同步redis
在服务端开发过程中,一般会使用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去完成。一. 什么是UDFUDF,是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 TypeC/C++ TypeSTRINGchar *INTEGERlong longREALdouble2. 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>34 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 }1011 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;78 static redis::client *m_client = NULL;910 extern "C" char *redis_hset(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error) {11 try {12 // 连接Redis13 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 }2122 if(!(args->args && args->args[0] && args->args[1] && args->args[2])) {23 *is_null = 1;24 return result;25 }2627 // 调用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 }3839 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', &