redis缓存双写一致性(代码片段)

风之轻殇 风之轻殇     2023-03-12     464

关键词:

目录

双写一致性

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,并且也避免不了数据不一致的问题
  • 如果业务应用中读取数据库和写缓存的时间不好估算,那么延迟双删中的等待时间就不好设置

redis什么是缓存与数据库双写不一致?怎么解决?(代码片段)

什么是缓存与数据库双写不一致?怎么解决?1.热点缓存重建1.1什么是热点缓存重建1.2基于DCL(doublechecklock)双重检测锁解决热点缓存并发重建问题1.3分布式锁解决热点缓存并发重建问题2.缓存与数据库双写不一致2.1CacheAsideP... 查看详情

redis7高级之缓存双写一致性之更新策略探讨(代码片段)

1.缓存双写一致性如果redis中有数据需要和数据库中的值相同如果redis中无数据数据库中的值是最新值,且准备回写redis缓存按照操作分只读缓存读写缓存同步直写策略写数据库后也同步写redis缓存,缓存中的数据和数据中... 查看详情

如何保证数据库和缓存双写一致性?(代码片段)

...大家见面了。前言数据库和缓存(比如:redis)双写数据一致性问题,是一个跟开发语言无关的公共问题。尤其在高并发的场景下,这个问题变得更加严重。我很负责的告诉大家,该问题无论在面试,还是工作中遇到的概率非常... 查看详情

美团二面:redis与mysql双写一致性如何保证?(代码片段)

...有位好朋友去美团面试。他说,被问到Redis与MySQL双写一致性如何保证?这道题其实就是在问缓存和数据库在双写场景下,一致性是如何保证的?本文将跟大家一起来探讨如何回答这个问题。公众号:捡田螺的... 查看详情

高并发下如何保证数据库和缓存双写一致性?(代码片段)

前言数据库和缓存(比如:redis)双写数据一致性问题,是一个跟开发语言无关的公共问题。尤其在高并发的场景下,这个问题变得更加严重。我很负责的告诉你,该问题无论在面试,还是工作中遇到... 查看详情

redis进阶:mysql,redis双写一致性,数据库更新后再删除缓存就够了吗?(代码片段)

...先更新数据库,然后删除缓存。这是我们常用的双写一致性的处理方法,但也正是这样的方式出现了问题。下面我们来详细讲解1.先更新数据库,再删除缓存为什么有问题?首先原来的方法,大概是这样的࿰... 查看详情

缓存数据库双写不一致问题处理(代码片段)

我们的数据库操作中,一般会封装同步修改缓存的写法,但是这是一个两步操作,有可能带来缓存数据库数据不一致的问题。使用redisson提供的分布式锁解决参考:基于redis的分布式锁在我们之前加锁的逻辑中࿰... 查看详情

java面试常被问到这道题:如何保证缓存与数据库的双写一致性?(代码片段)

面试原题:如何保证缓存与数据库的双写一致性?面试官心理分析你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?面试题剖析一般来说,如... 查看详情

java面试常被问到这道题:如何保证缓存与数据库的双写一致性?(代码片段)

面试原题:如何保证缓存与数据库的双写一致性?面试官心理分析你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?面试题剖析一般来说,如... 查看详情

如何保证redis缓存与数据库的一致性?(代码片段)

目录1、四种同步策略:2、更新缓存还是删除缓存2.1更新缓存2.2删除缓存3、先操作数据库还是缓存3.1先删除缓存再更新数据库3.2先更新数据库再删除缓存4、延时双删4.1采用读写分离的架构怎么办?5、利用消息队列进行删... 查看详情

redis缓存简介以及缓存的更新策略(代码片段)

目录一、什么是缓存二、为什么要使用缓存三、如何使用缓存四、添加商户缓存1、缓存模型和思路 2、代码如下五、缓存更新策略 1、数据库缓存不一致解决方案: 2、数据库和缓存不一致采用什么方案3、CacheAsidePattern实现... 查看详情

redis11_缓存和数据库一致性如何保证解决方案提供canel解决数据一致性问题

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

redission读写锁解决db和缓存双写不一致(代码片段)

db和缓存双写不一致多线程访问环境下,在更新完db后再去更新缓存,不加锁显而易见的就会出现缓存被覆盖的问题。线程1修改完db去更新缓存的时候慢了一拍。此时线程2在线程1之后修改完db更新成功了缓存。此时线程1... 查看详情

redis缓存一致性(代码片段)

文章目录缓存一致性读缓存**双检加锁**策略写缓存保障最终数据一致性解决方案先更新数据库,再更新缓存案例演示1->更新缓存异常案例演示2->并发导致先更新缓存,再更新数据库案例演示->并发导致先删除缓存&#... 查看详情

redis双写一致性看一篇成高手系列1

首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用。在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作。 但是在更新缓存方面,对于更新完数据库,是更新缓存呢,还是删除缓存。又或... 查看详情

什么是/使用缓存(cache),缓存更新策略数据库缓存不一致解决方案及实现缓存与数据库双写一致(代码片段)

(目录)实现这个方案:商户查询缓存商户查询缓存1.什么是缓存(Cache)?前言:什么是缓存?举个例子:例如:例1:StaticfinalConcurrentHashMap<K,V>map=newConcurrentHashMap<>();例2:staticfinalCache<K,V>USER_CACHE=CacheBuilder.newBuilder().b 查看详情

使用springcache实现广告缓存并基于rabbitmq实现双写一致

使用SpringCache实现广告缓存并基于RabbitMQ实现双写一致一、SpringCache简介1.SpringCache介绍​SpringCache是Spring-context-xxx.jar中提供的功能,可以结合EHCache,Redis等缓存工具使用。给用户提供非常方便的缓存处理,缓存基本判断等操作,可... 查看详情

使用springcache实现广告缓存并基于rabbitmq实现双写一致

使用SpringCache实现广告缓存并基于RabbitMQ实现双写一致一、SpringCache简介1.SpringCache介绍​SpringCache是Spring-context-xxx.jar中提供的功能,可以结合EHCache,Redis等缓存工具使用。给用户提供非常方便的缓存处理,缓存基本判断等操作,可... 查看详情