缓存数据同步技术Canal

说明:缓存数据同步,以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 {@Autowiredprivate 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 {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate UserService userService;private static final ObjectMapper MAPPER = new ObjectMapper();@Overridepublic void afterPropertiesSet() throws Exception {// 初始化缓存// 1.查询所有用户信息List<User> userList = userService.list();// 2.放入缓存for (User user : userList) {// 2.1.将user对象序列化为JSONString json = MAPPER.writeValueAsString(user);// 2.2.设置key前缀,存入redisredisTemplate.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)@Idprivate Integer id;@Column(name = "username")private String username;private String password;private String name;private Integer gender;private String image;@Transientprivate Integer job;private String entrydate;@Transientprivate 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 {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate UserService userService;private static final ObjectMapper MAPPER = new ObjectMapper();@Overridepublic void afterPropertiesSet() throws Exception {// 1.查询用户信息List<User> UserList = userService.list();// 2.放入缓存for (User User : UserList) {// 2.1.User序列化为JSONString json = MAPPER.writeValueAsString(User);// 2.2.设置key值,存入redisredisTemplate.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> {@Autowiredprivate RedisHandler redisHandler;@Autowiredprivate Cache<Long, User> userCache;@Overridepublic void insert(User user) {// 写数据到JVM进程缓存userCache.put(Convert.toLong(user.getId()), user);// 写数据到redisredisHandler.saveUser(user);}@Overridepublic void update(User before, User after) {// 写数据到JVM进程缓存userCache.put(Convert.toLong(after.getId()), after);// 写数据到redisredisHandler.saveUser(after);}@Overridepublic void delete(User user) {// 删除数据到JVM进程缓存userCache.invalidate(user.getId());// 删除数据到redisredisHandler.deleteUserById(Convert.toLong(user.getId()));}
}

第五步:启动测试

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

控制台报错了

在这里插入图片描述

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

在这里插入图片描述

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

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

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

在这里插入图片描述

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

在这里插入图片描述

总结

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

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

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

相关文章

Go Ethereum源码学习笔记 001 Geth Start

Go Ethereum源码学习笔记 前言[Chapter_001] 万物的起点: Geth Start什么是 geth&#xff1f;go-ethereum Codebase 结构 Geth Start前奏: Geth Consolegeth 节点是如何启动的NodeNode的关闭 Ethereum Backend附录 前言 首先读者需要具备Go语言基础&#xff0c;至少要通关菜鸟…

【wsl-windows子系统】安装、启用、禁用以及同时支持docker-desktop和vmware方案

如果你要用docker桌面版&#xff0c;很可能会用到wsl&#xff0c;如果没配置好&#xff0c;很可能wsl镜像会占用C盘很多空间。 前提用管理员身份执行 wsl-windows子系统安装和启用 pushd "%~dp0" dir /b %SystemRoot%\servicing\Packages\*Hyper-V*.mum >hyper…

06. 管理Docker容器数据

目录 1、前言 2、Docker实现数据管理的方式 2.1、数据卷&#xff08;Data Volumes&#xff09; 2.2、数据卷容器&#xff08;Data Volume Containers&#xff09; 3、简单示例 3.1、数据卷示例 3.2、数据卷容器示例 1、前言 在生产环境中使用 Docker&#xff0c;一方面…

211. 添加与搜索单词 - 数据结构设计---------------字典树

211. 添加与搜索单词 - 数据结构设计 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; 211. 添加与搜索单词 - 数据结构设计 https://leetcode.cn/problems/design-add-and-search-words-data-structure/descriptio…

Exadata磁盘损坏导致磁盘组无法mount恢复(oracle一体机磁盘组异常恢复)---惜分飞

Oracle Exadata客户,在换盘过程中,cell节点又一块磁盘损坏,导致datac1磁盘组&#xff08;该磁盘组是normal方式冗余)无法mount Thu Jul 20 22:01:21 2023 SQL> alter diskgroup datac1 mount force NOTE: cache registered group DATAC1 number1 incarn0x0728ad12 NOTE: ca…

【iOS】Frame与Bounds的区别详解

iOS的坐标系 iOS特有的坐标是&#xff0c;是在iOS坐标系的左上角为坐标原点&#xff0c;往右为X正方向&#xff0c;向下为Y正方向。 bounds和frame都是属于CGRect类型的结构体&#xff0c;系统的定义如下&#xff0c;包含一个CGPoint&#xff08;起点&#xff09;和一个CGSiz…

windows使用多账户Git,多远程仓库版本管理

1 清除全局配置 git config --global --list // 看一下是否配置过user.name 和 user.email git config --global --unset user.name // 清除全局用户名 git config --global --unset user.email // 清除全局邮箱 2 本地仓库&#xff0c;每个远程对应的本地仓库目录下执行 $…

求三个球面交点的高效解法

文章目录 一、问题描述二、推导步骤代数法几何法 三、MATLAB代码 一、问题描述 如图&#xff0c;已知三个球面的球心坐标分别为 P 1 ( x 1 , y 1 , z 1 ) , P 2 ( x 2 , y 2 , z 2 ) , P 3 ( x 3 , y 3 , z 3 ) P_1(x_1,y_1,z_1),P_2(x_2,y_2,z_2),P_3(x_3,y_3,z_3) P1​(x1​,…

idea项目依赖全部找不到

目录 1&#xff0c;出错现象2&#xff0c;解决3&#xff0c;其他尝试 1&#xff0c;出错现象 很久没打开的Java项目&#xff0c;打开之后大部分依赖都找不到&#xff0c;出现了所有的含有import语句的文件都会报错和一些注解报红报错&#xff0c;但pom文件中改依赖是确实被引入…

深度学习实践——循环神经网络实践

系列实验 深度学习实践——卷积神经网络实践&#xff1a;裂缝识别 深度学习实践——循环神经网络实践 深度学习实践——模型部署优化实践 深度学习实践——模型推理优化练习 代码可见于&#xff1a; 深度学习实践——循环神经网络实践 0 概况1 架构实现1.1 RNN架构1.1.1 RNN架…

管理类联考——写作——论说文——实战篇——标题篇

角度3——4种材料类型、4个立意对象、5种写作态度 老吕的“1342”&#xff0c;一个标题&#xff0c;三句开头&#xff0c;四层结构&#xff0c;两句结尾。 经过审题立意后&#xff0c;我们要根据我们的立意&#xff0c;确定一个主题&#xff0c;这个主题必须通过文章的标题直接…

【手撕】list

系列文章目录 文章目录 系列文章目录前言list_node<T>&#xff08;节点&#xff09;_list_iterator<T, Ref, Ptr>&#xff08;迭代器&#xff09;成员变量构造函数运算符重载 ReverseIterator<Iterator, Ref, Ptr>&#xff08;反向迭代器&#xff09;List<…

python+django+mysql项目实践一(环境准备)

python项目实践 环境说明: Pycharm 开发环境 Django 前端 MySQL 数据库 Navicat 数据库管理 创建Pycharm项目 安装Django 在pycharm文件—设置进行安装 新建Django项目 注意项目创建目录 项目默认目录文件说明: __init__.py asgi.py 【异步接受网络…

机器学习--课后作业--hw1

机器学习(课后作业–hw1) 本篇文章全文参考这篇blog 网上找了很多教程&#xff0c;这个是相对来说清楚的&#xff0c;代码可能是一模一样&#xff0c;只是进行了一些微调&#xff0c;但是一定要理解这个模型具体的处理方法&#xff0c;这个模型我认为最巧妙的它对于数据的处理…

Linux新手小程序——进度条

前言 目录 前言 需要先了解 1.\r和\n 2.缓冲区 一.理解字符的含义&#xff1a; 学习c语言时&#xff0c;我们可以粗略把字符分为可显字符和控制字符. 在按回车换到下一行开始的操作时&#xff0c;实际上是进行了两个操作&#xff1a;1.让光标跳到下一行&#xff08;只…

Spring注解开发,bean的作用范围及生命周期、Spring注解开发依赖注入

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaweb 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 Spring注解开发 一、注解开发定义Bean二、纯注解开发Bean三…

常见的几种排序

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C &#x1f525;座右铭&#xff1a;“不要等到什么都没有了&#xff0c;才下…

线程属性——线程分离应用

文章目录 相关函数初始化释放线程属性的资源获取线程分离的状态属性设置线程分离的状态属性获取线程的栈的大小线程分离应用 相关函数 可以通过man pthread_attr_然后按两次table键查询和属性相关的函数 初始化 释放线程属性的资源 获取线程分离的状态属性 设置线程分离的状…

RISCV - 4 ISA 扩展名命名约定

RISCV - 4 ISA 扩展名命名约定 1 Case Sensitivity2 Base Integer ISA3 Instruction-Set Extension Names4 Version Numbers5 Underscores6 Additional Standard Extension Names7 Supervisor-level Instruction-Set Extensions8 Hypervisor-level Instruction-Set Extensions9…

MD-MTSP:成长优化算法GO求解多仓库多旅行商问题MATLAB(可更改数据集,旅行商的数量和起点)

一、成长优化算法GO 成长优化算法&#xff08;Growth Optimizer&#xff0c;GO&#xff09;由Qingke Zhang等人于2023年提出&#xff0c;该算法的设计灵感来源于个人在成长过程中的学习和反思机制。学习是个人通过从外部世界获取知识而成长的过程&#xff0c;反思是检查个体自…