数据库

如何优雅得同步关系数据库至REDIS

很久没有更新博客了,最近在处理后端访问数据库的加速,主要方向是将对热数据的查询转移到redis上,这就涉及到关系数据库整体以及差异同步到redis的问题。考虑redis和关系数据库的数据结构差异,最后选择2种方式并用:

  • 将mysql表数据json化后存入redis
  • 将元数据生成hash键值对再存入的

 
以上就涉及到表如何转json以及如何异步对redis进行差异同步的问题。选择的方案是利用mysql的存储过程以及用户自定义函数来执行。

我选择的是一个名为mysql2redisUDF函数。函数有4个依赖

  • apr(依赖的库,最新版兼容,我用的是1.6.3)
  • apr-util(依赖的库,我用的是1.6.1,最新版应该是没问题的)
  • hiredis(mysql连接redis的库)
  • lib_mysqludf_json(mysql转json的库)

expat安装

因为很多人可能没有expat库,所以大家可以先检查一下,没有的话安装expat库,非常小的,安装极快

tar zxvf expat-2.0.1.tar.gz ##解压
cd expat-2.0.1
./configure ##编译
make && make install

apr安装

首先下载源码

wget http://mirror.bit.edu.cn/apache//apr/apr-util-1.6.1.tar.gz

解压->编译->make->make install

tar zxvf apr-1.6.3.tar.gz ##解压
cd apr-1.6.3
./configure --prefix=/usr/local/apr ##这里注意最好是指定一下安装目录
make
make test
make install

apr-util安装

下载同理,就不赘述了

tar zxvf apr-util-1.6.1.tar.gz 
cd apr-util-1.6.1
./configure --with-apr=/usr/local/apr/ ##此处要指定前面apr的安装目录,不然编译会出错
make && make install

hiredis安装

unzip hiredis-master.zip 
cd hiredis-master
make
make install
这里注意一下,一般这样安装就可以了,但是还是要检查一下链接库libhiredis.so的位置以及c文件hiredis.h的位置

lib_mysqludf_json安装

unzip lib_mysqludf_json-master.zip 
cd lib_mysqludf_json-master
gcc $(/mysql/bin/mysql_config --cflags) -shared -fPIC -o lib_mysqludf_json.so lib_mysqludf_json.c ##编译,注意要安装c++环境,"/mysql/bin/mysql_config"为mysql的配置文件路径,一般都不一样
cp lib_mysqludf_json.so /mysql/mysql56/lib/plugin/ ## "lib_mysqludf_json.so"是编译出来的mysql UDF函数插件,将它复制到mysql的插件目录

之后登录mysql创建UDF函数

mysql -uroot -p /* 登录mysql */
CREATE FUNCTION json_array RETURNS STRING SONAME 'lib_mysqludf_json.so';
CREATE FUNCTION json_members RETURNS STRING SONAME 'lib_mysqludf_json.so';
CREATE FUNCTION json_object RETURNS STRING SONAME 'lib_mysqludf_json.so';
CREATE FUNCTION json_values RETURNS STRING SONAME 'lib_mysqludf_json.so'; /* 这几个函数用途很广泛,大家可以看README用法 */

mysql2redis安装

最后是mysql2redis的安装,这里其实坑很多,但是官网就一个"make"了事。
解压之后先看看MAKEFILE

PLUGINDIR = "/mysql/mysql56/lib/plugin/"
INCLUDE=`/mysql/mysql56/bin/mysql_config --include` -I/usr/local/include  -I/usr/local/apr/include -I./
LIBS=-lhiredis -L$(PLUGINDIR)  -L/usr/local/apr/lib  -lapr-1  -laprutil-1

compile:
        gcc -Werror -O2 -g $(INCLUDE)  -I. -fPIC -shared -rdynamic lib_mysqludf_redis.c  $(LIBS) -o lib_mysqludf_redis_v2.so

install:
        gcc -Werror -O2 -g $(INCLUDE)  -I. -fPIC -shared -rdynamic lib_mysqludf_redis.c \
                $(LIBS) -o ${PLUGINDIR}/lib_mysqludf_redis_v2.so

uninstall:
        rm -f $(PLUGINDIR)/lib_mysqludf_redis_v2.so

clean:
        rm -f *~
        rm -f *.so
        rm -f *.out

首先是环境指定,"/mysql/mysql56/bin/mysql_config"是mysql配置文件目录,请修改为自己的,"/mysql/mysql56/lib/plugin/"myqsl插件目录,一并修改,其他主要是hiredis以及apr的环境,如果make出问题,注意检查环境设置。确认无误之后make,会生成一个名为"lib_mysqludf_redis_v2.so"的mysql插件,复制到插件目录cp lib_mysqludf_redis_v2.so /mysql/mysql56/lib/plugin/ 。

接着是最坑我的创建mysql2redis函数,主要是因为我的环境都是源码安装,各种库的目录有小问题,最后总结了几点。先把建函数过程贴出来

mysql -uroot -p
DROP FUNCTION IF EXISTS redis_servers_set_v2;
DROP FUNCTION IF EXISTS redis_command_v2;
DROP FUNCTION IF EXISTS free_resources;

CREATE FUNCTION redis_servers_set_v2 RETURNS int SONAME "lib_mysqludf_redis_v2.so";
CREATE FUNCTION redis_command_v2 RETURNS int SONAME "lib_mysqludf_redis_v2.so";
CREATE FUNCTION free_resources RETURNS int SONAME "lib_mysqludf_redis_v2.so";

如果创建过程中报xxx.so文件的问题,基本是链接库的路径。
首先执行ldd查看插件都链接到哪些库了

ldd /mysql/mysql56/lib/plugin/lib_mysqludf_redis_v2.so 

        linux-vdso.so.1 =>  (0x00007fffd4df6000)
        libhiredis.so.0.13 => /lib64/libhiredis.so.0.13 (0x00007f1bb871a000)
        libapr-1.so.0 => /usr/lib64/libapr-1.so.0 (0x00007f1bb84ee000)
        libaprutil-1.so.0 => /usr/lib64/libaprutil-1.so.0 (0x00007f1bb82c9000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f1bb7f35000)
        libuuid.so.1 => /lib64/libuuid.so.1 (0x00007f1bb7d31000)
        libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007f1bb7af9000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f1bb78dc000)
        libexpat.so.1 => /lib64/libexpat.so.1 (0x00007f1bb76b4000)
        libdb-4.7.so => /lib64/libdb-4.7.so (0x00007f1bb733f000)
        /lib64/ld-linux-x86-64.so.2 (0x000000375d800000)
        libfreebl3.so => /lib64/libfreebl3.so (0x00007f1bb70dd000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f1bb6ed9000)

如果出现文件not found的错误,我可以通过find / -name 文件名 先搜索到该文件,再将此文件复制到你的系统主lib下,怎么判断主lib呢,要么是/usr/lib,要么就是/usr/lib64,或者你搜索lib/lib64,把这几个文件都复制进去覆盖一次,基本就解决了。还有一种可能是显示文件链接正常,但是其实有几个是坏了的软链接,也会导致错误,我们把软链接删掉,或者直接把文件复制进对应的lib目录也可以解决。

环境配置好了,我们现在已经有了相关的UDF函数,现在需要将表数据全量写入REDIS,逻辑非常简单,分两种场景
在所有操作之前,请连接一下redis服务器,在sql中执行select redis_servers_set_v2("192.168.0.118",6379);
注意host和port修改成自己的

整个表数据写入

如果需要整个表数据,我选择将主键作为redis的key,字段名和单行数据组成key-value的json串作为redis的value,然后写入。逻辑是遍历表数据,将行数据转成json,再通过函数写入REDIS

-- 创建存储过程
drop procedure if exists dev.t; /* 如果存在同名存储过程则先删除 */
create procedure dev.t()
BEGIN


DECLARE done INT DEFAULT 0;
DECLARE json VARCHAR(255);
DECLARE prd_code VARCHAR(255);
DECLARE redis_key VARCHAR(255);
DECLARE redisvalue VARCHAR(16300);
DECLARE rs CURSOR for select userid from dev.wx_user; /*定义游标,值为表的某个字段(最好选择唯一性字段)*/
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET Done = 1;/*异常处理*/

open rs; /*打开游标*/
fetch next FROM  rs into prd_code; /*拿一条数据*/



REPEAT /*循环*/


SELECT
	json_object (
		u.openid,
		u.userid,
		u.mobilephone,
		u.createtime,
		u.username
	) into redisvalue  /*取出需要的字段利用函数json化之后赋值给变量redisvalue*/
FROM
	dev.wx_user u
WHERE
	userid = prd_code;

select concat("user_wx_", prd_code) INTO redis_key; /*按需设置key名*/
select redis_command_v2("set", redis_key, redisvalue); /*调用函数写入redis*/


fetch next from rs into prd_code; /*取下一条数据*/
UNTIL done end REPEAT;
close rs;

END

-- 执行存储过程
call dev.t();

某些字段写入

如果不需要所有字段,可以用hash键值对的方式写入所需字段,可以参考整个表写入的方式,但是这里介绍另外一种利用管道的方法

SELECT CONCAT(
 "*4\r\n",
 '$', LENGTH(redis_cmd), '\r\n',
 redis_cmd, '\r\n',
 '$', LENGTH(redis_key), '\r\n',
 redis_key, '\r\n',
 '$', LENGTH(hkey), '\r\n',
 hkey, '\r\n',
 '$', LENGTH(hval), '\r\n',
 hval, '\r'
)
FROM (
 SELECT
 'HSET' as redis_cmd,
 '_all_username' AS redis_key,
 userid AS hkey,
 username AS hval
 FROM wx_user where username is not null
) AS t

比较简单,如果不明白,大家可以查询下每个命令的作用。

全量写入之后,如果数据库发生增删改的操作,我们需要对redis进行同步,这里就涉及到增量写入(修改、删除)。我们利用mysql的触发器trigger来实现

-- insert触发器
DELIMITER $$
CREATE TRIGGER user_insert_trigger AFTER INSERT ON dev.wx_user
FOR EACH ROW BEGIN
 SET @ret=
 redis_command_v2("set",(select concat("user_", NEW.userid)),
 						  
 json_object
 (
NEW.openid as "openid",NEW.mobilephone as "mobilephone",
NEW.createtime as "createtime",NEW.username as "username",
NEW.userid as "userid",NEW.appid as "appid"
)
 									
 					    	
 				 );
END$$
DELIMITER ;

update类似,不赘述,delete有点小区别,主要是NEW和OLD

-- delete触发器
DELIMITER $$
CREATE TRIGGER user_delete_trigger AFTER delete ON dev.wx_user
FOR EACH ROW BEGIN
 SET @ret=
  	 redis_command_v2("del",(select concat("user_", OLD.userid)) 	
 				      );
END$$
DELIMITER ;

完毕,有问题请留言。

(2)

本文由 永烁星光 作者:linus 发表,转载请注明来源!

热评文章

发表评论