数据一致性处理 - Canal:让数据自动 “同步更新“ 的小助手

一、项目里的数据同步难题

在开发项目时,经常会遇到这样的问题:

问题 1:专辑数据不同步
我们把专辑信息存在数据库(MySQL)里,同时为了加快查询速度,还会把这些信息复制一份存在缓存(Redis)里。但是当专辑信息在数据库里修改后,缓存里的数据却不会自动更新,这样用户查到的可能就是过时的数据。

问题 2:用户信息不一致
用户登录后,系统会把用户信息缓存起来。如果用户后来修改了自己的资料,缓存里的信息还是老样子,导致显示的用户信息不准确。

二、解决数据同步问题的方法

解决这些问题有几种常见办法:

  1. Redis 延时双删策略:先删除缓存,再更新数据库,等一会儿后再删一次缓存。但这种方法不能保证数据实时一致,而且如果读写操作同时进行,还是可能出现数据不一致的情况。

  2. 消息队列同步:把数据库里的数据变化打包成消息,通过消息队列(比如 Kafka、RabbitMQ)发送出去,再由另一个程序接收消息并更新缓存。虽然能实现异步更新,但消息队列本身也会有延迟,而且可能出现消息丢失的问题。

  3. Canal 同步:Canal 就像一个 "数据监控员",它能实时监控数据库的变化,一旦发现数据有增删改,就马上把变化同步到缓存里。相比前两种方法,Canal 能更及时、可靠地保证数据一致,所以我们选择用它来解决问题。

三、Canal 是什么?

Canal 是阿里巴巴开发的一款工具,专门用来监控 MySQL 数据库的变化。它的名字原意是 "水道、沟渠",就像水渠把水从一个地方引到另一个地方一样,Canal 把数据库里的数据变化引到需要更新的地方。

Canal 的特点

  • 速度快:比其他同步方法效率更高
  • 灵活:支持多种数据格式,可以通过多种方式同步数据
  • 功能强:能精确监控指定的表,支持在单机或集群环境下使用,还可以通过插件扩展功能

Canal 的应用场景

  • 制作数据库副本
  • 实时备份数据库
  • 更新搜索索引
  • 刷新业务缓存

四、Canal 是怎么工作的?

Canal 的工作原理基于 MySQL 的主从复制机制:

  1. MySQL 主从复制

    • MySQL 主库会把数据变化记录在二进制日志(binlog)里
    • 从库会把主库的日志复制过来,然后根据日志更新自己的数据
  2. Canal 的工作方式

    • Canal 假装自己是 MySQL 的从库,向主库发送请求
    • 主库把 binlog 发给 Canal
    • Canal 解析 binlog,获取数据变化信息
    • 然后把这些变化同步到需要更新的地方,比如 Redis 缓存

Canal 的结构

Canal 由服务端(canal server)和客户端(canal client)组成:

  • 服务端:负责连接 MySQL,获取 binlog 并解析
  • 客户端:从服务端获取数据变化信息,然后进行处理

服务端里面又包含几个重要部分:

  • eventParser:负责和 MySQL 通信,获取 binlog
  • eventSink:对获取到的数据进行过滤、加工
  • eventStore:临时存储数据
  • metaManager:管理数据订阅和消费的信息

五、如何安装 Canal

安装 Canal 之前,需要先安装好 MySQL,并且要开启 binlog 功能。这里以 MySQL 8.0.29 和 Canal 1.1.4 版本为例:

1. 配置 MySQL

  1. 检查 MySQL 是否开启 binlog:

    SHOW VARIABLES LIKE '%log_bin%';
    
     

    如果log_bin的值是OFF,需要手动开启。

  2. 开启 binlog:
    编辑 MySQL 配置文件/etc/mysql/mysql.conf.d/mysqld.cnf,在[mysqld]下面添加:

    log-bin=mysql-bin
    binlog-format=ROW
    server_id=1
    
  3. 创建 Canal 用户并授权:

    create user canal@'%' IDENTIFIED by 'canal';
    GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%';
    FLUSH PRIVILEGES;
    
  4. 重启 MySQL。

2. 安装 Canal

  1. 下载 Canal 镜像:

    docker pull canal/canal-server:v1.1.4
    
  2. 创建并启动 Canal 容器:

    docker run -p 11111:11111 --name canal -id canal/canal-server:v1.1.4
    
  3. 配置 Canal:

    • 创建目录:mkdir -p /home/canal/conf
    • 拷贝配置文件:
      docker cp canal:/home/admin/canal-server/conf/canal.properties /mydata/canal/conf
      docker cp canal:/home/admin/canal-server/conf/test/instance.properties /mydata/canal/conf
      
    • 修改instance.properties文件,配置 MySQL 连接信息等。
  4. 重新创建容器,应用配置修改。

  5. 查看 Canal 启动日志,确认是否启动成功。

六、在 Spring Boot 项目中使用 Canal

1. 创建项目

新建一个 Spring Boot 项目,添加以下依赖:

<dependencies>
    <dependency>
        <groupId>com.alibaba.otter</groupId>
        <artifactId>canal.client</artifactId>
        <version>1.1.2</version>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.5.2</version>
    </dependency>
    <dependency>
        <groupId>top.javatool</groupId>
        <artifactId>canal-spring-boot-starter</artifactId>
        <version>1.2.1-RELEASE</version>
    </dependency>
</dependencies>

2. 编写代码

  • 配置文件:在application.yml里配置 Canal 和 Redis 连接信息,比如:
canal:
  destination: example
  server: 192.168.254.156:11111
spring:
  redis:
    host: 192.168.254.156
    port: 6379
  • 编写处理器:创建一个类,实现EntryHandler接口,处理数据变化事件。比如处理专辑信息同步:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import top.javatool.canal.client.annotation.CanalTable;
import top.javatool.canal.client.handler.EntryHandler;

@Slf4j
@Component
@CanalTable("album_info")
public class AlbumInfoCdcHandler implements EntryHandler<GenericEntity> {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public void insert(GenericEntity t) {
        log.info("监听到数据添加,ID:{}", t.getId());
        String key = "album:info:" + t.getId();
        redisTemplate.delete(key);
    }

    @Override
    public void update(GenericEntity before, GenericEntity after) {
        log.info("监听到数据修改,ID:{}", after.getId());
        String key = "album:info:" + after.getId();
        redisTemplate.delete(key);
    }

    @Override
    public void delete(GenericEntity t) {
        log.info("监听到数据删除,ID:{}", t.getId());
        String key = "album:info:" + t.getId();
        redisTemplate.delete(key);
    }
}

class GenericEntity {
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

源码地址:言韵流光: 基于微服务架构 + Spring Boot + MinIO的一站式听书平台,用户能够享受到流畅的在线音乐体验。该平台采用前后端分离的设计,利用小程序作为前端,后端由微服务构成,通过nacos进行服务治理和配置管理。数据持久层整合了MySQL、MongoDB和Redis等技术,确保数据的高效存取。此外,平台支持持续集成和持续部署,以及利用MQ实现分布式事务的最终一致性,保障系统的稳定性和扩展性。 - Gitee.com 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值