概述

当我们在使用缓存时,如果发生数据变更,那么你需要同时操作缓存和数据库,而它们两个又分属不同的系统,因此无法做到同时操作成功或失败,因此在并发读写下很可能出现缓存与数据库数据不一致的情况

理论上可以通过分布式事务保证同时操作成功或失败,但这会影响系统性能,一般很少使用。虽然没办法做到缓存和数据库强一致,但我们可以让他们的数据尽可能在绝大部分时间内保持一致,并保证最终是一致的

缓存更新设计

一般来说都是采用删除缓存的方式更新缓存,这就涉及到先删除缓存还是先更新数据库的顺序问题了

1. 先删除缓存,后更新数据库

先删除缓存,后更新数据库,如果数据库没有更新成功,下次读缓存发现不存在,则从数据库读取,并重建缓存,此时数据库和缓存依旧保持一致,但还是旧值

高并发下,假设有两个线程并发读写数据,可能会发生以下场景:

  • 线程 A 要更新 X = 2(原值 X = 1)
  • 线程 A 先删除缓存
  • 线程 B 读缓存,发现不存在,从数据库中读取到旧值(X = 1)
  • 线程 A 将新值写入数据库(X = 2)
  • 线程 B 将旧值写入缓存(X = 1)
  • 最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),发生不一致

可见,在高并发下这种方式容易出现长时间的脏数据,一般不建议使用

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

先更新数据库,后删除缓存,如果缓存没有删除成功,数据库是最新值,缓存中是旧值,会发生不一致

再看两个线程并发读写数据:

  • 某一时刻缓存中 X 失效不存在(数据库 X = 1)
  • 线程 A 读取数据库,得到旧值(X = 1)
  • 线程 B 更新数据库(X = 2)
  • 线程 B 删除缓存
  • 线程 A 将旧值写入缓存(X = 1)
  • 最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),发生不一致

这种方式依旧会出现数据不一致,但概率很低,所以普遍采用这种方式

更多优化

通过前面分析,我们采用了先更新数据库,再删除缓存的方式,还可以进一步优化

1. 保证两步都执行成功

前面提到,无论采用哪种方式,只要第二步失败都会有问题,所以我们需要保证第二步成功执行

一种简单的办法是失败就重试,但这会占用资源,并且立即重试大概率还是失败,所以可以采用异步重试,就是把重试请求写到消息队列,由专门的消费者来重试,直到成功

或者更直接的做法,为了避免第二步执行失败,我们可以把操作缓存这一步,直接放到消息队列中,由消费者来操作缓存,这样做的好处是即使系统重启了,消息也不会丢失

也可以通过订阅数据库变更日志,再操作缓存的方式,以 MySQL 举例,当一条数据发生修改时,MySQL 就会产生一条变更日志(Binlog),我们可以订阅这个日志,拿到具体操作的数据,然后再根据这条数据,去删除对应的缓存。订阅变更日志,目前也有了比较成熟的开源中间件,例如阿里的 canal

2. 延迟双删

一般数据库会使用【主从复制 + 读写分离】提高性能,这种情况下也有可能出现数据不一致:

  • 线程 A 更新主库 X = 2(原值 X = 1)
  • 线程 A 删除缓存
  • 线程 B 查询缓存,没有命中,查询「从库」得到旧值(从库 X = 1)
  • 从库「同步」完成(主从库 X = 2)
  • 线程 B 将「旧值」写入缓存(X = 1)
  • 最终 X 的值在缓存中是 1(旧值),在主从库中是 2(新值),也发生不一致

解决办法就是延时双删,比如线程 A 在更新数据库并删除缓存后,延迟一段时间再删除一次,延迟时间取决于主从复制的延迟时间,一般凭经验估算 1s - 5s 左右

Redis 数据一致性的更多相关文章

  1. redis缓存雪崩、缓存穿透、数据库和redis数据一致性

    一.缓存雪崩 回顾一下我们为什么要用缓存(Redis):减轻数据库压力或尽可能少的访问数据库. 在前面学习我们都知道Redis不可能把所有的数据都缓存起来(内存昂贵且有限),所以Redis需要对数据设 ...

  2. Redis分布式集群几点说道

    原文地址:http://www.cnblogs.com/verrion/p/redis_structure_type_selection.html  Redis分布式集群几点说道 Redis数据量日益 ...

  3. Azure Redis Cache (1) 入门

    <Windows Azure Platform 系列文章目录> Microsoft Azure Redis Cache基于流行的开源Redis Cache. 1.功能 Redis 是一种高 ...

  4. 【Redis】Redis分布式集群几点说道

    Redis数据量日益增大,使用的公司越来越多,不仅用于做缓存,同时趋向于存储这一块,这样必促使集群的发展,各个公司也在收集适合自己的集群方案,目前行业用的比较多的是下面几种集群架构,大部分都是采用分片 ...

  5. redis缓存介绍以及常见问题浅析

    # 没缓存的日子: 对于web来说,是用户量和访问量支持项目技术的更迭和前进.随着服务用户提升.可能会出现一下的一些状况: 页面并发量和访问量并不多,mysql足以支撑自己逻辑业务的发展.那么其实可以 ...

  6. 002 Redis使用及API

    Redis的使用及相关API 1.作用: 提高查询效率 一定程度上可以减轻数据库服务器的冲击压力,从而保护了数据库 //1.是否包含key redisTemplate.hasKey(key) //2. ...

  7. php架构之路

    鉴于最近跟小伙伴聊了很多PHP架构发展方向的问题,相关技术整理了一下,也顺便规划了一下自己的2019年. 一.常用的设计模式以及使用场景 以下是我用到过的   工厂,单例,策略,注册,适配,观察者,原 ...

  8. redis客户端、分布式锁及数据一致性

    Redis Java客户端有很多的开源产品比如Redission.Jedis.lettuce等. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持:Redis ...

  9. redis系列之数据库与缓存数据一致性解决方案

    redis系列之数据库与缓存数据一致性解决方案 数据库与缓存读写模式策略 写完数据库后是否需要马上更新缓存还是直接删除缓存? (1).如果写数据库的值与更新到缓存值是一样的,不需要经过任何的计算,可以 ...

  10. 高并发下Redis如何保持数据一致性(避免读后写)

    通常意义上我们说读后写是指针对同一个数据的先读后写,且写入的值依赖于读取的值. 关于这个定义要拆成两部分来看,一:同一个数据:二:写依赖于读.(记住这个拆分,后续会用到,记为定义一.定义二)只有当这两 ...

随机推荐

  1. (Good topic)快慢指针:链表的中间结点 (3.23leetcode每日打卡)

    给定一个带有头结点 head 的非空单链表,返回链表的中间结点. 如果有两个中间结点,则返回第二个中间结点.   示例 1: 输入:[1,2,3,4,5]输出:此列表中的结点 3 (序列化形式:[3, ...

  2. 字符串转换整数(atoi)(4.3leetcode每日打卡)

    一堆if不及python的一个正则表达式系列 请你来实现一个 atoi 函数,使其能将字符串转换成整数. 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止.接下来的转化规 ...

  3. dfs:马踏棋盘

    1 #include<stdio.h> 2 #include<time.h> 3 4 #define X 8 5 #define Y 8 6 7 int chess[X][Y] ...

  4. 实例讲解SpringBoot集成Dubbo的步骤及过程

    首先,让我们先了解一下Spring Boot和Dubbo. Spring Boot 是一个开源的 Java Web 框架,它可以帮助开发者快速创建独立的.生产级别的 Spring 应用程序.Sprin ...

  5. 1. Shell 基本用法

    重点: 条件测试. read. Shell 环境配置. case. for. find. xargs. gzip,bzip2,xz. tar. sed. 1)编程基础 Linus 说:Talk is ...

  6. stm32存储器:Flash

    先擦除后写入,stm32内置flash擦或写时,必须打开外部/内部高速振荡器. 擦除操作 以页为单位,每页1024个字节 起始地址0x0800 0000 擦写时要避开用户程序存储区 最多擦写10万次 ...

  7. mysql的begin end嵌套

    这个教程基本很少,因为这个很简单,但又会让(新手)人难以完成这嵌套. 为了方便读者理解,我把不需要嵌套的也嵌套起来了.(就比如下面这几行代码) delimiter $$ drop procedure ...

  8. [ABC262B] Triangle (Easier)

    Problem Statement You are given a simple undirected graph with $N$ vertices and $M$ edges. The verti ...

  9. 13 HTTP传输大文件的方法

    目录 如何在有限的带宽下高效快捷传输大文件? 数据压缩 分块传输 范围请求 多段数据 如何在有限的带宽下高效快捷传输大文件? 数据压缩 分块传输 范围请求 多段数据 数据压缩 思路:把大文件整体变小 ...

  10. 解决yolo+cudnn+opencv+gpu的一些问题

    问题描述: 在makefile文件中修改GPU=1 CUDNN=1 OPENCV=1,然后重新make,执行命令时出现: Yolov3 darknet: ./src/cuda.c:36: check_ ...