缓存数据同步技术Canal

news2025/1/22 9:07:35

说明:缓存数据同步,以Redis为例,如何保证从Redis中取出来的数据与MySQL中的一致?在微服务架构下,通常可以用以下两种技术来实现:

  • MQ:在修改数据的同时,发送一个消息修改缓存;

在这里插入图片描述

  • Canal:监听数据库,数据库发生改变时,同步更新缓存;

在这里插入图片描述

本文介绍Canal的实现,以下操作均在云服务上,操作系统是CentOS

设置MySQL主从

Canal是基于MySQL的主从同步功能,使用前需要先开启MySQL的主从功能,操作如下:

第一步:开启binlog

找到MySQL容器所挂载的日志文件,添加以下内容:

log-bin=/var/lib/mysql/mysql-bin
binlog-do-db=cache

log-bin=/var/lib/mysql/mysql-bin:指定库记录binary log events,取名为cache;

binlog-do-db=cache:设置binary log文件的存放地址和文件名;

在这里插入图片描述

查看mysql容器所挂载的数据卷可使用下面这个命令

docker volume inspect 数据卷名

如果不知道数据卷名,可停掉mysql容器,并删掉。在/tmp目录下创建一个mysql目录,并进入到mysql目录下,执行下面的命令启动mysql容器;

# 进入/tmp目录
cd /tmp
# 创建文件夹
mkdir mysql
# 进入mysql目录
cd mysql
# 启动mysql容器,设置密码为123456
docker run \
 -p 3306:3306 \
 --name mysql \
 -v $PWD/conf:/etc/mysql/conf.d \
 -v $PWD/logs:/logs \
 -v $PWD/data:/var/lib/mysql \
 -e MYSQL_ROOT_PASSWORD=123456 \
 --privileged \
 -d \
 mysql:5.7.25

第二步:设置用户权限

进入mysql命令行模式,如使用navicat或者使用CMD连接MySQL,敲以下命令(建议一行一行敲):

create user canal@'%' IDENTIFIED by 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%' identified by 'canal';
FLUSH PRIVILEGES;

在这里插入图片描述

第三部:重启容器

重启mysql容器

在这里插入图片描述

回到mysql命令行,敲下面的命令

show master status;

出现下面的内容,表示设置完成(如果报错了,可能是有延迟,可以等下再敲命令重试)

在这里插入图片描述

环境搭建

第一步:安装Canal

拉取Canal镜像,拉取前应该先去docker官方仓库查看可提供的版本号

docker pull canal:版本号

如果网络状态差()的话不推荐拉取,可使用本地加载的方式;

在这里插入图片描述

加载完成

在这里插入图片描述

第二步:创建网络

canal容器需要和mysql容器关联,创建一个网络,取名demo;

docker network create demo

把mysql容器加入到这个网络中;

docker network connect demo mysql

在这里插入图片描述

第三步:启动canal

输入下面的命令,启动canal容器,需要注意相关名称;

docker run -p 11111:11111 --name canal \
-e canal.destinations=cache \
-e canal.instance.master.address=mysql:3306  \
-e canal.instance.dbUsername=canal  \
-e canal.instance.dbPassword=canal  \
-e canal.instance.connectionCharset=UTF-8 \
-e canal.instance.tsdb.enable=true \
-e canal.instance.gtidon=false  \
-e canal.instance.filter.regex=db_user\\..* \
--network demo \
-d canal/canal-server:v1.1.5

canal.destinations=cache \:binlog-do-db的名称;

canal.instance.master.address=mysql:3306:数据库名称和端口;

--network demo:上面创建的网络名称;

在这里插入图片描述

代码实现

本项目基于Redis缓存预热(参考:http://t.csdn.cn/rZ4En),是一个很简单的项目,部分代码如下:

controller层代码:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 查询所有用户信息
     * @return
     */
    @GetMapping("list")
    public List<User> getUsers() {
        return userService.list();
    }

    /**
     * 根据ID查询用户信息
     * @param id
     * @return
     */
    @GetMapping("{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.getById(id);
    }

    /**
     * 根据ID删除用户
     * @param id
     */
    @DeleteMapping("{id}")
    public void deleteUserById(@PathVariable Long id){
        userService.removeById(id);
    }
}

Redis缓存预热代码

@Component
public class RedisHandler implements InitializingBean {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private UserService userService;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    @Override
    public void afterPropertiesSet() throws Exception {
        // 初始化缓存
        // 1.查询所有用户信息
        List<User> userList = userService.list();

        // 2.放入缓存
        for (User user : userList) {

            // 2.1.将user对象序列化为JSON
            String json = MAPPER.writeValueAsString(user);

            // 2.2.设置key前缀,存入redis
            redisTemplate.opsForValue().set("user:id:" + user.getId(), json);
        }
    }
}

因为使用了Redis缓存预热,在项目启动时会使用全查方法,将所有用户的数据存入到redis中;

在这里插入图片描述

第一步:引入依赖

先引入canal的依赖

	<dependency>
	    <groupId>top.javatool</groupId>
	    <artifactId>canal-spring-boot-starter</artifactId>
	    <version>1.2.1-RELEASE</version>
	</dependency>

添加相关配置

canal:
  destination: cache #binlog-do-db的名称;
  server: 服务器IP:11111

第二步:修改实体类

修改User类,添加一些注解(@Id注解、@Colume注解、@Transient注解),分别用于表示主键,关键字段名,容易发生变动的字段;

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_user")
public class User implements Serializable {

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

    @Column(name = "username")
    private String username;

    private String password;

    private String name;

    private Integer gender;

    private String image;

    @Transient
    private Integer job;

    private String entrydate;

    @Transient
    private Integer deptId;

    private String createTime;

    private String updateTime;

    @TableLogic(value = "1", delval = "0")
    private Integer isDel;
}

第三步:修改Redis缓存代码

修改RedisHandler代码如下,添加新增、删除相关的方法;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hzy.pojo.User;
import com.hzy.service.UserService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class RedisHandler implements InitializingBean {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private UserService userService;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    @Override
    public void afterPropertiesSet() throws Exception {
        // 1.查询用户信息
        List<User> UserList = userService.list();
        
        // 2.放入缓存
        for (User User : UserList) {
            
            // 2.1.User序列化为JSON
            String json = MAPPER.writeValueAsString(User);
            
            // 2.2.设置key值,存入redis
            redisTemplate.opsForValue().set("user:id:" + User.getId(), json);
        }
    }

    public void saveUser(User User) {
        try {
            String json = MAPPER.writeValueAsString(User);
            redisTemplate.opsForValue().set("user:id:" + User.getId(), json);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public void deleteUserById(Long id) {
        redisTemplate.delete("user:id:" + id);
    }
}

第四步:编写监听器类

写一个监听器类,当数据库中的数据发生变化时,会修改Redis中的缓存数据;

import cn.hutool.core.convert.Convert;
import com.github.benmanes.caffeine.cache.Cache;
import com.hzy.config.RedisHandler;
import com.hzy.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.javatool.canal.client.annotation.CanalTable;
import top.javatool.canal.client.handler.EntryHandler;

@CanalTable("tb_user")
@Component
public class UserHandler implements EntryHandler<User> {

    @Autowired
    private RedisHandler redisHandler;

    @Autowired
    private Cache<Long, User> userCache;

    @Override
    public void insert(User user) {
        // 写数据到JVM进程缓存
        userCache.put(Convert.toLong(user.getId()), user);
        // 写数据到redis
        redisHandler.saveUser(user);
    }

    @Override
    public void update(User before, User after) {
        // 写数据到JVM进程缓存
        userCache.put(Convert.toLong(after.getId()), after);
        // 写数据到redis
        redisHandler.saveUser(after);
    }

    @Override
    public void delete(User user) {
        // 删除数据到JVM进程缓存
        userCache.invalidate(user.getId());
        // 删除数据到redis
        redisHandler.deleteUserById(Convert.toLong(user.getId()));
    }
}

第五步:启动测试

项目启动后,会发现控制台在实时打印检测状态

在这里插入图片描述

让我们看下Redis中的数据,因为有Redis缓存预热,项目启动就会有所有用户的数据;

在这里插入图片描述

此时,让我们删掉一条用户信息,再查看Redis中的缓存数据,看有没有更新;

在这里插入图片描述

控制台报错了

在这里插入图片描述

百度了说是Druid连接池的问题,我试了下也没有解决

在这里插入图片描述

总之,以上就是缓存数据同步技术Canal的实现,我使用VM测试过,是可以跑通的,可能是云服务器带宽的原因或者是身份验证的原因,导致没有跑通。

另外说一句,即便删除了用户,在Redis缓存那边也还是有用户信息的,因为在User类中设置了“逻辑删除”的字段,所以并不会真的删除用户,但是Redis缓存中对应用户的逻辑删除字段应该是会发生改变的。

删除用户,数据库中对应用户的逻辑删除字段设置为0;

在这里插入图片描述

正常的话,Redis缓存中的该用户信息,逻辑删除字段的值应该要同步修改;

在这里插入图片描述

总结

使用Canal作为缓存数据同步,没有代码入侵,耦合低;

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot月度员工绩效考核管理系统【附任务书|ppt|万字文档(LW)和搭建文档】

主要功能 员工登录&#xff1a; ①首页、个人中心&#xff1a;修改密码、个人信息管理等 ②公告信息管理、绩效指标管理、绩效考核管理 管理员登录&#xff1a; ①首页、个人中心&#xff1a;修改密码、个人信息管理等 ②公告信息管理、部门管理、岗位管理、员工管理、绩效指标…

神奇数学世界的魔力迷踪:破解3的幂次方之谜

本篇博客会讲解力扣“326. 3 的幂”的解题思路&#xff0c;这是题目链接。 昨天刚刚讲解完2的幂&#xff0c;今天就来看看3的幂。 思路1 3的幂不能像2的幂那样&#xff0c;直接看二进制中是否有且仅有一位为1&#xff0c;所以“2的幂”那道题中的前两种方法就失效了&#xff…

《MySQL 实战 45 讲》课程学习笔记(一)

基础架构&#xff1a;一条 SQL 查询语句是如何执行的&#xff1f; MySQL 的基本架构 MySQL 可以分为 Server 层和存储引擎层两部分。 Server 层 包括连接器、查询缓存、分析器、优化器、执行器&#xff1b;涵盖 MySQL 的大多数核心服务功能&#xff0c;以及所有的内置函数&…

【雕爷学编程】MicroPython动手做(18)——掌控板之声光传感器2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

YOLOv5改进最新ICCV2023顶会LSKNet:大选择性卷积核的领域首次探索,助力小目标检测

YOLOv5改进最新ICCV2023顶会LSKNet:大选择性卷积核的领域首次探索,助力小目标检测 一、论文总结特征融合策略(即空间选择机制)二、代码部分,将LSKNet结构加入到YOLOv5中。论文:https://arxiv.org/pdf/2303.09030.pdf 代码https://github.com/zcablii/LSKNet/blob/main/mm…

【屏幕适配发展介绍 Objective-C语言】

一、接下来,我们花一天时间,给大家介绍这个屏幕适配 1.那么,屏幕适配,是什么意思啊 我们说,写程序的时候,我们有时候要做 1)系统适配 2)屏幕适配 1)系统适配:是指的你写的这个代码,在iOS6、iOS7、iOS8,在不同的iOS系统下,是不是运行的效果,一致吧 这个指的是…

【读书笔记】《太白金星有点烦》

哦吼&#xff01;这次开了一本轻松愉悦的书。 太白金星和观音分属于两个不同的部门&#xff0c;也有不同的领导&#xff0c;为了完成九九八十一难的策划而暂时合作。观音开始并瞧不上这个老头&#xff0c;对他极度欺瞒&#xff0c;但老头也不是任人揉捏的软柿子&#xff0c;给…

【雕爷学编程】MicroPython动手做(17)——掌控板之触摸引脚

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

Tailwind CSS:基础使用/vue3+ts+Tailwind

一、理解Tailwind 安装 - TailwindCSS中文文档 | TailwindCSS中文网 Installation - Tailwind CSS 1.1、词义 我们简单理解就是搭上CSS的顺风车&#xff0c;事半功倍。 1.2、Tailwind CSS有以下优势 1. 快速开发&#xff1a;Tailwind CSS 提供了一些现成的 class / 可复用…

Redis如何实现排行榜?

今天给大家简单聊聊 Redis Sorted Set 数据类型底层的实现原理和游戏排行榜实战。特别简单&#xff0c;一点也不深入&#xff0c;也就 7 张图&#xff0c;粉丝可放心食用&#xff0c;哈哈哈哈哈~~~~。 1. 是什么 Sorted Sets 与 Sets 类似&#xff0c;是一种集合类型&#xff…

C++笔记之迭代器失效问题处理

C笔记之迭代器失效问题处理 code review! 参考博文&#xff1a;CSTL迭代器失效的几种情况总结 文章目录 C笔记之迭代器失效问题处理一.使用返回新迭代器的插入和删除操作二.对std::vector 来说&#xff0c;擦除&#xff08;erase&#xff09;元素会导致迭代器失效 一.使用返回…

PM2.5传感器(PMS5003)STM32代码

PM2.5传感器型号&#xff1a;PMS5003 PMS5003简介如下&#xff1a; 详情&#xff1a;PMS5003资料链接 PM2.5传感器代码下载&#xff0c;本人所写&#xff0c;亲测有效&#xff0c;基于STM32F407(其他STM32型号皆可移植&#xff0c;只需修改UART参数即可),UART打印数据

SpringBoot —程序包org.springframework.boot.test.context不存在

一. 遇到问题 &#xff1a;程序包org.springframework.boot.test.context不存在 发生错误的原因是项目中缺少spring-boot-starter-test依赖导致的&#xff0c;解决方案如下: 在项目根目录的pom.xm文件中的<dependencies>节点下增加以下依赖即可&#xff1a; <depen…

未来十年最确定的事

变量&#xff08;比如人工智能&#xff09;增加后&#xff0c;世界变成一个超复杂的系统&#xff0c;我们甚至不知道未来十年是战争还是和平&#xff0c;是增长还是震荡。但有一个事却百分百确定&#xff1a;硅基智能注定崛起&#xff0c;然后在生产、生活等各个环节反复和碳基…

Spring Boot实践四 --集中式缓存Redis

随着时间的积累&#xff0c;应用的使用用户不断增加&#xff0c;数据规模也越来越大&#xff0c;往往数据库查询操作会成为影响用户使用体验的瓶颈&#xff0c;此时使用缓存往往是解决这一问题非常好的手段之一。Spring 3开始提供了强大的基于注解的缓存支持&#xff0c;可以通…

xcode中如何显示文件后缀

xcode14.3 用不惯mac电脑真恶心&#xff0c;改个显示文件后缀找半天 1、首先双击打开xcode软件 2、此时&#xff0c;电脑左上角出现xcode字样(左上角如果看不到xcode字样&#xff0c;再次点击xcode软件弹出来就有了)&#xff0c;鼠标右键它&#xff0c;点击setting或者Prefere…

算法通过村第二关-链表白银笔记|指定区间反转

文章目录 前言链表反转|指定区间内头插法&#xff1a;穿针引线法&#xff1a; 总结 前言 提示&#xff1a;人啊&#xff0c;果然跟花一样&#xff0c;开花前的等待无比漫长&#xff0c;绽放的魅力却转瞬即逝。 链表反转|指定区间内 参考题目&#xff1a;92. 反转链表 II - 力…

FreeRTOS学习之路,以STM32F103C8T6为实验MCU(2-1:任务)

学习之路主要为FreeRTOS操作系统在STM32F103&#xff08;STM32F103C8T6&#xff09;上的运用&#xff0c;采用的是标准库编程的方式&#xff0c;使用的IDE为KEIL5。 注意&#xff01;&#xff01;&#xff01;本学习之路可以通过购买STM32最小系统板以及部分配件的方式进行学习…

1.2 网络安全法律法规

数据参考&#xff1a;CISP官方 目录 国家立法体系网络安全法解析网络安全相关法律 一、国家立法体系 1、我国的立法体系 我国的立法体系在网络空间治理中扮演着基础工作的角色。为了应对快速发展的网络技术和威胁&#xff0c;我国采取了多级立法机制来完善网络空间的法律…

在OK3588板卡上部署模型实现OCR应用

一、主机模型转换 我们依旧采用FastDeploy来部署应用深度学习模型到OK3588板卡上 进入主机Ubuntu的虚拟环境 conda activate ok3588 安装rknn-toolkit2&#xff08;该工具不能在OK3588板卡上完成模型转换&#xff09; git clone https://github.com/rockchip-linux/rknn-to…