Redis和MySQL数据一致性中出现的几种情况

分布式的环境下, MySQL和Redis如何保持数据的一致性?-土地公问答
分布式的环境下, MySQL和Redis如何保持数据的一致性?
分布式的环境下, MySQL和Redis如何保持数据的一致性?
一台MySQL,一台Redis,两台应用服务器,用户的数据存储持久化在MySQL中,缓存在Redis,有请求的时候从Redis中获取缓存的用户数据,有修改则同时修改MySQL和Redis中的数据。现在问题是:1. 先保存到MySQL和先保存到Redis都面临着一个保存成功而另外一个保存失败的情况,这样,如何保证MySQL与Redis中的数据同步?2. 两台应用服务器的并发访问,如何保证数据的安全性?
如果要“保证”数据的安全性,那么会带来开销的进一步提升,以至于使用redis带来的性能优势都会丧失。正确的做法是区分不同的业务,使得并不需要“保证”数据一致性的场合,可以使用redis优化。而敏感的场合依然使用mysql。
说个大概吧,我们热数据基本都是redis,增删改都是操作mysql,对于读是保存到redis,这样就涉及到数据同步操作,同步操作分为两大块,我们的叫法是,一个是全量(将全部数据一次写入到redis,时间几小时不等),一个是增量(实时更新)。这里说的是增量,主要…
Redis只用作cache,写请求只交给MySQL处理。否则你就要自己去解决一个分布式事务的问题,这个目前还没有性价比高的解决方案。如果应用是write heavy的,请使用HBase和Cassandra。
缓存只做失效 不做更新
读从redis读,写从mysql写,之后通知redis对应的缓存失效,去数据库缓存新数据。
高一致性的情况,就不要搞缓存
同意一楼的说法,一致性要求高就不要往一边写,从另一边读了,另外如果允许redis数据相对于mysql有一定延迟的话可以写一个同步服务,从mysql读取binlog,分解出数据写到redis中,这样你的应用服务就不需要往两边写了
先写mysql,再删除redis数据~
为什么没人提Multi 部分业务需要在写的时候避免读请求进来,这时候可以用Multi,但也要看业务场景是否试用
我们的系统用REDIS作为库存数据的读写,已经不要求和实际数据库的强一致性,以REDIS为准,对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去完成。
一. 什么是UDF
UDF,是User Defined Function的缩写,用户定义函数。MySQL支持函数,也支持自定义的函数。UDF比存储方法有更高的执行效率,并且支持聚集函数。
UDF定义了5个API:xxx_init()、xxx_deinit()、xxx()、xxx_add()、xxx_clear()。给出了这些API的说明。相关的结构体定义在mysql_com.h里,它又被mysql.h包含,使用时只需#include&mysql.h&即可。他们之间的关系和执行顺序可以以下图来表示:
这是主函数,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++类型的映射:
C/C++ Type
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)
int a = *((long long *)args-&args[0]);
int b = *((long long *)args-&args[1]);
return a +
11 extern "C" my_bool simple_add_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
由于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) |
+-------------------+
+-------------------+
1 row in set (0.00 sec)
可以看到,UDF正确执行了加法。
创建UDF函数的语法是&CREATE FUNCTION xxx RETURNS [INTEGER/STRING/REAL] SONAME '[so name]';
删除UDF函数的语法是 DROP FUNCTION simple_
mysql& DROP FUNCTION simple_
Query OK, 0 rows affected (0.03 sec)
三. 在UDF中访问Redis
跟上述做法一样,只需在UDF里调用Redis提供的接口函数。Redis官方给出了,封装了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"
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) {
// 连接Redis
if(NULL == m_client) {
const char* c_host = getenv("REDIS_HOST");
string host = "127.0.0.1";
if(c_host) {
m_client = new redis::client(host);
if(!(args-&args && args-&args[0] && args-&args[1] && args-&args[2])) {
*is_null = 1;
// 调用hset插入一个哈希表
if(m_client-&hset(args-&args[0], args-&args[1], args-&args[2])) {
*error = 1;
} catch (const redis::redis_error& e) {
39 extern "C" my_bool redis_hset_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
if (3 != args-&arg_count) {
// hset(key, field, value) 需要三个参数
strncpy(message, "Please input 3 args for: hset('key', 'field', 'value');", MYSQL_ERRMSG_SIZE);
return -1;
if (args-&arg_type[0] != STRING_RESULT
args-&arg_type[1] != STRING_RESULT
args-&arg_type[2] != STRING_RESULT) {
// 检查参数类型
strncpy(message, "Args type error: hset('key', 'field', 'value');", MYSQL_ERRMSG_SIZE);
return -1;
args-&arg_type[0] = STRING_RESULT;
args-&arg_type[1] = STRING_RESULT;
args-&arg_type[2] = STRING_RESULT;
initid-&ptr = NULL;
编译链接:
[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', '');
+-----------------------------------------+
| redis_hset('zhxilin', 'id', '') |
+-----------------------------------------+
+-----------------------------------------+
1 row in set (0.00 sec)
打开redis-cli,查看结果:
127.0.0.1:6379& HGETALL zhxilin
四. 通过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包含起来:
[statement_list]
由于MySQL默认的结束分隔符是分号(;),如果我们在BEGIN...END中出现了分号,将被标记成结束,此时没法完成触发器的定义。有一个办法,可以调用DELIMITER命令来暂时修改结束分隔符,用完再改会分号即可。比如改成$:
mysql& DELIMITER $
我们开始定义一个触发器,监听对Student表的插入操作,Student表在上一篇文章中创建的,可以查看文章。
mysql & DELIMITER $
& CREATE TRIGGER tg_student
& AFTER INSERT on Student
& FOR EACH ROW
& 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))));
创建完触发器可以通过show查看,或者drop删除:
mysql& SHOW TRIGGERS;
mysql& DROP TRIGGER tg_
接下来我们调用一句插入语句,然后观察Redis和MySQL数据的变化:
mysql& INSERT INTO Student VALUES('', 'Rose', 19, 'F', 'SS3-205');
Query OK, 1 row affected (0.27 sec)
MySQL的结果:
mysql& SELECT * FROM S
+----------+---------+------+------+---------+
| Sage | Sgen | Sdept
+----------+---------+------+------+---------+
| AS2-123 |
| SS3-205 |
| MD8-208 |
| ZS4-630 |
| ZS4-731 |
| zhxilin |
| ZS4-722 |
+----------+---------+------+------+---------+
6 rows in set (0.00 sec)
Redis的结果:
127.0.0.1:6379& HGETALL stu_
7) "gender"
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的数据一致性解决方案)
[问题点数:0分]
紧急求助!!!(MySQL的数据一致性解决方案)
[问题点数:0分]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
匿名用户不能发表回复!|请教redis如何做到和mysql数据库的同步呢? - 知乎115被浏览<strong class="NumberBoard-itemValue" title="5分享邀请回答github.com/liukelin/canal_mysql_nosql_sync1添加评论分享收藏感谢收起1111 条评论分享收藏感谢收起&nbsp>&nbsp
&nbsp>&nbsp
&nbsp>&nbsp
Redis与Mysql数据同步解决方案整理
摘要:最近在做一个Redis数据同步到数据库MySQL的功能。自己想了想,也有大概方案。1.队列同步,变跟数据2份,使用消息队列,一份给Redis消费,一份给Mysql消费。2.后台定时任务,定时刷新Redis中信息到数据库。网上也到处找了下解决方案方案一:读:读redis-&没有,读mysql-&把mysql数据写回redis写:写mysql-&成功,写redis。就是读的话,先读Redis,Redis没有再读数据库,将数据库中的数据放入Redis。写(增删改)
最近在做一个Redis数据同步到数据库MySQL的功能。
自己想了想,也有大概方案。
1.队列同步,变跟数据2份,使用消息队列,一份给Redis消费,一份给Mysql消费。
2.后台定时任务,定时刷新Redis中信息到数据库。
网上也到处找了下解决方案
读: 读redis-&没有,读mysql-&把mysql数据写回redis
写: 写mysql-&成功,写redis。
就是读的话,先读Redis,Redis没有再读数据库,将数据库中的数据放入Redis。
写(增删改),先写数据库,然后写Redis。
可以对此稍微优化,比如要求一致性高的数据,从数据库读,比如金融,交易数据。不要求强一致性的从Reids中读取。
基于binlog使用mysql_udf_redis,将数据库中的数据同步到Redis。
基于MQ,也就是最上面想到的方式1。
官方有个memcached的udf插件,如果不是那么强烈非要redis的话,也可以考虑
用POSTGRESQL 替代 Mysql +Redis.
各种方案弊端
但是上面的方案都有各自的弊端。
方案一,明显对于数据量巨大,更新频繁的数据写入无能为力。比如数量巨大,每个变跟状态又很频繁,这样很容易把数据库写挂。
方案二,是使用的mysql的User Defined Function功能,mysql_udf_redis是有人实现的同步数据到Redis的功能,弊端:需要学习成本,而来,第三方的插件不稳定。
方案三:怎么保证到数据库和到Redis中的状态一致性。就是假设一条修改数据,从队列写入到Mysql成功,但是写入到Redis失败,这种如何搞。还有就是需要一个消息队列,使用第三方的比如Kafka,RabbitMq等来实现,管理起来不方便,系统整体稳定性不行,而且只是这么个比较小的箱格数据信息同步。有点杀鸡用牛刀。
其他的方案:
订阅key的变化进行数据库更新,写的时候写2份,一份往Redis写,一份是Redis数据的key网更新队列(也可以直接Reids存)里写,再写个定时程序从更新队列里取时间,根据key取出Redis数据到Mysql.
这个方案,其实和其他的不一样,弊端了,就是占用内存大,因为需要维护一份更新队列。
可以用定时任务,刷Redis中的信息到数据库。先是进行状态比对,状态不一致的放入集合,批量update数据库。
其实也会有写小问题,比如在比对的时候:
1.Redis中的数据状态变跟了,怎么办?我们不可能在比对的时候锁住Redis,200W次循环,这段时间完全可能发生状态变跟。
2.在比对的时候,有人更新了数据库,怎么办?因为有些操作是可以直接更新数据库的。比如更新layoutRow之类的信息。
Redis同步mysql大致有两种同步,第一种是mysql 同步到redis,为了兼容手动在mysql数据库操作的同步,可以解析mysql的binlog,当数据库发生增,删,改时自动同步到redis,可以使用的库有[open-replicator](GitHub - whitesock/open-replicator: Open Replicator is a high performance MySQL binlog parser written in Java. It unfolds the possibilities that you can parse, filter and broadcast the binlog events in a real time manner.),
如果想同步redis数据到mysql,可以使用[redis-replicator](leonchen83/redis-replicator)
&dependency&
&groupId&com.moilioncircle&/groupId&
&artifactId&redis-replicator&/artifactId&
&version&2.3.1&/version&&/dependency&
参考的几篇大神的文章:
1. &&缓存更新的套路&&:http://coolshell.cn/articles/17416.html
2. &&分布式系统的事务处理&&:http://coolshell.cn/articles/10910.html
3. &&redis与mysql数据同步代码演示&&:http://www.cnblogs.com/linjiqin/p/3569011.html
以上是的内容,更多
的内容,请您使用右上方搜索功能获取相关信息。
若你要投稿、删除文章请联系邮箱:zixun-group@service.aliyun.com,工作人员会在五个工作日内给你回复。
云服务器 ECS
可弹性伸缩、安全稳定、简单易用
&40.8元/月起
预测未发生的攻击
&24元/月起
为您提供0门槛上云实践机会
你可能还喜欢
你可能感兴趣
阿里云教程中心为您免费提供
Redis与Mysql数据同步解决方案整理相关信息,包括
的信息,所有Redis与Mysql数据同步解决方案整理相关内容均不代表阿里云的意见!投稿删除文章请联系邮箱:zixun-group@service.aliyun.com,工作人员会在五个工作日内答复
售前咨询热线
支持与服务
资源和社区
关注阿里云
International

我要回帖

更多关于 分布式系统数据一致性 的文章

 

随机推荐