RocketMQ源码 Broker-ConsumerFilterManager 消费者数据过滤管理组件源码分析

前言

ConsumerFilterManager 继承了ConfigManager配置管理组件,拥有将内存数据持久化到磁盘文件consumerFilter.json的能力。它主要负责,对在消费者拉取消息时,进行消息数据过滤,且只针对使用表达式过滤的消费者有效。


源码版本:4.9.3

源码架构图

核心数据结构

可以看到内存中维护了 topic -> consumer group -> ConsumerFilterData 映射关系的数据结构。

/**
 * Consumer filter data manager.Just manage the consumers use expression filter.
 * 消费者过滤数据管理组件。只管理使用表达式过滤的消费者。
 */
public class ConsumerFilterManager extends ConfigManager {

    // 核心数据结构:topic -> consumer group -> ConsumerFilterData
    private ConcurrentMap<String/*Topic*/, FilterDataMapByTopic>
        filterDataByTopic = new ConcurrentHashMap<String/*Topic*/, FilterDataMapByTopic>(256);

    private transient BrokerController brokerController;

    // 布隆过滤器
    private transient BloomFilter bloomFilter;
}

深入看下 FilterDataMapByTopic 类,是上面数据结构的一个子集,维护了 消费组 -> 消费组过滤数据映射关系。

    public static class FilterDataMapByTopic {

        // 核心数据结构:consumer group -> ConsumerFilterData
        private ConcurrentMap<String/*consumer group*/, ConsumerFilterData>
            groupFilterData = new ConcurrentHashMap<String, ConsumerFilterData>();

        private String topic;
    }

在深入一步,看下 ConsumerFilterData 的数据结构,包含了全部与消费者过滤有关的关键信息。

/**
 * Filter data of consumer.
 */
public class ConsumerFilterData {
    // 消费组
    private String consumerGroup;
    // 主题
    private String topic;
    // 过滤器表达式
    private String expression;
    // 过滤器类型
    private String expressionType;
    // 过滤器编译后的表达式
    private transient Expression compiledExpression;
    // 过滤器创建时间
    private long bornTime;
    // 过滤器过期时间
    private long deadTime = 0;
    // 过滤器版本
    private long version;
    // 布隆过滤器数据
    private BloomFilterData bloomFilterData;
    // 客户端版本
    private long clientVersion;
}

核心数据行为

从下面代码可以看到,ConsumerFilterManager的行为主要是注册订阅、取消订阅、清理过期订阅、序列化、反序列化等维护内存元数据的行为。过滤行为不在这个组件里体现,在其他调用方法中会有具体使用方式。

/**
 * Consumer filter data manager.Just manage the consumers use expression filter.
 * 消费者过滤数据管理组件。只管理使用表达式过滤的消费者。
 */
public class ConsumerFilterManager extends ConfigManager {

    private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.FILTER_LOGGER_NAME);

    private static final long MS_24_HOUR = 24 * 3600 * 1000;

    // 核心数据结构:topic -> consumer group -> ConsumerFilterData
    private ConcurrentMap<String/*Topic*/, FilterDataMapByTopic>
        filterDataByTopic = new ConcurrentHashMap<String/*Topic*/, FilterDataMapByTopic>(256);

    private transient BrokerController brokerController;
    // 布隆过滤器
    private transient BloomFilter bloomFilter;

    public ConsumerFilterManager() {
        // just for test
        this.bloomFilter = BloomFilter.createByFn(20, 64);
    }

    public ConsumerFilterManager(BrokerController brokerController) {
        this.brokerController = brokerController;
        this.bloomFilter = BloomFilter.createByFn(
            brokerController.getBrokerConfig().getMaxErrorRateOfBloomFilter(),
            brokerController.getBrokerConfig().getExpectConsumerNumUseFilter()
        );
        // then set bit map length of store config.
        brokerController.getMessageStoreConfig().setBitMapLengthConsumeQueueExt(
            this.bloomFilter.getM()
        );
    }

    /**
     * Build consumer filter data.Be care, bloom filter data is not included.
     *
     * @return maybe null
     */
    public static ConsumerFilterData build(final String topic, final String consumerGroup,
        final String expression, final String type,
        final long clientVersion) {
        if (ExpressionType.isTagType(type)) {
            return null;
        }

        ConsumerFilterData consumerFilterData = new ConsumerFilterData();
        consumerFilterData.setTopic(topic);
        consumerFilterData.setConsumerGroup(consumerGroup);
        consumerFilterData.setBornTime(System.currentTimeMillis());
        consumerFilterData.setDeadTime(0);
        consumerFilterData.setExpression(expression);
        consumerFilterData.setExpressionType(type);
        consumerFilterData.setClientVersion(clientVersion);
        try {
            consumerFilterData.setCompiledExpression(
                FilterFactory.INSTANCE.get(type).compile(expression)
            );
        } catch (Throwable e) {
            log.error("parse error: expr={}, topic={}, group={}, error={}", expression, topic, consumerGroup, e.getMessage());
            return null;
        }

        return consumerFilterData;
    }

    /**
     * 在指定消费组注册消费者过滤数据
     * @param consumerGroup
     * @param subList
     */
    public void register(final String consumerGroup, final Collection<SubscriptionData> subList) {
        for (SubscriptionData subscriptionData : subList) {
            register(
                subscriptionData.getTopic(),
                consumerGroup,
                subscriptionData.getSubString(),
                subscriptionData.getExpressionType(),
                subscriptionData.getSubVersion()
            );
        }

        // make illegal topic dead.
        Collection<ConsumerFilterData> groupFilterData = getByGroup(consumerGroup);

        Iterator<ConsumerFilterData> iterator = groupFilterData.iterator();
        while (iterator.hasNext()) {
            ConsumerFilterData filterData = iterator.next();

            boolean exist = false;
            for (SubscriptionData subscriptionData : subList) {
                if (subscriptionData.getTopic().equals(filterData.getTopic())) {
                    exist = true;
                    break;
                }
            }

            if (!exist && !filterData.isDead()) {
                filterData.setDeadTime(System.currentTimeMillis());
                log.info("Consumer filter changed: {}, make illegal topic dead:{}", consumerGroup, filterData);
            }
        }
    }

    public boolean register(final String topic, final String consumerGroup, final String expression,
        final String type, final long clientVersion) {
        // 不支持tag类型
        if (ExpressionType.isTagType(type)) {
            return false;
        }

        if (expression == null || expression.length() == 0) {
            return false;
        }

        // 获取topic对应的消费者过滤数据
        FilterDataMapByTopic filterDataMapByTopic = this.filterDataByTopic.get(topic);

        if (filterDataMapByTopic == null) {
            FilterDataMapByTopic temp = new FilterDataMapByTopic(topic);
            FilterDataMapByTopic prev = this.filterDataByTopic.putIfAbsent(topic, temp);
            filterDataMapByTopic = prev != null ? prev : temp;
        }
        // 创建布隆过滤器数据
        BloomFilterData bloomFilterData = bloomFilter.generate(consumerGroup + "#" + topic);
        // 注册过滤数据到topic
        return filterDataMapByTopic.register(consumerGroup, expression, type, bloomFilterData, clientVersion);
    }

    // 取消注册消费者过滤数据
    public void unRegister(final String consumerGroup) {
        for (Entry<String, FilterDataMapByTopic> entry : filterDataByTopic.entrySet()) {
            entry.getValue().unRegister(consumerGroup);
        }
    }

    public ConsumerFilterData get(final String topic, final String consumerGroup) {
        if (!this.filterDataByTopic.containsKey(topic)) {
            return null;
        }
        if (this.filterDataByTopic.get(topic).getGroupFilterData().isEmpty()) {
            return null;
        }

        return this.filterDataByTopic.get(topic).getGroupFilterData().get(consumerGroup);
    }

    // 获取消费组下所有过滤数据
    public Collection<ConsumerFilterData> getByGroup(final String consumerGroup) {
        Collection<ConsumerFilterData> ret = new HashSet<ConsumerFilterData>();

        Iterator<FilterDataMapByTopic> topicIterator = this.filterDataByTopic.values().iterator();
        while (topicIterator.hasNext()) {
            FilterDataMapByTopic filterDataMapByTopic = topicIterator.next();

            Iterator<ConsumerFilterData> filterDataIterator = filterDataMapByTopic.getGroupFilterData().values().iterator();

            while (filterDataIterator.hasNext()) {
                ConsumerFilterData filterData = filterDataIterator.next();

                if (filterData.getConsumerGroup().equals(consumerGroup)) {
                    ret.add(filterData);
                }
            }
        }

        return ret;
    }

    // 获取topic下所有过滤数据
    public final Collection<ConsumerFilterData> get(final String topic) {
        if (!this.filterDataByTopic.containsKey(topic)) {
            return null;
        }
        if (this.filterDataByTopic.get(topic).getGroupFilterData().isEmpty()) {
            return null;
        }

        return this.filterDataByTopic.get(topic).getGroupFilterData().values();
    }

    public BloomFilter getBloomFilter() {
        return bloomFilter;
    }

    @Override
    public String encode() {
        return encode(false);
    }

    @Override
    public String configFilePath() {
        if (this.brokerController != null) {
            // 配置存储路径 config/consumerFilter.json
            return BrokerPathConfigHelper.getConsumerFilterPath(
                this.brokerController.getMessageStoreConfig().getStorePathRootDir()
            );
        }
        return BrokerPathConfigHelper.getConsumerFilterPath("./unit_test");
    }

    // 将json字符串反序列化为ConsumerFilterManager对象
    @Override
    public void decode(final String jsonString) {
        ConsumerFilterManager load = RemotingSerializable.fromJson(jsonString, ConsumerFilterManager.class);
        if (load != null && load.filterDataByTopic != null) {
            boolean bloomChanged = false;
            for (Entry<String, FilterDataMapByTopic> entry : load.filterDataByTopic.entrySet()) {
                FilterDataMapByTopic dataMapByTopic = entry.getValue();
                if (dataMapByTopic == null) {
                    continue;
                }

                for (Entry<String, ConsumerFilterData> groupEntry : dataMapByTopic.getGroupFilterData().entrySet()) {

                    ConsumerFilterData filterData = groupEntry.getValue();

                    if (filterData == null) {
                        continue;
                    }

                    try {
                        filterData.setCompiledExpression(
                                FilterFactory.INSTANCE.get(filterData.getExpressionType()).compile(filterData.getExpression())
                        );
                    } catch (Exception e) {
                        log.error("load filter data error, " + filterData, e);
                    }

                    // check whether bloom filter is changed
                    // if changed, ignore the bit map calculated before.
                    if (!this.bloomFilter.isValid(filterData.getBloomFilterData())) {
                        bloomChanged = true;
                        log.info("Bloom filter is changed!So ignore all filter data persisted! {}, {}", this.bloomFilter, filterData.getBloomFilterData());
                        break;
                    }

                    log.info("load exist consumer filter data: {}", filterData);

                    if (filterData.getDeadTime() == 0) {
                        // we think all consumers are dead when load
                        long deadTime = System.currentTimeMillis() - 30 * 1000;
                        filterData.setDeadTime(
                                deadTime <= filterData.getBornTime() ? filterData.getBornTime() : deadTime
                        );
                    }
                }
            }

            if (!bloomChanged) {
                this.filterDataByTopic = load.filterDataByTopic;
            }
        }
    }

    // 将ConsumerFilterManager对象序列化为json字符串
    @Override
    public String encode(final boolean prettyFormat) {
        // clean
        {
            clean();
        }
        return RemotingSerializable.toJson(this, prettyFormat);
    }

    // 清理过期的过滤数据
    public void clean() {
        Iterator<Map.Entry<String, FilterDataMapByTopic>> topicIterator = this.filterDataByTopic.entrySet().iterator();
        while (topicIterator.hasNext()) {
            Map.Entry<String, FilterDataMapByTopic> filterDataMapByTopic = topicIterator.next();

            Iterator<Map.Entry<String, ConsumerFilterData>> filterDataIterator
                = filterDataMapByTopic.getValue().getGroupFilterData().entrySet().iterator();

            while (filterDataIterator.hasNext()) {
                Map.Entry<String, ConsumerFilterData> filterDataByGroup = filterDataIterator.next();

                ConsumerFilterData filterData = filterDataByGroup.getValue();
                if (filterData.howLongAfterDeath() >= (this.brokerController == null ? MS_24_HOUR : this.brokerController.getBrokerConfig().getFilterDataCleanTimeSpan())) {
                    log.info("Remove filter consumer {}, died too long!", filterDataByGroup.getValue());
                    filterDataIterator.remove();
                }
            }

            if (filterDataMapByTopic.getValue().getGroupFilterData().isEmpty()) {
                log.info("Topic has no consumer, remove it! {}", filterDataMapByTopic.getKey());
                topicIterator.remove();
            }
        }
    }

    public ConcurrentMap<String, FilterDataMapByTopic> getFilterDataByTopic() {
        return filterDataByTopic;
    }

    public void setFilterDataByTopic(final ConcurrentHashMap<String, FilterDataMapByTopic> filterDataByTopic) {
        this.filterDataByTopic = filterDataByTopic;
    }

    public static class FilterDataMapByTopic {

        // 核心数据结构:consumer group -> ConsumerFilterData
        private ConcurrentMap<String/*consumer group*/, ConsumerFilterData>
            groupFilterData = new ConcurrentHashMap<String, ConsumerFilterData>();

        private String topic;

        public FilterDataMapByTopic() {
        }

        public FilterDataMapByTopic(String topic) {
            this.topic = topic;
        }

        // 取消注册某个消费组的过滤器
        public void unRegister(String consumerGroup) {
            if (!this.groupFilterData.containsKey(consumerGroup)) {
                return;
            }

            ConsumerFilterData data = this.groupFilterData.get(consumerGroup);

            if (data == null || data.isDead()) {
                return;
            }

            long now = System.currentTimeMillis();

            log.info("Unregister consumer filter: {}, deadTime: {}", data, now);

            data.setDeadTime(now);
        }

        public boolean register(String consumerGroup, String expression, String type, BloomFilterData bloomFilterData,
            long clientVersion) {
            ConsumerFilterData old = this.groupFilterData.get(consumerGroup);

            if (old == null) {
                // 构建过滤器数据
                ConsumerFilterData consumerFilterData = build(topic, consumerGroup, expression, type, clientVersion);
                if (consumerFilterData == null) {
                    return false;
                }
                // 设置布隆过滤器
                consumerFilterData.setBloomFilterData(bloomFilterData);

                // 放入内存数据结构
                old = this.groupFilterData.putIfAbsent(consumerGroup, consumerFilterData);
                if (old == null) {
                    log.info("New consumer filter registered: {}", consumerFilterData);
                    return true;
                } else {
                    if (clientVersion <= old.getClientVersion()) {
                        if (!type.equals(old.getExpressionType()) || !expression.equals(old.getExpression())) {
                            log.warn("Ignore consumer({} : {}) filter(concurrent), because of version {} <= {}, but maybe info changed!old={}:{}, ignored={}:{}",
                                consumerGroup, topic,
                                clientVersion, old.getClientVersion(),
                                old.getExpressionType(), old.getExpression(),
                                type, expression);
                        }
                        if (clientVersion == old.getClientVersion() && old.isDead()) {
                            reAlive(old);
                            return true;
                        }

                        return false;
                    } else {
                        this.groupFilterData.put(consumerGroup, consumerFilterData);
                        log.info("New consumer filter registered(concurrent): {}, old: {}", consumerFilterData, old);
                        return true;
                    }
                }
            } else {
                // 当前版本号小于旧的版本号
                if (clientVersion <= old.getClientVersion()) {
                    if (!type.equals(old.getExpressionType()) || !expression.equals(old.getExpression())) {
                        log.info("Ignore consumer({}:{}) filter, because of version {} <= {}, but maybe info changed!old={}:{}, ignored={}:{}",
                            consumerGroup, topic,
                            clientVersion, old.getClientVersion(),
                            old.getExpressionType(), old.getExpression(),
                            type, expression);
                    }
                    if (clientVersion == old.getClientVersion() && old.isDead()) {
                        reAlive(old);
                        return true;
                    }

                    return false;
                }
                // 新版本号大于旧的版本号
                boolean change = !old.getExpression().equals(expression) || !old.getExpressionType().equals(type);
                if (old.getBloomFilterData() == null && bloomFilterData != null) {
                    change = true;
                }
                if (old.getBloomFilterData() != null && !old.getBloomFilterData().equals(bloomFilterData)) {
                    change = true;
                }

                // if subscribe data is changed, or consumer is died too long.
                if (change) {
                    // 构建过滤器数据
                    ConsumerFilterData consumerFilterData = build(topic, consumerGroup, expression, type, clientVersion);
                    if (consumerFilterData == null) {
                        // new expression compile error, remove old, let client report error.
                        this.groupFilterData.remove(consumerGroup);
                        return false;
                    }
                    consumerFilterData.setBloomFilterData(bloomFilterData);
                    // 设置过滤器数据
                    this.groupFilterData.put(consumerGroup, consumerFilterData);

                    log.info("Consumer filter info change, old: {}, new: {}, change: {}",
                        old, consumerFilterData, change);

                    return true;
                } else {
                    // 版本号一致,更新过滤器数据
                    old.setClientVersion(clientVersion);
                    if (old.isDead()) {
                        reAlive(old);
                    }
                    return true;
                }
            }
        }

        protected void reAlive(ConsumerFilterData filterData) {
            long oldDeadTime = filterData.getDeadTime();
            filterData.setDeadTime(0);
            log.info("Re alive consumer filter: {}, oldDeadTime: {}", filterData, oldDeadTime);
        }

        public final ConsumerFilterData get(String consumerGroup) {
            return this.groupFilterData.get(consumerGroup);
        }

        public final ConcurrentMap<String, ConsumerFilterData> getGroupFilterData() {
            return this.groupFilterData;
        }

        public void setGroupFilterData(final ConcurrentHashMap<String, ConsumerFilterData> groupFilterData) {
            this.groupFilterData = groupFilterData;
        }

        public String getTopic() {
            return topic;
        }

        public void setTopic(final String topic) {
            this.topic = topic;
        }
    }
}

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

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

相关文章

README

spark基础入门 环境搭建 localstandlonespark ha spark code spark corespark sqlspark streaming 环境搭建 准备工作 创建安装目录 mkdir /opt/soft cd /opt/soft下载scala wget https://downloads.lightbend.com/scala/2.13.12/scala-2.13.12.tgz -P /opt/soft解压scala…

Python内置类属性`__cmp__`属性的使用教程

概要 在Python中&#xff0c;__cmp__属性是一个特殊的方法&#xff0c;用于自定义类的实例之间的比较方式。深入了解和熟练运用这一特性&#xff0c;可以使自定义类更加灵活和强大。本教程将详细介绍__cmp__的基本概念、高级用法以及一些注意事项&#xff0c;通过丰富的示例代…

Android多进程和跨进程通讯方式

前言 我们经常开发过程中经常会听到线程和进程&#xff0c;在讲述Android进程多进程前我打算先简单梳理一下这俩者。 了解什么是进程与线程 进程&#xff1a; 系统中正在运行的一个应用程序&#xff0c;某个程序一旦运行就是一个进程&#xff0c;是资源分配的最小单位&#…

【TC3xx】GETH

目录 一、RGMII 二、SMI接口 三、TC3xx MCAL 3.1 MCU 3.2 Port 3.3 DMA 3.4 中断配置 3.5 ETH 3.6 集成 一、RGMII TC3xx支持MII/RMII/RGMII三种以太网数据通信接口。其中RGMII经常用于MAC和MAC之间&#xff0c;或MAC与PHY之间的通信&#xff0c;RGMII的带宽可以是10M…

vue2-安装elementUI时警告

警告内容&#xff1a;npm WARN deprecated core-js2.6.12: core-js<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up …

【通俗易懂】基于fabric8io操作k8s集群实战(pod、deployment、service、volume)

目录 前言一、基于fabric8io操作pod1.1 yaml创建pod1.2 fabric8io创建pod案例 二、基于fabric8io创建Service&#xff08;含Deployment&#xff09;2.1 yaml创建Service和Deployment2.2 fabric8io创建service案例 三、基于fabric8io操作Volume3.1 yaml配置挂载存储卷3.2 基于fa…

JAVA:注册表窗口的实现

目录 题目要求&#xff1a; 思路大意&#xff1a; 窗体的实现&#xff1a; 窗口A&#xff1a; 窗口B&#xff1a; 窗体之间的构思&#xff1a; 关键代码的实现&#xff1a; 窗口A&#xff1a; 封装列表&#xff1a; 窗口B&#xff1a; 题目要求&#xff1a; 使用…

国产数据库适配-南大通用(Gbase)问题整理

Gbase 函数 [GBase 8s 教程]GBase 8s 常用函数、表达式_gbase函数-CSDN博客 Gbase 8s hibernate方言包下载&#xff1a; Index of /dl/hibernate select * from sysmaster:sysdbslocale 导出数据 su - gbasedbt export DB_LOCALEzh_CN.57372 export CLIENT_LOCALEzh_cn…

复制粘贴——QT实现原理

复制粘贴——QT实现原理 QT 剪贴板相关类 QClipboard 对外通用的剪贴板类&#xff0c;一般通过QGuiApplication::clipboard() 来获取对应的剪贴板实例。 // qtbase/src/gui/kernel/qclipboard.h class Q_GUI_EXPORT QClipboard : public QObject {Q_OBJECT private:explici…

如何制作一份吸引人的家具展示册

对于家具销售商来说&#xff0c;一份吸引人的家具展示册是至关重要的。它不仅可以帮助销售商展示产品&#xff0c;还可以吸引潜在客户的注意力&#xff0c;并激发他们的购买欲望。 那现在就有人问我&#xff0c;新手该怎么办&#xff1f;不会制作&#xff1f;其实这个问题早就…

玩转大数据17:数据采集与实时流处理的架构设计

引言 随着大数据技术的不断发展&#xff0c;数据采集与实时流处理成为了许多企业和组织的核心需求。本文将介绍一种数据采集与实时流处理的架构设计&#xff0c;包括数据采集、实时流处理、数据存储和数据分析等方面。 一、数据采集 数据采集是整个架构的基础&#xff0c;它…

用栈解决迷宫问题

思想 使用栈来解决迷宫问题的思想是通过深度优先搜索算法来探索迷宫中的路径。栈的特点是后进先出&#xff0c;这正好符合深度优先搜索的思想&#xff0c;即先探索一个方向直到无法继续为止&#xff0c;然后回溯到上一个节点&#xff0c;再探索其他方向。 具体来说&#xff0…

ubuntu20.04里面安装目标检测数据标注软件labelImg的详细过程

1.在github克隆仓库到本地 地址&#xff1a;https://github.com/Ruolingdeng/labelImg.git 或者百度网盘下载 链接&#xff1a;https://pan.baidu.com/s/1p-478j5WOTN0TKmv3qh-YQ?pwdl8bj 提取码&#xff1a;l8bj 2、进入到labelimg的文件夹&#xff0c;安装pyqt相关依赖包 …

C# 实现图片的压缩和改变大小png、jpg和gif

环境 .net6 Magick.NET-Q16-AnyCPU 13.5 Magick.NET源码 代码 using ImageMagick;namespace ImageCompress {internal class Program{static void Main(string[] args){string inputPath "imgloading.gif"; // 输入的GIF文件路径 string outputPath "im…

vue2+datav可视化数据大屏(3)

接上一节所说&#xff0c;当我们将接口封装完了后&#xff0c;我们需要给大屏进行内容填充啦 1,新建组件 &#x1f4d3; 我们在ser-views文件夹下新建9个vue组件&#xff0c;如下图所示&#xff0c;我给编号为1到9 &#x1f4d3;在组件里写入内容我是第一块...一次类推&#x…

小机器人,电子锁,牙刷,表类开关,磁阀开关等一些安防直流驱动的选型介绍分析 5V,大电流,小封装

安防监控是一门被人们日益重视的新兴行业&#xff0c;就目前发展来看&#xff0c;应用普及程度越来越广&#xff0c;科技含量也越来越高&#xff0c;几乎所有高新科技都可促进其发展&#xff0c;尤其是信息时代的来临&#xff0c;更为该行业的发展提供契机。其中安防领域最为典…

【RTOS学习】FreeRTOS中的链表 | 堆的管理

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《RTOS学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f969;FreeRTOS中的链表&#x1f95e;初始化&#x1f95e;尾部插入&#x1f95e;按顺…

JS对象笔记

对象声明 对象也只是一种数据类型/字面值。写对象这个字面值有两种写法&#xff0c;一种是普通的对象&#xff0c;这种对象用new 构造函数&#xff08;&#xff09;&#xff0c;另一种是JS内特有的json对象。这个对象是直接{}就代表对象。且也是在堆内。 对象的构成 无论是上…

Ransac 算法的探索和应用

Ransac 算法python 应用和实现 Ransac 算法是一种常用的图像匹配算法&#xff0c;在参数估计领域也经常被使用到。针对估计各种曲线的鲁棒模型参数&#xff0c;效果显著。这里对ransac算法进行某些探索。 python program: import numpy as np import matplotlib.pyplot as p…

Profibus、Profinet、Ethernet有什么区别?

PROFINET 是一种新的以太网通讯系统&#xff0c;是由西门子公司和 Profibus 用户协会开发。 PROFINET 具有多制造商产品之间的通讯能力&#xff0c;自动化和工程模式&#xff0c;并针对分布式智能自动化系统进行了优化。其应用结果能够大大节省配置和调试费用。 PROFINET 系统集…