复习-面试题

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

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

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

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. LVS负载均衡(3)-- DR模型搭建实例

    目录 1. LVS DR模型搭建 1.1 DR模型网络规划 1.2 RS设备的VIP冲突解决方式 1.3 DR模型访问流程 1.4 DR模型配置 1.4.1 ROUTER设备配置 1.4.2 后端ng ...

  2. vue特殊atribute-is

    1.解决dom内模板限制 有些 HTML 元素,诸如 <ul>.<ol>.<table> 和 <select>,对于哪些元素可以出现在其内部是有严格限制 ...

  3. 微信小程序长按识别二维码

    微信小程序长按识别二维码 image 组件中二维码/小程序码图片不支持长按识别.仅在 wx.previewImage 中支持长按识别示例代码

  4. URP(Universal Render Pipeline)渲染管线在使用中的一些分享

    本篇文章整理了URP管线使用中的一些简单的心得记述 1.使用ScriptableRendererFeature自定义渲染特性 在内建(Build-in)管线中可以使用CommandBuffer并添加到 ...

  5. JDK源码阅读-------自学笔记(十一)(java.lang.String包装类)

    核心要点 String 类对象代表不可变的Unicode字符序列,因此我们可以将String对象称为"不可变对象" String的核心就是char[]字符串,外部写的string对 ...

  6. Javascript/DOM:如何删除 DOM 对象的所有事件侦听器

    Javascript/DOM:如何删除 DOM 对象的所有事件侦听器 一.重写 重写 EventTarget 添加监听事件方法 addEventListener if (EventTarget.pro ...

  7. kubernets之高级调度

    一 节点的污点以及pod的容忍度以及节点的亲缘性对比 1.1 首先需要介绍的是节点的污点以及pod的污点容忍度 污点是节点的属性,容忍度是pod的属性,只有当一个pod的容忍度包含节点的污点,pod才 ...

  8. UIView AutoLayout WrapContent,UIview 实现自动包裹

    一.需求 实现一个UI组件,要求组件内部的内容变化的时候,内容需要同时产生变化 二.实现 效果: 一个三个元素的组件,两边固定大小,中间的Label内容会变化 实现的约束: 首先保证三个元素同时居中, ...

  9. NOIP模拟55

    T1 Skip 解题思路 正解给的是线段树维护单调栈,但是我不会.. CDQ 维护斜率可做!!! 先得出一个朴素的 DP 方程:设 \(f_i\) 表示最后一场是 i 的最优解. 转移方程就是 \(f ...

  10. [SWPUCTF 2021 新生赛]easy_md5

    打开靶场可以看到一串代码,进行代码审计我们可以知道这个网页包含了一个叫flag2.php的文件,如果想要得到这个文件就得进行GET传参和POST传参. 并且这里用到一个MD5绕过,传参的值不能相等,但 ...