实时同步:使用 Canal 和 Kafka 解决 MySQL 与缓存的数据一致性问题

目录

1. 准备工作

2. 将需要缓存的数据存储 Redis

3. 监听 canal 存储在 Kafka Topic 中数据


1. 准备工作

1. 开启并配置MySQL的 BinLog(MySQL 8.0 默认开启)

修改配置:C:\ProgramData\MySQL\MySQL Server 8.0\my.ini

log-bin="HELONG-bin"
binlog_format=ROW     # 只能配置行模式, 因为 Cannal 不具备将SQL转化成数据的能力
binlog-do-db=aicloud    # 监控 AI Cloud 项目

如果要同步多个项目:

binlog-do-db=aicloud
binlog-do-db=aicloud2
binlog-do-db=aicloud3

2. 重启MySQL服务

3. 赋值数据同步权限

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

4. 安装并配置 Canal

下载地址:https://github.com/alibaba/canal/releases

① 修改canal.properties

canal.serverMode=kafka
canal.mq.servers=127.0.0.1:9092

canal 监控 binlog 日志,binlog 日志的传输默认使用 MySQL 的复制协议(基于 TCP/IP),

可以使用写代码的方式直接从 MySQL 服务器读取数据,此处使用本地 kafka 进行存储。

② 修改instance.properties

canal.instance.mysql.slaveId=100   # 大于 1 即可
canal.instance.master.address=127.0.0.1:3306
canal.mq.topic=ai-cloud-canal-to-kafka

slaveId 表示从节点 id,canal 的执行原理就是伪装成一个从库去主库同步数据

(主节点的 slaveId = 1)

address 配置连接本地的 MySQL

topic 配置数据发送到 Kafka 的某个主题下

5. 拷贝 Jar 包到 lib

将 canal 下 plugin 下的所有 jar 包拷贝到 lib 目录下。

6. 删除 bin 目录下 startup.bat 里的参数

如果启动时报错:

Unrecognized VM option 'PermSize=128m'

Error: Could not create the Java Virtual Machine.

Error: A fatal exception has occurred. Program will exit.

删除 -XX:PermSize=128m 参数即可。

7. 启动 canal

打开 cmd ,cd 到 bin 目录下,输入 startup.bat 回车

2. 将需要缓存的数据存储 Redis

此时我将这个查询列表接口的数据,存储在 Redis 中:

/**
 * 获取历史聊天记录(对话/绘图)
 *
 * @param type
 * @return {@link ResponseEntity }
 */
@RequestMapping("/list")
public ResponseEntity getHistoryList(Integer type, Integer model) {
    String listCacheKey = RedisUtil.getListCacheKey(SecurityUtil.getCurrentUser().getUid(), model, type);
    Object list = redisTemplate.opsForValue().get(listCacheKey);
    if (ObjectUtil.isNull(list)) {
        LambdaQueryWrapper<Answer> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Answer::getUid, SecurityUtil.getCurrentUser().getUid());
        queryWrapper.eq(Answer::getType, type);
        queryWrapper.eq(Answer::getModel, model);
        queryWrapper.orderByDesc(Answer::getAid);
        List<Answer> answerList = answerService.list(queryWrapper);
        List<Long> userIds = answerList.stream().map(Answer::getUid).collect(Collectors.toList());
        Map<Long, User> userIdMap = userService.selectByIds(userIds).stream().collect(Collectors.toMap(User::getUid, Function.identity()));
        List<AnswerVo> answerVoList = answerList.stream().map(answer -> AnswerVoUtil.getListAnswerVo(answer, userIdMap)).collect(Collectors.toList());
        // 缓存 1 天
        redisTemplate.opsForValue().set(listCacheKey, answerVoList, 1, TimeUnit.DAYS);
        return ResponseEntity.success(answerVoList);
    } else {
        return ResponseEntity.success(list);
    }
}
/**
 * 查询列表存储 Redis 缓存
 *
 * @param uid
 * @param model
 * @param type
 * @return {@link String }
 */
public static String getListCacheKey(Long uid, Integer model, Integer type) {
    return "LIST_CACHE_KEY_" + uid + "_" + model + "_" + type;
}

3. 监听 Kafka Topic 中数据并删除 Redis 缓存

首先对数据库中需要缓存的数据进行一些修改操作:

此时,使用 kafka ui(下载地址划到最底下),刷新 kafka 对应 topic 下的 message,就可以看到当前所作出的修改:

执行修改操作:将 “如何学习Spring???”修改成 “如何学习Spring??”

执行删除操作:

由此可见,对数据库的每一个修改操作,都是对应固定格式的一个数据,所以可以监听对应的  topic 并针对 data 中的数据进行一个提取,得到一个  cacheKey,然后删除对应的缓存,使得下一次的查询去访问数据库,并同步缓存。

【代码示例】

/**
 * canal 监控 binlog 日志,将修改的数据存储 kafka topic 中
 * 监听 kafka topic 中的数据
 *
 * @param data
 * @param ack
 * @throws JsonProcessingException
 */
@KafkaListener(topics = {KafkaConstant.CANAL_TOPIC})
public void canalListen(String data, Acknowledgment ack) throws JsonProcessingException {
    HashMap<String, Object> map = objectMapper.readValue(data, HashMap.class);
    if (map.isEmpty()) {
        ack.acknowledge();
        return;
    }
    // 匹配上对应的数据库和数据表
    if (KafkaConstant.TARGET_DATABASE.equals(map.get(KafkaConstant.DATABASE_KEY).toString()) &&
            KafkaConstant.TARGET_TABLE.equals(map.get(KafkaConstant.TABLE_KEY).toString())) {
        // 更新缓存 
        List<Map<String, Object>> list = (List<Map<String, Object>>) map.get(KafkaConstant.DATA_KEY);
        if (!CollectionUtils.isEmpty(list)) {
            for (Map<String, Object> answerMap : list) {
                String answerListCacheKey = RedisUtil.getListCacheKey(
                        Long.valueOf(answerMap.get("uid").toString()),
                        Integer.parseInt(answerMap.get("model").toString()),
                        Integer.parseInt(answerMap.get("type").toString()));
                // 删除缓存,让下一次查询走数据库,并同步缓存
                redisTemplate.delete(answerListCacheKey);
            }
        }
    }
    //  手动确认应答
    ack.acknowledge();
}
/**
 * canal 同步数据到 kafka
 */
public static final String CANAL_TOPIC = "ai-cloud-canal-to-kafka";


/**
 * 数据库,缓存数据一致性的
 */

public static final String DATABASE_KEY = "database";

public static final String TABLE_KEY = "table";

public static final String DATA_KEY = "data";

public static final String TARGET_DATABASE = "aicloud";

public static final String TARGET_TABLE = "answer";

【补充】

kafka ui 下载地址:​​​​​​https://github.com/provectus/kafka-ui/tags

修改配置

kafka:
  clusters:
    - name: kafka3_cluster
      bootstrapServers: 127.0.0.1:9092

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/869824.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Github个人网站搭建详细教程【Github+Jekyll模板】

文章目录 前言一、介绍1 Github Pages是什么2 静态网站生成工具3 Jekyll简介Jekyll 和 GitHub 的关系 4 Mac系统Jekyll的安装及使用安装Jekyll的简单使用 二、快速搭建第一个Github Pages网站三、静态网站模板——Chirpy1 个人定制 四、WordPress迁移到Github参考资料 前言 23…

机器学习笔记——决策树

定义 决策树是一种可以用来解决回归和分类的问题的算法 决策树使用树形结构&#xff0c;通过叶子节点上的条件层层推理&#xff0c;得到最终的结果 例如&#xff1a;通过上面的简单决策&#xff0c;我们可以通过形状这一条件决策出水果属于哪一类。 决策树的学习结果和取什么规…

在Windows安装、部署Tomcat的方法

本文介绍在Windows操作系统中&#xff0c;下载、配置Tomcat的方法。 Tomcat是一个开源的Servlet容器&#xff0c;由Apache软件基金会的Jakarta项目开发和维护&#xff1b;其提供了执行Servlet和Java Server Pages&#xff08;JSP&#xff09;所需的所有功能。其中&#xff0c;S…

ROS配置并同时驱动多个UVC相机(含功能包)

配置并同时驱动多个UVC相机&#xff0c;并将数据保存为ROS话题形式的bag文件。 ROS可以同时驱动多个UVC相机。要实现这个目标并将数据保存成ROS话题的形式&#xff0c;再保存为bag文件&#xff0c;可以按照以下步骤操作&#xff1a; 1. 安装必要的包 sudo apt-get update sud…

环境搭建-Docker搭建ClickHouse

Docker搭建ClickHouse 一、前言二、ClickHouse安装2.1 拉取镜像运行ClickHouse服务 三、测试安装3.1 进入clickhouse容器3.2 命令补充说明 四、测试连接五、设置CK的用户名密码 一、前言 本文使用的Docker使用Windows搭建&#xff0c;Linux版本的搭建方式一样。 Windows系统搭…

【笔记:3D航路规划算法】二、RRT*

目录 RRT*于RRT的不同之处1、路径优化&#xff1a;2、成本计算&#xff1a;3、重连线步骤&#xff1a; 图解1、初始化2、路径搜索3、效果展示 总结 3D路径规划是在三维空间中寻找从起点到终点的最短或最优路径的一种技术。它广泛应用于无人机导航、机器人运动规划、虚拟现实等领…

前台文本直接取数据库值doFieldSQL插入SQL

实现功能&#xff1a;根据选择的车间主任带出角色。 实现步骤&#xff1a;OA的“字段联动”功能下拉选项带不出表“hrmrolemembers”&#xff0c;所以采用此方法。 doFieldSQL("select roleid from HrmResource as a inner join hrmrolemembers as b on a.id b.resource…

c++笔记2

目录 2.2 栈底&#xff08;bottom&#xff09; } 大数乘大数 节点&#xff1a;包含一个数据元素及若干指向子树分支的信息 。 节点的度&#xff1a;一个节点拥有子树的数目称为节点的度 。 叶子节点&#xff1a;也称为终端节点&#xff0c;没有子树的节点或者度为零的节点…

环信+亚马逊云科技服务:助力出海AI社交应用扬帆起航

随着大模型技术的飞速发展&#xff0c;AI智能体的社交体验得到了显著提升&#xff0c;AI社交类应用在全球范围内持续火热。尤其是年轻一代对新技术和新体验的热情&#xff0c;使得AI社交产品在海外市场迅速崛起。作为领先的即时通讯解决方案提供商&#xff0c;环信与亚马逊云科…

【Python】sqlite加密库pysqlcipher3编译安装步骤

目录 说明准备工作openssl编译sqlitetcl setup.py修改quote_argumentopenssl路径 安装加密示例代码测试附录参考 说明 pysqlcipher3是针对Python 3使用的pysqlcipher的一个分支&#xff0c; 尽管仍然维护对Python 2的支持。它仍然处于测试阶段&#xff0c; 尽管这个库包含的最…

uniapp集成安卓原生录屏插件以及使用

概述 我们知道UniApp的出现简化了开发者的工作流程&#xff0c;并减少了代码的重复编写。开发者可以使用一套代码编译到iOS、Android、以及各种小程序的应用&#xff0c;节省了人力和时间成本&#xff0c;但是涉及到与系统交互的时候&#xff0c;比如录屏、录音、录像、文件操…

实现Nginx的反向代理和负载均衡

一、反向代理和负载均衡简介 1.1、反向代理 反向代理(reverse proxy)指:以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给Internet上请求连接的客户端。此时代理服务器对外就表现为一个反向代理服务器。 反向代…

为什么idea建议使用“+”拼接字符串

今天在敲代码的时候&#xff0c;无意间看到这样一个提示&#xff1a; 英文不太好&#xff0c;先问问ChatGPT&#xff0c;这个啥意思&#xff1f; IDEA 提示你&#xff0c;可以将代码中的 StringBuilder 替换为简单的字符串连接方式。 提示信息中说明了使用 StringBuilder 进行…

【VSCode实战】Go插件依赖无法安装

使用VSCode来进行Go的开发有一段时间了&#xff0c;一直忍受着无法跳转定义的尴尬。正常情况下&#xff0c;VSCode安装Go插件后&#xff0c;应该就能具备跳转定义的功能&#xff0c;但因为一些未知的原因&#xff0c;换了工作环境&#xff0c;突然就不能用了。 以前在网上找到…

数据传输安全--IPSEC

目录 IPSEC IPSEC可以提供的安全服务 IPSEC 协议簇 两种工作模式 传输模式 隧道模式 两个通信保护协议&#xff08;两个安全协议&#xff09; AH&#xff08;鉴别头协议&#xff09; 可以提供的安全服务 报头 安全索引参数SPI 序列号 认证数据 AH保护范围 传输模…

Vue3时间选择器datetimerange在数据库存开始时间和结束时间

♥️作者&#xff1a;小宋1021 &#x1f935;‍♂️个人主页&#xff1a;小宋1021主页 ♥️坚持分析平时学习到的项目以及学习到的软件开发知识&#xff0c;和大家一起努力呀&#xff01;&#xff01;&#xff01; &#x1f388;&#x1f388;加油&#xff01; 加油&#xff01…

【AIGC】构建自己的谷歌搜索引擎服务并使用

一、谷歌 谷歌的搜索引擎需要自己创建服务才能启用检索api。&#xff08;需自行翻墙和创建自己的谷歌账号&#xff09; 1.1 API服务创建 1&#xff09;登陆https://console.cloud.google.com/: 2&#xff09; 选择新建项目&#xff0c;取号项目名即可&#xff08;比如:Olin…

基于JSP、java、Tomcat、mysql三层交互的项目实战--校园交易网(1)-项目搭建(前期准备工作)

这是项目的初始页面 接下来我先写下我的初始项目搭建 技术支持&#xff1a;JAVA、JSP 服务器&#xff1a;TOMCAT 7.0.86 编程软件&#xff1a;IntelliJ IDEA 2021.1.3 x64 首先我们打开页面&#xff0c;准备搭建项目的初始准备 1.New Project 2.随后点击Next&#xff0c;勾…

Vue3 + Vite 打包引入图片错误

1. 具体报错 报错信息 报错代码 2. 解决方法 改为import引入&#xff0c;注意src最好引用为符引入&#xff0c;不然docker部署的时候可能也会显示不了 <template><img :src"loginBg" alt""> </template><script langts setup> …

深入理解SQL中的INNER JOIN操作

本文介绍了INNER JOIN的定义、使用场景、计算方法及与其他JOIN的比较。INNER JOIN是关系数据库中常用的操作&#xff0c;用于返回两个表中匹配的行&#xff0c;只有在连接条件满足时才返回数据。本文详细解释了INNER JOIN的语法及其在一对多、多对多关系中的应用&#xff0c;通…