# 消息中间件 RocketMQ 高级功能和源码分析(八)

消息中间件 RocketMQ 高级功能和源码分析(八)

一、消息中间件 RocketMQ 源码分析:实时更新消息消费队列与索引文件流程说明

1、实时更新消息消费队列与索引文件

消息消费队文件、消息属性索引文件都是基于 CommitLog 文件构建的,当消息生产者提交的消息存储在 CommitLog 文件中,ConsumerQueue、IndexFile 需要及时更新,否则消息无法及时被消费,根据消息属性查找消息也会出现较大延迟。RocketMQ 通过开启一个线程 ReputMessageService 来准实时转发 CommitLog 文件更新事件,相应的任务处理器根据转发的消息及时更新 ConsumerQueue、IndexFile 文件。

2、消息存储结构 示例图:

在这里插入图片描述

3、构建消息消费队列和索引文件 示例图:

在这里插入图片描述

4、 代码:DefaultMessageStore:start


//设置CommitLog内存中最大偏移量
this.reputMessageService.setReputFromOffset(maxPhysicalPosInLogicQueue);
//启动
this.reputMessageService.start();

5、 代码:DefaultMessageStore:run


public void run() {
    DefaultMessageStore.log.info(this.getServiceName() + " service started");
	//每隔1毫秒就继续尝试推送消息到消息消费队列和索引文件
    while (!this.isStopped()) {
        try {
            Thread.sleep(1);
            this.doReput();
        } catch (Exception e) {
            DefaultMessageStore.log.warn(this.getServiceName() + " service has exception. ", e);
        }
    }

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

6、 代码:DefaultMessageStore:deReput


//从result中循环遍历消息,一次读一条,创建DispatherRequest对象。
for (int readSize = 0; readSize < result.getSize() && doNext; ) {
	DispatchRequest dispatchRequest =                               DefaultMessageStore.this.commitLog.checkMessageAndReturnSize(result.getByteBuffer(), false, false);
	int size = dispatchRequest.getBufferSize() == -1 ? dispatchRequest.getMsgSize() : dispatchRequest.getBufferSize();

	if (dispatchRequest.isSuccess()) {
	    if (size > 0) {
	        DefaultMessageStore.this.doDispatch(dispatchRequest);
	    }
    }
}

7、 DispatchRequest

在这里插入图片描述


String topic; //消息主题名称
int queueId;  //消息队列ID
long commitLogOffset;	//消息物理偏移量
int msgSize;	//消息长度
long tagsCode;	//消息过滤tag hashCode
long storeTimestamp;	//消息存储时间戳
long consumeQueueOffset;	//消息队列偏移量
String keys;	//消息索引key
boolean success;	//是否成功解析到完整的消息
String uniqKey;	//消息唯一键
int sysFlag;	//消息系统标记
long preparedTransactionOffset;	//消息预处理事务偏移量
Map<String, String> propertiesMap;	//消息属性
byte[] bitMap;	//位图

二、消息中间件 RocketMQ 源码分析:转发数据到 ConsumerQueue 文件

1、转发到 ConsumerQueue 消息分发到消息消费队列 示例图:

在这里插入图片描述

2、 代码 CommitLogDispatcherBuildConsumeQueue 类:


class CommitLogDispatcherBuildConsumeQueue implements CommitLogDispatcher {
    @Override
    public void dispatch(DispatchRequest request) {
        final int tranType = MessageSysFlag.getTransactionValue(request.getSysFlag());
        switch (tranType) {
            case MessageSysFlag.TRANSACTION_NOT_TYPE:
            case MessageSysFlag.TRANSACTION_COMMIT_TYPE:
                //消息分发
                DefaultMessageStore.this.putMessagePositionInfo(request);
                break;
            case MessageSysFlag.TRANSACTION_PREPARED_TYPE:
            case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:
                break;
        }
    }
}

3、 代码:DefaultMessageStore#putMessagePositionInfo


public void putMessagePositionInfo(DispatchRequest dispatchRequest) {
    //获得消费队列
    ConsumeQueue cq = this.findConsumeQueue(dispatchRequest.getTopic(), dispatchRequest.getQueueId());
    //消费队列分发消息
    cq.putMessagePositionInfoWrapper(dispatchRequest);
}

4、 代码:DefaultMessageStore#putMessagePositionInfo


//依次将消息偏移量、消息长度、tag写入到ByteBuffer中
this.byteBufferIndex.flip();
this.byteBufferIndex.limit(CQ_STORE_UNIT_SIZE);
this.byteBufferIndex.putLong(offset);
this.byteBufferIndex.putInt(size);
this.byteBufferIndex.putLong(tagsCode);
//获得内存映射文件
MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(expectLogicOffset);
if (mappedFile != null) {
    //将消息追加到内存映射文件,异步输盘
    return mappedFile.appendMessage(this.byteBufferIndex.array());
}

三、消息中间件 RocketMQ 源码分析:转发 IndexFile 文件

1、转发到 Index 消息分发到索引文件 示例图:

在这里插入图片描述

2、 代码 CommitLogDispatcherBuildIndex 类:


class CommitLogDispatcherBuildIndex implements CommitLogDispatcher {

    @Override
    public void dispatch(DispatchRequest request) {
        if (DefaultMessageStore.this.messageStoreConfig.isMessageIndexEnable()) {
            DefaultMessageStore.this.indexService.buildIndex(request);
        }
    }
}

3、 代码:DefaultMessageStore#buildIndex


public void buildIndex(DispatchRequest req) {
    //获得索引文件
    IndexFile indexFile = retryGetAndCreateIndexFile();
    if (indexFile != null) {
        //获得文件最大物理偏移量
        long endPhyOffset = indexFile.getEndPhyOffset();
        DispatchRequest msg = req;
        String topic = msg.getTopic();
        String keys = msg.getKeys();
        //如果该消息的物理偏移量小于索引文件中的最大物理偏移量,则说明是重复数据,忽略本次索引构建
        if (msg.getCommitLogOffset() < endPhyOffset) {
            return;
        }

        final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag());
        switch (tranType) {
            case MessageSysFlag.TRANSACTION_NOT_TYPE:
            case MessageSysFlag.TRANSACTION_PREPARED_TYPE:
            case MessageSysFlag.TRANSACTION_COMMIT_TYPE:
                break;
            case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:
                return;
        }
		
        //如果消息ID不为空,则添加到Hash索引中
        if (req.getUniqKey() != null) {
            indexFile = putKey(indexFile, msg, buildKey(topic, req.getUniqKey()));
            if (indexFile == null) {
                return;
            }
        }
		//构建索引key,RocketMQ支持为同一个消息建立多个索引,多个索引键空格隔开.
        if (keys != null && keys.length() > 0) {
            String[] keyset = keys.split(MessageConst.KEY_SEPARATOR);
            for (int i = 0; i < keyset.length; i++) {
                String key = keyset[i];
                if (key.length() > 0) {
                    indexFile = putKey(indexFile, msg, buildKey(topic, key));
                    if (indexFile == null) {

                        return;
                    }
                }
            }
        }
    } else {
        log.error("build index error, stop building index");
    }
}

四、消息中间件 RocketMQ 源码分析:消息队列和索引文件恢复

1、消息队列和索引文件恢复

由于 RocketMQ 存储首先将消息全量存储在 CommitLog 文件中,然后异步生成转发任务更新 ConsumerQueue 和 Index 文件。如果消息成功存储到 CommitLog 文件中,转发任务未成功执行,此时消息服务器 Broker 由于某个愿意宕机,导致CommitLog、ConsumerQueue、IndexFile 文件数据不一致。如果不加以人工修复的话,会有一部分消息即便在 CommitLog 中文件中存在,但由于没有转发到 ConsumerQueue,这部分消息将永远复发被消费者消费。

2、文件恢复总体流程 示例图:

在这里插入图片描述

3、存储文件加载

代码:DefaultMessageStore#load

判断上一次是否异常退出。实现机制是 Broker 在启动时创建 abort 文件,在退出时通过 JVM 钩子函数删除 abort 文件。如果下次启动时存在 abort 文件。说明 Broker 时异常退出的,CommitLog 与 ConsumerQueue 数据有可能不一致,需要进行修复。


//判断临时文件是否存在
boolean lastExitOK = !this.isTempFileExist();
//根据临时文件判断当前Broker是否异常退出
private boolean isTempFileExist() {
    String fileName = StorePathConfigHelper
        .getAbortFile(this.messageStoreConfig.getStorePathRootDir());
    File file = new File(fileName);
    return file.exists();
}

4、 代码:DefaultMessageStore#load


//加载延时队列
if (null != scheduleMessageService) {
    result = result && this.scheduleMessageService.load();
}

// 加载CommitLog文件
result = result && this.commitLog.load();

// 加载消费队列文件
result = result && this.loadConsumeQueue();

if (result) {
	//加载存储监测点,监测点主要记录CommitLog文件、ConsumerQueue文件、Index索引文件的刷盘点
    this.storeCheckpoint =new StoreCheckpoint(StorePathConfigHelper.getStoreCheckpoint(this.messageStoreConfig.getStorePathRootDir()));
	//加载index文件
    this.indexService.load(lastExitOK);
	//根据Broker是否异常退出,执行不同的恢复策略
    this.recover(lastExitOK);
}

5、 代码:MappedFileQueue#load

加载 CommitLog 到映射文件


//指向CommitLog文件目录
File dir = new File(this.storePath);
//获得文件数组
File[] files = dir.listFiles();
if (files != null) {
    // 文件排序
    Arrays.sort(files);
    //遍历文件
    for (File file : files) {
		//如果文件大小和配置文件不一致,退出
        if (file.length() != this.mappedFileSize) {
            
            return false;
        }

        try {
            //创建映射文件
            MappedFile mappedFile = new MappedFile(file.getPath(), mappedFileSize);
            mappedFile.setWrotePosition(this.mappedFileSize);
            mappedFile.setFlushedPosition(this.mappedFileSize);
            mappedFile.setCommittedPosition(this.mappedFileSize);
            //将映射文件添加到队列
            this.mappedFiles.add(mappedFile);
            log.info("load " + file.getPath() + " OK");
        } catch (IOException e) {
            log.error("load file " + file + " error", e);
            return false;
        }
    }
}

return true;

6、 代码:DefaultMessageStore#loadConsumeQueue

加载消息消费队列


//执行消费队列目录
File dirLogic = new File(StorePathConfigHelper.getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()));
//遍历消费队列目录
File[] fileTopicList = dirLogic.listFiles();
if (fileTopicList != null) {

    for (File fileTopic : fileTopicList) {
        //获得子目录名称,即topic名称
        String topic = fileTopic.getName();
		//遍历子目录下的消费队列文件
        File[] fileQueueIdList = fileTopic.listFiles();
        if (fileQueueIdList != null) {
            //遍历文件
            for (File fileQueueId : fileQueueIdList) {
                //文件名称即队列ID
                int queueId;
                try {
                    queueId = Integer.parseInt(fileQueueId.getName());
                } catch (NumberFormatException e) {
                    continue;
                }
                //创建消费队列并加载到内存
                ConsumeQueue logic = new ConsumeQueue(
                    topic,
                    queueId,
                    StorePathConfigHelper.getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()),
            this.getMessageStoreConfig().getMapedFileSizeConsumeQueue(),
                    this);
                this.putConsumeQueue(topic, queueId, logic);
                if (!logic.load()) {
                    return false;
                }
            }
        }
    }
}

log.info("load logics queue all over, OK");

return true;

7、 代码:IndexService#load

加载索引文件


public boolean load(final boolean lastExitOK) {
    //索引文件目录
    File dir = new File(this.storePath);
    //遍历索引文件
    File[] files = dir.listFiles();
    if (files != null) {
        //文件排序
        Arrays.sort(files);
        //遍历文件
        for (File file : files) {
            try {
                //加载索引文件
                IndexFile f = new IndexFile(file.getPath(), this.hashSlotNum, this.indexNum, 0, 0);
                f.load();

                if (!lastExitOK) {
                    //索引文件上次的刷盘时间小于该索引文件的消息时间戳,该文件将立即删除
                    if (f.getEndTimestamp() > this.defaultMessageStore.getStoreCheckpoint()
                        .getIndexMsgTimestamp()) {
                        f.destroy(0);
                        continue;
                    }
                }
				//将索引文件添加到队列
                log.info("load index file OK, " + f.getFileName());
                this.indexFileList.add(f);
            } catch (IOException e) {
                log.error("load file {} error", file, e);
                return false;
            } catch (NumberFormatException e) {
                log.error("load file {} error", file, e);
            }
        }
    }

    return true;
}

8、 代码:DefaultMessageStore#recover

文件恢复,根据 Broker 是否正常退出执行不同的恢复策略


private void recover(final boolean lastExitOK) {
    //获得最大的物理便宜消费队列
    long maxPhyOffsetOfConsumeQueue = this.recoverConsumeQueue();

    if (lastExitOK) {
        //正常恢复
        this.commitLog.recoverNormally(maxPhyOffsetOfConsumeQueue);
    } else {
        //异常恢复
        this.commitLog.recoverAbnormally(maxPhyOffsetOfConsumeQueue);
    }
	//在CommitLog中保存每个消息消费队列当前的存储逻辑偏移量
    this.recoverTopicQueueTable();
}

9、 代码:DefaultMessageStore#recoverTopicQueueTable

恢复 ConsumerQueue 后,将在 CommitLog 实例中保存每隔消息队列当前的存储逻辑偏移量,这也是消息中不仅存储主题、消息队列 ID、还存储了消息队列的关键所在。


public void recoverTopicQueueTable() {
    HashMap<String/* topic-queueid */, Long/* offset */> table = new HashMap<String, Long>(1024);
    //CommitLog最小偏移量
    long minPhyOffset = this.commitLog.getMinOffset();
    //遍历消费队列,将消费队列保存在CommitLog中
    for (ConcurrentMap<Integer, ConsumeQueue> maps : this.consumeQueueTable.values()) {
        for (ConsumeQueue logic : maps.values()) {
            String key = logic.getTopic() + "-" + logic.getQueueId();
            table.put(key, logic.getMaxOffsetInQueue());
            logic.correctMinOffset(minPhyOffset);
        }
    }
    this.commitLog.setTopicQueueTable(table);
}

五、消息中间件 RocketMQ 源码分析:正常恢复和异常恢复

1、正常恢复

代码:CommitLog#recoverNormally


public void recoverNormally(long maxPhyOffsetOfConsumeQueue) {
	
    final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();
    if (!mappedFiles.isEmpty()) {
         //Broker正常停止再重启时,从倒数第三个开始恢复,如果不足3个文件,则从第一个文件开始恢复。
        int index = mappedFiles.size() - 3;
        if (index < 0)
            index = 0;
        MappedFile mappedFile = mappedFiles.get(index);
        ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();
        long processOffset = mappedFile.getFileFromOffset();
        //代表当前已校验通过的offset
        long mappedFileOffset = 0;
        while (true) {
            //查找消息
            DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover);
            //消息长度
            int size = dispatchRequest.getMsgSize();
           	//查找结果为true,并且消息长度大于0,表示消息正确.mappedFileOffset向前移动本消息长度
            if (dispatchRequest.isSuccess() && size > 0) {
                mappedFileOffset += size;
            }
			//如果查找结果为true且消息长度等于0,表示已到该文件末尾,如果还有下一个文件,则重置processOffset和MappedFileOffset重复查找下一个文件,否则跳出循环。
            else if (dispatchRequest.isSuccess() && size == 0) {
              index++;
              if (index >= mappedFiles.size()) {
                  // Current branch can not happen
                  break;
              } else {
                  //取出每个文件
                  mappedFile = mappedFiles.get(index);
                  byteBuffer = mappedFile.sliceByteBuffer();
                  processOffset = mappedFile.getFileFromOffset();
                  mappedFileOffset = 0;
                  
          		}
            }
            // 查找结果为false,表明该文件未填满所有消息,跳出循环,结束循环
            else if (!dispatchRequest.isSuccess()) {
                log.info("recover physics file end, " + mappedFile.getFileName());
                break;
            }
        }
		//更新MappedFileQueue的flushedWhere和committedWhere指针
        processOffset += mappedFileOffset;
        this.mappedFileQueue.setFlushedWhere(processOffset);
        this.mappedFileQueue.setCommittedWhere(processOffset);
        //删除offset之后的所有文件
        this.mappedFileQueue.truncateDirtyFiles(processOffset);

        
        if (maxPhyOffsetOfConsumeQueue >= processOffset) {
            this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);
        }
    } else {
        this.mappedFileQueue.setFlushedWhere(0);
        this.mappedFileQueue.setCommittedWhere(0);
        this.defaultMessageStore.destroyLogics();
    }
}

2、 代码:MappedFileQueue#truncateDirtyFiles


public void truncateDirtyFiles(long offset) {
    List<MappedFile> willRemoveFiles = new ArrayList<MappedFile>();
	//遍历目录下文件
    for (MappedFile file : this.mappedFiles) {
        //文件尾部的偏移量
        long fileTailOffset = file.getFileFromOffset() + this.mappedFileSize;
        //文件尾部的偏移量大于offset
        if (fileTailOffset > offset) {
            //offset大于文件的起始偏移量
            if (offset >= file.getFileFromOffset()) {
                //更新wrotePosition、committedPosition、flushedPosistion
                file.setWrotePosition((int) (offset % this.mappedFileSize));
                file.setCommittedPosition((int) (offset % this.mappedFileSize));
                file.setFlushedPosition((int) (offset % this.mappedFileSize));
            } else {
                //offset小于文件的起始偏移量,说明该文件是有效文件后面创建的,释放mappedFile占用内存,删除文件
                file.destroy(1000);
                willRemoveFiles.add(file);
            }
        }
    }

    this.deleteExpiredFile(willRemoveFiles);
}

3、异常恢复

Broker 异常停止文件恢复的实现为 CommitLog#recoverAbnormally。异常文件恢复步骤与正常停止文件恢复流程基本相同,其主要差别有两个。首先,正常停止默认从倒数第三个文件开始进行恢复,而异常停止则需要从最后一个文件往前走,找到第一个消息存储正常的文件。其次,如果 CommitLog 目录没有消息文件,如果消息消费队列目录下存在文件,则需要销毁。

代码:CommitLog#recoverAbnormally


if (!mappedFiles.isEmpty()) {
    // Looking beginning to recover from which file
    int index = mappedFiles.size() - 1;
    MappedFile mappedFile = null;
    for (; index >= 0; index--) {
        mappedFile = mappedFiles.get(index);
        //判断消息文件是否是一个正确的文件
        if (this.isMappedFileMatchedRecover(mappedFile)) {
            log.info("recover from this mapped file " + mappedFile.getFileName());
            break;
        }
    }
	//根据索引取出mappedFile文件
    if (index < 0) {
        index = 0;
        mappedFile = mappedFiles.get(index);
    }
    //...验证消息的合法性,并将消息转发到消息消费队列和索引文件
       
}else{
    //未找到mappedFile,重置flushWhere、committedWhere都为0,销毁消息队列文件
    this.mappedFileQueue.setFlushedWhere(0);
    this.mappedFileQueue.setCommittedWhere(0);
    this.defaultMessageStore.destroyLogics();
}

上一节关联链接请点击:
# 消息中间件 RocketMQ 高级功能和源码分析(七)

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

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

相关文章

mock-前端数据模拟

简介 数据模拟不是开发流程中的必要一环 Json-server 简介&#xff1a; json-server 是一个简单的 Node.js 服务端应用程序&#xff0c;这个工具的主要作用是提供一个模拟的后端服务&#xff0c;可以在前端开发过程中独立于后端进行简单工作。 使用&#xff1a; 1、 安装…

Python重力弹弓流体晃动微分方程模型和交直流电阻电容电路

&#x1f3af;要点 &#x1f3af;计算地球大气层中热层金属坠物运动轨迹 | &#x1f3af;计算炮弹最佳弹射角度耦合微分方程 | &#x1f3af;计算电磁拉莫尔半径螺旋运动 | &#x1f3af;计算航天器重力弹弓运动力学微分方程 | &#x1f3af;计算双摆的混沌运动非线性微分方程…

【无线传感网】分簇路由算法介绍

目录 1、LEACH路由算法 2、PEGASIS 算法 3、TEEN 算法 5、APTEEN 5、LEACH-C 算法 无线传感网中的路由协议就是寻找一条路径让网络中节点沿着这条路径将数据信息传输出去。路由协议的两大关键要点就是路径的优化和数据的分组,在传统计算机网络中,是将网络的拓扑…

windows系统实现应用程序开机即运行(不登录系统也行)

由于近期需要设置一个Java程序开机自启动&#xff0c;因此试了一下方法&#xff0c;总结了两点&#xff0c;一个是需要用户登录系统之后再启动&#xff0c;一种是不需要登录&#xff0c;只要开机就会启动。 先看准备工作&#xff0c;写一个启动脚本&#xff1a; echo on E: cd…

Flink入门实战详解

Flink入门实战 Flink项目构建 1)基于MavenIdea创建项目&#xff1a; 使用maven进行项目构建&#xff0c;如图1所示。 图-34 构建maven项目 输入项目中的maven的坐标和存储坐标&#xff0c;如图2所示。 图2 maven坐标和存储位置 2)Maven依赖&#xff1a; <properties>…

虚幻引擎 Gerstner Waves -GPU Gems 从物理模型中实现有效的水体模拟

1.1 目标与范围 我们从简单的正弦函数开始&#xff0c;然后逐步过渡到更复杂的函数&#xff0c;以适应需要。 本章主要解释系统参数的物理意义&#xff0c;表明将水表面近似为正弦波的总和并不像人们通常认为的那样是随意的。我们特别关注将基本模型转换为实际实现所需的数学方…

Linux系统资源监控nmon工具下载及使用介绍

一、资源下载 夸克网盘链接&#xff1a;https://pan.quark.cn/s/2684089bc34d 里面包含了各种分享的实用工具&#xff0c;nmon在 Linux服务器监控nmon工具 文件夹内 文件说明&#xff1a; nmon16p_binaries.tar.gz 为最新的nmon官方工具包&#xff0c;支持linux全平台 nmo…

钢琴块小游戏(附源码)

代码结构 app.png是游戏运行主界面的图片&#xff08;可以加载自己喜欢的主界面图片&#xff09; Assets文件夹里面装的是一些需要用到的游戏图片 全部都可以替换为自己喜欢的图片 Fonts里面装的是 Sounds文件夹里面装的是 一 . 主程序代码 1.运行这个代码使得游戏开始 2.主界面…

【机器学习 复习】第6章 支持向量机(SVM)

一、概念 1.支持向量机&#xff08;support vector machine&#xff0c;SVM&#xff09;&#xff1a; &#xff08;1&#xff09;基于统计学理论的监督学习方法&#xff0c;但不属于生成式模型&#xff0c;而是判别式模型。 &#xff08;2&#xff09;支持向量机在各个领域内的…

健康与生活助手:Kompas AI的高效应用

一、引言 在现代社会&#xff0c;随着生活节奏的加快和工作压力的增加&#xff0c;人们的健康问题日益凸显。健康管理已经成为每个人关注的重点。Kompas AI作为一款智能助手&#xff0c;通过其先进的人工智能技术&#xff0c;为用户提供全面的健康管理服务&#xff0c;帮助用户…

【C++知识点】类和对象:友元,运算符重载,多态

今天来继续了解类和对象&#xff01; PS.本博客参考b站up黑马程序员的相关课程&#xff0c;老师讲得非常非常好&#xff01; 封装 深拷贝与浅拷贝 浅拷贝&#xff1a;简单的赋值拷贝操作 深拷贝&#xff1a;在堆区重新申请空间&#xff0c;进行拷贝操作 首先&#xff0c…

【头歌】HBase扫描与过滤答案 解除复制粘贴限制

解除复制粘贴限制 当作者遇到这个限制的时候火气起来了三分&#xff0c;然后去网上搜索答案&#xff0c;然后发现了一位【碳烤小肥肠】居然不贴代码&#xff0c;XX链接&#xff0c;贴截图&#xff0c;瞬时火气冲顶&#xff0c;怒写此文 首先启动万能的控制台&#xff0c;然后C…

【Hadoop大数据技术】——期末复习(冲刺篇)

&#x1f4d6; 前言&#xff1a;快考试了&#xff0c;做篇期末总结&#xff0c;都是重点与必考点。 题型&#xff1a;简答题、编程题&#xff08;Java与Shell操作&#xff09;、看图分析题。题目大概率会从课后习题、实验里出。 课本&#xff1a; 目录 &#x1f552; 1. HDF…

数据结构--单链表(图文)

单链表的概念 在单链表中&#xff0c;每个元素&#xff08;称为节点&#xff09;包含两部分&#xff1a;一部分是存储数据的数据域&#xff0c;另一部分是存储下一个节点地址的指针域。这里的“单”指的是每个节点只有一个指向下一个节点的指针。 节点&#xff1a;链表中的基…

java-数据结构与算法-02-数据结构-01-数组

文章目录 1. 概述2. 动态数组3. 二维数组4. 局部性原理5. 越界检查6. 习题 1. 概述 定义 在计算机科学中&#xff0c;数组是由一组元素&#xff08;值或变量&#xff09;组成的数据结构&#xff0c;每个元素有至少一个索引或键来标识 In computer science, an array is a dat…

如何与精益管理咨询公司进行有效的沟通?

在现代企业管理中&#xff0c;精益管理咨询公司发挥着不可或缺的作用&#xff0c;它们通过提供专业的精益管理咨询服务&#xff0c;帮助企业优化运营流程&#xff0c;提升生产效率&#xff0c;降低成本&#xff0c;实现可持续发展。然而&#xff0c;与精益管理咨询公司进行有效…

软件测评中心▏软件安全测试的测试方法和注意事项介绍

软件安全测试是一种重要的测试活动&#xff0c;旨在评估和验证软件系统中潜在的安全风险&#xff0c;并提供可行的解决方案。通过对软件系统进行系统化的测试&#xff0c;可以及时发现和修复安全漏洞&#xff0c;保护软件系统的安全性。 软件安全测试的测试方法可以帮助测试人…

深度学习500问——Chapter11:迁移学习(4)

文章目录 11.3.8 流形学习方法 11.3.9 什么是finetune 11.3.10 finetune为什么有效 11.3.11 什么是网络自适应 11.3.12 GAN在迁移学习中的应用 参考文献 11.3.8 流形学习方法 什么是流行学习&#xff1f; 流行学习自从2000年在Science上被提出来以后&#xff0c;就成为了机器…

ASP.NET Core 中使用 Dapper 的 Oracle 存储过程输出参数

介绍 Oracle 数据库功能强大&#xff0c;在企业环境中使用广泛。在 ASP.NET Core 应用程序中使用 Oracle 存储过程时&#xff0c;处理输出参数可能具有挑战性。本教程将指导您完成使用 Dapper&#xff08;适用于 . NET 的轻量级 ORM&#xff08;对象关系映射器&#xff09;&am…

Python数据分析-对驾驶安全数据进行了预测

一、研究背景和意义 随着汽车保有量的不断增加&#xff0c;交通事故已成为全球范围内的重大公共安全问题。每年因交通事故造成的人员伤亡和财产损失给社会带来了巨大的负担。为了提高驾驶安全&#xff0c;减少交通事故的发生&#xff0c;许多研究致力于探索影响驾驶安全的因素…