5 分钟快速学习,缓存一致性优化方案!

缓存操作

读缓存

读缓存可以分为两种情况命中(cache hit)和未命中(cache miss):

缓存命中
  • 首先从缓存中获取数据
  • 将缓存中的数据返回
缓存未命中
  • 首先从缓存中获取数据
  • 此时缓存未命中,从数据库获取数据
  • 将数据写入缓存
  • 返回数据

读缓存的的处理由缓存中有没有数据? 决定,如果缓存中有数据那就是缓存命中,如果没有那就是缓存未命中

写缓存

写缓存可以分为更新缓存删除缓存

更新缓存

更新缓存时需要分两种情况:

  • 更新简单数据类型(如string)
  • 更新复杂数据类型 (如hash)

对于简单数据类型可以直接更新缓存,如果是复杂数据类型会增加额外的更新开销:

  1. 从缓存中获取数据
  2. 将数据序反列化成对象
  3. 更新对象数据
  4. 将更新后的数据序列化存入缓存

对复杂数据缓存的更新最少需要4步,而且每次写数据时都需要更新缓存,这样对读缓存较少的场景,可能更新数据7-8次读缓存才发生一次想想就划不来,另外每次更新缓存时都要对缓存数据进行计算,很明显写数据时计算缓存数据然后再更新缓存是没必要的,可以将缓存的更新,推迟到读缓存(缓存未命中)时

删除缓存

删除缓存也称为淘汰缓存,删除缓存的操作非常简单的,直接将缓存从缓存库中的删除就可以了。

缓存操作顺序

缓存一般都是配合数据库一起使用,从数据库中获取数据然后再更新缓存。为什么要讨论缓存操作的顺序呢?因为在有些情况下不同的操作顺序会产生不一样的结果,常见的操作顺序可以分为:

  • 先数据库,再缓存
  • 先缓存,再数据库

不管是哪种顺序都要经过数据库、缓存两步操作,这两操作不是一个原子性的操作在一些情况会出现数据不一致问题。下面来分别说明不同的顺序所带的数据不一致、并发等问题。

先数据库后缓存

如上图先将数据写入数据库,然后再去更新或删除缓存。两个步骤1、2都可能失败,如果是第一步失败可以通过抛出业务异常,业务调用方捕获异常信息进行处理,因为这个时候并没有操作缓存可以理解为写数据库失败了。

如果是第一步成功(写数据库成功),然后再操作缓存的时候失败,这里有两种情况:

  1. 数据库回滚:如果是业务需要保证缓存与数据库强一致性时,可以抛出业务异常给调用方。
  2. 不作处理:与数据库回滚相反,业务可以接受在缓存过期时间达到之前,缓存与数据库允许数据不一致。

举个例子,假设有一个字符串数据类型的缓存数据,它的key为name并且现在数据库和缓存中的值都是arch-digest

String name = "arch-digest";

现在要将name的值更新成juejin,按照先数据库后缓存的顺序:

//将name的值更新为juejin
public void update(String name){
    db.insert(...);  //更新数据库
    cache.delete(name); //更新缓存
}

正常情况下db.insert(...)cache.delete(name)都执行成功没有异议。如果是一些其他原因cache.delete(name)执行失败,那数据库中的值是更新后的值juejin,而缓存中的数据还是arch-digest这样在下次读取缓存的时候拿到的值就是arch-digest

public String getNameFromCache(String name){
    String value =  cache.get(name); //从缓存中获取数据
    ...
    return value;
}

读取缓存的时候在getNameFromCache方法中,如果name缓存没有过期那会一直拿到arch-digest,这样情况就会导致用户看到的数据不一致。

先缓存后数据库

先缓存后数据库和之前说到的先数据库后缓存差不多除了会可能导致数据不一致外,还会有并发问题。

如上面现在是更新数据,如果是在更新数据库的时候失败会发生什么呢?这里要根据缓存的操作分两种情况:

  1. 更新缓存:更新缓存数据,缓存中为最新数据,数据库中是老数据,下次读取时会拿到缓存中的新数据(数据不一致)。
  2. 删除缓存:删除缓存中的数据,下次读取时从数据库中获取(数据一致)。

更新缓存删除缓存操作上面已经介绍过了,不多做解释了。很明显关于更新缓存删除缓存在这种情况先删除缓存更合适,没有数据不一致的问题,但是在使用删除缓存时也要注意会引发并发问题:

  1. 线程A删除缓存成功
  2. 线程B读取缓存未命中
  3. 线程B从数据库中获取数据
  4. 线程B将数据库中的数据写入缓存
  5. 线程A写入数据库成功

在高并发场景下,缓存和数据库数据不一致的情况还是会出现。那要解决数据库和缓存的数据一致性有哪些解决方案呢?

数据一致性优化方案

这里说的是优化方案不是解决方案哦,因为在分布式环境下事务是个难题,现在也没有好的解决方案。只能找到最适合业务的优化方案,使数据不一致的可能性或延迟降到一个业务可接受的范围内。

常见的几种优化方案可以包括:

  1. 不处理
  2. 延时双删
  3. 订阅Binglog

3 种方案从简单到复杂,可以根据业务需要选择最合适的优化方案。

不处理

不处理是最简单的方式了,即数据库与缓存中的数据不一致时在业务允许的情况下不做处理。虽然有点不合适,但是很香!

延时双删

延时双删可以用来优化在先缓存后数据库中的并发问题:

  1. 线程A删除缓存成功
  2. 线程B读取缓存未命中
  3. 线程B从数据库中获取数据
  4. 线程B将数据库中的数据写入缓存
  5. 线程A写入数据库成功
  6. 线程A休眠1秒然后删除缓存

这种方案增加第6步,写入数据库完成后使写入线程休眠1秒,然后再将缓存数据删除掉,使其他线程再次读取数据时导致缓存未命中从数据库获取数据并更新缓存。

这个1秒怎么确定的,具体该休眠多久呢?

针对上面的情形,应该自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

采用这种同步淘汰策略,吞吐量降低怎么办?

第二次删除作为异步的。自己起一个线程,异步删除。这样,写的请求就不用沉睡一段时间后了,再返回。这么做,加大吞吐量。

binlog订阅

使用binlog订阅,这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。

其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。

这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。

当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新缓存。

每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性能、高稳定)、大数据、机器学习、Java架构等各个热门领域。

原文地址:https://www.cnblogs.com/xwgblog/p/12009836.html

时间: 2024-10-11 19:21:19

5 分钟快速学习,缓存一致性优化方案!的相关文章

怎样能快速学习网站seo优化?如何快速学习网络营销?

很多朋友都很想想学习seo优化与网络营销 ,但是一说学习就不知道该从哪里做起了,首先我先告诉你们一个网络营销seo优化的技术网站,橘子工作室-网络营销技术分享基地!网站实实在在解决各种问题难题,提供了很多有关网络营销seo优化的技术方法,怎么说到这是不是都有点不相信? 那你就去看看吧,绝对是网络营销seo优化的好去向. 学习网络营销seo优化是要讲究方式方法的,不是你简单的给自己网站选个关键词然后到处去发链接到处做外链的.在你接到一个网站的时候,首先你需要了解一下公司的定位吧,例如:公司它主要是

5分钟快速学习——Linux Centos7--账号安全控制和命令历史自动注销

一.账号安全控制 用户账号是计算机使用者的身份凭证或标识,每个要访问系统资源的人,必须凭借其用户账号才能进入计算机.在 Linux 系统中,提供了多种机制来确保用户账号的正当.安全使用. 1.系统账号清理**** hattr +i /etc/passwd /etc/shadow锁定用户与密码文件 lsattr /etc/passwd /etc/shadow查看文件状态 chattr -i /etc/passwd /etc/shadow解锁用户与密码文件 2.密码安全控制 在不安全的网络环境中,为

60分钟Python快速学习(转)

60分钟Python快速学习(给发哥一个交代) 阅读目录 第一步:开发环境搭建: 第一个Python功能:初识Python 02.Python中定义变量不需要数据类型 03.在Pythod中定义方法 04.在Python中书写自己的类 60分钟Python快速学习 之前和同事谈到Python,每次下班后跑步都是在听他说,例如Python属于“胶水语言啦”,属于“解释型语言啦!”,是“面向对象的语言啦!”,另外没有数据类型,逻辑全靠空格缩进表示等. 今天自己用了60分钟快速学习了下Python的语

60分钟Python快速学习(给发哥一个交代)

60分钟Python快速学习 之前和同事谈到Python,每次下班后跑步都是在听他说,例如Python属于“胶水语言啦”,属于“解释型语言啦!”,是“面向对象的语言啦!”,另外没有数据类型,逻辑全靠空格缩进表示等. 今天自己用了60分钟快速学习了下Python的语法.和大家分享下,也算是自己这一个小时的学习总结吧! 第一步:开发环境搭建: PyCharm 4.5.4 下载地址:http://www.jetbrains.com/pycharm/download/ 支持多种类型的操作系统,我这次是在

Unity学习-优化_卡顿原因定位以及优化方案

除了Unity的一些组件优化技巧之外,更多的细节处于代码层面上 最近学习优化,看到一篇文章,写的很详细,从底层原理到我们 的实际处理,都有一些非常好的建议,可以推荐给小伙伴们看看 https://www.jianshu.com/p/289de89a6609 ===========如何定位程序的哪一个环节产生了过大的开销============ 使用Uinty的Profiler工具,可以比较精准快速的定位程序的哪一个位置产生了大开销 首先在build setting里面勾选Autoconnect

ASP.NET快速学习方案(.NET菜鸟的成长之路)

想要快速学习ASP.NET网站开发的朋友可以按照下面这个学习安排进度走.可以让你快速入门asp.net网站开发!但也局限于一般的文章类网站!如果想学习更多的技术可以跟着我的博客更新走!我也是一名.NET学习者!正在进行系统的学习!在学习的过程中我会把更多的学习心得以及学习资料分享给大家! 课表内容: 课程名 课时数 课程详情 html 4 1.HTML语言的语法及主体结构 2.HTML常用标签 3.绝对路径和相对路径 4.表单设计等等 div+css 10 1.CSS引入方式 2.CSS选择器

Redis学习笔记(11)——Redis缓存集群方案

由于单台Redis服务器的内存管理能力有限,使用过大内存的Redis又会使得服务器的性能急剧下降,一旦服务器发生故障将会影响更大范围业务,而Redis 3.0 beta1支持的集群功能还不适合生产环境的使用.于是为了获取更好的Redis缓存性能及可用性,很多公司都研发了Redis缓存集群方案.现对NetFlix.Twitter.国内的豌豆荚在缓存集群方面的解决方案进行一个汇总,以供读者参考,具体内容如下: 1.NetFlix对Dynamo的开源通用实现Dynomite Dynomite是NetF

GNU Linux高并发性能优化方案

/*********************************************************** * Author : Samson * Date : 07/14/2015 * Test platform: * gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2 * GNU bash, 4.3.11(1)-release (x86_64-pc-linux-gnu) * Nginx version: * Nginx 1.6.2 * Nginx 1.8.0

zt:缓存一致性(Cache Coherency)入门 cach coherency

http://www.infoq.com/cn/articles/cache-coherency-primer http://www.cnblogs.com/xybaby/p/6641928.html 本文是RAD Game Tools程序员Fabian “ryg” Giesen在其博客上发表的<Cache coherency primer>一文的翻译,经作者许可分享至InfoQ中文站.该系列共有两篇,本文系第一篇. 我计划写一些关于多核场景下数据组织的文章.写了第一篇,但我很快意识到有大量的