缓存数据同步技术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 {

    @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作为缓存数据同步,没有代码入侵,耦合低;

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

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

相关文章

谷粒商城第七天-商品服务之分类管理下的删除、新增以及修改商品分类

目录 一、总述 1.1 前端思路 1.2 后端思路 二、前端部分 2.1 删除功能 2.2 新增功能 2.3 修改功能 三、后端部分 3.1 删除接口 3.2 新增接口 3.3 修改接口 四、总结 一、总述 1.1 前端思路 删除和新增以及修改的前端无非就是点击按钮&#xff0c;就向后端发送请求…

7.事件类型

7.1鼠标事件 案例-轮播图点击切换 需求&#xff1a;当点击左右的按钮&#xff0c;可以切换轮播图 分析: ①右侧按钮点击&#xff0c;变量&#xff0c;如果大于等于8&#xff0c;则复原0 ②左侧按钮点击&#xff0c;变量–&#xff0c;如果小于0&#xff0c;则复原最后一张 ③鼠…

OpenCV实现高斯模糊加水印

# coding:utf-8 # Email: wangguisendonews.com # Time: 2023/4/21 10:07 # File: utils.pyimport cv2 import PIL from PIL import Image import numpy as np from watermarker.marker import add_mark, im_add_mark import matplotlib.pyplot as plt# PIL Image转换成OpenCV格…

【后端面经】微服务构架 (1-6) | 隔离:如何确保心悦会员体验无忧?唱响隔离的鸣奏曲!

文章目录 一、前置知识1、什么是隔离?2、为什么要隔离?3、怎么进行隔离?A) 机房隔离B) 实例隔离C) 分组隔离D) 连接池隔离 与 线程池隔离E) 信号量隔离F) 第三方依赖隔离二、面试环节1、面试准备2、基本思路3、亮点方案A) 慢任务隔离B) 制作库与线上库分离三、章节总结 …

windows环境安装elasticsearch+kibana并完成JAVA客户端查询

下载elasticsearch和kibana安装包 原文连接&#xff1a;https://juejin.cn/post/7261262567304298554 elasticsearch官网下载比较慢&#xff0c;有时还打不开&#xff0c;可以通过https://elasticsearch.cn/download/下载&#xff0c;先找到对应的版本&#xff0c;最好使用迅…

Python零基础入门(十)——模块与包

系列文章目录 个人简介&#xff1a;机电专业在读研究生&#xff0c;CSDN内容合伙人&#xff0c;博主个人首页 Python入门专栏&#xff1a;《Python入门》欢迎阅读&#xff0c;一起进步&#xff01;&#x1f31f;&#x1f31f;&#x1f31f; 码字不易&#xff0c;如果觉得文章不…

CAN通信的位定时与同步

位定时与同步 1.位时间 1.1相关基本概念 1&#xff09;系统时钟&#xff1a;记为 t c l k t_{clk} tclk​&#xff1b; 2&#xff09;CAN时钟周期&#xff1a;CAN时钟是由系统时钟分频而来的一个时间长度值&#xff0c;表示CAN控制器的工作时钟&#xff0c;实际上就是一个时…

某行动态cookie反爬虫分析

某行动态cookie反爬虫分析 1. 预览 反爬网址(base64): aHR0cDovL3d3dy5wYmMuZ292LmNu 反爬截图&#xff1a; 需要先加载运行js代码&#xff0c;可能是对环境进行检测&#xff0c;反调试之类的 无限debugger 处理办法 网上大部分人说的都是添加cookie来解决。 那个noscrip…

《Kubernetes故障篇:unable to retrieve OCI runtime error》

一、背景信息 1、环境信息如下&#xff1a; 操作系统K8S版本containerd版本Centos7.6v1.24.12v1.6.12 2、报错信息如下&#xff1a; Warning FailedCreatePodSandBox 106s (x39 over 10m) kubelet (combined from similar events): Failed to create pod sandbox: rpc error: …

【Docker 学习笔记】Windows Docker Desktop 安装

文章目录 一、前言二、Windows Docker 安装1. 基于Hyper-V后端和Windows容器的安装2. 基于WSL2后端的安装&#xff08;推荐&#xff09;3. 安装Docker Desktop on Windows4. 启动并验证Docker Desktop 一、前言 Docker并非是一个通用的容器工具&#xff0c;它依赖于已存在并运…

《零基础入门学习Python》第070讲:GUI的终极选择:Tkinter7

上节课我们介绍了Text组件的Indexs 索引和 Marks 标记&#xff0c;它们主要是用于定位&#xff0c;Marks 可以看做是特殊的 Indexs&#xff0c;但是它们又不是完全相同的&#xff0c;比如在默认情况下&#xff0c;你在Marks指定的位置中插入数据&#xff0c;Marks 的位置会自动…

SAMBA 文件分享相关 笔记

目标说明 在Linux 安装Samba&#xff0c;然后在Windows端映射为网络硬盘 流程 Linux 端命令 apt install samba -y 默认情况下软件会询问是否迁移系统网络设置以搭建协议&#xff0c;选择迁移即可修改配置文件 vim /etc/samba/smb.conf Samba 的配置文件中会带一个名为 prin…

[VRTK4.0]将Unity输入系统与VRTKv4结合使用

学习目标&#xff1a; 展示了如何在Unity项目中设置Unity输入系统&#xff0c;以及如何导入输入系统Tilia包以支持VRTKTilia包与新的Unity输入系统操作一起工作。 流程&#xff1a; 步骤一&#xff1a; 首先我们需要再次检查项目设置是否具有新的Unity输入系统。通过Project S…

JAVA SE -- 第十三天

&#xff08;全部来自“韩顺平教育”&#xff09; 集合 一、集合框架体系 集合主要是两组&#xff08;单列集合、双列集合&#xff09; Collection接口有两个重要的子接口List 、Set&#xff0c;它们的实现子类都是单列集合 Map接口的实现子类是双列集合&#xff0c;存放的…

Java019-1——面向对象的三大特性

一、封装性 将类的某些信息隐藏在类内部&#xff0c;不允许外部程序直接访问&#xff0c;而是通过该类提供的方法来实现对隐藏信息的操作和访问。&#xff08;这里说的信息就是类中的属性和方法&#xff09; 1.1、封装性的体现 想要通过代码体现封装性之前&#xff0c;需要先…

使用vscode+platformio搭建arduino开发环境

存在的问题&#xff1a; Arduino编译时会将所有的C文件都编译一遍造成编译很慢&#xff0c;一个简单的工程稍加修改有可能都需要三四分钟才能编译完成&#xff0c;同时arduino也不支持代码跳转查看功能&#xff0c;不方便代码查看。 解决方法&#xff1a; 使用vscodeplatfor…

【c++】类和对象

类和对象 面向过程和面向对象的初步认识 我们用军事为例&#xff0c;要完成一次作战&#xff0c;需要侦察、后勤保障、战略部署、战术部署...等等 面向过程&#xff1a; 更加关注过程&#xff0c;关注如何侦察&#xff08;无人机侦察、火力侦察、侦察小组侦察&#xff09;&…

vue 文件扩展名中 esm 、common 、global 以及 mini 、 dev 、prod 、runtime 的含义

vue 文件扩展名中 esm 、common 、global 以及 mini 、 dev 、prod 、runtime 的含义 vue.js 直接用在 script 标签中的完整版本&#xff08;同时包含编译器 compiler 和运行时 runtime&#xff09;&#xff0c;可以看到源码&#xff0c;适用于开发环境。 这个版本视图可以写在…

微服务的各种边界在架构演进中的作用

演进式架构 在微服务设计和实施的过程中&#xff0c;很多人认为&#xff1a;“将单体拆分成多少个微服务&#xff0c;是微服务的设计重点。”可事实真的是这样吗&#xff1f;其实并非如此&#xff01; Martin Fowler 在提出微服务时&#xff0c;他提到了微服务的一个重要特征—…

【Chat GPT】用 ChatGPT 运行 Python

前言 ChatGPT 是一个基于 GPT-2 模型的人工智能聊天机器人&#xff0c;它可以进行智能对话&#xff0c;同时还支持 Python 编程语言的运行&#xff0c;可以通过 API 接口进行调用。本文将介绍如何使用 ChatGPT 运行 Python 代码&#xff0c;并提供一个实际代码案例。 ChatGPT …