RocketMQ5.0.0消息消费<二> _ 消息队列负载均衡机制

news2024/9/28 3:20:41

目录

一、消费队列负载均衡概览

二、消费队列负载均衡实现

1. 负载均衡UML

2. 启动RebalanceService线程

3. PUSH模式负载均衡

三、负载均衡策略

四、参考资料


一、消费队列负载均衡概览

        RocketMQ默认一个主题下有4个消费队列,集群模式下同一消费组内要求每个消费队列在同一时刻只能被一个消费者消费。那么集群模式下多个消费者是如何负载主题的多个消费队列呢?并且如果有新的消费者加入时,消费队列又会如何重新分布呢?

        RocketMQ消费端每20s周期执行一次消费队列重新负载,每次进行队列重新负载时会从Broker实时查询当前消费组内所有消费者,并且对消息队列、消费者列表进行排序,这样新加入的消费者就会在队列重新分布时分配到消费队列从而消费消息。如下所示,是消息拉取与消费队列负载均衡的交互图。

消息拉取与消费队列负载均衡的交互流程

二、消费队列负载均衡实现

1. 负载均衡UML

2. 启动RebalanceService线程

        参考《RocketMQ5.0.0消息消费<一> _ PUSH模式的消息拉取》
章节,消费者启动时,当前消费者添加到MQClientInstance#consumerTable属性中,并启动MQClientInstance实例。启动MQClientInstance实例时,会启动org.apache.rocketmq.client.impl.consumer.RebalanceService消费队列负载均衡服务线程。下图所示是该线程run()调用链。

        以下代码是MQClientInstance维护整个JVM的所有生产者和消费者的属性。

// 生产者容器
private final ConcurrentMap<String/* 生产组 */, MQProducerInner> producerTable = new ConcurrentHashMap<>();
// 消费者容器
private final ConcurrentMap<String/* 消费组 */, MQConsumerInner> consumerTable = new ConcurrentHashMap<>();

        org.apache.rocketmq.client.impl.consumer.RebalanceService#run()周期20s执行负载均衡任务。-Drocketmq.client.rebalance. waitlnterval参数修改执行周期,默认20s

@Override
public void run() {
    log.info(this.getServiceName() + " service started");

    while (!this.isStopped()) {
        // 线程等待20s
        this.waitForRunning(waitInterval);
        // topic下消费队列的负载均衡
        this.mqClientFactory.doRebalance();
    }

    log.info(this.getServiceName() + " service end");
}

        org.apache.rocketmq.client.impl.factory.MQClientInstance#doRebalance方法遍历MQClientInstance实例中所有消费组下消费者。每一个消费者DefaultMQPushConsumerImpl拥有一个org.apache.rocketmq.client.impl.consumer.RebalanceImpl对象(实现负载均衡),给每个消费者找到一个消费队列(重新负载)

// 消费队列负载均衡
public void doRebalance() {
    for (Map.Entry<String/* 消费组 */, MQConsumerInner> entry : this.consumerTable.entrySet()) {
        // 获取消费者
        MQConsumerInner impl = entry.getValue();
        if (impl != null) {
            try {
                // 消费者负载均衡
                impl.doRebalance();
            } catch (Throwable e) {
                log.error("doRebalance exception", e);
            }
        }
    }
}

3. PUSH模式负载均衡

        org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#doRebalance是PUSH模式的负载均衡的入口方法,其调用链如下。

        每个消费者DefaultMQPushConsumerImpl拥有一个RebalanceImpl对象,其中org.apache.rocketmq.client.impl.consumer.RebalanceImpl#doRebalance方法是对消费者的所有订阅主题进行负载均衡,即:消费者的所有订阅主题重新分配一个或多个消费队列来进行消费。其代码如下。注意事项:

  • Map<String/* topic */, SubscriptionData> subTable:获取当前消费者订阅的主题信息;
  • rebalanceByTopic():每个主题进行重新负载均衡
/**
 * 对消费者订阅的每个topic进行消费队列重新负载
 * step1:获取消费者订阅的主题信息,注意:消费者可以订阅多个主题
 * step2:遍历消费者的每个topic
 * step3:消费者订阅的topic进行消费队列重新负载
 *        {@link RebalanceImpl#rebalanceByTopic(String, boolean)}
 * @param isOrder 是否顺序消息
 * @return true所有topic重新负载成功
 */
public boolean doRebalance(final boolean isOrder) {
    boolean balanced = true;
    // 获取消费者订阅的主题信息,注意:消费者可以订阅多个主题
    Map<String/* topic */, SubscriptionData> subTable = this.getSubscriptionInner();
    if (subTable != null) {
        // 遍历消费者的每个topic
        for (final Map.Entry<String, SubscriptionData> entry : subTable.entrySet()) {
            final String topic = entry.getKey();
            try {
                if (!clientRebalance(topic) && tryQueryAssignment(topic)) {
                    balanced = this.getRebalanceResultFromBroker(topic, isOrder);
                } else {
                    // 消费者订阅的topic进行消费队列重新负载
                    balanced = this.rebalanceByTopic(topic, isOrder);
                }
            } catch (Throwable e) {
                if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    log.warn("rebalance Exception", e);
                    balanced = false;
                }
            }
        }
    }

    this.truncateMessageQueueNotMyTopic();

    return balanced;
}

        org.apache.rocketmq.client.impl.consumer.RebalanceImpl#rebalanceByTopic方法是对每个主题进行重新负载均衡的核心逻辑,如下代码所示。 这里介绍集群模式下负载均衡,注意事项:

  • MQClientInstance#findConsumerIdList():从Broker上获取所有订阅该topic且同属一个消费组的所有消费者ID。
  • 对消费队列、消费者ID集合排序:原因是同一个消费组内视图一致,确保同一个消费队列不会被多个消费者分配
  • AllocateMessageQueueStrategy#allocate:根据均衡策略,获取当前消费者的消息队列。
  • RebalanceImpl#updateProcessQueueTableInRebalance:重新负载后,消费者对应的分配后的消息队列是否变化: 新增、删除(其他消费者占用)
/**
 * 消费者订阅的topic进行消费队列重新负载
 * 集群模式下的步骤:
 * step1:从主题订阅信息缓存表(topicSubscribeInfoTable)中获取当前topic的消费队列
 * step2:从Broker上获取所有订阅该topic + 同属一个消费组 的所有消费者ID
 * step3:对消费队列、消费者ID排序,很重要,原因是:同一个消费组内视图一致,确保同一个消费队列不会被多个消费者分配
 * step4:根据均衡策略,获取当前消费者的消息队列
 *        {@link AllocateMessageQueueStrategy#allocate}
 * step5:消费者对应的分配消息队列是否变化: 新增、删除(其他消费者占用)
 *        {@link RebalanceImpl#updateProcessQueueTableInRebalance}
 * @param topic 主题
 * @param isOrder 是否是顺序消息
 * @return true重新分配消息队列成功
 */
private boolean rebalanceByTopic(final String topic, final boolean isOrder) {
    boolean balanced = true;
    switch (messageModel) {
        case BROADCASTING: {
            Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
            if (mqSet != null) {
                boolean changed = this.updateProcessQueueTableInRebalance(topic, mqSet, isOrder);
                if (changed) {
                    this.messageQueueChanged(topic, mqSet, mqSet);
                    log.info("messageQueueChanged {} {} {} {}", consumerGroup, topic, mqSet, mqSet);
                }

                balanced = mqSet.equals(getWorkingMessageQueue(topic));
            } else {
                this.messageQueueChanged(topic, Collections.<MessageQueue>emptySet(), Collections.<MessageQueue>emptySet());
                log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
            }
            break;
        }
        case CLUSTERING: {
            // 从主题订阅信息缓存表中获取当前topic的消费队列
            Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
            // 从Broker上获取所有订阅该topic + 同属一个消费组 的所有消费者ID
            List<String> cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);
            if (null == mqSet) {
                if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    this.messageQueueChanged(topic, Collections.<MessageQueue>emptySet(), Collections.<MessageQueue>emptySet());
                    log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
                }
            }

            if (null == cidAll) {
                log.warn("doRebalance, {} {}, get consumer id list failed", consumerGroup, topic);
            }

            if (mqSet != null && cidAll != null) {
                List<MessageQueue> mqAll = new ArrayList<MessageQueue>();
                mqAll.addAll(mqSet);

                /*
                    消费队列、消费者ID排序很重要:同一个消费组内视图一致,确保同一个消费队列不会被多个消费者分配
                 */
                // 消费队列排序
                Collections.sort(mqAll);
                // 消费者ID排序
                Collections.sort(cidAll);

                // 均衡策略
                AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;

                List<MessageQueue> allocateResult = null;
                try {
                    // 根据均衡策略,获取当前消费者的消息队列
                    allocateResult = strategy.allocate(
                        this.consumerGroup,
                        this.mQClientFactory.getClientId(), // 当前消费者ID
                        mqAll,
                        cidAll);
                } catch (Throwable e) {
                    log.error("allocate message queue exception. strategy name: {}, ex: {}", strategy.getName(), e);
                    return false;
                }

                Set<MessageQueue> allocateResultSet = new HashSet<MessageQueue>();
                if (allocateResult != null) {
                    allocateResultSet.addAll(allocateResult);
                }

                // 消费者对应的分配消息队列是否变化: 新增、删除(其他消费者占用)
                boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder);
                if (changed) {
                    log.info(
                        "client rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, mqAllSize={}, cidAllSize={}, rebalanceResultSize={}, rebalanceResultSet={}",
                        strategy.getName(), consumerGroup, topic, this.mQClientFactory.getClientId(), mqSet.size(), cidAll.size(),
                        allocateResultSet.size(), allocateResultSet);
                    this.messageQueueChanged(topic, mqSet, allocateResultSet);
                }

                balanced = allocateResultSet.equals(getWorkingMessageQueue(topic));
            }
            break;
        }
        default:
            break;
    }

    return balanced;
}

        org.apache.rocketmq.client.impl.consumer.RebalanceImpl#updateProcessQueueTableInRebalance重新分配后消费队列集合与上次负载的分配集合是否改变(新增或删除)来重新拉取消息。如下代码所示。

  • 删除(消费队列分配给其他消费者):暂停消费并移除,且持久化待移除消费队列的消费进度。
  • 新增(缓存表没有的消费队列):

                step1:删除内存中该消费队列的消费进度;

                step2:创建broker的消费队列;

                step3:从磁盘中获取该消费队列的消费进度(若进度<0时,则根据配置矫正消费进度),创建拉取消息请求。

  • 新增消费队列:重新创建拉取请求PullRequest加入到PullMessageService线程中,唤醒该线程拉取消息RebalanceImpl#dispatchPullRequest。
  • 若是顺序消息:是局部顺序消息,尝试向Broker请求锁定该消费队列,锁定失败延迟时则重新负载。
/**
 * 消费者对应的分配消息队列是否变化
 * step1:消费队列缓存表中不在本次均衡分配的消费队列时,则暂停消费并移除,且持久化待移除消费队列的消费进度;
 * step2:本次均衡分配的消费队列不在消费队列缓存表中,则新增:
 *         1):删除内存中该消费队列的消费进度;
 *         2):创建broker的消费队列;
 *         3):从磁盘中获取该消费队列的消费进度(若进度<0时,则根据配置矫正消费进度),创建拉取消息请求
 *              {@link RebalanceImpl#computePullFromWhere}
 * step3: 新增消费队列,则创建{@link PullRequest}加入到{@link PullMessageService},唤醒该线程拉取消息
 *              {@link RebalanceImpl#dispatchPullRequest}
 * step4:顺序消息时,则尝试向Broker请求锁定该消费队列,锁定失败延迟重新负载
 * @param topic 主题
 * @param mqSet 本次均衡分配的消费队列
 * @param isOrder 是否顺序
 * @return true变化;false未改变
 */
private boolean updateProcessQueueTableInRebalance(final String topic, final Set<MessageQueue> mqSet,
    final boolean isOrder) {
    boolean changed = false;

    // drop process queues no longer belong me 当前消费队列不在分配队列中
    HashMap<MessageQueue, ProcessQueue> removeQueueMap = new HashMap<MessageQueue, ProcessQueue>(this.processQueueTable.size());
    // 遍历当前消费队列缓存表
    Iterator<Entry<MessageQueue, ProcessQueue>> it = this.processQueueTable.entrySet().iterator();
    while (it.hasNext()) {
        Entry<MessageQueue, ProcessQueue> next = it.next();
        MessageQueue mq = next.getKey();
        ProcessQueue pq = next.getValue();

        // 是该topic的消费队列
        if (mq.getTopic().equals(topic)) {
            // 当前消费队列不在现有的分配消息队列中,则暂停消费、废弃当前消费队列并移除(分配给其他消费者)
            if (!mqSet.contains(mq)) {
                pq.setDropped(true);
                removeQueueMap.put(mq, pq);
            } else if (pq.isPullExpired() && this.consumeType() == ConsumeType.CONSUME_PASSIVELY) {
                pq.setDropped(true);
                removeQueueMap.put(mq, pq);
                log.error("[BUG]doRebalance, {}, try remove unnecessary mq, {}, because pull is pause, so try to fixed it",
                    consumerGroup, mq);
            }
        }
    }

    // remove message queues no longer belong me 移除不在分配的消费队列
    for (Entry<MessageQueue, ProcessQueue> entry : removeQueueMap.entrySet()) {
        MessageQueue mq = entry.getKey();
        ProcessQueue pq = entry.getValue();

        /*
            判断是否将{@link MessageQueue}、{@link ProcessQueue}缓存表中移除
                a. 持久化待移除的{@link MessageQueue}消费进度;
                b. 顺序消息时,需先解锁队列
         */
        if (this.removeUnnecessaryMessageQueue(mq, pq)) {
            this.processQueueTable.remove(mq);
            changed = true;
            log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq);
        }
    }

    // add new message queue 遍历本次负载均衡分配的消费队列,缓存表中没有,则新增的消费队列
    boolean allMQLocked = true; // 消费队列是否有锁定(顺序消息使用)
    List<PullRequest> pullRequestList = new ArrayList<PullRequest>();
    for (MessageQueue mq : mqSet) {
        // 新增的消费队列
        if (!this.processQueueTable.containsKey(mq)) {
            // 若是顺序消息,则尝试向Broker请求锁定该消费队列,锁定失败延迟重新负载
            if (isOrder && !this.lock(mq)) {
                log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq);
                allMQLocked = false;
                continue;
            }

            // 删除内存中该消费队列的消费进度
            this.removeDirtyOffset(mq);
            // 创建broker的消费队列
            ProcessQueue pq = createProcessQueue(topic);
            pq.setLocked(true);
            // 从磁盘中获取该消费队列的消费进度(若进度<0时,则根据配置矫正消费进度),创建拉取消息请求
            long nextOffset = this.computePullFromWhere(mq);
            if (nextOffset >= 0) {
                ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
                if (pre != null) {
                    log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq);
                } else {
                    log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
                    // 创建拉取消息请求
                    PullRequest pullRequest = new PullRequest();
                    pullRequest.setConsumerGroup(consumerGroup);
                    pullRequest.setNextOffset(nextOffset);
                    pullRequest.setMessageQueue(mq);
                    pullRequest.setProcessQueue(pq);
                    pullRequestList.add(pullRequest);
                    changed = true;
                }
            } else {
                log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq);
            }
        }

    }

    // 锁定消费队列失败,延迟重新负载
    if (!allMQLocked) {
        mQClientFactory.rebalanceLater(500);
    }

    // 将拉取消息对象{@link PullRequest}加入到{@link PullMessageService},唤醒该线程拉取消息
    this.dispatchPullRequest(pullRequestList, 500);

    return changed;
}

        根据RebalanceImpl#updateProcessQueueTableInRebalance来判定消费者对应的分配到的消息队列是否变化(新增或删除)时,若是新增,则先删除内存消费进度,再从Broker端获取该消费队列的消费进度;若是删除,持久化消费进度同时删除旧的消费队列。 

a. 删除操作

        org.apache.rocketmq.client.impl.consumer.RebalanceImpl#removeUnnecessaryMessageQueue负载均衡时删除未分配的消费队列,其调用链如下。

b. 新增操作

        先删除该消费队列旧的内存消费进度,执行方法RebalanceImpl#removeDirtyOffset,其调用链如下。

        再从Broker磁盘获取该消费队列消费进度,执行RebalanceImpl#computePullFromWhere,其调用链如下。 

三、负载均衡策略

        org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy是消费队列负载均衡策略的接口,其有6个实现类,UML图如下。其中:

  • AllocateMessageQueueAveragely:平均分配算法(默认),如:8个消息消费队列q1、q2、q3、q4、q5、q6、q7、q8,有3个消费者c1、c2、c3,则分配如下:

                c1:q1、q2、q3

                c2:q4、q5、q6

                c3:q7、q8

  • AllocateMessageQueueAveragelyByCircle:平均轮询算法,如:8个消息消费队列q1、q2、q3、q4、q5、q6、q7、q8,有3个消费者c1、c2、c3,则分配如下:

                c1:q1、q4、q7

                c2:q2、q5、q8

                c3:q3、q6

四、参考资料

RocketMQ(十三) RocketMQ负载均衡_每天都要进步一点点的博客-CSDN博客

https://www.cnblogs.com/alisystemsoftware/p/16935521.html

消费者负载均衡 | RocketMQ

RocketMQ5.0.0消息消费<一> _ PUSH模式的消息拉取_爱我所爱0505的博客-CSDN博客

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

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

相关文章

合并链表相关的练习

目录 一、合并两个有序链表 二、两数相加 一、合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&…

熬夜30天吃透这九大Java核心专题,我收割了3个大厂offer

这次一共收割了3个大厂offer&#xff0c;分别是蚂蚁金服、美团和网易&#xff0c;特意分享这次对我帮助非常大的宝典资料&#xff0c;一共涉及九大核心专题&#xff0c;分别是计算机网络、操作系统、MySQL、Linux、JAVA、JVM、Redis、消息队列与分布式、网站优化相关&#xff0…

MySQL8启动错误“Neither found #innodb_redo subdirectory, nor ib_logfile* files”

今天做MySQL备份文件回复测试,用来检验MySQL备份文件可用性。 MySQL版本8.0.32 备份文件为腾讯云MySQL实例,版本8.0 使用xtrabackup恢复备份。执行过程顺利,启动MySQL时发生错误。提示如下: 注意,这里使用了systemctl stop mysql。虽然启动失败了,但是如果不执行这条…

全国青少年软件编程(Scratch)等级考试一级真题——2019.9

青少年软件编程&#xff08;Scratch&#xff09;等级考试试卷&#xff08;一级&#xff09;分数&#xff1a;100 题数&#xff1a;37一、单选题(共25题&#xff0c;每题2分&#xff0c;共50分)1.小明在做一个采访的小动画&#xff0c;想让主持人角色说“大家好&#xff01;”3秒…

AcWing 3555. 二叉树

第一种做法是dfs。但是注意&#xff0c;如果是非常单纯的dfs&#xff0c;要把每个节点可以到达的所有节点都记录下来&#xff08;父节点两个子节点&#xff09;。如果只记录了子节点&#xff0c;没记录父节点&#xff0c;就失去了一个方向&#xff0c;肯定出错。 例如求2和6之…

Git常见问题:Your branch and ‘xxx/xxx‘ have diverged

报这个错就是因为你和远程库出现分叉了&#xff0c;具体表现是是由于同一个分支有多人修改&#xff0c;多人commit造成的 用户1操作 用户2操作 T1 git co -b brv_1 git co -b brv_1 T2 git add test2.java git commit -m m2 git push T3 git add test1.java git comm…

VR全景博物馆,打造7*24小时的线上参访体验

导语&#xff1a;博物馆作为人们了解历史、文化和艺术的重要场所&#xff0c;现在可以通过VR全景技术来进行展览&#xff0c;让参观者身临其境地感受历史文化的魅力。本文将介绍博物馆VR全景的特点、优势&#xff0c;以及如何使用VR全景技术来丰富博物馆的展览和教育活动。什么…

数据库:Mysql数据库安装及使用

目录 一、数据库介绍 1、基本概念 2、数据库类型 3、版本演变 二、Mysql安装 1、官网下载yum安装 2、手动配置yum安装 三、Mysql基本操作 1、登录与改密 2、检测数据库健康 3、 库的创建与使用 4、数据类型 5、修饰符 6、表的创建与使用 7、分组查询 8、查询排…

hive之正则函数研究学习regex/regex_replace/regex_extract

首先学习这个之前要先知道一些正则的基本知识。 随便百度一下正则表达式 – 元字符 | 菜鸟教程 字符描述\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如&#xff0c;n 匹配字符 "n"。\n 匹配一个换行符。序列 \\ 匹…

残酷现实:大部分的App小程序,日活<100

残酷现实:99%的APP小程序&#xff0c;日活<100 日活跃用户数量(DAU&#xff09;是一个核心指标 Daily Active Users 互联网的难度系数一路拉高 只有流过血的战士&#xff0c;才能意识到战场的残酷 趣讲大白话&#xff1a;赵本山小品台词&#xff0c; 残酷的现实已直逼我心理…

千访 | 互动千万!小红书博主修炼手册

采访手记&#xff1a; 截止到目前&#xff0c;姜老师的护肤成分室在小红书的粉丝量已达150W。在赛道内卷的美妆区&#xff0c;突破真人出镜讲解&#xff0c;采用二次元漫画&#xff0b;变美内容结合&#xff0c;成为护肤动画领域的头部大号。 接下来&#xff0c;就让我们通过…

[算法]希尔排序——插入排序的升级

参考&#xff1a;《漫画算法-小灰的算法之旅》 目录 1、希尔排序的思想 2、例子 3、希尔排序代码 4、希尔排序的优化 5、希尔排序是不稳定排序 问&#xff1a;什么情况下插入排序的工作量会比较小呢&#xff1f; 答&#xff1a;首先&#xff0c;当数组元素较小时&#xf…

基于Halcon的MLP(多层感知神经网络)分类器分类操作实例

一、介绍 人工神经网络(Artificial Neural Network,ANN)简称神经网络(Neural Network,NN)或类神经网络,是一种模仿生物神经网络的结构和功能的数学模型或计算模型,用于对函数进行估计或近似。 MLP神经网络是一种基于神经网络、动态的分类器。MLP分类器使用神经…

C51---智能感应垃圾桶

1.器件&#xff1a;C51、蜂鸣器、SG90舵机、HC-SR04超声波测距 2.实现功能&#xff1a;超声波感应&#xff0c;舵机转动打开垃圾桶&#xff0c;蜂鸣器发出提示&#xff0c;LED灯亮 3.代码&#xff1a; #include "reg52.h" #include "intrins.h" //距离小…

关于进程与进程调度

目录什么是进程进程管理进程的结构体(PCB)里的属性并行与并发什么是进程 一个运行起来的程序就是进程. 比如文件名是以 exe 结尾的就是一可执行文件(程序) 双击QQ.exe文件, 这个程序就跑起来了, 它在系统中形成了一个进程, 那我们怎么看到进程呢? 可以打开任务管理器, 点开进…

Spark MLlib 模型训练

Spark MLlib 模型训练决策树随机森林GBDTSpark MLlib 开发框架下 : 监督学习 : 回归 (Regression) , 分类 (Classification) , 协同过滤 (Collaborative Filtering)非监督学习 : 聚类 (Clustering) 、频繁项集 (Frequency Patterns) 例子分类 : 算法分类 : 算法分类算法子分类…

探究SMC局部代码加密技术以及在CTF中的运用

前言 近些日子在很多线上比赛中都遇到了smc文件加密技术,比较出名的有Hgame杭电的比赛,于是我准备实现一下这项技术&#xff0c;但是在网上看了很多文章&#xff0c;发现没有讲的特别详细的&#xff0c;或者是无法根据他们的方法进行实现这项技术&#xff0c;因此本篇文章就是…

Springboot怎么快速集成Redis?

前言其实在Springboot中集成redis是一个非常简单的事情&#xff0c;但是为什么要单独输出一篇文章来记录这个过程呢&#xff1f;第一个原因是&#xff0c;我记性不是太好&#xff0c;这次把这个过程记录下&#xff0c;在新的项目搭建的时候或者需要在本地集成redis做一些其他相…

51红外循迹智能车——红外循迹模块设计

目录 赛道环境 红外传感器的特征 TCRT5000传感器 LM339单限电压比较器 LM339简介 ​编辑 单限电压比较器仿真 红外循迹模块的设计 红外循迹模块原理图 红外循迹模块原理图讲解 赛道环境 上图为赛道示意图&#xff0c;两端为黑色&#xff0c;中间为白色 红外传感器的…

小孩用什么样的台灯比较好?2023眼科医生青睐的儿童台灯推荐

小孩子属于眼睛比较脆弱的人群&#xff0c;所以选购护眼台灯时&#xff0c;选光线温和的比较好&#xff0c;而且调光、显色效果、色温、防蓝光等方面也要出色&#xff0c;否则容易导致孩子近视。 1、调光。台灯首先是照度高&#xff0c;国AA级&#xff0b;大功率发光&#xff0…