热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

8张图带你分析Redis与MySQL数据一致性问题

8张图带你分析Redis与MySQL数据一致性问题。高频面

前言

对于Web来说,并发量和访问量增加一定程度上推动项目技术和架构的更迭和进步。可能会有以下的一些状况:

  1. 页面并发量和访问量并不多,MySQL足以支撑
    自己逻辑业务的发展。那么其实可以不加缓存。最多对静态页面进行缓存即可。

  2. 页面的并发量显著增多,数据库有些压力,并且有些数据更新频率较低反复被查询
    或者查询速度较慢
    。那么就可以考虑使用缓存技术优化。对高命中的对象存到key-value形式的Redis中,那么,如果数据被命中,那么可以不经过效率很低的db。从高效的redis中查找到数据。

  3. 当然,可能还会遇到其他问题,你还通过静态页面缓存页面、cdn加速、甚至负载均衡这些方法提高系统并发量。这里就不做介绍。


缓存思想无处不在

我们从一个算法问题开始了解缓存的意义。

问题1:

  • 输入一个数n(n<20),求n!

分析1

  • 单单考虑算法,不考虑数值越界问题。
    当然我们知道n!=n * (n-1) * (n-2) * … * 1= n * (n-1)!
    ;
    那么我们可以用一个递归函数解决问题。

static long jiecheng(int n)
{
    if(n==1||n==0)return 1;
    else {
      return n*jiecheng(n-1);
    }
}

这样每输入求一次需要执行n
次。
问题2:

  • 输入t组数据(可能成百上千),每组一个xi(xi<20),求xi!

分析2

  • 如果使用递归
    ,输入t组数据,每次输入为xi,那么每次都要执行次数为:




    当每次输入的Xi过大或者t过大都会造成不小的负担!时间复杂度为O(n2)

  • 那么能否换个思想的。没错、是打表
    。打表常用于ACM算法中,常用于解决多组输入输出、图论搜索结果、路径储存问题。那么,对于这个求阶乘。我们只需要申请一个数组,按照编号从前往后将在需求的数存到数组中,后面再取得时候直接输出数组值就可以,思想很明确吧:

import java.util.Scanner;
public class test {
public static void main(String[] args) {
    // TODO Auto-generated method stub
    Scanner sc=new Scanner(System.in);
    int t=sc.nextInt();
    long jiecheng[]=new long[21];
    jiecheng[0]=1;
    for(int i=1;i<21;i++)
    {
        jiecheng[i]=jiecheng[i-1]*i;
    }
   for(int i=0;i        int x=sc.nextInt();
        System.out.println(jiecheng[x]);
    }
}  
}

  • 时间复杂度才O(n)这里的思想就和缓存
    思想差不多。先将数据在jiecheng[21]数组中储存。执行一次计算。当后面继续访问的时候就相当于访问数组值。每次都为O(1的操作)。

缓存的应用场景

缓存适用于高并发的场景,提升服务容量。主要是将从经常被访问的数据
或者查询成本较高
从慢的介质中存到比较快的介质中,比如从硬盘
—>内存
。我们知道大多数关系数据库是基于硬盘读写
的,其效率和资源有限,而redis是基于内存的,其读写速度差别差别很大。当并发过高关系数据库性能达到瓶颈时候,就可以策略性将常访问数据放到Redis提高系统吞吐和并发量。

对于常用网站和场景,关系数据库主要可能慢在两个地方:

  • 读写IO性能较差

  • 一个数据可能通过较大量计算得到

所以使用缓存能够减少磁盘IO次数和关系数据库的计算次数。读取上速度快也从两个方面体现:

  • 基于内存,读写较快

  • 使用哈希算法直接定位结果不需要计算

所以对于像样的,有点规模的网站,缓存是很 necessary
的,而Redis无疑是最好的选择之一。

需要注意的问题

缓存使用不当会带来很多问题。所以需要对一些细节进行认真考量和设计。当然最难得数据一致性在下面单独分析。

是否用缓存

项目不能为了用缓存而用缓存,缓存并一定适合所有场景,如果对数据一致性要求极高,又或者数据频繁更改而查询并不多,又或者根本没并发量的、查询简单的不一定需要缓存,还可能浪费资源使得项目变得臃肿难维护,并且使用redis缓存多多少少可能会遇到数据一致性问题需要考虑。

缓存合理设计

在设计缓存的时候,很可能会遇到多表查询,如果遇到多表查询缓存的键值对就需要合理考虑,是拆分还是合在一起?当然如果组合种类多但常出现的不多也可以直接缓存,具体的设计要根据项目业务需求来看,并没有一个非常绝对的标准。

过期策略选择

  • 缓存装的是相对热点和常用的数据,Redis资源也是有限,需要选择一个合理的策略让缓存过期删除。我们学过操作系统
    也知道在计算机的缓存实现中有先进先出的算法(FIFO);最近最少使用算法(LRU);最佳淘汰算法(OPT);最少访问页面算法(LFR)等磁盘调度算法。设计Redis缓存时候也可以借鉴。根据时间来的FIFO是最好实现的。且Redis在全局key
    支持过期策略。

  • 并且过期时间也要根据系统情况合理设置,如果硬件好点当前可以稍微久一点,但是过期时间过久或者过短可能都不太好,过短可能缓存命中率不高,而过久很可能造成很多冷门数据存储在Redis中不释放。

数据一致性问题★

上面其实提到数据一致性问题。如果对一致性要求极高那么不建议使用缓存。下面稍微梳理一下缓存的数据。
在Redis缓存中经常会遇到数据一致性问题。对于一个缓存,下面罗列几种情况:

read
:从Redis中读取,如果Redis中没有,那么就从MySQL中获取更新Redis缓存。
下面流程图描述常规场景,没啥争议:


写1:先更新数据库,再更新缓存(普通低并发)


更新数据库信息,再更新Redis缓存。这是常规做法,缓存基于数据库,取自数据库。

但是其中可能遇到一些问题,例如上述如果更新缓存失败(宕机等其他状况),将会使得数据库和Redis数据不一致。造成DB新数据,缓存旧数据

写2:先删除缓存,再写入数据库(低并发优化)


解决的问题

这种情况能够有效避免写1中防止写入Redis失败的问题。将缓存删除进行更新。理想是让下次访问Redis为空去MySQL取得最新值到缓存中。但是这种情况仅限于低并发的场景中而不适用高并发场景。

存在的问题

写2虽然能够看似写入Redis异常的问题
。看似较为好的解决方案但是在高并发的方案中其实还是有问题的。我们在写1讨论过如果更新库成功,缓存更新失败会导致脏数据。我们理想是删除缓存让下一个线程
访问适合更新缓存。问题是:如果这下一个线程来的太早、太巧了呢?

image-20201106191042265

因为多线程你也不知道谁先谁后,谁快谁慢。如上图所示情况,将会出现Redis缓存数据和MySQL不一致。当然你可以对key进行上锁
。但是锁这种重量级的东西对并发功能影响太大,能不用锁就别用!上述情况就高并发下依然会造成缓存是旧数据,DB是新数据。并且如果缓存没有过期这个问题会一直存在。

写3:延时双删策略


这个就是延时双删策略,能过缓解在写2中在更新MySQL过程中有读的线程进入造成Redis缓存与MySQL数据不一致。方法就是删除缓存->更新缓存->延时(几百ms)(可异步)再次删除缓存。即使在更新缓存途中发生写2的问题。造成数据不一致,但是延时(具体间根据业务来,一般几百ms)再次删除也能很快的解决不一致。

但是就写的方案看其实还是有漏洞的,比如第二次删除错误、多写多读高并发情况下对MySQL访问的压力等等。当然你可以选择用MQ等消息队列异步解决。其实实际的解决很难顾及到万无一失,所以不少大佬在设计这一环节可能会因为一些纰漏会被喷。作为菜菜的笔者在这里就更不献丑了,各位大佬欢迎贡献你们的方案。

写4:直接操作缓存,定期写入sql(适合高并发)

当有一堆并发(写)
扔过来的后,前面几个方案即使使用消息队列异步通信但也很难给用户一个舒适的体验。并且对大规模操作sql对系统也会造成不小的压力。所以还有一种方案就是直接操作缓存,将缓存定期写入sql。因为Redis这种非关系数据库又基于内存操作KV相比传统关系型要快很多。


上面适用于高并发情况下业务设计,这个时候以Redis数据为主,MySQL数据为辅助。定期插入(好像数据备份库一样)。当然,这种高并发往往会因为业务对

的顺序等等可能有不同要求,可能还要借助消息队列
以及
完成针对业务上对数据和顺序可能会因为高并发、多线程带来的不确定性和不稳定性,提高业务可靠性。

总之,越是高并发
、越是对数据一致性要求高
的方案在数据一致性的设计方案需要考虑和顾及
越复杂、越多
。上述也是笔者针对Redis数据一致性问题的学习和自我发散(胡扯)学习。如果有解释理解不合理或者还请各位大佬指正!

近期精彩:
硬核!手写一个优先队列
回溯算法 | 追忆那些年曾难倒我们的八皇后问题
图解|双轴快排分析
MongoDB助力一个物流订单系统
面试官:什么是缓存穿透、缓存雪崩、缓存击穿?
按照这个步骤来刷题,迷茫的你两个月亦能成为王者

最后,原创不易感觉不错的话还请一键三连,欢迎关注原创公众号:「bigsai」,在这里,不仅能学到知识和干货,还给你准备了很多进阶资料,回复「bigsai」口令即可领取!


原创不易!点赞、在看支持下谢谢



推荐阅读
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 基于事件驱动的并发编程及其消息通信机制的同步与异步、阻塞与非阻塞、IO模型的分类
    本文介绍了基于事件驱动的并发编程中的消息通信机制,包括同步和异步的概念及其区别,阻塞和非阻塞的状态,以及IO模型的分类。同步阻塞IO、同步非阻塞IO、异步阻塞IO和异步非阻塞IO等不同的IO模型被详细解释。这些概念和模型对于理解并发编程中的消息通信和IO操作具有重要意义。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • MySQL中的MVVC多版本并发控制机制的应用及实现
    本文介绍了MySQL中MVCC的应用及实现机制。MVCC是一种提高并发性能的技术,通过对事务内读取的内存进行处理,避免写操作堵塞读操作的并发问题。与其他数据库系统的MVCC实现机制不尽相同,MySQL的MVCC是在undolog中实现的。通过undolog可以找回数据的历史版本,提供给用户读取或在回滚时覆盖数据页上的数据。MySQL的大多数事务型存储引擎都实现了MVCC,但各自的实现机制有所不同。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
author-avatar
Utopia
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有