09 Caso de aterrizaje del proyecto de consistencia de doble escritura de datos de Redis y MySQL

canal

qué es

  • canal [kə'næl], traducido al chino como vía fluvial/tubería/zanja/canal, se utiliza principalmente para la suscripción, el consumo y el análisis de datos de registro incrementales de la base de datos MySQL. Es desarrollado y de código abierto por Alibaba, y está desarrollado en lenguaje Java. ;
  • El trasfondo histórico es que en los primeros días de Alibaba, debido a la implementación de salas de computadoras duales en Hangzhou y los Estados Unidos, había un requisito comercial para la sincronización de datos entre salas de computadoras. El método de implementación se basó principalmente en disparadores comerciales ( disparadores) para obtener cambios incrementales. Desde 2010, Alibaba ha intentado gradualmente utilizar registros de bases de datos de análisis para obtener cambios incrementales para la sincronización y, por lo tanto, derivó el proyecto del canal;
  • Canal es un componente basado en MySQL changelog incremental de suscripción y consumo

Qué puedes hacer

  • reflejo de la base de datos
  • Copia de seguridad en tiempo real de la base de datos
  • Construcción de índices y mantenimiento en tiempo real (índice heterogéneo dividido, índice invertido, etc.)
  • Actualización de caché empresarial
  • Procesamiento incremental de datos con lógica de negocios

dónde ir

principio de funcionamiento

Principio de funcionamiento de la replicación tradicional maestro-esclavo de MySQL

inserte la descripción de la imagen aquí
La replicación maestro-esclavo de MySQL seguirá los siguientes pasos:

  1. Cuando los datos en el servidor maestro maestro cambian, el cambio se escribe en el archivo de registro de eventos binarios;
  2. El servidor esclavo salve detectará el registro binario en el servidor maestro maestro dentro de un cierto intervalo de tiempo para detectar si ha cambiado.Si detecta que el registro de eventos binarios del servidor maestro maestro ha cambiado, iniciará un subproceso de E/S. solicitar el registro de eventos binarios maestros;
  3. Al mismo tiempo, el servidor maestro inicia un subproceso de volcado para cada subproceso de E/S para enviarle registros de eventos binarios;
  4. El esclavo guarda el registro de eventos binarios recibido del servidor en su propio archivo de registro de retransmisión local;
  5. El servidor esclavo salve iniciará SQL Thread para leer el registro binario del registro de retransmisión y reproducirlo localmente para que sus datos sean coherentes con el servidor maestro;
  6. Finalmente, el subproceso de E/S y el subproceso de SQL se dormirán y esperarán la próxima activación;

como funciona el canal

inserte la descripción de la imagen aquí

  • canal simula el protocolo interactivo del esclavo MySQL, pretende ser el esclavo MySQL y envía el protocolo de volcado al maestro MySQL
  • El maestro MySQL recibe una solicitud de volcado y comienza a enviar el registro binario al esclavo (es decir, el canal) el canal analiza el objeto de registro binario (originalmente flujo de bytes)
  • Los sistemas distribuidos solo tienen consistencia final y es difícil lograr una consistencia fuerte

mysql-canal-redis consistencia de doble escritura Codificación

mysql

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=utf8mb4
  • Habilite la función de escritura binlog de MySQL
  • Autorizar canal para conectarse a la cuenta de MySQL
    • El usuario predeterminado de mysql está en la tabla de usuarios de la biblioteca mysql
    • No hay una cuenta de canal por defecto, cree una nueva + autorice aquí

servidor de canales

  • descargar
  • Después de la descompresión, colóquelo en la ruta de /mycanal como un todo
  • modificación de configuración
    • /mycanal/canal.deployer-1.1.5/conf/ruta de ejemplo

    • inserte la descripción de la imagen aquí

    • instancia.propiedades

    • inserte la descripción de la imagen aquí

    • Reemplácelo con su propia cuenta de canal nueva en mysql

    • inserte la descripción de la imagen aquí

  • puesta en marcha
    • Ejecutar en la ruta /mycanal/canal.deployer-1.1.5/bin
    • ./inicio.sh
  • Ver registros del servidor
  • inserte la descripción de la imagen aquí
  • Ver registros de instancias
  • inserte la descripción de la imagen aquí

cliente canal (programa de negocios escrito en Java)

  • módulo de construcción

  • cambiar 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>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.5.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.zzyy.study</groupId>
        <artifactId>canal_demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>canal_demo</name>
        <description>Demo project for Spring Boot</description>
    
    
        <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>
            <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>com.alibaba.otter</groupId>
                <artifactId>canal.client</artifactId>
                <version>1.1.0</version>
            </dependency>
            <!--guava-->
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>23.0</version>
            </dependency>
            <!--web+actuator-->
            <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>
            <!--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>
            <!-- jedis -->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>3.1.0</version>
            </dependency>
            <!-- springboot-aop 技术-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <!-- redisson -->
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.13.4</version>
            </dependency>
            <!--Mysql数据库驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <!--集成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>
            <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>
            <!-- 添加springboot对amqp的支持 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-amqp</artifactId>
            </dependency>
            <!--通用基础配置-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </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>
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>RELEASE</version>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.73</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
  • escribir YML

    server.port=5555
    
  • arranque principal

  • clase de negocios

    • RedisUtils
      package com.zzyy.study.util;
      
      import redis.clients.jedis.Jedis;
      import redis.clients.jedis.JedisPool;
      import redis.clients.jedis.JedisPoolConfig;
      
      /**
       * @auther zzyy
       * @create 2020-10-11 14:33
       */
      public class RedisUtils
      {
              
              
          public static JedisPool jedisPool;
      
          static {
              
              
              JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
              jedisPoolConfig.setMaxTotal(20);
              jedisPoolConfig.setMaxIdle(10);
              jedisPool=new JedisPool(jedisPoolConfig,"192.168.111.147",6379);
          }
      
          public static Jedis getJedis() throws Exception {
              
              
              if(null!=jedisPool){
              
              
                  return jedisPool.getResource();
              }
              throw new Exception("Jedispool is not ok");
          }
      
      
          /*public static void main(String[] args) throws Exception
          {
              try(Jedis jedis = RedisUtils.getJedis())
              {
                  System.out.println(jedis);
      
                  jedis.set("k1","xxx2");
                  String result = jedis.get("k1");
                  System.out.println("-----result: "+result);
                  System.out.println(RedisUtils.jedisPool.getNumActive());//1
              }catch (Exception e){
                  e.printStackTrace();
              }
          }*/
      }
      
    • RedisCanalClientEjemplo
       
      package com.zzyy.study.t1;
      
      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.zzyy.study.util.RedisUtils;
      import org.springframework.beans.factory.annotation.Autowired;
      import redis.clients.jedis.Jedis;
      
      import java.net.InetSocketAddress;
      import java.util.List;
      import java.util.concurrent.TimeUnit;
      
      /**
       * @auther zzyy
       * @create 2020-11-11 17:13
       */
      public class RedisCanalClientExample
      {
              
              
      
          public static final Integer _60SECONDS = 60;
      
          public static void main(String args[]) {
              
              
      
              // 创建链接canal服务端
              CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.111.147",
                      11111), "example", "", "");
              int batchSize = 1000;
              int emptyCount = 0;
              try {
              
              
                  connector.connect();
                  //connector.subscribe(".*\\..*");
                  connector.subscribe("db2020.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());
                      }
                      connector.ack(batchId); // 提交确认
                      // connector.rollback(batchId); // 处理失败, 回滚数据
                  }
                  System.out.println("empty too many times, exit");
              } finally {
              
              
                  connector.disconnect();
              }
          }
      
          private static void printEntry(List<Entry> entrys) {
              
              
              for (Entry entry : entrys) {
              
              
                  if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
              
              
                      continue;
                  }
      
                  RowChange rowChage = null;
                  try {
              
              
                      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());
                      }
                  }
              }
          }
      
          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();
                  }
              }
          }
      
      
      }
      

Supongo que te gusta

Origin blog.csdn.net/m0_56709616/article/details/131033196
Recomendado
Clasificación