在服务端开发过程中,一般会使用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
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>
4 extern "C" long long simple_add(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
6 int a = *((long long *)args->args[0]);
7 int b = *((long long *)args->args[1]);
8 return a + b;
11 extern "C" my_bool simple_add_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
12 {
13 return 0;
接下来编译成动态库.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;
8 static redis::client *m_client = NULL;
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 }
22 if(!(args->args && args->args[0] && args->args[1] && args->args[2])) {
23 *is_null = 1;
24 return result;
25 }
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 }
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 }
53 args->arg_type[0] = STRING_RESULT;
54 args->arg_type[1] = STRING_RESULT;
55 args->arg_type[2] = STRING_RESULT;
57 initid->ptr = NULL;
58 return 0;
编译链接:
[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)
四. 通过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]
由于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 $
接下来我们调用一句插入语句,然后观察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做缓存,用户访问自动缓存数据。 但是这样子有不少缺点,1,没缓存用户访问接口的时候比较慢。2,缓存失效后同时请求数据库压力过大。 redis有这种功能:当缓存过期或
UDF是mysql的一个拓展接口,UDF(Userdefinedfunction)用户自定义函数。在什么地方使用这个功能呢,试想有如下场景:你的网站使用mysql作为最终数据落地的存储引擎,而redi...
来自: liberalman的专栏
Redis是一个高性能的key-value数据库。redis的出现,很大程度补偿了memcached这类key-value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了Python...
来自: yangyuscript的博客
需求:将消息先保存到redis,然后将redis中的数据定时保存到mysql中分析:redis保存为list,然后使用系统的定时任务调用脚本程序,通过脚本调用php文件进行处理。windows脚本(l...
来自: leedaning的专栏
本文转自:http://3gods.com/2016/06/23/Redis-Sync-DB.html部分图片来自:http://blog.csdn.net/stubborn_cow/article/...
来自: 秋叶原 && Mike || 麦克
上篇文章讲了如何配置redis,这篇文章我们就来配置定时器,定时把缓存在redis中的点击量更新到数据库中。Springboot中配置定时器就比较简单了。1、在application中添加注解@Ena...
来自: juewang_love的博客
持久层数据库和redis数据库同步方案,目前据我所知大都是通过自己写代码实现同步。同步方案有如下几种:方案一:通过canal+mysql实现同步,关于canal之前有说过,该方案的好处:实现了数据同步...
来自: qq_23374741的博客
最近接触redis,发现其读取速度快,突然想到,redis怎么和数据库同步呢,怎么能把数据库的所有数据存到redis里面,能使用户更快速的查找。方法1:mysql同步到redis:解析mysql的bi...
来自: 小小的博客
1.同步MySQL数据到Redis(1)在redis数据库设置缓存时间,当该条数据缓存时间过期之后自动释放,去数据库进行重新查询,但这样的话,我们放在缓存中的数据对数据的一致性要求不是很高才能放入缓存...
来自: 似梦似意境
思路:当后台修改内容信息后(也就数据库信息发生修改时),只需要把redis中缓存的数据删除即可。后台系统不直接操作redis数据库。可以发布一个服务,当后台对内容信息修改后,调用服务即可。服务的功能就...
来自: GitHub地址:https://github.com/zaiyunduan123
最近在做一个Redis数据同步到数据库MySQL的功能。自己想了想,也有大概方案。1.队列同步,变跟数据2份,使用消息队列,一份给Redis消费,一份给Mysql消费。2.后台定时任务,定时刷新Red...
来自: tbdp6411的专栏
【java】gearman进行Mysql到Redis的复制一.整体思路说明以mysql数据为主,写操作(insert,update,delete)交于mysql,读操作交于redis。当数据库数据发生...
来自: shenbug的博客
redis与数据库数据一致性问题是个老生常谈的问题了,这里也没啥新鲜玩意,就是总结一下不一致产生的原因我们在使用redis过程中,或者网上一些资料,通常会这样做:先读取缓存,如果缓存不存在,则读取数据...
来自: 刘本龙的专栏
方式1:数据库保存数据,redis不persistredis启动后,从数据库加载数据不要求强一致实时性的读请求,都由redis处理要求强一致实时性的读请求,由数据库处理写请求有2种处理方式,由数据库处...
来自: feicongcong的博客
OS:Ubuntu16.04.4x64更新并安装必要的工具aptupdate&amp;&amp;aptupgrade-y&amp;&amp;aptdist-upgrad...
来自: 神棍之路
介绍在实际项目中,MySQL数据库服务器有时会位于另外一台主机,需要通过网络来访问数据库;即使应用程序与MySQL数据库在同一个主机中,访问MySQL也涉及到磁盘IO操作(MySQL也有一些数据预读技...
来自: gx_1983的专栏
对于Query而言,没有数据是不会返回error,同时sql.rows也没有返回关于长度的属性,因此只能自己遍历rows属性,这是比较操蛋的事情,对比而言QueryRow就相对于友好一点,如果没有返回...
来自: rocky0503的博客
前置文章:某网站Redis与MySql同步方案分析使用Canal的主要目的:让自动同步代替部分手动同步,降低开发人员工作量,避免部分数据一致性问题。本文主要讲解如何配置Canal,以保证某网站的Red...
来自: 韩超的博客 (hanchao5272)
需求起因在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库。这个业务场景,主要是解...
来自: 勇往直前的专栏
一、原理简介 1.简介: canal为阿里巴巴产品,它主要模拟了mysql的Slave向Master发送请求,当mysql有增删改查时则会出发请求将数据发送到canal服务中,canal将数据...
来自: zxljsbk的博客
之前一直都是顺口都说异步刷新,今天就好好的学习了一下Ajax的异步、同步与异步刷新。 异步与同步他与刷新并不关联。要注意! JQuery中的Ajax方法有个属性async用于控制同步与异步的,默...
来自: h_tinkinginjava的博客
一、redis的安装先在下载安装包解压后进入目录应为已经有Makefile了所以直接make编译这里会报错,需要gcc编译器安装好gcc,再次make编译,还是会报错;这个错误根据Readme可知需要...
来自: Wk_yyy的博客
1:读写分离读:读redis->没有,读mysql->把mysql数据写回redis写:写mysql->成功,写redis2:使用redis作为mysql的二级缓存即可,实现org.apache.ib...
来自: qq_38714573的博客
最近在做一个Redis数据同步到数据库MySQL的功能。自己想了想,也有大概方案。1.队列同步,变跟数据2份,使用消息队列,一份给Redis消费,一份给Mysql消费。2.后台定时任务,定时刷新Red...
来自: Think Different
1、redis作为一个缓存在应用中使用,客户端对数据的写操作是对DB,读操作是对redis,这就会出现数据不同步问题。解决办法将DB中的数据更新到redis中。更新数据的两种情况:1、及时更新:设置定...
来自: adolph_jun的博客
应用Redis实现数据的读写,同时利用队列处理器定时将数据写入mysql,此种情况存在的问题主要是如何保证mysql与redis的数据同步,二者数据同步的关键在于mysql数据库中主键,方案是在red...
来自: hpb21的专栏
现在在中集E栈工作,最近在做一个Redis箱格信息数据同步到数据库Mysql的功能。自己想了想,也有大概方案。1.队列同步,变跟数据2份,使用消息队列,一份给Redis消费,一份给Mysql消费。2....
来自: LANGZI7758521的专栏
第一步:下载Hiredis并且编译,过程略,百度一大堆第二步:查看mysql的UDF编写方法(其实就导入mysql.h头文件,然后根据规则创建接口函数就行了)第三步:写代码。不喜欢多BB直接贴代码re...
来自: m0_37272764的博客
mysql与memcache的利用udf的数据勾取很好的解决了二者之间的数据同步,也提高了效率。因此在研究redis时也在想是否也有这样的工具,没想到一google还真有。http://code.go...
来自: hpb21的专栏
redis系列之数据库与缓存数据一致性解决方案 --来自中华石杉老师视频数据库与缓存读写模式策略写完数据库后是否需要马上...
来自: simba_1986的专栏
转载:http://blog.csdn.net/stubborn_cow/article/details/50586990转载:http://blog.csdn.net/liubenlong007/a...
来自: Invoker's Tower
Redis介绍redis是一个高性能的key-value数据库。redis的出现,很大程度补偿了memcached这类keyvalue存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了...
来自: Dream_ya的博客
最近在做一个Redis数据同步到数据库MySQL的功能。自己想了想,也有大概方案。1.队列同步,变跟数据2份,使用消息队列,一份给Redis消费,一份给Mysql消费。2.后台定时任务,定时刷新Red...
首先将所有实体类都继承BaseEntity:BaseEntity:packageorg.ty.cloudCourse.entity;importjava.util.HashMap;importjava...
来自: Hiytunes的博客
作为网络服务的中心,网络服务器,经常会受到来自外部的攻击,今天简单概括一下,作为服务端重要组成部分--数据库服务,存在哪些不安全的地方,以及如何去防范;常见服务的安全问题:1、redis服务;2、my...
来自: dengjiexian123的专栏
最近做的一个项目中很多地方用到了redis,其中纠结了一下redis的数据持久化问题,毕竟是缓存,数据随时都有可能丢失,虽然概率不大,况且redis还会将数据持久到安装路径的一个文件中,但还是要保证缓...
来自: seapeak007的博客
本文基于Springboot2.0.4Causedby:java.lang.ClassNotFoundException:org.apache.commons.pool2.impl.GenericOb...
来自: yingziisme的博客
问题上一篇SpringBootCache+redis设置有效时间和自动刷新缓存,时间支持在配置文件中配置,说了一种时间方式,直接扩展注解的Value值,如:@Override@Cacheable(va...
来自: xiaolyuh123的专栏
最近导师安排了一个关于redis的任务: 每天定时从数据库中搜索数据,然后更新到redis中。 通过java的Timer和quartz都分别能实现定时任务更新redis中的数据, 但导师对这样的实现并
场景:某网站需要对其项目做一个投票系统,投票项目上线后一小时之内预计有100万用户进行投票,希望用户投票完就能看到实时的投票情况这个场景可以使用redis+mysql冷热数据交换来解决。何为冷热数据交...
来自: webbc的博客
应用Redis实现数据的读写,同时利用队列处理器定时将数据写入mysql,此种情况存在的问题主要是如何保证mysql与redis的数据同步,二者数据同步的关键在于mysql数据库中主键,方案是在red...
来自: pingyan158的专栏
微信扫码关注下方公众号,获取更多学习资源游戏中存在各种各样的排行榜,比如玩家的等级排名、分数排名等。玩家在排行榜中的名次是其实力的象征,位于榜单前列的玩家在虚拟世界中拥有无尚荣耀,所以名次也就成了核心...
来自: igo9go_zq的博客
mysql的主从复制主要有3种模式:a..主从同步复制:数据完整性好,但是性能消耗高b.主从异步复制:性能消耗低,但是容易出现主从数据唯一性问题c.主从半自动复制:介于上面两种之间。既能很好的保持完整...
来自: yuanfen99xia的博客
单台机器所能承载的量是有限的,用户的量级上万,基本上服务都会做分布式集群部署。很多时候,会遇到对同一资源的方法。这时候就需要锁,如果是单机版的,可以利用java等语言自带的并发同步处理。如果是多台机器...
来自: Java_fenxiang的博客
MySQL安装:sudoapt-getinstallmysql-serversudoaptisntallmysql-clientsudoaptinstalllibmysqlclient-dev 安装...
来自: 零境交错
指定redis数据库信息和mysql数据库,然后无限循环从redis读取数据,最后写入mysql里#!/usr/bin/envpython#coding=utf-8#将获取到的产品信息从redis数据...
来自: 執筆冩回憶
nginx是个好东西,Nginx (engine x) 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambl...
来自: maoyuanming0806的博客
一个例子高斯混合模型(Gaussian Mixed Model)指的是多个高斯分布函数的线性组合,理论上GMM可以拟合出任意类型的分布,通常用于解决同一集合下的数据包含多个不同的分布的情况(或者是同一...
来自: 小平子的专栏
本文是笔者刚刚接触QGIS相关博客资源时找到的一篇文章,全文比较长,因此分成两篇发布。就内容而言不代表笔者观点,留待后续一一验证。QGIS和ArcGIS的比较你也许伴随着ArcGIS或者QGIS而成长...
来自: 番茄园