Redis缓存双写一致性

目录

    • 双写一致性
      • Redis与Mysql双写一致性
        • canal
          • 配置流程
          • 代码案例
        • 双写一致性理解
          • 缓存操作细分
        • 缓存一致性多种更新策略
          • 挂牌报错,凌晨升级
          • 先更新数据库,在更新缓存
          • 先删除缓存,在更新数据库
          • 先更新数据库,在删除缓存
          • 延迟双删策略
      • 总结

双写一致性

Redis与Mysql双写一致性

canal

主要是用于MySQL数据库增量日志数据的订阅,消费和解析(由阿里开源的Java项目),canal是通过伪装成MySQL的slave节点来转储master节点的binlog日志的一个中间件,他拿到日志内容以后,就可以把日志的相关数据变更重放到任何地方,可以是其他的MySQL,也可以是消息队列,redis甚至是文件中.

配置流程
  • 开启MySQL的binlog写入功能(需要重启MySQL,阿里云的好像默认就开启了)
  • 授权canal连接MySQL的账号,其实就是新建一个canal专用的账号便于区分(权限可以稍微高一些)
  • 去官网下载并解压canal到自己的目录下,修改instance.properties配置文件
  • 换成自己mysql主机所在的ip地址
  • 换成自己刚才给MySQL新建的用户及其密码
  • 启动canel并查看server和instance实例的日志来确保启动运行成功
代码案例
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.Message;
import com.google.protobuf.InvalidProtocolBufferException;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class RedisCanalClientExample {

    public static final int _60SECONDS = 60;

    public static void main(String[] args) {
        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(
                "127.0.0.1", 1111), "example", "", "");
        int batchSize = 1000;
        int emptyCount = 0;
        System.out.println("---------程序启动,开始监听MySQL的变化: ");
        try {
            connector.connect();
            //这个就是你要订阅的变化的那个库表
            connector.subscribe("db_test.t_user");
            connector.rollback();
            int totalEmptyCount = 10 * _60SECONDS;

            while (emptyCount < totalEmptyCount) {
                //获取指定数量的数据
                Message message = connector.getWithoutAck(batchSize);
                long batchId = message.getId();
                int size = message.getEntries().size();
                if (batchId == -1 || size == 0) {
                    emptyCount++;
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    emptyCount = 0;
                    printEntry(message.getEntries());
                    System.out.println();
                }
                //提交确认
                connector.ack(batchId);
                //处理失败,回滚数据
                //connector.rollback(batchId);
            }
            System.out.println("empty too many times,exit");
        } finally {
            connector.disconnect();
        }
    }

    private static void printEntry(List<CanalEntry.Entry> entries) {
        for (CanalEntry.Entry entry : entries) {
            if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
                continue;
            }
            CanalEntry.RowChange rowChange = null;
            try {
                rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            } catch (InvalidProtocolBufferException e) {
                throw new RuntimeException(e);
            }
            CanalEntry.EventType eventType = rowChange.getEventType();
            System.out.printf("==========binlog[%s:%s],name[%s,%s],eventType : %s%n",
                    entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                    entry.getHeader().getSchemaName(), entry.getHeader().getTableName(), eventType);
            for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
                if (eventType == CanalEntry.EventType.INSERT) {
                    redisInsert(rowData.getAfterColumnsList());
                } else if (eventType == CanalEntry.EventType.UPDATE) {
                    redisUpdate(rowData.getAfterColumnsList());
                } else {
                    redisDelete(rowData.getAfterColumnsList());
                }
            }
        }
    }

    private static void redisInsert(List<CanalEntry.Column> columns) {
        //实现省略,往redis插入数据
    }

    private static void redisUpdate(List<CanalEntry.Column> columns) {
        //实现省略,往redis修改数据
    }

    private static void redisDelete(List<CanalEntry.Column> columns) {
        //实现省略,往redis删除数据
    }
}

双写一致性理解

  • redis中有数据,需要和数据库中的值相同
  • redis中无数据,需要数据库中的值要是最新值
缓存操作细分
  • 只读缓存
  • 读写缓存
  • 同步直写策略:写数据库时也同步写缓存,缓存和数据库中的数据一致(对于读写缓存来说,要想保证缓存和数据库中的数据一致,就要采用同步直写策略)

缓存一致性多种更新策略

挂牌报错,凌晨升级

让客户稍作等待,然后趁机更新mysql和redis(特别重要级别的数据最好不要多线程)

给缓存设置过期时间,是保证最终一致性的解决方案.所有的写操作以数据库为准,对缓存操作只是尽最大的努力即可.也就是说如果数据库写入成功,缓存更新失败,那么只要到达过期时间.后面的请求自然会从数据库中读取新数据然后回填缓存,达到一致性.切记以mysql的数据库写入为准.

先更新数据库,在更新缓存

在高并发的情境下,这个操作是跨两个不同的系统的,就一定会可能发生数据不一致的问题,导致读到脏数据(比如某方更新失败了)

先删除缓存,在更新数据库

容易出现的异常问题:A线程删除了缓存,去更新mysql. B线程过来又要读取,A还在更新中,这时候有可能发生

  • 有可能缓存击穿(看你有没有双端检索加锁来初始化缓存)
  • B从mysql获得了旧值
  • B会把获得的旧值写回到Redis缓存(被A删除掉的旧数据,又被B给写会了,缓存的更新就失败了)
  • 请求A更新完成,MySQL与Redis发生了数据不一致的情况

这种方案尽量不要用

先更新数据库,在删除缓存

还是会出现短时间的数据不一致(可能会从缓存中读取到旧数据)

canal就是类似的思想

延迟双删策略

先删除Redis的缓存,在更新完数据库之后,再删除一次Redis的缓存(延迟删除),这时候能保证数据的最终一致性.

  • 这个删除该休眠多久
  • 自己根据业务进行一个具体的评估,在此耗时基础上面加个**百毫秒**左右即可
  • 如果MySQL是主从分离如何
  • 从库更可能导致数据不一致问题(还有个主从复制的延迟时间),所以更加需要采用延迟双删的策略了(延迟时间可能需要再加上百毫秒时间)
  • 这种同步淘汰策略,吞吐量降低了怎么办
  • 可以新起来一个线程去后台做这个事情(用CompletableFuture等实现)

分布式系统只有最终一致性,很难去做到强一致性

总结

把Redis作为只读缓存的话还好,没有一致性的问题,但是如果把Redis作为读写缓存来用.建议使用先更新数据库,再删除缓存的方案.理由如下:

  • 先删除缓存的值在更新数据库,有可能缓存击穿打满MySQL,并且也避免不了数据不一致的问题
  • 如果业务应用中读取数据库和写缓存的时间不好估算,那么延迟双删中的等待时间就不好设置

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/5067.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

低代码开发公司:用科技强力开启产业分工新时代!

实现办公自动化&#xff0c;是不少企业的共同追求。低代码开发公司会遵循时代发展规律&#xff0c;注入强劲的科技新生力量&#xff0c;在低代码开发市场厚积爆发、努力奋斗&#xff0c;推动企业数字化转型升级&#xff0c;为每一个企业的办公自动化升级创新贡献应有的力量。 一…

【数据结构与算法】堆的实现(附源码)

目录 一.堆的概念及结构 二.接口实现 A.初始化 Heapinit 销毁 Heapdestroy B.插入 Heappush 向上调整 AdjustUp 1.Heappush 2.AdjustUp C.删除 Heappop 向下调整 AdjustDown D.堆的判空 Heapempty 堆顶数据 Heaptop 堆的大小 Heapsize 三.源码 Heap.h He…

【模板】带权并查集

文章目录1. 奇偶游戏2. 银河英雄传说1. 奇偶游戏 239. 奇偶游戏 题意&#xff1a; 依次给出多个区间的含 111 的个数的奇偶性&#xff0c;找出第一个不符合的答案的回答。 思路&#xff1a; 已知区间[a,b][a,b][a,b][b,c][b,c][b,c]的奇偶性&#xff0c;那么具有传递性&…

分享一个国内可用的免费ChatGPT网站(自己写的)

背景 ChatGPT作为一种基于人工智能技术的自然语言处理工具&#xff0c;近期的热度直接沸腾&#x1f30b;。 作为一个程序员&#xff0c;我也忍不住做了一个基于ChatGPT的网站&#xff0c;免费&#xff01;免登陆&#xff01;&#xff01;国内可直接对话ChatGPT&#xff0c;也…

10.线性表代码实战

10.1 与408关联解析及本节内容介绍 链表比顺序表出现的顺序更加的频繁。 10.2线性表地顺序表示原理解析 线性表的特点&#xff1a; &#xff08;1&#xff09;表中的元素的个数是有限的 &#xff08;2&#xff09;表中元素的数据类型相同。意味着每一个元素占用相同大小的空…

使用Dism++和360安全卫士搞定Windows10离线升级

Windows10有很多版本&#xff0c;常见的由1903、1909、20H1、21H2等&#xff0c;在离线状态下&#xff0c;很难下载到匹配的升级补丁。期间尝试多种方法均失败&#xff0c;最后用Dism和360安全卫士组合拳搞定。 1、使用下载补丁&#xff0c;升级失败 比如这里介绍了常见补丁&a…

【SL101】 传感器接入chirpstack平台

【SL101】 传感器接入chirpstack平台使用硬件SL100工程师答疑chirpstack 中 net-server 使能 80-87 频段网关开启80-87 频段设备传感器端配置频点连接成功测试结果---chirpstackSL100系列温湿度传感器产品&#xff08;墨水屏版&#xff09;接入chirpstack 平台笔记记录 使用硬件…

mysql学习之数据系统概述

☀️马上要成为打工人&#xff0c;这几天把前面的知识都捡了捡&#xff0c;发现自己对关系数据库这块的学习还有所缺失&#xff0c;于是本章开始学习mysql 这里写目录标题1. 数据库系统的发展1.1 人工管理阶段1.2 文件系统阶段1.3 数据库阶段1.4 大数据阶段2 数据库系统的组成2…

了解这7个Node.js库,让你的开发效率提升不止一点点

Node.js是一个流行的JavaScript运行时环境&#xff0c;拥有庞大的生态系统和丰富的库&#xff0c;使得在Node.js上构建高效、可靠的应用程序变得非常容易。在这篇文章中&#xff0c;我们将分享七个有用的Node.js库&#xff0c;它们可以提高您的工作效率&#xff0c;让您更轻松地…

android:手搓一个即时消息聊天框(包含消息记录)

先看一下效果 1.后端 要实现这个&#xff0c;先说一下后端要实现的接口 1.创建会话id 传入“发送id”和“接收id”给服务端&#xff0c;服务端去创建“会话id” 比如 get请求&#xff1a;http://xxxx:8110/picasso/createSession?fromUserId1&toUserId2 返回seesionId…

【SSconv:全色锐化:显式频谱-空间卷积】

SSconv: Explicit Spectral-to-Spatial Convolution for Pansharpening &#xff08;SSconv&#xff1a;用于全色锐化的显式频谱-空间卷积&#xff09; 全色锐化的目的是融合高空间分辨率的全色&#xff08;PAN&#xff09;图像和低分辨率的多光谱&#xff08;LR-MS&#xff…

HTML5 Web 存储

HTML5 Web 存储 在HTML5之前&#xff0c;主要是使用cookies存储&#xff0c;cookies的缺点有&#xff1a;需要在请求头上带着数据&#xff0c;存储大小不过&#xff0c;在4k之内。本节&#xff0c; HTML5 web 存储&#xff0c;一个比cookie更好的本地存储方式。 什么是 HTML5 …

Redis技术详解

Redis技术详解 Redis是一种支持key-value等多种数据结构的存储系统。可用于缓存&#xff0c;事件发布或订阅&#xff0c;高速队列等场景。支持网络&#xff0c;提供字符串&#xff0c;哈希&#xff0c;列表&#xff0c;队列&#xff0c;集合结构直接存取&#xff0c;基于内存&…

Proxmox VE 超融合集群虚拟的NFS服务性能很差的问题解决

作者&#xff1a;田逸&#xff08;formyz&#xff09; 场景描述 五节点Proxmox VE集群&#xff0c;万兆网络,数据网络与存储网络独立&#xff0c;接口两两bond&#xff0c;交换机堆叠。 单机配置两颗AMD 宵龙CPU&#xff0c;核心数48&#xff0c;单台线程数192&#xff0c;单台…

服务器版RstudioServer安装与配置详细教程

Docker部署Rstudio server 背景&#xff1a;如果您想在服务器上运行RstudioServer&#xff0c;可以按照如下方法进行操作&#xff0c;笔者测试时使用腾讯云服务器&#xff08;系统centos7&#xff09;&#xff0c;需要在管理员权限下运行 Rstudio 官方提供了使用不同 R 版本的 …

Baumer工业相机中偏振相机如何使用Baumer堡盟GAPI SDK来进行偏振数据的计算转换输出(C++)

项目场景 Baumer工业相机堡盟相机是一种高性能、高质量的工业相机&#xff0c;可用于各种应用场景&#xff0c;如物体检测、计数和识别、运动分析和图像处理。 Baumer的万兆网相机拥有出色的图像处理性能&#xff0c;可以实时传输高分辨率图像。此外&#xff0c;该相机还具…

【ansible】管理变量与事实详解

目录 管理变量与事实 一&#xff0c;变量 1&#xff0c;变量命名 2&#xff0c;变量优先级&#xff08;高--低&#xff09; 3&#xff0c;命令行引用 4&#xff0c; 引用playbook中的变量 5&#xff0c; 在主机清单中定义变量 6&#xff0c; 在自定义变量文件中定义变量 7&…

Linux基础IO - 文件描述符、重定向

前面的文章中我们讲述了C语言中文件相关的操作与系统文件IO的接口&#xff0c;这篇文章中将会讲述文件描述符与重定向的知识。 运行在前文中的系统文件程序&#xff0c;通过观察可以看到图中的数据3非常的奇怪没头没尾的&#xff0c;下面我们就来从这里开始。 通过查看man手册…

console使用方法介绍

console是在写前端Javascript时经常会使用到&#xff0c;我平时使用最多的是console.log&#xff0c;相比大多数人也是如此吧&#xff01; 下面一起来看一下强大的console吧&#xff01; 01函数&#xff08;属性&#xff09; 包含如下函数 / 属性&#xff1a;memory、assert、c…

Hadoop三大框架之HDFS

一、概述HDFS产生的背景及定义HDFS产生背景随着数据量越来越大&#xff0c;在一个操作系统存不下所有的数据&#xff0c;那么就分配到更多的操作系统管理的磁盘中&#xff0c;但是不方便管理和维护&#xff0c;需要一种系统来管理多台机器上的文件&#xff0c;这就是分布式文件…