浅谈canal实例 在docker里面安装canal镜像 Canal监听MySQL数据库变更并同步更新Redis和Elasticsearch 示例

目录

[1. 环境准备](#1. 环境准备)

[1.1 MySQL配置](#1.1 MySQL配置)

[1.2 部署Canal Server](#1.2 部署Canal Server)

[2. Spring Boot项目配置](#2. Spring Boot项目配置)

[2.1 添加依赖](#2.1 添加依赖)

[2.2 配置参数](#2.2 配置参数)

[3. 实现Canal监听与同步](#3. 实现Canal监听与同步)

[3.1 Canal客户端监听](#3.1 Canal客户端监听)

[3.2 同步到Redis](#3.2 同步到Redis)

[3.3 同步到Elasticsearch](#3.3 同步到Elasticsearch)

[4. 注意事项](#4. 注意事项)


在Spring Boot中通过Canal监听MySQL数据库变更并同步更新Redis和Elasticsearch,可按照以下步骤实现:


1. 环境准备

1.1 MySQL配置
  • 开启Binlog并设置为ROW模式:

    [mysqld]
    log-bin=mysql-bin
    binlog-format=ROW
    server-id=1

  • 创建Canal用户并授权:

    CREATE USER 'canal'@'%' IDENTIFIED BY 'canal';
    GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON . TO 'canal'@'%';
    FLUSH PRIVILEGES;

1.2 部署Canal Server
  1. 下载Canal Server:Canal Releases

  2. 修改配置 conf/example/instance.properties

    canal.instance.master.address=127.0.0.1:3306
    canal.instance.dbUsername=canal
    canal.instance.dbPassword=canal
    canal.instance.filter.regex=.\.. # 监听所有库表,或指定如test.user


2. Spring Boot项目配置

2.1 添加依赖
复制代码
<!-- Canal客户端 -->
<dependency>
    <groupId>com.alibaba.otter</groupId> 
    <artifactId>canal.client</artifactId> 
    <version>1.1.6</version>
</dependency>
<!-- Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Elasticsearch -->
<dependency>
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2.2 配置参数

application.yml

复制代码
canal:
  server: 127.0.0.1:11111
  destination: example
  username: canal
  password: canal

spring:
  redis:
    host: localhost
    port: 6379
  data:
    elasticsearch:
      cluster-nodes: localhost:9200

3. 实现Canal监听与同步

3.1 Canal客户端监听
复制代码
@Component
public class CanalListener {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private ElasticsearchRestTemplate esTemplate;

    @PostConstruct
    public void init() {
        CanalConnector connector = CanalConnectors.newSingleConnector( 
                new InetSocketAddress("127.0.0.1", 11111), "example", "canal", "canal");
        
        Thread thread = new Thread(() -> {
            connector.connect(); 
            connector.subscribe(".*\\..*"); 
            while (true) {
                Message message = connector.getWithoutAck(100); 
                long batchId = message.getId(); 
                if (batchId != -1) {
                    processEntry(message.getEntries()); 
                    connector.ack(batchId); 
                }
            }
        });
        thread.start(); 
    }

    private void processEntry(List<Entry> entries) {
        for (Entry entry : entries) {
            if (entry.getEntryType()  == EntryType.ROWDATA) {
                RowChange rowChange = RowChange.parseFrom(entry.getStoreValue()); 
                for (RowData rowData : rowChange.getRowDatasList())  {
                    String tableName = entry.getHeader().getTableName(); 
                    EventType eventType = rowChange.getEventType(); 
                    
                    // 解析变更前后的数据
                    Map<String, String> before = parseColumns(rowData.getBeforeColumnsList()); 
                    Map<String, String> after = parseColumns(rowData.getAfterColumnsList()); 
                    
                    // 根据事件类型同步数据
                    switch (eventType) {
                        case INSERT:
                        case UPDATE:
                            syncToRedis(tableName, after);
                            syncToElasticsearch(tableName, after);
                            break;
                        case DELETE:
                            deleteFromRedis(tableName, before);
                            deleteFromElasticsearch(tableName, before);
                            break;
                    }
                }
            }
        }
    }

    private Map<String, String> parseColumns(List<Column> columns) {
        return columns.stream() 
                .collect(Collectors.toMap(Column::getName,  Column::getValue));
    }
}
3.2 同步到Redis
复制代码
private void syncToRedis(String tableName, Map<String, String> data) {
    String key = tableName + ":" + data.get("id");  // 假设主键为id
    redisTemplate.opsForValue().set(key,  data);
}

private void deleteFromRedis(String tableName, Map<String, String> data) {
    String key = tableName + ":" + data.get("id"); 
    redisTemplate.delete(key); 
}
3.3 同步到Elasticsearch
复制代码
private void syncToElasticsearch(String tableName, Map<String, String> data) {
    IndexQuery indexQuery = new IndexQueryBuilder()
            .withId(data.get("id")) 
            .withObject(data)
            .build();
    esTemplate.index(indexQuery,  IndexCoordinates.of(tableName)); 
}

private void deleteFromElasticsearch(String tableName, Map<String, String> data) {
    esTemplate.delete(data.get("id"),  IndexCoordinates.of(tableName)); 
}

4. 注意事项

  1. 异常处理:增加重试机制或记录错误日志,确保网络波动时的数据一致性。
  2. 性能优化:批量处理Canal消息,减少Redis/ES的频繁写入。
  3. 数据结构:确保Elasticsearch的索引Mapping与MySQL表结构兼容。
  4. 事务管理:如需强一致性,可结合本地事务表或消息队列(如RocketMQ)做可靠投递。

通过以上步骤,Spring Boot应用能够实时监听MySQL变更,并自动同步到Redis和Elasticsearch,保障数据一致性。

相关推荐
全干engineer5 分钟前
ClickHouse 入门详解:它到底是什么、优缺点、和主流数据库对比、适合哪些场景?
数据库·clickhouse
Hellyc2 小时前
基于模板设计模式开发优惠券推送功能以及对过期优惠卷进行定时清理
java·数据库·设计模式·rocketmq
lifallen2 小时前
Paimon LSM Tree Compaction 策略
java·大数据·数据结构·数据库·算法·lsm-tree
hdsoft_huge3 小时前
SpringBoot 与 JPA 整合全解析:架构优势、应用场景、集成指南与最佳实践
java·spring boot·架构
百锦再3 小时前
详细解析 .NET 依赖注入的三种生命周期模式
java·开发语言·.net·di·注入·模式·依赖
程序员的世界你不懂4 小时前
基于Java+Maven+Testng+Selenium+Log4j+Allure+Jenkins搭建一个WebUI自动化框架(2)对框架加入业务逻辑层
java·selenium·maven
风吹落叶花飘荡4 小时前
2025 Next.js项目提前编译并在服务器
服务器·开发语言·javascript
失败又激情的man4 小时前
python之requests库解析
开发语言·爬虫·python
web_Hsir4 小时前
vue3.2 前端动态分页算法
前端·算法