【Canal 中间件】Canal 实现 MySQL 增量数据的异步缓存更新

news2024/11/5 16:34:51

文章目录

    • 一、安装 MySQL
      • 1.1 启动 mysql 服务器
      • 1.2 开启 Binlog 写入功能
        • 1.2.1创建 binlog 配置文件
        • 1.2.2 修改配置文件权限
        • 1.2.3 挂载配置文件
        • 1.2.4 检测 binlog 配置是否成功
      • 1.3 创建账户并授权
    • 二、安装 RocketMQ
      • 2.1 创建容器共享网络
      • 2.2 启动 NameServer
      • 2.3 启动 Broker
      • 2.4 启动 rocketmq-console
    • 三、安装 canal
      • 3.1 启动容器
      • 3.2 查看日志
    • 四、安装 Redis
      • 4.1 启动 Redis
      • 4.2 使用 Another Redis Desktop Manager 客户端
    • 五、实现客户端代码
      • 5.1 导入依赖
      • 5.2 配置 application.yaml
      • 5.3 实现Canal同步服务代码
        • 5.3.1 Canal同步服务接口
        • 5.3.2 抽象Canal-RocketMQ通用处理服务
        • 5.3.3 具体类的同步服务实现
      • 5.4 实体类
      • 5.5 RocketMQ 消费者
      • 5.6 后续优化方案
    • 六、测试客户端代码
      • 6.1 创建数据库及表
      • 6.2 插入数据
    • 参考资料

一、安装 MySQL

QuickStart · alibaba/canal Wiki (github.com)

1.1 启动 mysql 服务器

docker run --name mysql-canal ^
-p 3306:3306 ^
-e MYSQL_ROOT_PASSWORD=root ^
-d mysql:5.7.36

1.2 开启 Binlog 写入功能

对于自建 MySQL容器 , 我们需要开启 Binlog 写入功能。

1.2.1创建 binlog 配置文件

在宿主机上创建 my.cnf 文件,配置 binlog-format 为 ROW 模式。my.cnf 的配置内容如下:

[mysqld]
# 开启 binlog
log-bin=mysql-bin 
# 选择 ROW 模式
binlog-format=ROW 
# 配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复
server_id=1 
1.2.2 修改配置文件权限

进入 MySQL 容器并修改 MySQL 容器配置文件 /etc/mysql/my.cnf 权限,以避免权限警告:

# 进入 MySQL 容器
$ docker exec -it mysql-canal bash

# 修改文件权限
$ chmod 644 /etc/mysql/my.cnf
$ exit

注意,在没有修改配置文件并启动 MySQL 容器情况下,MySQL 会警告配置文件 /etc/mysql/my.cnf 权限设置不当,允许所有用户写入(world-writable)。由于安全原因,MySQL 会忽略这个配置文件

[Warning] World-writable config file '/etc/mysql/my.cnf' is ignored.
1.2.3 挂载配置文件

在 MySQL 容器运行后,使用以下命令将创建的 my.cnf 文件覆盖容器内的 /etc/mysql/my.cnf

# 覆盖配置文件
$ docker cp D:\Learning\java-demos\middleware-demos\spring-boot-canal\src\main\resources\conf\my.cnf mysql-canal:/etc/mysql/

# 为了使新的配置生效,重启 MySQL 容器
$ docker restart mysql-canal

注意,MySQL 容器的 /etc/mysql/my.cnf 是一个符号链接,直接指定完整路径时会导致问题。

MySQL 启动时会首先加载主配置文件 /etc/mysql/my.cnf,然后加载 conf.d 目录下的所有配置文件。

1.2.4 检测 binlog 配置是否成功

进入 MySQL, 利用 show variables like 'log_bin'; 查看是否打开 binlog 模式:

$ docker exec -it mysql-canal bash

# 查看挂载后的 my.cnf 文件
$ tail /etc/mysql/my.cnf

# 查看 binlog 是否开启
$ mysql -uroot -proot
mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | ON    |
+---------------+-------+
1 row in set (0.01 sec)

# 查看 binlog 日志文件列表
mysql> show binary logs;

# 查看正在写入的 binlog 文件
mysql> show master status;

# 查看 Binlog 文件内容
mysql> mysqlbinlog /var/lib/mysql/mysql-bin.000001


1.3 创建账户并授权

授权 canal 链接 MySQL 账号具有作为 MySQL slave 的权限, 如果已有账户可直接 grant

# 进入 mysql 容器
$ docker exec -it mysql-canal mysql -uroot -proot

# 创建用户名和密码都为 canal 的账户
mysql> CREATE USER canal IDENTIFIED BY 'canal';

# 授予权限 GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
mysql> GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;

二、安装 RocketMQ

2.1 创建容器共享网络

RocketMQ 中有多个服务,需要创建多个容器,创建 docker 网络便于容器间相互通信。

$ docker network create rocketmq

2.2 启动 NameServer

在 Docker 容器中运行 RocketMQ 的 NameServer 服务:

# 拉取RocketMQ镜像
$ docker pull apache/rocketmq:5.3.0

# 启动 NameServer
$ docker run -d ^
-p 9876:9876  ^
--name rmqnamesrv-canal ^
--network rocketmq ^
apache/rocketmq:5.3.0 sh mqnamesrv

# 验证 NameServer 是否启动成功
$ docker logs -f rmqnamesrv

2.3 启动 Broker

Brocker 部署相对麻烦一点,主要是在系统里面创建一个配置文件。然后,通过 docker 的 -v 参数使用 volume 功能,将本地配置文件映射到容器内的配置文件上。否则所有数据都默认保存在容器运行时的内存中,重启之后就又回到最初的起点。

(1) 新建配置文件 broker.conf

创建配置文件 broker.conf, 放到指定的目录 D:\Learning\java-demos\middleware-demos\spring-boot-canal-redis\src\main\resources\rocketmq\conf

brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
brokerIP1 = 10.8.12.174 # 此处为本地ip, 如果部署服务器, 需要填写服务器外网ip

注意,如果其他容器和本地客户端都要与该容器通信,不要使用 localhost 和 host.docker.internal ,可以使用 ipconfig 查看当前宿主机的 IP 地址。

(2) 启动容器

# 启动 Broker 和 Proxy
$ docker run -d ^
--name rmqbroker-canal ^
--network rocketmq ^
-p 10912:10912 -p 10911:10911 -p 10909:10909 ^
-p 8080:8080 -p 8081:8081 ^
-e "NAMESRV_ADDR=rmqnamesrv-canal:9876" ^
-e "JAVA_OPTS=-Duser.home=/opt" ^
-e "JAVA_OPT_EXT=-server -Xms512m -Xmx512m" ^
-v D:\Learning\java-demos\middleware-demos\spring-boot-canal-redis\src\main\resources\rocketmq\conf\broker.conf:/home/rocketmq/rocketmq-5.3.0/conf/broker.conf ^
apache/rocketmq:5.3.0 sh mqbroker --enable-proxy ^
-c /home/rocketmq/rocketmq-5.3.0/conf/broker.conf

# 验证 Broker 是否启动成功
$ docker exec -it rmqbroker-canal bash -c "tail -n 10 /home/rocketmq/logs/rocketmqlogs/proxy.log"

(3)创建 Topic

进入 broker 容器,通过 mqadmin 创建 Topic。

# 进入名为rmqbroker的容器,并启动一个交互式的Bash shell
$ docker exec -it rmqbroker-canal bash 

# 在容器内部使用 mqadmin 工具创建名为 TestTopic 的主题配置
$ sh mqadmin updatetopic -t canal-test-topic -c DefaultCluster

2.4 启动 rocketmq-console

启动容器

docker run -d ^
--name rmqconsole-test ^
--network rocketmq ^
--link rmqnamesrv-canal:namesrv ^
-e "JAVA_OPTS=-Drocketmq.config.namesrvAddr=namesrv:9876 -Drocketmq.config.isVIPChannel=false" ^
-p 8088:8080 ^
-t pangliang/rocketmq-console-ng

运行成功,稍等几秒启动时间,浏览器输入 http://localhost:8088 查看。

三、安装 canal

3.1 启动容器

# 拉取 Canal Server 的 Docker 镜像
$ docker pull canal/canal-server:v1.1.7

# 启动 Canal Server 容器
$ docker run -d ^
  --name canal-server ^
  --restart always ^
  -p 11111:11111 ^
  --privileged=true ^
  -e canal.destinations=test ^
  -e canal.serverMode=rocketMQ ^
  -e rocketmq.producer.group=my-producer_canal-test-topic ^
  -e rocketmq.namesrv.addr=host.docker.internal:9876 ^
  -e canal.instance.master.address=host.docker.internal:3306 ^
  -e canal.instance.filter.regex=test_db.users,.*\\..* ^
  -e canal.mq.topic=canal-test-topic ^
  -m 4096m ^
  canal/canal-server:v1.1.7

3.2 查看日志

在 canal 启动成功后,查看启动日志:

$ docker logs canal-server
2024-10-28 21:29:00 DOCKER_DEPLOY_TYPE=VM
2024-10-28 21:29:00 ==> INIT /alidata/init/02init-sshd.sh
2024-10-28 21:29:00 ==> EXIT CODE: 0
2024-10-28 21:29:00 ==> INIT /alidata/init/fix-hosts.py
2024-10-28 21:29:00 ==> EXIT CODE: 0
2024-10-28 21:29:00 ==> INIT DEFAULT
2024-10-28 21:29:00 ==> INIT DONE
2024-10-28 21:29:00 ==> RUN /home/admin/app.sh
2024-10-28 21:29:01 ==> START ...
2024-10-28 21:29:01 start canal ...
2024-10-28 21:29:00 Failed to get D-Bus connection: Operation not permitted
2024-10-28 21:29:00 Failed to get D-Bus connection: Operation not permitted
2024-10-28 21:29:36 start canal successful
2024-10-28 21:29:36 ==> START SUCCESSFUL ...

看到 successful 之后,就代表 canal-server 启动成功,然后就可以在 canal-admin 上进行任务分配了。

四、安装 Redis

4.1 启动 Redis

docker run ^
--restart=always ^
-p 6379:6379 ^
--name redis-canal ^
-d redis:latest  --requirepass 123456

4.2 使用 Another Redis Desktop Manager 客户端

在 github 上面下载 nother Redis Desktop Manager 客户端,并连接到 redis-canal。

在这里插入图片描述

五、实现客户端代码

5.1 导入依赖

创建 Spring Boot 项目,并导入以下依赖。

<dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.client</artifactId>
    <version>1.1.7</version>
</dependency>

<!-- Message、CanalEntry.Entry等来自此安装包 -->
<dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.protocol</artifactId>
    <version>1.1.7</version>
</dependency>

<dependency>
    <groupId>org.rocketmq.spring.boot</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.3.0</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

5.2 配置 application.yaml

application.yaml 的内容如下:

spring:
  application:
    name: spring-boot-canal-redis
  datasource:
    url: jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  data:
    redis:
      host: localhost
      port: 6379
      database: 0
      password: "123456"
      lettuce:
        pool:
          max-active: 8
          max-idle: 8
          min-idle: 0
          max-wait: -1ms

rocketmq:
  name-server: localhost:9876
  producer:
    group: my-producer_canal-test-topic
    send-message-timeout: 60000
    retry-times-when-send-failed: 2
    retry-times-when-send-async-failed: 2
#  consumer:
#    group: my-consumer_canal-test-topic

server:
  port: 8089

5.3 实现Canal同步服务代码

5.3.1 Canal同步服务接口
/**
 *  Canal同步服务接口,用于处理来自Canal的数据同步请求
 *  该接口主要定义了如何处理数据变更事件,包括DDL语句执行和DML操作(插入、更新、删除)
 *
 * @author zouhu
 * @data 2024-10-31 15:16
 */
public interface CanalSyncService<T> {
    /**
     * 处理数据变更事件
     * <p>
     *     该方法用于处理来自Canal的数据变更事件,包括DDL语句执行和其他数据操作(如插入、更新和删除)
     * </p>
     *
     * @param flatMessage CanalMQ数据
     */
    void process(FlatMessage flatMessage);

    /**
     * DDL语句处理
     *
     * @param flatMessage CanalMQ数据
     */
    void ddl(FlatMessage flatMessage);

    /**
     * 插入
     *
     * @param list 新增数据
     */
    void insert(Collection<T> list);

    /**
     * 更新
     *
     * @param list 更新数据
     */
    void update(Collection<T> list);

    /**
     * 删除
     *
     * @param list 删除数据
     */
    void delete(Collection<T> list);
}

5.3.2 抽象Canal-RocketMQ通用处理服务
/**
 * 抽象Canal-RocketMQ通用处理服务
 *
 *
 * @author zouhu
 * @data 2024-10-31 15:21
 */
@Slf4j
@RequiredArgsConstructor
public abstract class AbstractCanalRocketMqRedisService<T> implements CanalSyncService<T> {

    private final RedisTemplate<String, Object> redisTemplate;

    private Class<T> classCache;


    /**
     * 获取Model名称
     *
     * @return Model名称
     */
    protected abstract String getModelName();

    /**
     * 处理数据
     * <p>
     *     后续优化:可以使用策略模式来封装不同表的操作,不一定要统一
     * </p>
     *
     * @param flatMessage CanalMQ数据
     */
    @Override
    public void process(FlatMessage flatMessage) {

        if (flatMessage.getIsDdl()) {
            ddl(flatMessage);
            return;
        }

        Set<T> data = getData(flatMessage);

        if (SqlType.INSERT.getType().equals(flatMessage.getType())) {
            insert(data);
        }

        if (SqlType.UPDATE.getType().equals(flatMessage.getType())) {
            update(data);
        }

        if (SqlType.DELETE.getType().equals(flatMessage.getType())) {
            delete(data);
        }

    }

    /**
     * DDL语句处理
     *
     * @param flatMessage CanalMQ数据
     */
    @Override
    public void ddl(FlatMessage flatMessage) {
        //TODO : DDL需要同步,删库清空,更新字段处理
    }

    /**
     * 插入
     *
     * @param list 新增数据
     */
    @Override
    public void insert(Collection<T> list) {
        insertOrUpdate(list);
    }

    /**
     * 更新
     *
     * @param list 更新数据
     */
    @Override
    public void update(Collection<T> list) {
        insertOrUpdate(list);
    }

    /**
     * 删除
     *
     * @param list 删除数据
     */
    @Override
    public void delete(Collection<T> list) {
        Set<String> keys = Sets.newHashSetWithExpectedSize(list.size());

        for (T data : list) {
            keys.add(getWrapRedisKey(data));
        }

        redisTemplate.delete(keys);
    }

    /**
     * 插入或者更新redis
     * <p>
     *     data 对象里面还包含 getTypeArgument()的返回值,但是没有写到 Redis 里面
     * </p>
     *
     * @param list 数据
     */
    private void insertOrUpdate(Collection<T> list) {
        for (T data : list) {
            log.info("redis data:{}", data);
            String key = getWrapRedisKey(data);
            log.info("redis key:{}", key);
            redisTemplate.opsForValue().set(key, data);
        }
    }

    /**
     * 封装redis的key
     *
     * @param t 原对象
     * @return key
     */
    protected String getWrapRedisKey(T t) {
        return getModelName() + ":" + getIdValue(t);
    }

    /**
     * 获取类泛型
     *
     * @return 泛型Class
     */
    @SuppressWarnings("unchecked")
    protected Class<T> getTypeArgument() {
        if (classCache == null) {
            classCache = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        }
        return classCache;
    }

    /**
     * 获取 Object 标有 @TableId 注解的字段值
     *
     * @param t 对象
     * @return id值
     */
    protected Object getIdValue(T t) {
        Field fieldOfId = getIdField();
        ReflectionUtils.makeAccessible(fieldOfId);
        return ReflectionUtils.getField(fieldOfId, t);
    }

    /**
     * 获取Class标有@TableId注解的字段名称
     *
     * @return id字段名称
     */
    protected Field getIdField() {
        Class<T> clz = getTypeArgument();
        Field[] fields = clz.getDeclaredFields();
        for (Field field : fields) {
            TableId annotation = field.getAnnotation(TableId.class);

            if (annotation != null) {
                return field;
            }
        }
        log.error("PO类未设置@TableId注解");
        throw new RuntimeException("PO类未设置@TableId注解");
    }

    /**
     * 转换 Canal 的 FlatMessage中的data成泛型对象
     *
     * @param flatMessage Canal发送MQ信息
     * @return 泛型对象集合
     */
    protected Set<T> getData(FlatMessage flatMessage) {
        List<Map<String, String>> sourceData = flatMessage.getData();
        Set<T> targetData = Sets.newHashSetWithExpectedSize(sourceData.size());
        for (Map<String, String> map : sourceData) {
            // 将Type类型的数据和T对象合并转换为泛型对象T
            T t = JSON.parseObject(JSON.toJSONString(map), getTypeArgument());
            targetData.add(t);
        }
        return targetData;
    }

}
5.3.3 具体类的同步服务实现
/**
 * User类的 Canal-RocketMQ通用处理服务实现
 *
 * @author zouhu
 * @data 2024-10-31 17:23
 */
@Component
public class UserCanalRocketMqRedisService extends AbstractCanalRocketMqRedisService<User> {
    public UserCanalRocketMqRedisService(RedisTemplate<String, Object> redisTemplate) {
        super(redisTemplate);
    }

    @Override
    protected String getModelName() {
        return "User";
    }
}

5.4 实体类

后续将根据这个实体类来进行测试。

/**
 * User 实体类
 *
 * @author zouhu
 * @data 2024-10-31 13:29
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class User extends Model<User> {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String name;

    private String email;
}

5.5 RocketMQ 消费者

/**
 * 监听所有表的数据修改 binlog
 * <p>
 *     目前只实现了单个表的处理逻辑, 后续可以使用策略模式实现不同表的处理逻辑
 * </p>
 *
 * @author zouhu
 * @data 2024-10-27 23:18
 */
@Slf4j
@Component
@RequiredArgsConstructor
@RocketMQMessageListener(
        topic = "canal-test-topic",
        consumerGroup = "my-consumer_test-topic-1"
)
public class CanalCommonSyncBinlogConsumer implements RocketMQListener<FlatMessage> {

    private final UserCanalRocketMqRedisService userCanalRocketMqRedisService;

    @Override
    public void onMessage(FlatMessage flatMessage) {
        log.info("consumer message {}", flatMessage);
        try {
            userCanalRocketMqRedisService.process(flatMessage);
        } catch (Exception e) {
            log.warn(String.format("message [%s] 消费失败", flatMessage), e);
            throw new RuntimeException(e);
        }
    }
}

5.6 后续优化方案

使用策略模式实现不同表的处理策略.

六、测试客户端代码

6.1 创建数据库及表

执行以下 sql 语句,创建数据库及表

CREATE DATABASE test_db;
USE test_db;

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    email VARCHAR(100)
);

查看 /home/canal-server//logs/test/meta.log 日志文件,数据库的每次增删改操作,都会在meta.log中生成一条记录,查看该日志可以确认 Canal 是否有采集到数据。

查看客户端控制台输出的信息

2024-10-31T22:56:24.607+08:00  INFO 13228 --- [spring-boot-canal-redis] [_test-topic-1_6] .z.s.c.r.m.CanalCommonSyncBinlogConsumer : consumer message FlatMessage [id=22, database=test_db, table=, isDdl=true, type=QUERY, es=1730386584000, ts=1730386584600, sql=CREATE DATABASE test_db, sqlType=null, mysqlType=null, data=null, old=null, gtid=]
2024-10-31T22:56:24.608+08:00  INFO 13228 --- [spring-boot-canal-redis] [_test-topic-1_7] .z.s.c.r.m.CanalCommonSyncBinlogConsumer : consumer message FlatMessage [id=22, database=test_db, table=users, isDdl=true, type=CREATE, es=1730386584000, ts=1730386584600, sql=CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    email VARCHAR(100)
), sqlType=null, mysqlType=null, data=null, old=null, gtid=]

6.2 插入数据

执行以下 sql 语句

INSERT INTO users (name, email) VALUES ('Alice8', 'alice@example.com');

查看客户端控制台输出的信息

2024-10-31T22:57:12.601+08:00  INFO 13228 --- [spring-boot-canal-redis] [_test-topic-1_8] .z.s.c.r.m.CanalCommonSyncBinlogConsumer : consumer message FlatMessage [id=23, database=test_db, table=users, isDdl=false, type=INSERT, es=1730386632000, ts=1730386632592, sql=, sqlType={id=4, name=12, email=12}, mysqlType={id=INT, name=VARCHAR(100), email=VARCHAR(100)}, data=[{id=1, name=Alice8, email=alice@example.com}], old=null, gtid=]
2024-10-31T22:57:12.601+08:00  INFO 13228 --- [spring-boot-canal-redis] [_test-topic-1_8] .r.c.s.AbstractCanalRocketMqRedisService : redis data:User(id=1, name=Alice8, email=alice@example.com)
2024-10-31T22:57:12.601+08:00  INFO 13228 --- [spring-boot-canal-redis] [_test-topic-1_8] .r.c.s.AbstractCanalRocketMqRedisService : redis key:User:1

使用 another Redis Desktop Manager 客户端查看 Redis 是否更新

在这里插入图片描述

参考资料

Canal + RocketMQ 同步 MySQL 数据到 Redis 实战-一只小松徐吖

Canal Kafka RocketMQ QuickStart · alibaba/canal Wiki (github.com)

使用Canal和RocketMQ实现数据库变更订阅处理_云消息队列 RocketMQ 版(RocketMQ)-阿里云帮助中心

超详细的canal入门,看这篇就够了-阿里云开发者社区 (aliyun.com)

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

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

相关文章

Spring Boot2.x教程:(十)从Field injection is not recommended谈谈依赖注入

从Field injection is not recommended谈谈依赖注入 1、问题引入2、依赖注入的三种方式2.1、字段注入&#xff08;Field Injection&#xff09;2.2、构造器注入&#xff08;Constructor Injection&#xff09;2.3、setter注入&#xff08;Setter Injection&#xff09; 3、为什…

解决 ClickHouse 高可用集群中 VRID 冲突问题:基于 chproxy 和 keepalived 的实践分析

Part1背景描述 近期&#xff0c;我们部署了两套 ClickHouse 生产集群&#xff0c;分别位于同城的两个数据中心。这两套集群的数据保持一致&#xff0c;以便在一个数据中心发生故障时&#xff0c;能够迅速切换应用至另一个数据中心的 ClickHouse 实例&#xff0c;确保服务连续性…

【Android】View的事件分发机制

文章目录 分发顺序ActivityViewGroupView 协作方法整体流程注意 Activity事件分发ViewGroup事件分发View点击事件总结 分发顺序 Activity->ViewGroup->View Activity 分发事件&#xff1a;Activity 通过 dispatchTouchEvent 方法分发事件&#xff0c;首先尝试将事件传递…

java项目之微服务在线教育系统设计与实现(springcloud)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的闲一品交易平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 微服务在线教育系统设计与…

ChatGPT:真如吹的那般神乎其神吗?

ChatGPT的确是个神奇的东西。短短600多天&#xff0c;就已成全球访问量最大的网站之一。 ChatGPT已经出现在与这些大佬顶级大佬Google、Youtube、X.com、Baidu、Yahoo、amazon、Tiktok一起。 当然ChatGPT很优秀&#xff0c;这没有疑问&#xff0c;主要问题还是对度的把握上。…

【深度学习】实验 — 动手实现 GPT【二】:注意力机制、注意力掩码、多头注意力机制

【深度学习】实验 — 动手实现 GPT【二】&#xff1a;注意力机制、多头注意力机制 注意力机制简单示例&#xff1a;单个元素的情况简单示例&#xff1a;计算所有输入词元的注意力权重推广到所有输入序列词元&#xff1a; 注意力掩码代码实现多头注意力测试 注意力机制 简单示例…

简单的kafkaredis学习之kafka

简单的kafka&redis学习整理之kafka 1. kafka 1.1 什么是消息队列 在学习Kafka之前我们先来看一下什么是消息队列&#xff0c;消息队列(Message Queue)&#xff1a;可以简称为MQ 例如&#xff1a;Java中的Queue队列&#xff0c;也可以认为是一个消息队列 消息队列&#x…

基于人工智能的搜索和推荐系统

互联网上的搜索历史分析和用户活动是个性化推荐的基础&#xff0c;这些推荐已成为电子商务行业和在线业务的强大营销工具。随着人工智能的使用&#xff0c;在线搜索也在改进&#xff0c;因为它会根据用户的视觉偏好提出建议&#xff0c;而不是根据每个客户的需求和偏好量身定制…

ssm042在线云音乐系统的设计与实现+jsp(论文+源码)_kaic

摘 要 随着移动互联网时代的发展&#xff0c;网络的使用越来越普及&#xff0c;用户在获取和存储信息方面也会有激动人心的时刻。音乐也将慢慢融入人们的生活中。影响和改变我们的生活。随着当今各种流行音乐的流行&#xff0c;人们在日常生活中经常会用到的就是在线云音乐系统…

TVS 静电管 选型

参数选型举例: 静电管选型举例: 针对信号引脚一般只需ESD防护,关注其在IEC 61000−4−2波形下的测试结果:最大耐压值、钳位电压等,注意此时钳位电压的限值就不是Absolute maximum ratings值了,原因有2 1、Absolute maximum ratings值是指持续加压会损坏芯片 2、如果关…

监控调度台在交通运输行业的优势?

在当今快速发展的交通运输行业中&#xff0c;高效、安全的管理成为确保运营顺畅和乘客满意的关键。监控调度台作为这一领域的核心设备&#xff0c;正发挥着越来越重要的作用。它集成了视频监控、数据分析、实时通讯等多种功能&#xff0c;为交通运输行业带来了诸多优势。下面我…

华为ENSP--ISIS路由协议

项目背景 为了确保资源共享、办公自动化和节省人力成本&#xff0c;公司E申请两条专线将深圳总部和广州、北京两家分公司网络连接起来。公司原来运行OSFP路由协议&#xff0c;现打算迁移到IS-IS路由协议&#xff0c;张同学正在该公司实习&#xff0c;为了提高实际工作的准确性和…

设计模式07-结构型模式2(装饰模式/外观模式/代理模式/Java)

4.4 装饰模式 4.4.1 装饰模式的定义 1.动机&#xff1a;在不改变一个对象本身功能的基础上给对象增加额外的新行为 2.定义&#xff1a;动态地给一个对象增加一些额外的职责&#xff0c;就增加对象功能来说&#xff0c;装饰模式比生成子类实现更为灵活 4.4.2 装饰模式的结构…

Spring @RequestMapping 注解

文章目录 Spring RequestMapping 注解一、引言二、RequestMapping注解基础1、基本用法2、处理多个URI 三、高级用法1、处理HTTP方法2、参数和消息头处理 四、总结 Spring RequestMapping 注解 一、引言 在Spring框架中&#xff0c;RequestMapping 注解是构建Web应用程序时不可…

【Linux】IPC 进程间通信(一):管道(匿名管道命名管道)

✨ 无人扶我青云志&#xff0c;我自踏雪至山巅 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;Linux—登神长阶 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#…

单片机串口接收状态机STM32

单片机串口接收状态机stm32 前言 项目的芯片stm32转国产&#xff0c;国产芯片的串口DMA接收功能测试不通过&#xff0c;所以要由原本很容易配置的串口空闲中断触发DMA接收数据的方式转为串口逐字节接收的状态机接收数据 两种方式各有优劣&#xff0c;不过我的芯片已经主频跑…

信息学科平台系统开发:基于Spring Boot的最佳实践

3系统分析 3.1可行性分析 通过对本基于保密信息学科平台系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本基于保密信息学科平台系统采用Spring Boot框架&a…

探索 ONLYOFFICE 8.2 版本:更高效、更安全的云端办公新体验

引言 在当今这个快节奏的时代&#xff0c;信息技术的发展已经深刻改变了我们的工作方式。从传统的纸质文件到电子文档&#xff0c;再到如今的云端协作&#xff0c;每一步技术进步都代表着效率的飞跃。尤其在后疫情时代&#xff0c;远程办公成为常态&#xff0c;如何保持团队之间…

51c自动驾驶~合集4

我自己的原文哦~ https://blog.51cto.com/whaosoft/12413878 #MCTrack 迈驰&旷视最新MCTrack&#xff1a;KITTI/nuScenes/Waymo三榜单SOTA paper&#xff1a;MCTrack: A Unified 3D Multi-Object Tracking Framework for Autonomous Driving code&#xff1a;https://gi…

STM32HAL-最简单的长、短、多击按键框架(多按键)

概述 本文章使用最简单的写法实现长、短、多击按键框架,非常适合移植各类型单片机,特别是资源少的芯片上。接下来将在stm32单片机上实现,只需占用1个定时器作为时钟扫描按键即可。 一、开发环境 1、硬件平台 STM32F401CEU6 内部Flash : 512Kbytes,SARM …