缓存和数据库同步问题解决方案

缓存在当今互联网应用和实践中是比不可少,由于其高并发和高性能的特性,已经在项目中广泛使用,比较有代表性的缓存如Redis,memcached,并且也逐渐成为很多大厂的面试中开始逐渐成为热点和难点。其中一个重要的难点就是缓存和数据库同步问题。本文通过借鉴计算机系统结构上缓存的设计模式,并结合互联网应用下的场景给出缓存和数据库同步问题解决方案。

1. 同步问题的产生

首先简单来一个读缓存的场景

直接以伪代码的形式展示这一过程:先读缓存中的数据,如果有直接返回;如果没用读数据库并加载到缓存中:

public Object read(String key){Object data  = redis.get(key); // 先读缓存中的数据if(data ==null){data = db.selectData(key) // 缓存中没用读, 数据库redis.set(key, data ) // 并加载到缓存中}return data;
}

可以看到非常的简单就实现了读缓存,在实际使用中也非常常见。比如将用户信息缓存起来,不用每次都去数据库查询了,大大加快了查询的速度, 岂不美哉。但是问题就来了,假如我需要更新数据怎么办?有的人可能马上就想到:那我先更新缓存再更新数据库不就行了。

先更新缓存再更新数据库?

这种方法非常直观,我读的时候先读缓存再读数据库;那我更新的时候,先更新缓存再更新数据库不就行了。伪代码形式如下:

public Object update(String key,Object data){redis.set(key,data) // 先更新缓存db.update(key, data); // 再更新数据库return data;
}

但是问题以往直观的往往是错的,这个方式有很多的问题。

1. 在并发场景下数据不一致问题

设想一个场景:
同时有请求A和请求B进行更新操作,那么会出现
(1)请求A更新了缓存
(2)请求B更新了缓存
(3)请求B更新了数据库
(4)请求A更新了数据库

这样请求A将旧值写入了数据库造成了脏写,并且数据库中可能永远无法恢复到真实的数据,缓存和数据库出现了数据不一致的现象。

2. 数据库更新不成功

如果数据库更新失败,而缓存更新成功,也会造成数据不一致现象。并且如果更新操作位于一个事务之中,事务发生回滚,也会造成数据不一致现象

3. 资源消耗

从业务场景来说,如果是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能

双写一致性问题

经过上面的分析可以得知,先更新缓存再更新数据库肯定是不正确的,而且更新时主要问题是双写一致性的问题,也就是当更新缓存和数据库时数据不一致的问题。这也是缓存和数据库同步问题的主要方面。我们的主要目标也就是尽可能消除这个问题。
先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。

2.解决同步问题的方式

从分布式系统角度理解这个问题

首先从分布式系统的角度看这个问题,可以看到Redis缓存和数据库组成一个分布式存储系统,既然是分布式系统就可以通过CAP理论去理解:
在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。必须要保证P那只能选择损失强一致性,或者损失可用性。因此也就诞生了两个解决问题的主要方案:
1. 对一致性要求比较高: 实时同步方案
尽可能的保证一致性,尽量对缓存和数据库都进行处理之后才返回,尽可能减少数据不一致的现象。适用场景:一般缓存场景
2. 对并发性要求比较高: 异步方案
尽可能提高响应速度,提高并发性,更新其中一个,然后异步更新另一个。比如先更新缓存,然后异步更新(比如采用消息队列)数据库。适用场景:如秒杀系统

下面主要介绍一下这两种方案的主要实现手段

实时同步方案

实时同步方案,比较简单并且适用场景广泛,对于并发要求不高的场景都可以适用。并且在应用层就可以实现,关键在于操作数据库和缓存的顺序,可能影响数据的一致性

1. 先更新数据库,再更新缓存

这个操作方法与之前先更新缓存再更新数据库的情况差不多,依然可能出现脏写问题,只不过此时脏写发生在缓存中,如果缓存设置了过期时间依然可以达到最终一致性。
(1)线程A更新了数据库
(2)线程B更新了数据库
(3)线程B更新了缓存
(4)线程A更新了缓存
不推荐使用

2. 先删除缓存,再更新数据库

既然如果更新数据库同时更新缓存总会出现脏写的问题,那直接删除缓存来强行达到数据一致性。但是操作顺序依然有影响,如果是先删除缓存再更新数据库,同样会出现数据不一致的场景:

(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库

又出现了我们熟悉的脏写缓存的问题,但是可以通过延迟双删的方法解决这个问题,伪代码如下:

public void write(String key,Object data){redis.delKey(key); // 先淘汰缓存db.updateData(data); // 再写数据库Thread.sleep(1000); // 休眠一段时间,再次淘汰缓存redis.delKey(key);
}

通过这种方式就可以有效的清楚脏写缓存的问题,可以通过延迟的淘汰缓存操作,淘汰掉脏写的缓存。
难点在于,延迟时间的确定,需要综合考量系统所需的吞吐量,系统响应时间,数据库执行时间。甚至可以异步执行第二个删除操作,开增加系统吞吐量。但是延迟时间的确定依然是经验的,因此产生了第三种操作方式:

3. 先更新数据库,再删除缓存

这是最常用最常用的模型了也被称为Cache-Aside pattern。其具体逻辑如下:
查询:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
更新:先把数据存到数据库中,成功后,再让缓存失效。

这种情况难道不存在并发脏写问题吗?

不是的。假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生:
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存

当然这种情况发生的前提是(3)要比(2)执行的更快才能保证,(4)比(5)执行的更快。但是实际情况写操作要比读操作慢很多。因此这种情况发生的概率很低。如果有人依然无法忍受这种极低概率情况的发生,那依然可以采用延迟双删的方法解决。

还有一个问题就是当删除缓存失败依然可能造成数据不一致问题。解决方案也很简单:提供一个保障的重试机制即可。比如通过消息队列异步执行删除缓存的命令。

作者强烈推荐这种方式

异步方案

异步方案,需要并发要求比较高的场景,通常依赖其他一些中间件实现异步操作。在如今互联网应用并发要求比较高的场景,也是比较主流的解决方案。但是因为依赖其他中间件实现,系统的复杂度提高,可靠性降低。主要的实现方式可以通过消息队列canal等中间件实现,本文主要介绍这两种大致的实现方式

消息队列

消息队列是实现异步的一种重要方式,一般先通过更新缓存,然后将更新数据库的操作封装成消息队列异步执行。

canal

是阿里开源的中间件可以完成订阅Mysql的binlog日志的功能,大致实现流程如下:

(1)更新数据库数据
(2)数据库会将操作信息写入binlog日志当中
(3)订阅程序提取出所需要的数据以及key
(4)另起一段非业务代码,获得该信息
(5)删除缓存操作

这样就可以实现先更新数据库然后异步更新/删除缓存

采用各种中间件的方式实现,需要

3. 写到最后

不管怎样,如果对强一致性要求非常高,那就可以不用考虑缓存了,老老实实的每次访问数据库操作就可以了。并且异步的方案在越来越多的大型应用中越来越多的实现,但是具体的实现细节还是需要不断的经验打磨。这作者就无能为力了,而且作为一个小菜鸡,我也是最近有感而发,学习之后总结出这篇文章,仅供学习和总结,如果有写的不对的地方希望大家批评指正。

欢迎评论交流,欢迎关注我这各菜鸡小博主》

缓存和数据库同步问题解决方案相关推荐

  1. [转]跨机房数据库同步问题解决方案

    转载:http://blog.sina.com.cn/s/blog_73b41ab20102uy10.html 近期正在考虑多或问题,所有将相关文章收集下,自己思考的方案类似于Google的Megas ...

  2. 通过SQL Server 2008数据库复制实现数据库同步备份

    通过SQL Server 2008数据库复制实现数据库同步备份 原文 通过SQL Server 2008数据库复制实现数据库同步备份 SQL Server 2008数据库复制是通过发布/订阅的机制进行 ...

  3. 索引使用的限制条件,sql优化有哪些,数据同步问题(缓存和数据库),缓存优化

    索引使用的限制条件,sql优化有哪些,数据同步问题(缓存和数据库),缓存优化 索引使用的限制条件,sql优化有哪些 a,选取最适用的字段:在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设 ...

  4. Redis 缓存实战——缓存、数据库一致性问题分析与解决方案

    引言 缓存与数据库一致性的问题自从出现了缓存概念后就一直被提及,它是一个缓存方案的先天缺陷,只要存在缓存,就势必会讨论缓存与数据库一致性的问题. 一致性问题还广泛存在于各种分布式存储场景中,如主从一致 ...

  5. 高并发下缓存与数据库双写不一致解决方案

    高并发下缓存与数据库双写不一致解决方案 参考文章: (1)高并发下缓存与数据库双写不一致解决方案 (2)https://www.cnblogs.com/wlwl/p/11601632.html (3) ...

  6. SQL Server主从数据库同步方式及同步问题解决方案总结

    SQL Server主从数据库同步方式及同步问题解决方案总结 参考文章: (1)SQL Server主从数据库同步方式及同步问题解决方案总结 (2)https://www.cnblogs.com/zh ...

  7. 阿里MySQL读写一致_缓存与数据库读写一致的解决方案

    在高并发业务场景中,会发生缓存与数据库读写不一致的问题. 如数据发生了变化,先执行删缓存,然后去修改数据库数据,在这个过程中如果有新的请求到了(高并发,访问量高),去读缓存会发现缓存空了:去查询数据库 ...

  8. REDIS11_缓存和数据库一致性如何保证、解决方案、提供Canel解决数据一致性问题

    文章目录 ①. 缓存和数据库双写一致保证 ②. 缓存数据一致性-解决方案 ③. 缓存数据一致性-解决-Canal ①. 缓存和数据库双写一致保证 ①. 只要用缓存,就可能会涉及到缓存与数据库双存储双写 ...

  9. sqlserver数据库同步软件_sqlserver同步工具_ 数据库同步解决方案

    SyncNavigator v8.6.2 SyncNavigator是一款功能强大的数据库同步软件,适用于SQL SERVER, MySQL,具有自动/定时同步数据.无人值守.故障自动恢复.同构/异构 ...

  10. pi数据库同步解决方案_MySQL数据库主主同步配置实战

    最近云服务器大减价,趁机买了几台,博客就放在其中一台上,为了不让剩下的两台服务器闲置,打算都利用起来,对博客网站进行负载均衡.使用两台数据库进行主主同步配置,扩展网站数据库架构,提高数据库的读写性能. ...

最新文章

  1. 通过Dockerfile构建Docker镜像
  2. 搭建nexus后,进入首页的时候出现warning: Could not connect to Nexus.错误
  3. 一文读懂云计算、边缘计算、移动边缘计算和自动驾驶的前世今生!
  4. Yann LeCun专访:我不觉得自己有天分,但是我一直往聪明人堆里钻
  5. Pygame初始-模仿windows待机画面
  6. shell命令tree
  7. 5个编码技巧以减少GC开销
  8. 用html5做一个简单网页_用新款ws2812灯带做一个简单的窗花
  9. 是时候让 JavaScript 面向对象了!
  10. ax200黑苹果蓝牙驱动_家庭网络升级计划篇一:将无线进行到底,AX200网卡升级体验...
  11. 机器学习十大经典算法——逻辑回归
  12. Android ViewModel组件详解
  13. 火狐浏览器自动刷新网页插件
  14. GBDT训练分类器时,残差是如何计算的?
  15. origin绘制双Y轴柱状图
  16. 读书笔记-干法-反省
  17. 企业邮箱哪个最好用?企业邮箱哪个安全?
  18. 「万达董事会大换血」背后 | 一点财经
  19. GBase 8c 全局死锁解除
  20. SDM(Supervised Descent Method)代码实现在Windows下的配置与使用

热门文章

  1. HTML——1.Sublime快捷键、HTML常用标签
  2. 软件质量模型详解————思维导图
  3. iPhone不配Lightning to 3.5mm转换线:试试这几款蓝牙接收器
  4. 使用深度学习打造智能聊天机器人
  5. Nifi介绍、安装、实践案例
  6. 视频时代的下一幕 ABC Inspire:读懂视频
  7. linux flash文件系统,需要了解Linux flash文件系统
  8. 来吧,我和你聊聊操作系统
  9. 原来PDF解密有这么多方法,你知道几个?
  10. python编写小程序、模拟实现自动按下键盘_Python 实现键盘鼠标按键模拟