1. SpringBoot 整合 Canal

news2024/9/28 1:01:43

勿以恶小而为之,勿以善小而不为----- 刘备

SpringBoot 整合 Canal

pom.xml 添加 canal.client 依赖

(1.1.5 改动很大,这儿客户端用 1.1.4)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>top.yueshushu</groupId>
    <artifactId>learn</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>Canal</name>
    <description>学习 Canal</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!--导入自动热步署的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <!--引入MySql的驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--引入springboot与mybatis整合的依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <!-- 引入pagehelper分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.5</version>
        </dependency>
        <!--添加 druid-spring-boot-starter的依赖的依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.14</version>
        </dependency>
        <!--SpringBoot 的aop 模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!--添加canal的依赖. 重要.  使用  1.1.4-->
        <dependency>
            <groupId>com.alibaba.otter</groupId>
            <artifactId>canal.client</artifactId>
            <version>1.1.4</version>
        </dependency>

        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.9.4</version>
        </dependency>
    </dependencies>
    <build>
        <!--将该目录下的文件全部打包成类的路径-->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

业务功能处理

简单连接程序

/**
     * 一个简单的canal 的连接测试程序
     */
    @Test
    public void connectionTest() {
        //1. 创建连接  填充对应的地址信息 ,要监控的实例和相应的用户名和密码
        CanalConnector canalConnector = CanalConnectors.newSingleConnector(
                new InetSocketAddress(
                        "127.0.0.1", 11111
                ),
                "example",
                "canal",
                "canal"
        );
        //2. 进行连接
        canalConnector.connect();
        log.info(">>>连接成功:{}", canalConnector);
    }

17:26:32.179 [main] INFO top.yueshushu.learn.CanalDemoTest - >>>连接成功:com.alibaba.otter.canal.client.impl.SimpleCanalConnector@31ef45e3

单次获取数据

/**
     * 获取数据信息. 可以发现,未获取到数据 .  这个应该是实时的.
     */
    @Test
    public void getDataTest() {
        //1. 创建连接
        CanalConnector canalConnector = CanalConnectors.newSingleConnector(
                new InetSocketAddress("127.0.0.1", 11111),
                "example",
                "canal",
                "canal"
        );
        // 进行连接
        canalConnector.connect();

        //3. 注册,看使用哪个数据库表
        canalConnector.subscribe("springboot.user");

        //4. 获取 1条数据
        Message message = canalConnector.get(1);
        log.info("获取的数据:id:{},数据:{}", message.getId(), message);
        if (message.getId() == -1) {
            log.info(">>>未获取到数据");
            return;
        }
        //5. 获取相应的数据集合
        List<CanalEntry.Entry> entries = message.getEntries();
        for (CanalEntry.Entry entry : entries) {
            log.info(">>>获取数据 {}", entry);
            //获取表名
            CanalEntry.Header header = entry.getHeader();
            log.info(">>>获取表名:{}", header.getTableName());
            CanalEntry.EntryType entryType = entry.getEntryType();
            log.info(">>获取类型 {}:,对应的信息:{}", entryType.getNumber(), entryType.name());

            //获取数据
            ByteString storeValue = entry.getStoreValue();
            log.info(">>>输出存储的值:{}", storeValue);
        }
    }

在主库里面插入一条数据

insert into springboot.user(id,name,age,sex,description)  values(1,'canal添加用户',24,'男','学习canal');

再次执行:

循环获取数据

/**
     * 获取数据信息. 获取现在的数据.  再次执行时,就没有这个数据了.
     */
    @Test
    public void getNowDataTest() {
        //1. 创建连接
        CanalConnector canalConnector = CanalConnectors.newSingleConnector(
                new InetSocketAddress("127.0.0.1", 11111),
                "example",
                "canal",
                "canal"
        );
        // 进行连接
        canalConnector.connect();

        //3. 注册,看使用哪个数据库表
        canalConnector.subscribe("springboot.user");
        for (;;) {
            //4. 获取 1条数据
            Message message = canalConnector.get(1);
            log.info("获取的数据:id:{},数据:{}", message.getId(), message);
            if (message.getId() == -1) {
                log.info(">>>未获取到数据");
                try {
                    TimeUnit.MILLISECONDS.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                continue;
            }
            //5. 获取相应的数据集合
            List<CanalEntry.Entry> entries = message.getEntries();
            for (CanalEntry.Entry entry : entries) {
                log.info(">>>获取数据 {}", entry);
                //获取表名
                CanalEntry.Header header = entry.getHeader();
                log.info(">>>获取表名:{}", header.getTableName());
                CanalEntry.EntryType entryType = entry.getEntryType();
                log.info(">>获取类型 {}:,对应的信息:{}", entryType.getNumber(), entryType.name());

                //获取数据
                ByteString storeValue = entry.getStoreValue();
                log.info(">>>输出存储的值:{}", storeValue);
            }
        }
    }

可以随时获取相应的数据变更信息。
会发现, storeValue 的值是很难解读的。 需要将这个数据解析出来。

解析 storeValue 值

/**
     * 将 storeValue 进行解析,解析成我们能看懂的语句.
     * 对数据库 cud 进行处理操作观看一下.
     * 发现,点是不好的,也有多余的记录信息.
     *
     * @throws Exception 异常
     */
    @Test
    public void convertDataTest() throws Exception {
        //1. 创建连接
        CanalConnector canalConnector = CanalConnectors.newSingleConnector(
                new InetSocketAddress("127.0.0.1", 11111),
                "example",
                "canal", "canal"
        );
        //2. 进行连接
        canalConnector.connect();
        canalConnector.subscribe("springboot.user");

        for (;;) {
            //获取信息
            Message message = canalConnector.get(1);
            if (message.getId() == -1L) {
                // log.info("未获取到数据");
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                continue;
            }
            List<CanalEntry.Entry> entryList = message.getEntries();
            //对获取到的数据进行处理
            log.info(">>获取到{}条数据", entryList.size());
            for (CanalEntry.Entry entry : entryList) {
                CanalEntry.Header header = entry.getHeader();
                log.info(">>>获取表名:{}", header.getTableName());
                //获取类型.
                CanalEntry.EntryType entryType = entry.getEntryType();
                log.info(">>类型编号 {},类型名称:{}", entryType.getNumber(), entryType.name());

                //获取存入日志的值
                ByteString storeValue = entry.getStoreValue();
                //将这个值进行解析
                CanalEntry.RowChange rowChange = RowChange.parseFrom(storeValue);
                String sql = rowChange.getSql();
                log.info(">>>获取对应的sql:{}", sql);
                // 这个sql 可能是 批量的sql语句
                List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
                for (CanalEntry.RowData rowData : rowDatasList) {
                    log.info(">>>获取信息:{}", rowData);
                    //对数据进行处理
                    List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
                    List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();

                    beforeColumnsList.forEach(
                            n -> log.info("哪个列{},原先是{},是否被更新{}", n.getName(),
                                    n.getValue(), n.getUpdated())
                    );
                    afterColumnsList.forEach(
                            n -> log.info("哪个列{},后来是{},是否被更新{}", n.getName(), n.getValue(), n.getUpdated())
                    );
                }
            }
        }

    }

再次执行sql

insert into springboot.user(id,name,age,sex,description) 
values(2,'canal添加用户2',25,'男','学习canal2');

不同的类型,进行不同的处理

发现 其他类型的 如: TRANSACTIONBEGIN 也进行了处理

/**
     * 类型转换数据
     *
     * @throws Exception 异常
     */
    @Test
    public void dataTypeTest() throws Exception {
        CanalConnector canalConnector = CanalConnectors.newSingleConnector(
                new InetSocketAddress(
                        "127.0.0.1", 11111
                ),
                "example",
                "canal", "canal"
        );
        canalConnector.connect();
        canalConnector.subscribe("springboot.user");

        for(;;){
            Message message = canalConnector.get(1);
            if (message.getId() == -1) {
                TimeUnit.SECONDS.sleep(1);
                continue;
            }

            List<CanalEntry.Entry> entries = message.getEntries();
            for (CanalEntry.Entry entry : entries) {
                CanalEntry.EntryType entryType = entry.getEntryType();
                //只要 RowData 数据类型的
                if (!CanalEntry.EntryType.ROWDATA.equals(entryType)) {
                    continue;
                }
                String tableName = entry.getHeader().getTableName();
                log.info(">>>对表 {} 进行操作", tableName);
                ByteString storeValue = entry.getStoreValue();
                RowChange rowChange = RowChange.parseFrom(storeValue);
                //行改变
                CanalEntry.EventType eventType = rowChange.getEventType();
                switch (eventType) {
                    case INSERT: {
                        insertHandler(rowChange);
                        break;
                    }
                    case UPDATE: {
                        updateHandler(rowChange);
                        break;
                    }
                    case DELETE: {
                        deleteHandler(rowChange);
                        break;
                    }
                    default: {
                        break;
                    }
                }
            }
        }

    }

    private void deleteHandler(RowChange rowChange) {
        log.info(">>>>执行删除的方法");
        List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
        for (CanalEntry.RowData rowData : rowDatasList) {

            List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
            for (CanalEntry.Column column : beforeColumnsList) {

                log.info(">>>>>字段 {} 删除数据 {}", column.getName(), column.getValue());
            }
        }
    }

    private void updateHandler(RowChange rowChange) {
        log.info(">>>执行更新的方法");
        List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
        for (CanalEntry.RowData rowData : rowDatasList) {

            List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
            List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
            Map<String, String> beforeValueMap = beforeColumnsList.stream().collect(
                    Collectors.toMap(
                            CanalEntry.Column::getName,
                            CanalEntry.Column::getValue
                    )
            );
            Map<String, String> afterValueMap = afterColumnsList.stream().collect(
                    Collectors.toMap(
                            CanalEntry.Column::getName,
                            CanalEntry.Column::getValue
                    )
            );
            beforeValueMap.forEach((column, beforeValue) -> {
                String afterValue = afterValueMap.get(column);
                Boolean update = beforeValue.equals(afterValue);
                log.info("修改列:{},修改前的值:{},修改后的值:{},是否更新:{}", column, beforeValue, afterValue,
                        update);
            });
        }

    }

    /**
     * 插入数据. 只有后的数据.
     *
     * @param rowChange 行改变
     */
    private void insertHandler(RowChange rowChange) {
        log.info(">>>执行添加 的方法");
        List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
        for (CanalEntry.RowData rowData : rowDatasList) {
            List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();

            for (CanalEntry.Column column : afterColumnsList) {
                if (!StringUtils.hasText(column.getValue())) {
                    continue;
                }
                log.info("字段 {} 插入了数据 {}", column.getName(), column.getValue());
            }

        }
    }

插入,更新,删除,分别进行了处理.
先启动测试程序:

不打印任何信息。
主表执行添加语句:

insert into springboot.user(id,name,age,sex,description) 
values(4,'canal添加用户4',25,'男','学习canal4');

会打印信息:

这个可读性就非常高了.

主表执行修改的操作.

update springboot.user set name='开开心心',age=26,description='岳泽霖' where id =4;

更新时,若每一个字段都跟原先一样,不会产生日志消费。

主表执行删除的操作:

delete from springboot.user where id =4;

上面的获取,都是一条数据一条数据获取的。效率比较低

一次性获取多条数据

/**
     * 一次性获取多条数据。
     * sql 执行多条。
     */
    @Test
    public void dataMoreTest() throws Exception {
        //1. 创建 canal连接对象
        CanalConnector canalConnector = CanalConnectors.newSingleConnector(
                new InetSocketAddress(
                        "127.0.0.1", 11111
                ),
                "example",
                "canal",
                "canal"
        );
        canalConnector.connect();
        // 订阅哪个对象
        canalConnector.subscribe("springboot.user");

        for (; ; ) {

           // Message message = canalConnector.get(3, 5L, TimeUnit.SECONDS);
            Message message = canalConnector.get(3);
            if (message.getId() == -1) {
                // 未获取到数据
                continue;
            }
            List<CanalEntry.Entry> entries = message.getEntries();
            for (CanalEntry.Entry entry : entries) {
                CanalEntry.EntryType entryType = entry.getEntryType();
                if (!CanalEntry.EntryType.ROWDATA.equals(entryType)) {
                    continue;
                }
                String tableName = entry.getHeader().getTableName();
                log.info(">>>>对表{} 执行操作", tableName);

                CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
                //对类型进行处理
                CanalEntry.EventType eventType = rowChange.getEventType();
                switch (eventType) {
                    case INSERT: {
                        insertHandler(rowChange);
                        break;
                    }
                    case UPDATE: {
                        updateHandler(rowChange);
                        break;
                    }
                    case DELETE: {
                        deleteHandler(rowChange);
                        break;
                    }
                    default: {
                        break;
                    }
                }

            }

        }
    }

    private void deleteHandler(CanalEntry.RowChange rowChange) {
        log.info(">>>>执行删除的方法");
        List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
        for (CanalEntry.RowData rowData : rowDatasList) {

            List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
            for (CanalEntry.Column column : beforeColumnsList) {

                log.info(">>>>>字段 {} 删除数据 {}", column.getName(), column.getValue());
            }
        }
    }

    private void updateHandler(CanalEntry.RowChange rowChange) {
        log.info(">>>执行更新的方法");
        List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
        for (CanalEntry.RowData rowData : rowDatasList) {

            List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
            List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
            Map<String, String> beforeValueMap = beforeColumnsList.stream().collect(
                    Collectors.toMap(
                            CanalEntry.Column::getName,
                            CanalEntry.Column::getValue
                    )
            );
            Map<String, String> afterValueMap = afterColumnsList.stream().collect(
                    Collectors.toMap(
                            CanalEntry.Column::getName,
                            CanalEntry.Column::getValue
                    )
            );
            beforeValueMap.forEach((column, beforeValue) -> {
                String afterValue = afterValueMap.get(column);
                Boolean update = beforeValue.equals(afterValue);
                log.info("修改列:{},修改前的值:{},修改后的值:{},是否更新:{}", column, beforeValue, afterValue,
                        update);
            });
        }

    }

    /**
     * 插入数据. 只有后的数据.
     *
     * @param rowChange 行改变
     */
    private void insertHandler(CanalEntry.RowChange rowChange) {
        log.info(">>>执行添加 的方法");
        List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
        for (CanalEntry.RowData rowData : rowDatasList) {
            List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();

            for (CanalEntry.Column column : afterColumnsList) {
                if (!StringUtils.hasText(column.getValue())) {
                    continue;
                }
                log.info("字段 {} 插入了数据 {}", column.getName(), column.getValue());
            }

        }
    }

修改点:

// Message message = canalConnector.get(3, 5L, TimeUnit.SECONDS);
    Message message = canalConnector.get(3);

.get(3) 表示 一次性获取3条记录.

canalConnector.get(3, 5L, TimeUnit.SECONDS); 表示5秒之内获取3条记录,
有两个触发条件,一个是获取了3条,一个是到了5秒。
效果展示信息与之前是一致的,就不重新演示了。

ack 配置信息

/**
     * 一次性获取多条数据。
     * sql 执行多条。 
     */
    @Test
    public void dataMoreTest() throws Exception {
        //1. 创建 canal连接对象
        CanalConnector canalConnector = CanalConnectors.newSingleConnector(
                new InetSocketAddress(
                        "127.0.0.1", 11111
                ),
                "example",
                "canal",
                "canal"
        );
        canalConnector.connect();
        // 订阅哪个对象
        canalConnector.subscribe("springboot.user");

        for (; ; ) {

             Message message = canalConnector.getWithoutAck(3, 2L, TimeUnit.SECONDS);
            if (message.getId() == -1) {
                // 未获取到数据
                TimeUnit.MILLISECONDS.sleep(500);
                continue;
            }
            log.info(">>>>获取对应的 id: {}",message.getId());
            List<CanalEntry.Entry> entries = message.getEntries();
            for (CanalEntry.Entry entry : entries) {
                CanalEntry.EntryType entryType = entry.getEntryType();
                if (!CanalEntry.EntryType.ROWDATA.equals(entryType)) {
                    continue;
                }
                String tableName = entry.getHeader().getTableName();
                log.info(">>>>对表{} 执行操作", tableName);

                CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
                //对类型进行处理
                CanalEntry.EventType eventType = rowChange.getEventType();
                switch (eventType) {
                    case INSERT: {
                        insertHandler(rowChange);
                        break;
                    }
                    case UPDATE: {
                        updateHandler(rowChange);
                        break;
                    }
                    case DELETE: {
                        deleteHandler(rowChange);
                        break;
                    }
                    default: {
                        break;
                    }
                }

            }
            //进行回滚
           // canalConnector.rollback();

            //确认ack 配置
           canalConnector.ack(message.getId());
        }
    }

    private void deleteHandler(CanalEntry.RowChange rowChange) {
        log.info(">>>>执行删除的方法");
        List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
        for (CanalEntry.RowData rowData : rowDatasList) {

            List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
            for (CanalEntry.Column column : beforeColumnsList) {

                log.info(">>>>>字段 {} 删除数据 {}", column.getName(), column.getValue());
            }
        }
    }

    private void updateHandler(CanalEntry.RowChange rowChange) {
        log.info(">>>执行更新的方法");
        List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
        for (CanalEntry.RowData rowData : rowDatasList) {

            List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
            List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
            Map<String, String> beforeValueMap = beforeColumnsList.stream().collect(
                    Collectors.toMap(
                            CanalEntry.Column::getName,
                            CanalEntry.Column::getValue
                    )
            );
            Map<String, String> afterValueMap = afterColumnsList.stream().collect(
                    Collectors.toMap(
                            CanalEntry.Column::getName,
                            CanalEntry.Column::getValue
                    )
            );
            beforeValueMap.forEach((column, beforeValue) -> {
                String afterValue = afterValueMap.get(column);
                Boolean update = beforeValue.equals(afterValue);
                log.info("修改列:{},修改前的值:{},修改后的值:{},是否更新:{}", column, beforeValue, afterValue,
                        update);
            });
        }

    }

    /**
     * 插入数据. 只有后的数据.
     *
     * @param rowChange 行改变
     */
    private void insertHandler(CanalEntry.RowChange rowChange) {
        log.info(">>>执行添加 的方法");
        List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
        for (CanalEntry.RowData rowData : rowDatasList) {
            List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();

            for (CanalEntry.Column column : afterColumnsList) {
                if (!StringUtils.hasText(column.getValue())) {
                    continue;
                }
                log.info("字段 {} 插入了数据 {}", column.getName(), column.getValue());
            }

        }
    }

主要信息:
Message message = canalConnector.getWithoutAck(3, 2L, TimeUnit.SECONDS);
//进行回滚 // canalConnector.rollback();
//确认ack 配置canalConnector.ack(message.getId());
手动确认消息消费了.
当消息 rollback() 回滚后,会再次消费这条消息.
canalConnector.rollback();
执行语句:

insert into springboot.user(id,name,age,sex,description) 
values(5,'canal添加用户5',25,'男','学习canal5');

如果变成 手动确认,
canalConnector.ack(message.getId());
则只消费一次.

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

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

相关文章

【云原生进阶之容器】第一章Docker核心技术1.5.3节——cgroups数据结构剖析

1 cgroups数据结构解析 从进程的角度出发来剖析 cgroups 相关数据结构之间的关系。在 Linux 中管理进程的数据结构是 task_struct。cgroup表示进程的行为控制,因为子系统必须要知道进程是位于哪一个cgroup,所以在struct task_struct和cgroup中存在一种映射。 1.1 task_struc…

【数据结构进阶】二叉平衡树

一、 二叉平衡树 概念 二叉搜索树有称 二叉排序树&#xff0c;它也可以是一个空树。 如果它的左子树不为空&#xff0c;则左子树上所有结点的值都小于根结点的值如果他的右子树不为空&#xff0c;则右子树上所有结点的值都大于根结点的值它的左右子树也分别是二叉搜索树 由…

【Acwing 周赛#82】AcWing 4783. 多米诺骨牌

目录 4782.第k个数 4783. 多米诺骨牌 - bfs 记录时间 4782.第k个数 java大顶堆 import java.util.*;public class Main {public static void main(String[] args ){Scanner scnew Scanner(System.in);int nsc.nextInt(),ksc.nextInt();k--;PriorityQueue<Integer> qn…

HarmonyOS玩转ArkUI动效 - 水母动画

前言 本文会详细讲解我参加&#xff1a; HarmonyOS【挑战赛第三期】玩转ArkUI动效的项目 我的参赛项目源码&#xff1a;【挑战赛第三期】JellyfishAnimation 动画效果参考自&#xff1a;cassie-codes的水母SVG 华为鸿蒙已经放弃Java作为鸿蒙的开发语言&#xff0c;开发了一个申…

基于java+springmvc+mybatis+vue+mysql的校运会管理系统小程序

项目介绍 运动是伴随人类一生的一种行为和活动&#xff0c;只有不断的运动才能够彰显生命的意义&#xff0c;尤其是当代的学生&#xff0c;课业繁重往往忽略了体育锻炼&#xff0c;为了能够提高学子们对体育运动的积极性&#xff0c;基本所有的高校每年都会定期的举办运动会。…

软件设计师常考知识点

絮絮叨叨&#xff1a;哈喽大家好&#xff5e;这里是一口八宝周[送花花]。前段时间闲来无事报考了今年的软件设计师考试&#xff0c;觉得凭借自己“自律”的学习&#xff0c;一定可以把书看完&#xff0c;把题刷完顺利上岸&#x1f60e;。 书确实没看完&#xff0c;但是视频学完…

MySQL or条件命中

需求如下&#xff1a;当写入SQL语句中有任意一个字段在数据库中存在时&#xff0c;不可写入&#xff0c;并返回具体的重复字段。 使用Java Steam处理数据集循环执行SQL需要多次执行SQL&#xff0c;适合单条件索引的情况下使用&#xff0c;现状是想执行少量的SQL实现需求&#…

操作系统,计算机网络,数据库刷题笔记12

操作系统&#xff0c;计算机网络&#xff0c;数据库刷题笔记12 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xf…

u盘/移动硬盘的视频文件出现损坏怎么办?修复损坏视频办法分享!

一般情况下&#xff0c;视频文件都是比较大&#xff0c;如果直接存放于电脑&#xff0c;就会占用比较大的存储空间。不少小伙伴都会把它存放于U盘或者移动硬盘&#xff0c;而且作为一种便携式硬盘&#xff0c;可以在各电脑之间使用&#xff0c;非常方便。但这也造成文件很容易出…

【OpenCV-Python】教程:6-4 Depth Map from Stereo Images 立体图像的深度图

OpenCV Python Depth Map from Stereo Images 立体图像的深度图 【目标】 通过立体图像创建一个深度图 【理论】 上一节中&#xff0c;我们学习了一些基本概念&#xff0c;如对极约束和其他一些相关术语。我们还可以看到&#xff0c;如果我们有同一个场景的两张图像&#x…

无尘室中高效过滤器的更换时间

广州特耐苏净化设备有限公司详细介绍无尘室中高效过滤器使用多久需要更换 为了保证产品的生产质量和人员工作环境的舒适性&#xff0c;无尘室对环境的湿度、温度、新鲜空气量、状态、照明等都有严格的规定。无尘室系统一般配备三级过滤器的空气净化系统&#xff0c;采用初效、…

西门子博途与上位机TCPIP通信

1、PLC硬件IP设定及组态如下图&#xff1a; 堆垛机 1号机 IP地址&#xff1a;190.20.0.72 掩码 255.255.255.0 2、PLC与上位机TCP网络连接组态如下图&#xff1a; WCS上位机IP地址设定 IP地址&#xff1a;190.20.0.&#xff12;&#xff15;&#xff10; 掩码 255.255.255.0…

[强网杯 2019]Upload

一、信息收集 打开界面是一个登陆&#xff0c;界面随便注册一个然后登陆 然后是一个上传文件的操作&#xff0c;点击php后发现的回显 然后又上传了一个图片文件&#xff0c;显示出了照片的存放路径&#xff0c;这里可以上传图片码 然后抓包看一下&#xff0c;base64解码 a:5:{…

想要学习次世代3d建模,需要用到哪些软件,制作流程是什么?

在校大学生想要学习游戏次世代建模&#xff0c;首先就要先了解次世代是什么&#xff1f;制作建模所需要用到的软件有哪些&#xff0c;随着次世代游戏的不断发展&#xff0c;游戏美术制作流程也迎来了全新的制作方式&#xff0c;像ZBrush、SP等软件就解放了我们的双手和制作方式…

Spark-Spark Sql(DataFrame、DataSet、Scala代码开发、数据的加载和保存)

文章目录Spark SqlHive and SparkSQL特点DataFrame 是什么DataSet 是什么核心编程新的起点DataFrame创建SQL语法DSL 语法RDD > DataFrameDataFrame > RDDDataSet创建RDD > DataSetDataSet > RDDDataFrame > DataSetDataSet > DataFrameRDD、DataFrame、DataS…

CSND近期推出的猿如意到底有没有必要安装

可能很多人还不知道猿如意是什么&#xff0c;先给大家科普一下 猿如意 工具代码&#xff0c;一搜就有 程序员的如意兵器 猿如意是一款面向开发者的辅助开发工具箱&#xff0c;包含了效率工具、开发工具下载&#xff0c;教程文档&#xff0c;代码片段搜索&#xff0c;全网搜索等…

Raydium被盗造成巨额损失,但Zebec Protocol以及$ZBC并未受影响

在12月17日&#xff0c;Solana上最大的DEX Raydium因木马攻击导致流动性资金池所有者帐户的私钥泄露&#xff0c;攻击者访问了资金池所有者帐户&#xff0c;然后能够调用withdraw pnl函数&#xff0c;该函数用于收集池中掉期所赚取的交易/协议费用。 而受影响的资金池包括SOL-…

论文翻译:LiDAR Based Negative Obstacle Detection for Field Autonomous Land Vehicles

论文地址&#xff1a;https://onlinelibrary.wiley.com/doi/full/10.1002/rob.21609?saml_referrer &#xff08;机翻&#xff0c;自己保存观看的&#xff09; Abstract&#xff1a; 野外自动驾驶陆地车辆&#xff08;ALV&#xff09;的负障碍是指沟渠、坑或具有负坡度的地形&…

在Vue3这样子写页面更快更高效

前言 在开发管理后台过程中&#xff0c;一定会遇到不少了增删改查页面&#xff0c;而这些页面的逻辑大多都是相同的&#xff0c;如获取列表数据&#xff0c;分页&#xff0c;筛选功能这些基本功能。而不同的是呈现出来的数据项。还有一些操作按钮。 对于刚开始只有 1&#xff…

【JavaScrip】for循环

文章目录for循环案例1&#xff1a;两数相加案例2&#xff1a;绘制九九乘法表案例3&#xff1a;水仙花数案例4&#xff1a;绘制菱形案例5:计算表达式的结果break和continuefor循环 for循环理解成循环的一种简洁(结构)的写法. 语法结构&#xff1a; for(初始化;限制条件;变量值的…