Canal实现Mysql数据同步至Redis、Elasticsearch

2023-11-10

文章目录

1.Canal简介

官网
https://github.com/alibaba/canal
在这里插入图片描述
在这里插入图片描述

canal [kə’næl] ,译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费

早期阿里巴巴因为杭州和美国双机房部署,存在跨机房同步的业务需求,实现方式主要是基于业务 trigger 获取增量变更。从 2010 年开始,业务逐步尝试数据库日志解析获取增量变更进行同步,由此衍生出了大量的数据库增量订阅和消费业务。

基于日志增量订阅和消费的业务包括

  • 数据库镜像
  • 数据库实时备份
  • 索引构建和实时维护(拆分异构索引、倒排索引等)
  • 业务 cache 刷新
  • 带业务逻辑的增量数据处理

当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x

1.1 MySQL主备复制原理

在这里插入图片描述

  • MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)
  • MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
  • MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据

1.2 canal工作原理

  • canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
  • MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
  • canal 解析 binary log 对象(原始为 byte 流)

2.开启MySQL Binlog

对于自建 MySQL , 需要先开启 Binlog 写入功能,
配置 binlog-format 为 ROW 模式,这里以mysql8.0.27为例,my.cnf 中配置如下

#开启bInlog
log-bin=mysql-bin
#以数据的方式写binlog日志 :statement 是记录SQL,row是记录数据
binlog-format=ROW
binlog-ignore-db=mysql

在这里插入图片描述
在这里插入图片描述
修改后,重启mysql服务。

  • 创建cannal
  • 授权 canal 链接 MySQL 账号具有作为 MySQL slave 的权限,
flush privileges;
#创建用户cannal
CREATE USER canal IDENTIFIED BY 'canal';
#把所有权限赋予canal,密码也是canal
GRANT ALL PRIVILEGES ON canaldb.user TO 'canal'@'%' identified by "canal";
//GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%' identified by "canal";
#刷新权限
flush privileges;

如果已有账户可直接 grant

ALTER USER 'canal'@'%' IDENTIFIED WITH mysql_native_password BY 'canal'; #更新一下用户密码
FLUSH PRIVILEGES; #刷新权限

通过以下命令,可以查看mysql用户信息

#查看所有数据库
show databases;
#使用mysql数据库
use mysql;
#查看当前库下所有表
show tables;
#查看user表
select Host, User from user;

3.安装Canal

3.1 下载Canal

点击下载地址,选择版本后点击canal.deployer文件下载

https://github.com/alibaba/canal/releases
在这里插入图片描述

3.2 修改配置文件

打开目录下conf/example/instance.properties文件,主要修改以下内容

## mysql serverId,不要和 mysql 的 server_id 重复
canal.instance.mysql.slaveId = 10
#position info,需要改成自己的数据库信息
canal.instance.master.address = 127.0.0.1:3306 
#username/password,需要改成自己的数据库信息,与刚才添加的用户保持一致
canal.instance.dbUsername = canal  
canal.instance.dbPassword = canal
复制代码

3.3 启动和关闭

#进入文件目录下的bin文件夹
#Linux启动
sh startup.sh
##Linux关闭
sh stop.sh
#Windows启动
双击 startup.bat

4.SpringCloud集成Canal

4.1 Canal数据结构在这里插入图片描述

4.2 引入依赖

父工程规定版本,子工程引用

<!-- 统一管理jar包版本 -->
    <properties>
      <canal.version>1.2.1-RELEASE</canal.version>
    </properties>


    <!-- 子模块继承之后,提供作用:锁定版本+了modlue不用写groupId和version -->
    <dependencyManagement>
        <dependencies>
            <!--Canal 依赖-->
            <dependency>
                <groupId>top.javatool</groupId>
                <artifactId>canal-spring-boot-starter</artifactId>
                <version>${canal.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

4.3 配置多个数据同步的目的地

由于我们这里是多个服务对应同一个canal端,则需要配置多个数据同步的目的地

在canal的安装目录下打开canal.deployer-1.1.6\conf\canal.properties文件

在canal.destinations = example后面添加多个数据目录,用逗号分割,一个服务对应一个目录,这里默认只有一个example

#################################################
######### 		destinations		#############
#################################################
canal.destinations = example,ad,goods,course,order,secKill,auth

在这里插入图片描述

配置好后,重启canal服务
之后会看到会看到canal/conf目录下新增了这些数据目录的文件夹
我们需要将默认的example文件夹中的instance.properties配置文件复制到新创建的自定义数据目录中
在这里插入图片描述
在这里插入图片描述

4.4 application.yml

canal:
  #canal的地址
  server: 127.0.0.1:11111 
  #数据同步的目的地
  destination: goods

4.5 监听配置

去实现EntryHandler接口,添加自己的业务逻辑,比如缓存的删除更新插入,实现对增删改查的逻辑重写。

4.5 监听配置

去实现EntryHandler接口,添加自己的业务逻辑,比如缓存的删除更新插入,实现对增删改查的逻辑重写。

canal-client提供了EntryHandler,该handler中提供了insert,delete,update方法,当监听到某张表的相关操作后,会回调对应的方法把数据传递进来,我们就可以拿到数据往Redis同步了。

  • @CanalTable(“employee”) :监听的表
  • EntryHandler<Employee> : 拿到employee表的改变后的数据之后,会封装为Employee实体 投递给我们
4.5.1 监听测试类
@CanalTable("test")
@Component
@Slf4j
public class TestHandler implements EntryHandler<Test> {

    @Resource
    private RedisService redisService;

    @Override
    public void insert(Test test) {
        log.info("新增Test:"+test);
    }

    @Override
    public void delete(Test test) {
        log.debug("删除Test:"+test);
    }

    @Override
    public void update(Test before, Test after) {
        log.info("修改前Test:"+before);
        log.info("修改后Test:"+after);
    }

}

在这里插入图片描述
通过日志可以看到我们在navicat中对test这张表的增删改操作均被监听到了

4.5.2 redis数据同步
@CanalTable("employee")
@Component
@Slf4j
public class EmployeeHandler implements EntryHandler<Employee> {

	//把数据往Redis同步
    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Override
    public void insert(Employee employee) {
        redisTemplate.opsForValue().set("EMP:"+employee.getId(),employee);
    }

    @Override
    public void delete(Employee employee) {
        redisTemplate.delete("EMP:"+employee.getId());
    }

    @Override
    public void update(Employee before, Employee after) {
        redisTemplate.opsForValue().set("EMP:"+after.getId(),after);
    }
}


4.5.3 Elasticsearch数据同步
import top.javatool.canal.client.annotation.CanalTable;
import top.javatool.canal.client.handler.EntryHandler;

@CanalTable("t_goods")
@Component
@Slf4j
public class GoodsHanlder implements EntryHandler<Goods> {

    @Resource
    private EsApiService esApiService;

    /**
     * 新增商品后同步es
     *
     * @param goods
     */
    @SneakyThrows
    @Override
    public void insert(Goods goods) {
        log.info("新增:" + goods);
        //同步至es
        try {
            esApiService.bulkRequest(EsConst.GOODS, String.valueOf(goods.getId()), goods);
        } catch (Exception e) {
            log.error("同步es新增:" + e.getMessage());
        }
    }

    /**
     * 更新商品后同步es
     *
     * @param before
     * @param after
     */
    @SneakyThrows
    @Override
    public void update(Goods before, Goods after) {
        log.info("修改前:" + before);
        log.info("修改后:" + after);
        //同步至es
        try {
            esApiService.updateDocument(EsConst.GOODS, String.valueOf(after.getId()), after);
        } catch (Exception e) {
            log.error("同步es更新:" + e.getMessage());
        }
    }

    /**
     * 删除商品后同步es
     *
     * @param goods
     */
    @SneakyThrows
    @Override
    public void delete(Goods goods) {
        log.info("删除:" + goods);
        //同步至es
        try {
            esApiService.deleteDocument(EsConst.GOODS, String.valueOf(goods.getId()));
        } catch (Exception e) {
            log.error("同步es更新:" + e.getMessage());
        }
    }
}

4.5.4 Es Api 封装业务类 EsApiService
package com.youzi.elasticsearch.service;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

/**
 * EsService : es API封装类
 *
 * @author zyw
 * @create 2023/9/19 17:31
 */
@Component
@Slf4j
public class EsApiService {

    @Autowired
    @Qualifier("restHighLevelClient")
    private RestHighLevelClient client;


    /**
     * 更新文档信息
     * @param index 索引名称
     * @param id 更新文档的id
     * @param data 更新的对象
     * @return
     * @throws IOException
     */
    public boolean updateDocument(String index, String id, Object data) throws IOException {
        UpdateRequest request = new UpdateRequest(index, id);
        request.timeout(TimeValue.timeValueSeconds(1));
        request.doc(JSON.toJSONString(data), XContentType.JSON);
        UpdateResponse update = client.update(request, RequestOptions.DEFAULT);
        log.info("更新文档信息:" + update);
        return true;
    }

    /**
     * 插入单个
     *
     * @param index 索引名称
     * @param id 新增的文档id
     * @param data 新增的对象
     * @return
     * @throws IOException
     */
    public boolean bulkRequest(String index, String id,Object data) throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");
        //批处理请求
        bulkRequest.add(new IndexRequest(index)
                .id(id)
                .source(JSON.toJSONString(data), XContentType.JSON));
        BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        return bulk.hasFailures();
    }


    /**
     * 删除文档信息
     *
     * @param index 索引名称
     * @param id    删除文档的id
     * @throws IOException
     */
    public boolean deleteDocument(String index, String id) throws IOException {
        DeleteRequest request = new DeleteRequest(index, id);
        request.timeout(TimeValue.timeValueSeconds(1));
        DeleteResponse delete = client.delete(request, RequestOptions.DEFAULT);
        log.info("删除文档信息:" + delete);
        return true;
    }

4.6 canal日志

如果不想让控制台一直打印某些信息,可以配置如下配置屏蔽AbstractCanalClient类process()一直打印this.log.info(“获取消息 {}”, message)。

logging:
 level:
   #禁止AbstractCanalClient 打印常規日志 获取消息 {}
  top.javatool.canal.client: warn  

4.7 第二种方案(解决数据库存在下划线,用上述方法,某些字段会为空)

上面的方式只适合数据库字段和实体类字段,属性完全一致的情况;当数据库字段含有下划线的适合,因为我们直接去监听的binlog日志,里面的字段是数据库字段,因为跟实体类字段不匹配,所以会出现字段为空的情况,这个适合需要去获取列的字段,对字段进行属性转换,实现方法如下:

4.7.1 引入依赖
        <dependency>
            <groupId>com.xpand</groupId>
            <artifactId>starter-canal</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
4.7.2 创建监听
@CanalEventListener
@Slf4j
public class KafkaListener {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * @param eventType 当前操作数据库的类型
     * @param rowData   当前操作数据库的数据
     */
    @ListenPoint(schema = "maruko", table = "kafka_test")
    public void listenKafkaTest(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
        KafkaTest kafkaTestBefore = new KafkaTest();
        KafkaTest kafkaTestAfter = new KafkaTest();


        //遍历数据获取k-v
        List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
        List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();


        getEntity(beforeColumnsList, kafkaTestBefore);
        log.warn("获取到提交前的对象为:" + kafkaTestBefore);

        getEntity(afterColumnsList, kafkaTestAfter);
        log.warn("获取到提交后的对象为:" + kafkaTestAfter);

        //判断是新增还是更新还是删除
        switch (eventType.getNumber()) {
            case CanalEntry.EventType.INSERT_VALUE:
            case CanalEntry.EventType.UPDATE_VALUE:
                redisTemplate.opsForValue().set("kafka_test" + kafkaTestAfter.getId(), kafkaTestAfter);
                break;
            case CanalEntry.EventType.DELETE_VALUE:
                redisTemplate.delete("kafka_test" + kafkaTestBefore.getId());
                break;
        }
    }

    /**
     * 遍历获取属性转换为实体类
     *
     * @param columnsList
     * @param kafkaTest
     */
    private void getEntity(List<CanalEntry.Column> columnsList, KafkaTest kafkaTest) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        for (CanalEntry.Column column : columnsList) {
            String name = column.getName();
            String value = column.getValue();
            switch (name) {
                case KafkaTest.ID:
                    if (StringUtils.hasLength(value)) {
                        kafkaTest.setId(Integer.parseInt(value));
                    }
                    break;
                case KafkaTest.CONTENT:
                    if (StringUtils.hasLength(value)) {
                        kafkaTest.setContent(value);
                    }
                    break;
                case KafkaTest.PRODUCER_STATUS:
                    if (StringUtils.hasLength(value)) {
                        kafkaTest.setProducerStatus(Integer.parseInt(value));
                    }
                    break;
                case KafkaTest.CONSUMER_STATUS:
                    if (StringUtils.hasLength(value)) {
                        kafkaTest.setConsumerStatus(Integer.parseInt(value));
                    }
                    break;
                case KafkaTest.UPDATE_TIME:
                    if (StringUtils.hasLength(value)) {
                        try {
                            kafkaTest.setUpdateTime(format.parse(value));
                        } catch (ParseException p) {
                            log.error(p.getMessage());
                        }
                    }
                    break;
                case KafkaTest.TOPIC:
                    if (StringUtils.hasLength(value)) {
                        kafkaTest.setTopic(value);
                    }
                    break;
                case KafkaTest.CONSUMER_ID:
                    if (StringUtils.hasLength(value)) {
                        kafkaTest.setConsumerId(value);
                    }
                    break;
                case KafkaTest.GROUP_ID:
                    if (StringUtils.hasLength(value)) {
                        kafkaTest.setGroupId(value);
                    }
                    break;
                case KafkaTest.PARTITION_ID:
                    if (StringUtils.hasLength(value)) {
                        kafkaTest.setPartitionId(Integer.parseInt(value));
                    }
                    break;
                case KafkaTest.PRODUCER_OFFSET:
                    if (StringUtils.hasLength(value)) {
                        kafkaTest.setProducerOffset(Long.parseLong(value));
                    }
                    break;
                case KafkaTest.CONSUMER_OFFSET:
                    if (StringUtils.hasLength(value)) {
                        kafkaTest.setConsumerOffset(Long.parseLong(value));
                    }
                    break;
                case KafkaTest.TEST:
                    if (StringUtils.hasLength(value)) {
                        kafkaTest.setTest(value);
                    }
                    break;
            }
        }
    }
}
4.7.3 实体类
@Data
@TableName("kafka_test")
public class KafkaTest {

    public static final String ID = "id";

    public static final String CONTENT = "content";

    public static final String PRODUCER_STATUS = "producer_status";

    public static final String CONSUMER_STATUS = "consumer_status";

    public static final String UPDATE_TIME = "update_time";

    public static final String TOPIC = "topic";

    public static final String CONSUMER_ID = "consumer_id";

    public static final String GROUP_ID = "group_id";

    public static final String PARTITION_ID = "partition_id";

    public static final String PRODUCER_OFFSET = "consumer_offset";

    public static final String CONSUMER_OFFSET = "producer_offset";

    public static final String TEST = "test";

    @TableId(type = IdType.AUTO)
    private Integer id;

    @TableField("content")
    private String content;

    @TableField("producer_status")
    private Integer producerStatus;

    @TableField("consumer_status")
    private Integer consumerStatus;

    @TableField("update_time")
    private Date updateTime;

    @TableField("topic")
    private String topic;

    @TableField("consumer_id")
    private String consumerId;

    @TableField("group_id")
    private String groupId;

    @TableField("partition_id")
    private int partitionId;

    @TableField("consumer_offset")
    private Long consumerOffset;

    @TableField("producer_offset")
    private Long producerOffset;

    @TableField("test")
    private String test;
}

4.8 第三种方案(解决数据库存在下划线,某些字段会为空)

实体类加上@Column注解

/**
 * 商品表(TGoods)实体类
 *
 * @author zyw
 * @since 2023-07-15 17:09:12
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName("t_goods")
public class Goods implements Serializable {
    private static final long serialVersionUID = -53376609655570945L;
    /**
     * 商品ID
     */
    @TableId(type = IdType.AUTO)
    @Column(name = "id")
    private Long id;
    /**
     * 商品名称
     */
    @TableField("goods_name")
    @Column(name = "goods_name")
    private String goodsName;
    /**
     * 商品标题
     */
    @TableField("goods_title")
    @Column(name = "goods_title")
    private String goodsTitle;
    /**
     * 商品图片
     */
    @TableField("goods_img")
    @Column(name = "goods_img")
    private String goodsImg;
    /**
     * 商品详情
     */
    @TableField("goods_detail")
    @Column(name = "goods_detail")
    private String goodsDetail;
    /**
     * 商品价格
     */
    @TableField("goods_price")
    @Column(name = "goods_price")
    private Double goodsPrice;
    /**
     * 商品库存
     */
    @TableField("goods_stock")
    @Column(name = "goods_stock")
    private Integer goodsStock;

    /**
     * 商家
     */
    @TableField("shop")
    @Column(name = "shop")
    private String shop;

    /**
     * 创建时间
     */
    @TableField("start_date")
    @Column(name = "start_date")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date startDate;

    /**
     * 删除时间
     */
    @TableField("end_date")
    @Column(name = "end_date")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date endDate;




}

在这里插入图片描述

4.9 canal整合异常问题排查思路

4…1 无法正常启动

canal配置文件中mysql连接方式是否有效,

是否已为canal单独配置mysql账号,并赋予权限

服务对应的自定义数据存储目的地中配置文件是否完整,初始配置文件需要与默认的exmple中配置文件一致

更改配置文件之后如果自定义的数据目录还是无法连接,则将对应目录下的meta.dat文件删除之后再重启

4.9.2 使用canal监听数据 启动成功了 没有报错 不过一直监听不到消息
  1. 检查canal的配置,确保配置正确,特别是数据库的连接信息;
  2. 检查canal的日志,确保没有报错;
  3. 检查数据库的binlog是否开启,确保binlog格式为row;
  4. 检查数据库的binlog是否有变更,确保有变更;
  5. 检查canal的过滤规则,确保过滤规则正确;
  6. 检查canal的版本,确保版本与数据库版本兼容;
  7. 检查canal的运行环境,确保环境正确;
  8. 检查canal的配置文件,确保配置文件正确;
  9. 检查canal的运行状态,确保运行正常;
  10. 检查canal的监听端口,确保端口没有被占用。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Canal实现Mysql数据同步至Redis、Elasticsearch 的相关文章

  • 为什么这会返回资源 id #2? [复制]

    这个问题在这里已经有答案了 可能的重复 我如何从 PHP 中的 MySql 响应中 回显 资源 id 6 https stackoverflow com questions 4290108 how do i echo a resource
  • 第三个下拉菜单不从数据库填充

    我有以下 Index php
  • 如何在Mysql中仅将不同的值从一个表复制到另一个表?

    我有一个大约 2 5GB 的 MySql 数据库 表 A 具有以下列 anoid query date item rank url 我刚刚创建了另一个仅包含列的表 b query and date 我想在查询列中插入所有不同的记录 及其各自
  • 如何在Sequelize中设置查询超时?

    我想看看如何在 Sequelize 中设置查询的超时时间 我查看了 Sequelize 文档以获取一些信息 但我找不到我要找的东西 我发现的最接近的是 pools acquire 选项 但我不想设置传入连接的超时 而是设置正在进行的查询的超
  • Laravel leftJoin 仅右表的最后一条记录

    我是 Laravel 的新手 我有两张桌子 1 产品 2 价格 products id product int p key name varchar prices id price int p key id product int
  • Mysql 创建定义器

    我创建了一个在 CentOS Web 服务器上运行的 Intranet Web 应用程序 该应用程序使用另一个本地服务器 始终是 CentOS 作为 MySQL 数据库 在数据库内部我创建了例程 这些例程总是这样开始 CREATE DEFI
  • 将第三个表链接到多对多关联中的桥接表

    设计这个数据库的正确方法是什么 这是我设置表格的方式 我在名为 教师 的表和名为 仪器 的表之间存在多对多关系 然后我有一个连接两者的桥接表 我想将另一个表与 BRIDGE 表关联起来 意思是乐器 老师的组合 该表有 3 行 指定老师可以教
  • MySQL连接字符集问题

    我在 Mac 上使用带有 MySQL 的 velosurf 没有任何编码问题 但是当我切换到 Linux 计算机时 从 velosurf 获得的值未正确编码 我发现这可能是默认连接字符集的问题 在 Mac 上我得到 mysql gt sho
  • 为什么Redis SET性能优于GET?

    根据Redis基准 http redis io topics benchmarkss Redis 可以执行 100 000 SET 操作 秒和 80 000 GET 操作 秒 Redis 是一种内存数据库 这似乎令人惊讶 因为通常人们会认为
  • 如何在 Play java 中创建数据库线程池并使用该池进行数据库查询

    我目前正在使用 play java 并使用默认线程池进行数据库查询 但了解使用数据库线程池进行数据库查询可以使我的系统更加高效 目前我的代码是 import play libs Akka import scala concurrent Ex
  • 在 PHP 字符串中格式化 MySQL 代码

    是否有任何程序 IDE 可以在 PHP 字符串中格式化 MySQL 代码 例如 我使用 PHPStorm IDE 但它无法做到这一点 它对 PHP 和 MYSQL 执行此操作 但不适用于 php 字符串内的 MYSQL 我已准备好使用新的
  • 如果没有过期的内容,Redis maxmemory-policy volatile-lru 是否会被驱逐?

    我有一个 redis 服务器 设置了maxmemory policy set to volatile lru 文档表明 当达到内存限制时 这将从设置过期的条目集中逐出 在这种情况下 redis 是否只驱逐过期的项目 如果内存中的所有内容都设
  • Redis如何存储关联数组?设置、散列还是列表?

    我对 Redis 的所有可用存储选项有点困惑 我想做一些简单的事情 并且不想过度设计它 我正在与phpredis and Redis v2 8 6 我有一个需要存储的简单关联数组 我还需要能够通过其键检索项目并循环遍历所有项目 a arra
  • 显示标准化数据

    跟进问题 添加 2 个不同表的总和 https stackoverflow com questions 39717541 adding sum from 2 different tables 我创建了3个表 members videos v
  • Galera 集群问题

    我想在我们的生产环境中使用Galera集群 但我有一些顾虑 每个表必须至少定义一个显式主键 每个表必须运行在InnoDB或XtraDB存储引擎下 分批处理您的大额交易 例如 不要让一个事务插入 100 000 行 而是将其分成更小的块 例如
  • 海量记录的bulk_create最佳实践

    I use bulk create将 1 mio 记录插入到新表中 需要 80 秒 Django 只使用一个 CPU 核心 大约 25 CPU 但没有一个核心达到 100 我相信有改进的潜力 这是代码 class Stock models
  • 在 C# 中,当有人插入、删除或修改记录时,如何从 MySQL 获取事件?

    我正在 WPF Net 中开发一个程序 我需要知道何时有人对数据库的任何表进行更改 这个想法是在数据库发生更改时从数据库接收一个事件 我读了很多文章 但找不到解决我的问题的方法 亲切的问候 最好的解决方案是使用消息队列 在您的应用程序向数据
  • 如何在mysql中选择具有相同值集的列?

    我的桌子是 patients pid name city disease did dname has disease did pid 我想列出具有相同疾病组的患者 pid 和 did 分别是患者和疾病表中的主键 并且是 has diseas
  • 在elasticSearch中查询时定义分析器

    我对 Elasticsearch 还很陌生 只需要一些说明 我们可以在查询搜索服务器时定义一个分析器吗 我尝试使用 文本 和 字段 查询 效果很好 Query curl XPOST http localhost 9200 test user
  • db:schema:load 与 db:migrate 使用 capistrano

    我有一个 Rails 应用程序 我正在将其移动到另一台服务器 我认为我应该使用 db schema load 来创建 mysql 数据库 因为这是推荐的 我的问题是我正在使用 capistrano 进行部署 并且它似乎默认为 rake db

随机推荐

  • C++14 新特性

    一 新的语言特性 1 泛型的 Lambda 函数 在 C 11 中 lambda 函数的形式参数需要被声明为具体的类型 C 14 放宽了这一要求 允许 lambda 函数的形式参数声明中使用类型说明符 auto auto lambda au
  • Alist V3版本 API使用文档 -个人整理

    Alist V3 API 整理 Alist V3是一个支持多种存储 支持网页浏览和 WebDAV 的文件列表程序 由 gin 和 Solidjs 驱动 Alist的官方文档提供了V2版本的API说明 但对于最新的V3版本并没有 这里个人整理
  • 主析取范式与主合取范式原理探究

    主析取范式 对任意一个命题公式来说 主析取范式与主合取范式都是唯一的 命题变元指原子化的 P Q命题 极小项的定义 包含全部N个命题变元的合取式 称其为极小项 且N个命题变元中 每个变元与它的否定不能同时存在 但两者中必有一个出现且仅出现一
  • 怎样加入马云,马化腾,李彦宏的微信

    让马化腾出如今你的微信聊天里面 首先声明不是 PS 我不会 PS 的 这是程序截图 例如以下图 程序源码 http git oschina net LittleDY isWeiXin 我在他的基础上 又一次设计了图片和聊天记录 图片来自百度
  • C++ 内存模型

    C 内存模型 未完 数据存储 程序数据段 程序代码段 stack栈内存 栈内存属于执行期函数 编译时确定大小 函数执行时栈空间自动分配 结束时栈空间自动销毁 栈对象是线性分配 连续排列 没有内存碎片化效应 栈内存具有函数本地性 不存再多线程
  • mysql 数据库合并命令_mysql多源复制及合并数据库

    背景 机器1 10 1 6 99 机器2 10 1 6 100 机器3 10 1 6 101 1 分别在三台主机上安装mysql数据库 1 配置yum源 root master lucky front cat etc yum repos d
  • Python 发邮件

    来源 微信公众号Crossin的编程教室 0 前言 发送电子邮件是个很常见的开发需求 比如你写了个监控天气的脚本 发现第二天要下雨 或者网站上关注的某个商品降价了 就可以发个邮件到邮箱来提醒自己 使用 Python 脚本发送邮件并不复杂 不
  • maven学习笔记

    1 什么是Maven maven是跨平台的项目管理工具 主要服务于基于java平台 包括j2ee和j2se 的项目构建 依赖管理和项目信息管理 可以帮助开发者管理jar包 一步构建项目 从清理 编译 测试 报告直接到打包 部署 2 Mave
  • 关于洗牌算法的一点总结

    之前写斗地主的时候简单写了一个洗牌函数 基本思路是先产生一个顺序数组 遍历数组 每次产生一个 1 n 随机数 把这个随机数作为下标取出数组里的数与当前位置的数交换 当时也没多想 反正能打乱数组顺序就行 后来跟师兄吃饭的时候聊起来 说到面试里
  • 解决MATLAB Simulink 无法打开高版本模型的问题

    参考及致谢MATLAB版本 R2019a 不同版本的设置方式可能不同 报错内容 解决方案 Step1 Step2 Step3 按照上述流程操作后 就可以在低版本simulink中浏览使用高版本创建的模型文件 slx文件 了
  • 上门维修保养小程序系统开发

    用户通过小程序添加绑定需要售后维保的机器设备 然后用户通过小程序线上提交报修需求清单 后台分配安排人员上门维修 标记确认订单完成 同时提供在线服务商城 积分 优惠劵抵扣会员卡管理会员等级服务 核心功能 维修服务 保养服务 使用攻略 服务商城
  • c++数据读取、保存之dcm格式(需要有dcmtk)

    include
  • 【Shader笔记】Unity Shader基础

    参考书籍 UNITY SHADER入门精要 一 材质 Material 与 Unity Shader 效果的实现需要材质和Unity Shader配合使用 常见流程为 1 创建一个材质 2 创建一个Unity Shader 并赋予给上一步新
  • python粒子群算法工具包_python进阶教程:实现粒子群算法(PSO)详解

    本文来源于公众号 csdn2299 喜欢可以关注公众号 程序员学府 这篇文章主要介绍了Python编程实现粒子群算法 PSO 详解 涉及粒子群算法的原理 过程 以及实现代码示例 具有一定参考价值 需要的朋友可以了解下 文章目录 1 原理 2
  • Transformer学习笔记

    Transformer是第一个完全依赖于自我注意力机制来计算输入和输出表征的转导模型 而不使用序列对齐的RNNs或卷积 Figure 1 左 Transformer整体结构 右 编解码器内部结构图 Encoder Decoder 编码器 由
  • 虚拟机没有显示ip地址

    之所以写这篇博客是因为有个同学刚搭建完虚拟机但没有IP地址 为了帮助那个同学和回顾知识 所以有了这篇博客 而且网上大部分博客都没提到networkmanager的问题 所以这边就记录一下 一 检查网络连接模式是否为NAT模式 在VMware
  • Redis学习:Redis实现乐观锁

    实际这部分是接着事务那一块 加了一个watch命令 这里要有一个乐观锁和悲观锁的概念 悲观锁 很悲观 认为什么时候都会出现问题 无论做什么都会加锁 乐观锁 很乐观 认为什么时候都不会出现问题 所以不会上锁 更新数据的时候去判断一下 在此期间
  • Python提取PDF中的图片

    插播一条老家自产的糖心苹果 多个品种 欢迎选购 有问题随时私信我 来自雪域高原的馈赠 海拔2000米的大凉山高原生态糖心苹果 https blog csdn net qq 15969343 article details 126107252
  • VUE H5 页面借助 dsbridge 嵌入到 app 中(前端)

    H5 页面嵌入 app 中 不得不面对 web 和 native 之间进行交互的问题 比如 传递参数 调用函数等 至于交互的桥梁目前 github 上有一些开源的 其中使用最广的是 jsBridge 然而 最近刚开源了一个新项目 dsbri
  • Canal实现Mysql数据同步至Redis、Elasticsearch

    文章目录 1 Canal简介 1 1 MySQL主备复制原理 1 2 canal工作原理 2 开启MySQL Binlog 3 安装Canal 3 1 下载Canal 3 2 修改配置文件 3 3 启动和关闭 4 SpringCloud集成