复习-面试题

多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。

其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。

后面的线程进来发现已经有缓存了,就直接走缓存。

canal

canal [kə'næl],中文翻译为 水道/管道/沟渠/运河,主要用途是用于 MySQL 数据库增量日志数据的订阅、消费和解析,是阿里巴巴开发并开源的,采用Java语言开发;

历史背景是早期阿里巴巴因为杭州和美国双机房部署,存在跨机房数据同步的业务需求,实现方式主要是基于业务 trigger(触发器) 获取增量变更。从2010年开始,阿里巴巴逐步尝试采用解析数据库日志获取增量变更进行同步,由此衍生出了canal项目;

能干嘛?

  • 数据库镜像

  • 数据库实时备份

  • 索引构建和实时维护(拆分异构索引、倒排索引等)

  • 业务cache刷新

  • 带业务逻辑的增量数据处理

传统MySQL主从复制原理

MySQL的主从复制将经过如下步骤:

1、当 master 主服务器上的数据发生改变时,则将其改变写入二进制事件日志文件中;

2、salve 从服务器会在一定时间间隔内对 master 主服务器上的二进制日志进行探测,探测其是否发生过改变,

如果探测到 master 主服务器的二进制事件日志发生了改变,则开始一个 I/O Thread 请求 master 二进制事件日志;

3、同时 master 主服务器为每个 I/O Thread 启动一个dump Thread,用于向其发送二进制事件日志;

4、slave 从服务器将接收到的二进制事件日志保存至自己本地的中继日志文件中;

5、salve 从服务器将启动 SQL Thread 从中继日志中读取二进制日志,在本地重放,使得其数据和主服务器保持一致;

6、最后 I/O Thread 和 SQL Thread 将进入睡眠状态,等待下一次被唤醒;

canal工作原理

原理:

  1. canal 模拟 MySQL slave的交互协议,伪装自己为MySQL slave,向MySQL master发送dump协议。

  2. MySQL master收到dump请求,开始推送 binary log 给 slave(即canal)

  3. canal解析为 binary log对象(原始为byte 流)

案例

官网案例(java) https://github.com/alibaba/canal/wiki/ClientExample)

MySQL准备

  1. 查看MySQL版本 show version();

  2. 当前的主机二进制日志show master status; 类似于偏移量,MySQL有改动时该值会变化。

  3. 查看是否开启binlogshow variables like 'log_bin'; 如果没开则需要打开

  4. 开启binlog的写入功能:修改MySQL的 my.ini 文件(修改后需重启)

    在 [mysqld]下加入以下三行

    log-bin=mysql-bin #开启 binlog
    binlog-format=ROW #选择 ROW 模式
    server_id=1   #配置MySQL replaction需要定义,不要和canal的 slaveId重复

    ROW模式 除了记录sql语句之外,还会记录每个字段的变化情况,能够清楚的记录每行数据的变化历史,但会占用较多的空间。

    STATEMENT模式只记录了sql语句,但是没有记录上下文信息,在进行数据恢复的时候可能会导致数据的丢失情况;

    MIX模式比较灵活的记录,理论上说当遇到了表结构变更的时候,就会记录为statement模式。当遇到了数据更新或者删除情况下就会变为row模式;

  5. 重启后再查看 show variables like 'log_bin';

  6. 授权canal连接MySQL的账号

    MySQL默认的用户在MySQL的user表里

    select * from mysql.user;

    默认没有canal账户,新建+授权

    DROP USER IF EXISTS 'canal'@'%';
    CREATE USER 'canal'@'%' IDENTIFIED BY 'canal';  
    GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' IDENTIFIED BY 'canal';  
    FLUSH PRIVILEGES;

canal服务端

配置:修改canal/conf/example路径下的instance.properties文件

修改mysql的主机地址、修改自己在mysql新建的canal账户

在bin目录下执行 ./startup.sh

查看日志判断canal是否启动成功。

查看server日志:

查看样例example的日志

canal客户端(Java编写业务程序)

  1. 先建一个库

    # 1 随便选个数据库,本例bigdata,按照下面建表
    CREATE TABLE `t_user` (

     `id` bigint(20) NOT NULL AUTO_INCREMENT,

     `userName` varchar(100) NOT NULL,

     PRIMARY KEY (`id`)

    ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb
  2. pom

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
       <modelVersion>4.0.0</modelVersion>

       <groupId>com.atguigu.canal</groupId>
       <artifactId>canal_demo02</artifactId>
       <version>1.0-SNAPSHOT</version>

       <parent>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-parent</artifactId>
           <version>2.5.14</version>
           <relativePath/>
       </parent>

       <properties>
           <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
           <maven.compiler.source>1.8</maven.compiler.source>
           <maven.compiler.target>1.8</maven.compiler.target>
           <junit.version>4.12</junit.version>
           <log4j.version>1.2.17</log4j.version>
           <lombok.version>1.16.18</lombok.version>
           <mysql.version>5.1.47</mysql.version>
           <druid.version>1.1.16</druid.version>
           <mapper.version>4.1.5</mapper.version>
           <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
       </properties>

       <dependencies>
           <!--canal-->
           <dependency>
               <groupId>com.alibaba.otter</groupId>
               <artifactId>canal.client</artifactId>
               <version>1.1.0</version>
           </dependency>
           <!--SpringBoot通用依赖模块-->
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-web</artifactId>
           </dependency>
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-actuator</artifactId>
           </dependency>
           <!--swagger2-->
           <dependency>
               <groupId>io.springfox</groupId>
               <artifactId>springfox-swagger2</artifactId>
               <version>2.9.2</version>
           </dependency>
           <dependency>
               <groupId>io.springfox</groupId>
               <artifactId>springfox-swagger-ui</artifactId>
               <version>2.9.2</version>
           </dependency>
           <!--SpringBoot与Redis整合依赖-->
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-data-redis</artifactId>
           </dependency>
           <dependency>
               <groupId>org.apache.commons</groupId>
               <artifactId>commons-pool2</artifactId>
           </dependency>
           <!--SpringBoot与AOP-->
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-aop</artifactId>
           </dependency>
           <dependency>
               <groupId>org.aspectj</groupId>
               <artifactId>aspectjweaver</artifactId>
           </dependency>
           <!--Mysql数据库驱动-->
           <dependency>
               <groupId>mysql</groupId>
               <artifactId>mysql-connector-java</artifactId>
               <version>5.1.47</version>
           </dependency>
           <!--SpringBoot集成druid连接池-->
           <dependency>
               <groupId>com.alibaba</groupId>
               <artifactId>druid-spring-boot-starter</artifactId>
               <version>1.1.10</version>
           </dependency>
           <dependency>
               <groupId>com.alibaba</groupId>
               <artifactId>druid</artifactId>
               <version>${druid.version}</version>
           </dependency>
           <!--mybatis和springboot整合-->
           <dependency>
               <groupId>org.mybatis.spring.boot</groupId>
               <artifactId>mybatis-spring-boot-starter</artifactId>
               <version>${mybatis.spring.boot.version}</version>
           </dependency>
           <!--通用基础配置junit/devtools/test/log4j/lombok/hutool-->
           <!--hutool-->
           <dependency>
               <groupId>cn.hutool</groupId>
               <artifactId>hutool-all</artifactId>
               <version>5.2.3</version>
           </dependency>
           <dependency>
               <groupId>junit</groupId>
               <artifactId>junit</artifactId>
               <version>${junit.version}</version>
           </dependency>
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-test</artifactId>
               <scope>test</scope>
           </dependency>
           <dependency>
               <groupId>log4j</groupId>
               <artifactId>log4j</artifactId>
               <version>${log4j.version}</version>
           </dependency>
           <dependency>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok</artifactId>
               <version>${lombok.version}</version>
               <optional>true</optional>
           </dependency>
           <!--persistence-->
           <dependency>
               <groupId>javax.persistence</groupId>
               <artifactId>persistence-api</artifactId>
               <version>1.0.2</version>
           </dependency>
           <!--通用Mapper-->
           <dependency>
               <groupId>tk.mybatis</groupId>
               <artifactId>mapper</artifactId>
               <version>${mapper.version}</version>
           </dependency>
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-autoconfigure</artifactId>
           </dependency>
           <dependency>
               <groupId>redis.clients</groupId>
               <artifactId>jedis</artifactId>
               <version>3.8.0</version>
           </dependency>
       </dependencies>

       <build>
           <plugins>
               <plugin>
                   <groupId>org.springframework.boot</groupId>
                   <artifactId>spring-boot-maven-plugin</artifactId>
               </plugin>
           </plugins>
       </build>

    </project>

  3. properties

    server.port=5555

    # ========================alibaba.druid=====================
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/bigdata?useUnicode=true&characterEncoding=utf-8&useSSL=false
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.druid.test-while-idle=false
  4. 业务类

    package com.atguigu.canal.util;

    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;

    /**
    * Redis工具类
    */
    public class RedisUtils
    {
       public static final String  REDIS_IP_ADDR = "192.168.111.185";
       public static final String  REDIS_pwd = "111111";
       public static JedisPool jedisPool;

       static {
           JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
           jedisPoolConfig.setMaxTotal(20);
           jedisPoolConfig.setMaxIdle(10);
           jedisPool=new JedisPool(jedisPoolConfig,REDIS_IP_ADDR,6379,10000,REDIS_pwd);
      }

       public static Jedis getJedis() throws Exception {
           if(null!=jedisPool){
               return jedisPool.getResource();
          }
           throw new Exception("Jedispool is not ok");
      }

    }
    package com.atguigu.canal.biz;

    import com.alibaba.fastjson.JSONObject;
    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.atguigu.canal.util.RedisUtils;
    import redis.clients.jedis.Jedis;
    import java.net.InetSocketAddress;
    import java.util.List;
    import java.util.UUID;
    import java.util.concurrent.TimeUnit;

    /**
    * 来自canal官方GitHub
    */
    public class RedisCanalClientExample
    {
       public static final Integer _60SECONDS = 60;
       public static final String  REDIS_IP_ADDR = "192.168.111.185";// Redis地址

       private static void redisInsert(List<Column> columns)
      {
           JSONObject jsonObject = new JSONObject();
           for (Column column : columns)
          {
               System.out.println(column.getName() + " : " + column.getValue() + "   update=" + column.getUpdated());
               jsonObject.put(column.getName(),column.getValue());
          }
           if(columns.size() > 0)
          {
               try(Jedis jedis = RedisUtils.getJedis())
              {
                   jedis.set(columns.get(0).getValue(),jsonObject.toJSONString());
              }catch (Exception e){
                   e.printStackTrace();
              }
          }
      }


       private static void redisDelete(List<Column> columns)
      {
           JSONObject jsonObject = new JSONObject();
           for (Column column : columns)
          {
               jsonObject.put(column.getName(),column.getValue());
          }
           if(columns.size() > 0)
          {
               try(Jedis jedis = RedisUtils.getJedis())
              {
                   jedis.del(columns.get(0).getValue());
              }catch (Exception e){
                   e.printStackTrace();
              }
          }
      }

       private static void redisUpdate(List<Column> columns)
      {
           JSONObject jsonObject = new JSONObject();
           for (Column column : columns)
          {
               System.out.println(column.getName() + " : " + column.getValue() + "   update=" + column.getUpdated());
               jsonObject.put(column.getName(),column.getValue());
          }
           if(columns.size() > 0)
          {
               try(Jedis jedis = RedisUtils.getJedis())
              {
                   jedis.set(columns.get(0).getValue(),jsonObject.toJSONString());
                   System.out.println("---------update after: "+jedis.get(columns.get(0).getValue()));
              }catch (Exception e){
                   e.printStackTrace();
              }
          }
      }

       public static void printEntry(List<Entry> entrys) {
           for (Entry entry : entrys) {
               if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                   continue;
              }

               RowChange rowChage = null;
               try {
                   //获取变更的row数据
                   rowChage = RowChange.parseFrom(entry.getStoreValue());
              } catch (Exception e) {
                   throw new RuntimeException("ERROR ## parser of eromanga-event has an error,data:" + entry.toString(),e);
              }
               //获取变动类型
               EventType eventType = rowChage.getEventType();
               System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",
                       entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                       entry.getHeader().getSchemaName(), entry.getHeader().getTableName(), eventType));

               for (RowData rowData : rowChage.getRowDatasList()) {
                   if (eventType == EventType.INSERT) {
                       redisInsert(rowData.getAfterColumnsList());
                  } else if (eventType == EventType.DELETE) {
                       redisDelete(rowData.getBeforeColumnsList());
                  } else {//EventType.UPDATE
                       redisUpdate(rowData.getAfterColumnsList());
                  }
              }
          }
      }


       public static void main(String[] args)
      {
           System.out.println("---------O(∩_∩)O哈哈~ initCanal() main方法-----------");

           //=================================
           // 创建链接canal服务端
           CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(REDIS_IP_ADDR,
                   11111), "example", "", "");
           int batchSize = 1000;
           //空闲空转计数器
           int emptyCount = 0;
           System.out.println("---------------------canal init OK,开始监听mysql变化------");
           try {
               connector.connect();
               //connector.subscribe(".*\\..*");
               connector.subscribe("bigdata.t_user");// bigdata:数据库名 t_user:表名
               connector.rollback();
               int totalEmptyCount = 10 * _60SECONDS;
               while (emptyCount < totalEmptyCount) {
                   System.out.println("我是canal,每秒一次正在监听:"+ UUID.randomUUID().toString());
                   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());
                  }
                   connector.ack(batchId); // 提交确认
                   // connector.rollback(batchId); // 处理失败, 回滚数据
              }
               System.out.println("已经监听了"+totalEmptyCount+"秒,无任何消息,请重启重试......");
          } finally {
               connector.disconnect();
          }
      }
    }
  5.  

MySQL与Redis数据双写一致性工程落地案例的更多相关文章

  1. 【Redis】- 双写一致性

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

  2. Redis双写一致性与缓存更新策略

    一.双写一致性 双写一致性,也就是说 Redis 和 mysql 数据同步 双写一致性数据同步的方案有: 1.先更新数据库,再更新缓存 这个方案一般不用: 因为当有两个请求AB先后更新数据库后,A应该 ...

  3. 第三节:Redis缓存雪崩、击穿、穿透、双写一致性、并发竞争、热点key重建优化、BigKey的优化 等解决方案

    一. 缓存雪崩 1. 含义 同一时刻,大量的缓存同时过期失效. 2. 产生原因和后果 (1). 原因:由于开发人员经验不足或失误,大量热点缓存设置了统一的过期时间. (2). 产生后果:恰逢秒杀高峰, ...

  4. 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制

    [原创]分布式之数据库和缓存双写一致性方案解析(三)   正文 博主本来觉得,<分布式之数据库和缓存双写一致性方案解析>,一文已经十分清晰.然而这一两天,有人在微信上私聊我,觉得应该要采用 ...

  5. 《Redis Mysql 双写一致性问题》

    一:序 - 最近在对数据做缓存时候,会涉及到如何保证 数据库/Redis 一致性问题. - 刚好今天来总结下 一致性问题 产生的问题,和可能存在的解决方案. 二:(更新策略)-  先更新数据库,后更新 ...

  6. Redis Mysql 双写一致性问题

    一:序 - 最近在对数据做缓存时候,会涉及到如何保证 数据库/Redis 一致性问题. - 刚好今天来总结下 一致性问题 产生的问题,和可能存在的解决方案. 二:(更新策略)-  先更新数据库,后更新 ...

  7. 转载:MySQL和Redis 数据同步解决方案整理

    from: http://blog.csdn.net/langzi7758521/article/details/52611910 最近在做一个Redis箱格信息数据同步到数据库Mysql的功能. 自 ...

  8. 并发中如何保证缓存DB双写一致性(JAVA栗子)

    并发场景中大部分处理的是先更新DB,再(删缓.更新)缓存的处理方式,但是在实际场景中有可能DB更新成功了,但是缓存设置失败了,就造成了缓存与DB数据不一致的问题,下面就以实际情况说下怎么解决此类问题. ...

  9. PHP经典面试题:如何保证缓存与数据库的双写一致性?

    只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题? 面试题剖析 一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说 ...

  10. PHP中高级面试题 一个高频面试题:怎么保证缓存与数据库的双写一致性?

    分布式缓存是现在很多分布式应用中必不可少的组件,但是用到了分布式缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题? Cache Aside ...

随机推荐

  1. linux基础命令及bash shell特性

    linux基础命令及bash shell特性 目录 linux基础命令及bash shell特性 1.linux基础命令 1.1 查看内核版本和linux发行版本 1.2 查看服务器硬件信息 1.3 ...

  2. 密码学—Vigenere破解Python程序

    文章目录 概要 预备知识点学习 整体流程 技术名词解释 技术细节 小结 代码 概要 破解Vigenere需要Kasiski测试法与重合指数法的理论基础 具体知识点细节看下面这两篇文章 预备知识点学习 ...

  3. C语言:渔夫捕鱼算法问题

    题目:渔夫捕鱼 A,B,C,D,E五个渔夫夜间合伙捕鱼,,第二天清A先醒来,他把鱼均分五份,把多余的一条扔回湖中,便拿了自己的一份回家了,B醒来后,也把鱼均分五份,把多余的一条扔回湖中,便拿了自己的一 ...

  4. 记一次Nacos漏洞的复现 --> 身份认证绕过漏洞(QVD-2023-6271)

    前记 端午前两天,遇到公司某客户的站点是Nacos,随后就是网上搜一波漏洞,搜到 QVD-2023-6271,故做以下记录 漏洞复现 漏洞描述 漏洞原理为开源服务管理平台 Nacos在默认配置下未对 ...

  5. 用STM32F4的DMA实现高速、实时的同步并行通信——以读取高速ADC为例[原创www.cnblogs.com/helesheng]

    大概6-7年前,在网上看到过一篇用STM32F1的DMA控制GPIO输出高速数字波形的帖子.觉得很有意思,就自己试了试:控制GPIO输出波形翻转的速度最高只能达到3-4MHz,且容易受到STM32F1 ...

  6. uni-app前端图表组件库,折线图横屏android失败的解决办法

    网址:https://www.ucharts.cn/v2/#/demo/index,需要登录,微信就可以 我在使用折现图时,业务需要把曲线图在点击全屏时横屏全屏显示.就做个全屏页面,点击后,把opti ...

  7. Newtonsoft.Json 首字母小写

    // 首字母小写 JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings(); jsonSerialize ...

  8. Linux内核Kernel启动过程

    在上一篇计算机启动过程文章中介绍了计算机启动的基本流程,本篇文章主要介绍Linux内核Kernel的启动过程. 一.内核启动的基本流程 sequenceDiagram participant Boot ...

  9. React 的 KeepAlive 探索

    什么是 KeepAlive? 用过 Vue 的童鞋都知道 Vue 官方自带了 Keep-Alive 组件,它能够使组件在切换时仍能保留原有的状态信息,并且有专门的生命周期方便去做额外的处理.该组件在很 ...

  10. 8.5考试总结(NOIP模拟31)[Game·Time·Cover]

    我们总是在注意错过太多,却不注意自己拥有多少. 前言 考场上疯狂搞第一题,终于把人给搞没了.. T1 Game 解题思路 线段树+二分 总体来讲就是用线段树维护三个值: 没有产生贡献的 a(小 B 的 ...