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

文章目录

    • 一、安装 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)

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

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

相关文章

深度学习(九):推荐系统的新引擎(9/10)

一、深度学习与推荐系统的融合 深度学习在推荐系统中的融合并非偶然。随着互联网的飞速发展&#xff0c;数据量呈爆炸式增长&#xff0c;传统推荐系统面临着诸多挑战。例如&#xff0c;在处理大规模、高维度的数据时&#xff0c;传统方法往往显得力不从心。而深度学习以其强大的…

masm汇编字符串输出演示

assume cs:code, ds:datadata segmentmassage db zhouzunjie, 0dh, 0ah, $ data endscode segmentstart:mov ax, datamov ds, axmov ah, 09hlea dx, massageint 21hmov ax, 4c00hint 21hcode ends end start 效果演示&#xff1a;

在昇腾Ascend 910B上运行Qwen2.5推理

目前在国产 AI 芯片&#xff0c;例如昇腾 NPU 上运行大模型是一项广泛且迫切的需求&#xff0c;然而当前的生态还远未成熟。从底层芯片的算力性能、计算架构的算子优化&#xff0c;到上层推理框架对各种模型的支持及推理加速&#xff0c;仍有很多需要完善的地方。 今天带来一篇…

HarmonyOS一次开发多端部署三巨头之界面级一多开发

界面级一多开发 引言1. 布局能力1.1 自适应布局1.1.1 拉伸能力1.1.2 均分能力1.1.3 占比能力1.1.4 缩放能力1.1.5延伸能力1.1.6 隐藏能力1.1.7 折行能力 1.2 响应式布局1.2.1 断点和媒体查询1.2.2 栅格布局 2. 视觉风格2.1 分层参数2.2 自定义资源 3. 交互归一4. IDE多设备预览…

(58)LMS自适应滤波算法与系统辨识的MATLAB仿真

文章目录 前言一、LMS算法的基本步骤二、LMS算法的一些主要应用1. 通信系统2. 信号分离与增强3. 控制系统4. 生物医学信号处理5. 机器学习与模式识别6. 其他应用 三、LMS算法用于系统辨识的MATLAB仿真四、仿真结果 前言 LMS&#xff08;Least Mean Squares&#xff0c;最小均方…

bootstrap应用1——计算n从1-100000的每个整数,第j个观测在自助法样本里的概率。

计算n从1-100000的每个整数&#xff0c;第j个观测在自助法样本里的概率。 pr function(n) return(1 - (1 - 1/n)^n) x 1:10000 plot(x, pr(x))

AI-基本概念-向量、矩阵、张量

1 需求 需求&#xff1a;Tensor、NumPy 区别 需求&#xff1a;向量、矩阵、张量 区别 2 接口 3 示例 4 参考资料 【PyTorch】PyTorch基础知识——张量_pytorch张量-CSDN博客

【设计模式】策略模式定义及其实现代码示例

文章目录 一、策略模式1.1 策略模式的定义1.2 策略模式的参与者1.3 策略模式的优点1.4 策略模式的缺点1.5 策略模式的使用场景 二、策略模式简单实现2.1 案例描述2.2 实现代码 三、策略模式的代码优化3.1 优化思路3.2 抽象策略接口3.3 上下文3.4 具体策略实现类3.5 测试 参考资…

2025年PMP考试的3A好考吗?

确实&#xff0c;PMP正式抛弃第六版用第七版教材了&#xff0c;但是考纲还是跟24年一样的&#xff0c;情景题多&#xff0c;考的比之前灵活&#xff0c;但是 3A 的人也不少&#xff0c;按照机构的计划来学习并没有很难&#xff0c;给大家说说我的备考经历吧&#xff0c;希望对你…

VScode + PlatformIO 了解

​Visual Studio Code Visual Studio Code&#xff08;简称 VS Code&#xff09;是一款由微软开发且跨平台的免费源代码编辑器。该软件以扩展的方式支持语法高亮、代码自动补全&#xff08;又称 IntelliSense&#xff09;、代码重构功能&#xff0c;并且内置了工具和 Git 版本…

完美日记营销模式对开源 AI 智能名片 2 + 1 链动模式 S2B2C 商城小程序的启示

摘要&#xff1a;本文通过分析完美日记在营销中利用社会基础设施升级红利、网红与新流量平台、KOL 和私域流量等策略取得成功的案例&#xff0c;探讨其对开源 AI 智能名片 2 1 链动模式 S2B2C 商城小程序在营销推广、用户获取与留存、提升复购率等方面的启示&#xff0c;为商城…

Failed to install Visual Studio Code update

当关闭vsCode的时候&#xff0c;出现了下面的报错&#xff1a; 可能是之前将vscode文件换了位置导致的&#xff0c;并且vscode在桌面的图标也变成了下面这个&#xff1a; 解决方法&#xff1a; 找到上图路径的log文件并打开&#xff1a; 搜索电脑中的Code.exe文件 并粘贴到上…

python在word的页脚插入页码

1、插入简易页码 import win32com.client as win32 from win32com.client import constants import osdoc_app win32.gencache.EnsureDispatch(Word.Application)#打开word应用程序 doc_app.Visible Truedoc doc_app.Documents.Add() footer doc.Sections(1).Footers(cons…

Rust 力扣 - 73. 矩阵置零

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们使用两个变量记录矩阵初始状态的第一行与第一列是否存在0 然后我们遍历矩阵&#xff08;跳过第一行与第一列&#xff09;&#xff0c;如果矩阵中元素为0则将该元素映射到矩阵第一行与矩阵第一列的位置置为0…

Python | Leetcode Python题解之第537题复数乘法

题目&#xff1a; 题解&#xff1a; class Solution:def complexNumberMultiply(self, num1: str, num2: str) -> str:real1, imag1 map(int, num1[:-1].split())real2, imag2 map(int, num2[:-1].split())return f{real1 * real2 - imag1 * imag2}{real1 * imag2 imag1…

tauri开发中如果取消了默认的菜单项,复制黏贴撤销等功能也就没有了,解决办法

取消默认的菜单项&#xff1a;清除tauri默认的菜单项&#xff0c;让顶部的菜单menu不显示-CSDN博客 就是通过配置空菜单&#xff0c;让菜单不显示&#xff0c;但是这个引发的问题就是复制黏贴撤销等功能也就没有了&#xff0c;解决办法&#xff1a; 新增加编辑下的子菜单&…

STM32F103C8T6学习笔记3--按键控制LED灯

1、实验内容 S4、S5分别接PB12和PB13&#xff0c;实验要求&#xff0c;按下S4&#xff0c;D1亮&#xff0c;D2灭&#xff1b;按下S5&#xff0c;D2亮&#xff0c;D1灭。 由于按键学习的是GPIO口的输入功能&#xff0c;和输出功能的配置略有区别。本次通过按键触发相应功能没有…

微服务核心——网关路由

目录 前言 一、登录存在的问题归纳 二、*微服务网关整体方案 三、认识微服务网关 四、网关鉴权实现 五、OpenFeign微服务间用户标识信息传递实现 六、微服务网关知识追问巩固 前言 本篇文章具体讲解微服务中网关的实现逻辑、用于解决什么样的问题。其中标题中标注* 涉…

如何压缩pdf文件的大小?5分钟压缩pdf的方法推荐

如何压缩pdf文件的大小&#xff1f;在现代办公和学习中&#xff0c;PDF文件因其稳定性和广泛的兼容性被广泛使用。然而&#xff0c;随着文件内容的增多&#xff0c;制作好的PDF文件常常变得过大&#xff0c;给使用带来了诸多不便。无论是电子邮件附件的发送&#xff0c;还是在线…

entos7离线安装xrdp和图形化桌面

1、查看Linux系统环境 cat /etc/os-release NAME"CentOS Linux" VERSION"7 (Core)" ID"centos" ID_LIKE"rhel fedora" VERSION_ID"7" PRETTY_NAME"CentOS Linux 7 (Core)" ANSI_COLOR"0;31" CPE_NAME…