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

news2024/11/16 11:42:28

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

一、消费队列负载均衡概览
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
    

在这里插入图片描述
四、参考资料

  1. https://blog.csdn.net/Weixiaohuai/article/details/123898841
  2. https://www.cnblogs.com/alisystemsoftware/p/16935521.html

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

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

相关文章

第四十四章Java访问对象的属性和行为以及销毁

Java访问对象的属性和行为 每个对象都有自己的属性和行为&#xff0c;这些属性和行为在类中体现为成员变量和成员方法&#xff0c;其中成员变量对应对象的属性&#xff0c;成员方法对应对象的行为。 在Java中&#xff0c;要引用对象的属性和行为&#xff0c;需要使用点…

【MySQL 】MySQL 创建数据库, MySQL 删除数据库,MySQL 选择数据库

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 七七的闲谈 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f…

iPhone苹果手机桌面上快速记录笔记的步骤

现在越来越多的人喜欢上记笔记&#xff0c;因为记笔记是一种提升效率和组织思维的重要方式。随着移动设备的普及&#xff0c;手机逐渐成为我们生活中不可或缺的工具之一。在手机上记笔记就成为一种很快捷的记录方式&#xff0c;可以让我们随时随地记录灵感和重要信息。在众多记…

计算机体系结构基础知识介绍之动态调度Tomasulo 算法(二)

Tomasulo方法是一种计算机硬件架构的算法&#xff0c;用于动态调度指令的执行&#xff0c;允许乱序执行以及更有效率的使用多个执行单元。它由IBM公司在1967年提出&#xff0c;首次应用是在IBM System/360 Model 91的浮点单元上。Tomasulo方法的主要创新包括在硬件中进行寄存器…

轻松学习阿里云原生内存数据库Tair

&#x1f4d6;轻松学习阿里云原生内存数据库Tair &#x1f680;前言☁️什么是Redis&#xff1f;☁️什么是云原生内存数据库&#xff1f;✨特点 &#x1f680;阿里云原生内存数据库Tair&#x1f47b;简介✨功能特性&#x1f5fa;️应用场景 ✍️上手案例&#x1f3af; 基于Red…

PCB笔记(PCB设计流程)

双层PCB设计流程&#xff08;以AD10为例&#xff09; 1. Preferences常规设置2. 画好原理图后3.编译工程&#xff0c;看是否有错4.然后执行更新到PCB5. 布线前常规规则设置6. 布局之后开始布线7.布线结束之后&#xff0c;开始铺铜8. 创建铜皮之前调丝印&#xff0c;将所有丝印调…

力扣 -- 剑指 Offer II 091. 粉刷房子

题目链接&#xff1a;剑指 Offer II 091. 粉刷房子 - 力扣&#xff08;LeetCode&#xff09; 下面是用动态规划的思想解决这道题的过程&#xff0c;相信各位小伙伴都能看懂并且掌握这道经典的动规题目滴。 参考代码&#xff1a; class Solution { public:int minCost(vector…

MIT 6.S081 教材第八章内容 -- ext3 -- 05

MIT 6.S081 教材第八章内容 -- ext3 -- 05 引言为什么需要日志系统XV6 File system logging回顾ext3 file system log formatext3如何提升性能ext3文件系统调用格式ext3 transaction commit步骤ext3 file system恢复过程为什么新transaction需要等前一个transaction中系统调用执…

FFMPEG 编译流程(极客版)

前言 依葫芦画瓢 全程30分钟从零完成ffmpeg编译&#xff0c;主打的就是一个极客 编译环境 OS 要求&#xff1a;Ubuntu 20.04 LTS VMware新建虚拟机 NDK 要求&#xff1a;android-ndk-r20b FFmpeg 功能模块&#xff1a; libavformat:多媒体文件或协议的封装和解封装库&a…

DynaSLAM代码详解(5) — Tracking.cc跟踪线程

目录 5.1 DynaSLAM中Tracking线程简介 5 .2 RGBD模式下跟踪流程 5.3 DynaSLAM的低成本跟踪 (1) Tracking::LightTrack() 低成本跟踪函数 (2) Tracking::LightTrackWithMotionModel() 低成本的恒速模型跟踪流程 5.4 DynaSLAM的正常跟踪 文章着重将与ORB-SLAM2不同的地方&a…

智能工厂:智能制造数字化转型解决方案

数字化已经成为制造业发展的必由之路。要提高生产效率和管理水平&#xff0c;就需要提高对生产运维各环节的数据采集、处理和利用效率。当前工厂的数据采集仍存在诸多不足&#xff0c;可以利用具有多种设备接入能力、通信协议转换能力、数据通信能力、控制维护能力的工业智能网…

UE5《Electric Dreams》项目PCG技术解析 之 PCGCustomNodes详解(一)

《Electric Dreams》项目中提供了一些自定义节点和子图&#xff08;文件位置:“/Content/PCG/Assets/PCGCustomNodes”&#xff09;&#xff0c;这些节点和子图在《Electric Dreams》被广泛使用&#xff0c;对于理解《Electric Dreams》非常重要&#xff0c;而且它们可以直接移…

Qt的对话框与窗口--多文档界面MDI(Multi-document Interface))

多文档界面MDI MDI应用程序就是在主窗口里创建多个同类型的MDI子窗口&#xff0c;这些MDI子窗口在主窗口里显示&#xff0c;并共享主窗口上的工具栏和菜单等操作功能&#xff0c;主窗口上的操作都针对当前活动的MDI子窗口进行。 设计MDI应用程序需要在主窗口工作区放置一个QMdi…

MySQL:我的从库竟是我自己!?

本文将通过复制场景下的异常分析&#xff0c;介绍手工搭建MySQL主从复制时需要注意的关键细节。 作者&#xff1a;秦福朗 爱可生 DBA 团队成员&#xff0c;负责项目日常问题处理及公司平台问题排查。热爱互联网&#xff0c;会摄影、懂厨艺&#xff0c;不会厨艺的 DBA 不是好司机…

多行文本转成一行的实现方法

哈喽大家好&#xff0c;我是咸鱼 不知道你们有没有遇到过下面的情况&#xff0c;以我为例 有时候我会收到批量操作服务器的需求&#xff0c;且我会拿到一个服务器 ip 列表&#xff0c;它是一个多行的形式&#xff0c;如下所示 # ip 列表 192.168.0.1 192.168.0.2 192.168.0.…

原油天然气的区别和用途

原油天然气在市场交易中都是重要的交易产品&#xff0c;经常有小伙伴在后台咨询Forexclub&#xff0c;原油天然气的区别和用途&#xff0c;今天这篇文章就和小伙伴一起交流研究。 其实在Forexclub看来原油和天然气的提取方法、来源和用途几乎相同&#xff0c;只是在适用范围和运…

推荐系统构建

从0到1打造推荐系统工程实战_推荐系统_Jay Wu_InfoQ写作社区

并行程序设计 pthread

配置环境 pthread是c的扩展库&#xff0c;需要配置环境&#xff0c;不过vscode的mingw里面本来就有&#xff0c;谢谢呢^_^ cd “d:\sylvia\文件夹等等等” ; if ($?) { g helloworld.cpp -o helloworld } ; //编译 if ($?) { .\helloworld 线程数} //运行 常用变量声明及函…

三雄极光“设计有光·亚洲设计师迪拜对话”逐光之旅圆满收官

7月8日&#xff0c;三雄极光照明学院“设计有光亚洲设计师迪拜对话”游学旅程圆满收官。过去两周内&#xff0c;此次活动备受关注&#xff0c;设计大咖纷纷为此次迪拜游学一带一路逐光之旅打call。出发前&#xff0c;启动礼于三雄极光总部隆重举行&#xff0c;总裁张宇涛出席并…

F - Desktop Rearrangement

大意: 给你一个桌面状态,每次俩种操作桌面可以表示为一个大小为nm的矩形矩阵&#xff0c;由字符.&#xff08;桌面上的空单元格&#xff09;和*&#xff08;一个图标&#xff09;组成。 操作: 输入<x,y>表示改变其状态的单元格的位置&#xff08;如果该单元格以前包含…