RocketMQ5.0.0消息存储<四>_刷盘机制

news2024/10/3 8:29:20

目录

一、刷盘概览

二、Broker刷盘机制

1. 同步刷盘

2. 异步刷盘

        1):未开启堆外内存池

        2):开启堆外内存池

三、参考资料


一、刷盘概览

        RocketMQ存储与读写是基于JDK NIO的内存映射机制(MappedByteBuffer),消息存储时首先将消息追加到文件内存映射(commit操作),再根据配置的刷盘策略在不同时间进行刷写到磁盘(flush操作)。同步刷盘,消息提交到文件内存映射后,将等待同步调用MappedByteBuffer的force()方法写入磁盘后返回给生产者;异步刷盘,消息提交到文件内存映射后,立刻返回给生产者。如下图所示,两种刷盘方式对比。

同步刷盘和异步刷盘模式对比

        RocketMQ启动单独的线程周期执行刷盘操作。 broker.conf中配置flushDiskType来设定刷盘方式,值:ASYNC_FLUSH(异步刷盘)、SYNC_FLUSH(同步刷盘),默认为异步刷盘。

        本章节以Commitlog文件刷盘机制为例来剖析RocketMQ的刷盘机制,ConsumeQueue、IndexFile刷盘的实现原理与Commitlog刷盘机制类似。注意,IndexFile文件的刷盘并不是采取定时刷盘机制,而是每更新一次索引文件就会将上一次的改动刷写到磁盘

二、Broker刷盘机制

        org.apache.rocketmq.store.CommitLog#asyncPutMessage是RocketMQ执行消息提交到文件内存映射的核心方法,消息存储流程参考《RocketMQ5.0.0消息存储<二>_消息存储流程》。其中调用org.apache.rocketmq.store.CommitLog#handleDiskFlushAndHA方法来执行同步或异步刷盘、HA主从同步复制等,该方法是刷盘入口。其调用链、代码如下所示。

/**
 * 执行同步或异步刷盘、HA主从同步复制等
 * @param putMessageResult 消息
 * @param messageExt 消息扩展属性
 * @param needAckNums 消息数量
 * @param needHandleHA 是否需要HA主从复制
 * @return
 */
private CompletableFuture<PutMessageResult> handleDiskFlushAndHA(PutMessageResult putMessageResult,
                                                                 MessageExt messageExt, int needAckNums, boolean needHandleHA) {
    // 刷盘操作
    CompletableFuture<PutMessageStatus> flushResultFuture = handleDiskFlush(putMessageResult.getAppendMessageResult(), messageExt);
    CompletableFuture<PutMessageStatus> replicaResultFuture;
    if (!needHandleHA) {
        replicaResultFuture = CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
    } else {
        replicaResultFuture = handleHA(putMessageResult.getAppendMessageResult(), putMessageResult, needAckNums);
    }

    return flushResultFuture.thenCombine(replicaResultFuture, (flushStatus, replicaStatus) -> {
        if (flushStatus != PutMessageStatus.PUT_OK) {
            putMessageResult.setPutMessageStatus(flushStatus);
        }
        if (replicaStatus != PutMessageStatus.PUT_OK) {
            putMessageResult.setPutMessageStatus(replicaStatus);
        }
        return putMessageResult;
    });
}

        org.apache.rocketmq.store.CommitLog.FlushManager是刷盘管理器接口,其接口的实现类是org.apache.rocketmq.store.CommitLog.DefaultFlushManager刷盘管理器实现类维护刷盘线程执行周期性刷盘操作。其UML图如下。

public DefaultFlushManager() {
    // 同步刷盘
    if (FlushDiskType.SYNC_FLUSH == CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
        this.flushCommitLogService = new CommitLog.GroupCommitService();
    }
    // 异步刷盘
    else {
        this.flushCommitLogService = new CommitLog.FlushRealTimeService();
    }

    // 消息提交线程
    this.commitLogService = new CommitLog.CommitRealTimeService();
}

1. 同步刷盘

        同步刷盘:将消息追加到内存映射文件中(内存)后,立即将数据从内存刷写到磁盘文件。org.apache.rocketmq.store.CommitLog.DefaultFlushManager#handleDiskFlush是刷盘核心方法,含有同步、异步刷盘逻辑,如下代码所示。

/**
 * 刷盘操作:消息从内存映射文件写入到磁盘
 * 同步刷盘:
 *      step1:创建组提交线程GroupCommitService
 *      step2:创建刷盘任务请求对象GroupCommitRequest,并提交任务
 *      step3:刷盘任务请求对象添加到flushDiskWatcher,监控刷盘,如:刷盘超时处理
 *      step4:阻塞,获取刷盘结果
 * 异步刷盘:
 *      transientStorePoolEnable是否开启堆外内存池:
 *      作用:申请与目前Commitlog文件大小相同的堆外内存,并锁定内存避免与虚拟内存置换
 *      过程:有堆外内存:消息追加到堆外内存,然后commit文件内存映射,最后flush写入磁盘
 *           无堆外内存:消息直接追加到文件内存映射,最后flush写入磁盘
 *      {@link CommitRealTimeService}:默认每200ms将消息追加到文件内存映射中(commitPosition移到当前wrotePosition且flushedPosition移到当前wrotePosition)
 *      {@link FlushRealTimeService}:默认每500ms将文件内存映射写入磁盘(flushedPosition移到当前wrotePosition)
 * @param result 追加消息到内存映射文件的结果
 * @param messageExt 消息扩展
 * @return 同步或异步返回写入结果
 */
@Override
public CompletableFuture<PutMessageStatus> handleDiskFlush(AppendMessageResult result, MessageExt messageExt) {
    // Synchronization flush 同步刷盘SYNC_FLUSH
    if (FlushDiskType.SYNC_FLUSH == CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
        // 组提交线程
        final GroupCommitService service = (GroupCommitService) this.flushCommitLogService;
        // 消息成功提交到文件内存映射中
        if (messageExt.isWaitStoreMsgOK()) {
            // 创建刷盘任务请求对象GroupCommitRequest
            GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(), CommitLog.this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
            // 监控刷盘是否完成线程,如:刷盘超时处理
            flushDiskWatcher.add(request);
            // 提交任务到线程
            service.putRequest(request);

            // 阻塞,获取刷盘结果
            return request.future();
        }
        // 消息还未提交完成,则当前线程处于等待状态,则唤醒线程,异步
        else {
            service.wakeup();
            return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
        }
    }
    // Asynchronous flush 异步刷盘ASYNC_FLUSH
    ......
}

        org.apache.rocketmq.store.CommitLog.GroupCommitService是一个线程,用于处理同步刷盘任务,处理对象是org.apache.rocketmq.store.CommitLog.GroupCommitRequest。

        GroupCommitService类中的关键属性如下。注意,采用两个容器requestsWrite、requestsRead,避免刷盘任务提交和执行的锁冲突。

/**
 * 注意,采用两个容器requestsWrite、requestsRead,避免任务提交和执行的锁冲突
 */
// 刷盘任务暂存容器
private volatile LinkedList<GroupCommitRequest> requestsWrite = new LinkedList<GroupCommitRequest>();
// 每次处理任务的容器
private volatile LinkedList<GroupCommitRequest> requestsRead = new LinkedList<GroupCommitRequest>();
private final PutMessageSpinLock lock = new PutMessageSpinLock();

        GroupCommitService线程的run()调用链,及刷盘核心方法org.apache.rocketmq.store.CommitLog.GroupCommitService#doCommit代码,如下所示。

/**
 * 写入磁盘操作
 * step1:逐一从requestsRead容器取出刷盘任务进行刷盘操作
 * step2:this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset(),表示刷盘任务完成,避免两次刷盘操作
 * step3:最终使用FileChannel.force()完成刷盘
 * step4:每个刷盘任务完成后,通知调用方刷盘结果
 * step5:requestsRead的所有任务完成后,进行更新Checkpoint(只是更新Checkpoint内存映射,并没有进行刷盘操作: Commitlog转发到消费队列中进行触发Checkpoint刷盘)
 */
private void doCommit() {
    if (!this.requestsRead.isEmpty()) {
        for (GroupCommitRequest req : this.requestsRead) {
            // 当前刷盘位置 >= 下一个消息刷盘偏移量,如大于则消息刷盘成功,避免两次刷盘
            // There may be a message in the next file, so a maximum of
            // two times the flush
            boolean flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset();
            for (int i = 0; i < 2 && !flushOK; i++) {
                // 写入磁盘,调用:MappedFile.flush()方法,最终使用FileChannel.force()完成
                CommitLog.this.mappedFileQueue.flush(0);
                flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset();
            }

            // 该刷盘任务完成后,将消息发送线程唤醒,通知调用方GroupCommitRequest刷盘结果
            req.wakeupCustomer(flushOK ? PutMessageStatus.PUT_OK : PutMessageStatus.FLUSH_DISK_TIMEOUT);
        }

        // 所有刷盘任务完成后,更新Checkpoint,注意:只是更新Checkpoint内存映射,并没有进行刷盘操作(Commitlog转发到消费队列中进行触发Checkpoint刷盘)
        long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();
        if (storeTimestamp > 0) {
            CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);
        }

        this.requestsRead = new LinkedList<>();
    } else {
        // Because of individual messages is set to not sync flush, it
        // will come to this process
        CommitLog.this.mappedFileQueue.flush(0);
    }
}

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

    while (!this.isStopped()) {
        try {
            // 线程每处理一批刷盘任务后,就休息10毫秒
            this.waitForRunning(10);
            // 写入磁盘操作
            this.doCommit();
        } catch (Exception e) {
            CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
        }
    }

    // Under normal circumstances shutdown, wait for the arrival of the
    // request, and then flush
    try {
        Thread.sleep(10);
    } catch (InterruptedException e) {
        CommitLog.log.warn("GroupCommitService Exception, ", e);
    }

    // 两个容器每执行完任务后,交互,继续消费任务
    synchronized (this) {
        this.swapRequests();
    }

    this.doCommit();

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

        消息生产者在消息服务端将消息内容追加到内存映射文件中后,需要同步将内存的内容立刻刷写到磁盘。通过调用内存映射文件(MappedByteBuffer的force方法)可将内存中的数据写入磁盘。 

2. 异步刷盘

        异步刷盘:将消息追加到内存映射文件中(内存)后,立刻返回给消息发送端。org.apache.rocketmq.store.CommitLog.DefaultFlushManager#handleDiskFlush是刷盘核心方法,含有同步、异步刷盘逻辑,如下代码所示。注意异步刷盘根据是否开启堆外内存池(transientStorePoolEnable默认false,未开启),执行不同的线程:

  • 未开启堆外内存池:使用类org.apache.rocketmq.store.CommitLog.FlushRealTimeService,直接执行刷盘,即:commitPosition移到当前wrotePosition,flushedPosition移到当前wrotePosition
  • 开启堆外内存池:使用类org.apache.rocketmq.store.CommitLog.CommitRealTimeService,提交所有消息到文件内存映射,再执行刷盘,即:flushedPosition移到当前wrotePosition
/**
 * 刷盘操作:消息从内存映射文件写入到磁盘
 * 同步刷盘:
 *      step1:创建组提交线程GroupCommitService
 *      step2:创建刷盘任务请求对象GroupCommitRequest,并提交任务
 *      step3:刷盘任务请求对象添加到flushDiskWatcher,监控刷盘,如:刷盘超时处理
 *      step4:阻塞,获取刷盘结果
 * 异步刷盘:
 *      transientStorePoolEnable是否开启堆外内存池:
 *      作用:申请与目前Commitlog文件大小相同的堆外内存,并锁定内存避免与虚拟内存置换
 *      过程:有堆外内存:消息追加到堆外内存,然后commit文件内存映射,最后flush写入磁盘
 *           无堆外内存:消息直接追加到文件内存映射,最后flush写入磁盘
 *      {@link CommitRealTimeService}:默认每200ms将消息追加到文件内存映射中(commitPosition移到当前wrotePosition且flushedPosition移到当前wrotePosition)
 *      {@link FlushRealTimeService}:默认每500ms将文件内存映射写入磁盘(flushedPosition移到当前wrotePosition)
 * @param result 追加消息到内存映射文件的结果
 * @param messageExt 消息扩展
 * @return 同步或异步返回写入结果
 */
@Override
public CompletableFuture<PutMessageStatus> handleDiskFlush(AppendMessageResult result, MessageExt messageExt) {
    // Synchronization flush 同步刷盘SYNC_FLUSH
    if (FlushDiskType.SYNC_FLUSH == CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
        .....
    }
    // Asynchronous flush 异步刷盘ASYNC_FLUSH
    /*
        transientStorePoolEnable是否开启堆外内存池:
        作用:申请与目前Commitlog文件大小相同的堆外内存,并锁定内存避免与虚拟内存置换
        过程:有堆外内存:消息追加到堆外内存,然后commit文件内存映射,最后flush写入磁盘
             无堆外内存:消息直接追加到文件内存映射,最后flush写入磁盘
        CommitRealTimeService:默认每200ms将消息追加到文件内存映射中(commitPosition移到当前wrotePosition且flushedPosition移到当前wrotePosition)
        FlushRealTimeService:默认每500ms将文件内存映射写入磁盘(flushedPosition移到当前wrotePosition)
     */
    else {
        // 没有开启堆外内存
        if (!CommitLog.this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
            // 实现类FlushRealTimeService
            flushCommitLogService.wakeup();
        }
        // 开启堆外内存
        else {
            // 实现类CommitRealTimeService
            commitLogService.wakeup();
        }
        return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
    }
}

        1):未开启堆外内存池

        FlushRealTimeService线程默认每500ms将文件内存映射写入磁盘(flushedPosition移到当前wrotePosition),如下代码所示。

/**
 * 刷盘过程
 * step1:根据休息方式flushCommitLogTimed,刷盘线程默认每次休息500ms
 * step2:刷盘,其最终调用FileChannel#force(boolean)
 * step3:更新检查点Checkpoint文件Commitlog的时间戳
 *      注意:并没有进行Checkpoint文件刷盘,其刷盘为消费队列刷盘时触发,入口DefaultMessageStore.FlushConsumeQueueService
 */
@Override
public void run() {
    CommitLog.log.info(this.getServiceName() + " service started");

    while (!this.isStopped()) {
        // 等待方式:默认false,表示await()方法等待;true表示sleep()方法等待
        boolean flushCommitLogTimed = CommitLog.this.defaultMessageStore.getMessageStoreConfig().isFlushCommitLogTimed();

        // 线程运行间隔时间,默认500ms
        int interval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushIntervalCommitLog();
        // 一次刷盘任务的页数,默认4页,小于该值则忽略本次刷盘
        int flushPhysicQueueLeastPages = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogLeastPages();

        // 两次真实提交的最大间隔时间,默认10s
        int flushPhysicQueueThoroughInterval =
                CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogThoroughInterval();

        boolean printFlushProgress = false;

        // 当前刷盘时间 与 上次刷盘时间 差值超出flushPhysicQueueThoroughInterval,则忽略flushPhysicQueueLeastPages参数
        long currentTimeMillis = System.currentTimeMillis();
        if (currentTimeMillis >= (this.lastFlushTimestamp + flushPhysicQueueThoroughInterval)) {
            this.lastFlushTimestamp = currentTimeMillis;
            flushPhysicQueueLeastPages = 0;
            printFlushProgress = (printTimes++ % 10) == 0; // Print flush progress 打印刷盘进度
        }

        try {
            // 根据等待方式,刷盘线程休息
            if (flushCommitLogTimed) {
                Thread.sleep(interval);
            } else {
                this.waitForRunning(interval);
            }

            // 打印刷盘进度
            if (printFlushProgress) {
                this.printFlushProgress();
            }

            // 开始刷盘,其最终调用FileChannel#force(boolean)
            long begin = System.currentTimeMillis();
            CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages);
            long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();
            /*
                刷盘完成后,更新检查点Checkpoint文件Commitlog的时间戳
                注意:并没有进行Checkpoint文件刷盘,其刷盘为消费队列刷盘时触发,入口DefaultMessageStore.FlushConsumeQueueService
             */
            if (storeTimestamp > 0) {
                CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);
            }
            long past = System.currentTimeMillis() - begin;
            CommitLog.this.getMessageStore().getPerfCounter().flowOnce("FLUSH_DATA_TIME_MS", (int) past);
            if (past > 500) {
                log.info("Flush data to disk costs {} ms", past);
            }
        } catch (Throwable e) {
            CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
            this.printFlushProgress();
        }
    }

    // 刷盘线程正常关闭后,则确保所有消息写入磁盘
    // Normal shutdown, to ensure that all the flush before exit
    boolean result = false;
    for (int i = 0; i < RETRY_TIMES_OVER && !result; i++) {
        result = CommitLog.this.mappedFileQueue.flush(0);
        CommitLog.log.info(this.getServiceName() + " service shutdown, retry " + (i + 1) + " times " + (result ? "OK" : "Not OK"));
    }

    this.printFlushProgress();

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

        2):开启堆外内存池

        CommitRealTimeService线程默认每200ms将消息追加到文件内存映射中(commitPosition移到当前wrotePosition),再flush刷盘(flushedPosition移到当前wrotePosition),如下所示,开启堆外内存池的刷盘流程。

开启堆外内存池的刷盘流程

        具体步骤说明:

step1:首先将消息直接追加到ByteBuffer(缓存),wrotePosition随着消息的不断追加向后移动;
step2:CommitRealTimeService线程默认每200ms将ByteBuffer新追加的内容(wrotePosihon减去commitedPosition)的数据提交到MappedByteBuffer中(内存映射文件)
step3:MappedByteBuffer在内存中追加提交的内容,wrotePosition指针向前后移动,然后返回;
step4:commit操作成功返回,将commitedPosition向前后移动本次提交的内容长度,此时wrotePosition指针依然可以向前推进;
step5:FlushRealTimeService线程默认每500ms将MappedByt巳Buffer中新追加的内存(wrotePosition减去上一次刷写位置flushedPositiont)通过调用MappedByteBuffer#force()方法将数据刷写到磁盘。 

/**
 * 消息提交过程,先commit再flush
 */
@Override
public void run() {
    CommitLog.log.info(this.getServiceName() + " service started");
    while (!this.isStopped()) {
        // 线程运行间隔时间,默认200ms
        int interval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getCommitIntervalCommitLog();

        // 一次提交任务的页数,默认4页,小于该值则忽略本次提交
        int commitDataLeastPages = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getCommitCommitLogLeastPages();

        // 两次真实提交的最大间隔时间,默认200ms
        int commitDataThoroughInterval =
                CommitLog.this.defaultMessageStore.getMessageStoreConfig().getCommitCommitLogThoroughInterval();

        // 当前提交时间 与 上次提交时间 差值超出commitDataThoroughInterval,则忽略commitDataLeastPages参数
        long begin = System.currentTimeMillis();
        if (begin >= (this.lastCommitTimestamp + commitDataThoroughInterval)) {
            this.lastCommitTimestamp = begin;
            commitDataLeastPages = 0;
        }

        try {
            // 消息提交:true所有消息都提交;false部分消息已提交
            boolean result = CommitLog.this.mappedFileQueue.commit(commitDataLeastPages);
            long end = System.currentTimeMillis();
            if (!result) {
                this.lastCommitTimestamp = end; // result = false means some data committed.
                // 唤醒flush线程
                CommitLog.this.flushManager.wakeUpFlush();
            }
            CommitLog.this.getMessageStore().getPerfCounter().flowOnce("COMMIT_DATA_TIME_MS", (int) (end - begin));
            if (end - begin > 500) {
                log.info("Commit data to file costs {} ms", end - begin);
            }

            // 本次commit后,休息
            this.waitForRunning(interval);
        } catch (Throwable e) {
            CommitLog.log.error(this.getServiceName() + " service has exception. ", e);
        }
    }

    // commit线程关闭时,重试次数
    boolean result = false;
    for (int i = 0; i < RETRY_TIMES_OVER && !result; i++) {
        result = CommitLog.this.mappedFileQueue.commit(0);
        CommitLog.log.info(this.getServiceName() + " service shutdown, retry " + (i + 1) + " times " + (result ? "OK" : "Not OK"));
    }
    CommitLog.log.info(this.getServiceName() + " service end");
}

        文件检测点文件(Checkpoint文件)的刷盘动作在刷盘消息消费队列线程中执行,其入口为org.apache.rocketmq.store.DefaultMessageStore.FlushConsumeQueueService。由于消息消费队列、索引文件的刷盘实现原理与Commitlog文件的刷盘机制类同,本章节不做介绍。

三、参考资料

RocketMQ部署及刷盘机制_谁喝了我的菊花茶的博客-CSDN博客

RocketMQ刷盘策略_chongshui129727的博客-CSDN博客

https://blog.csdn.net/m0_37543627/article/details/128601312

https://blog.csdn.net/m0_37543627/article/details/128666747

https://blog.csdn.net/m0_37543627/article/details/128696152 

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

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

相关文章

深度解析 JavaScript 严格模式:利弊长远的考量

前言 ECMAScript 5首次引入严格模式的概念。严格模式用于选择以更严格的条件检查JavaScript代码错误&#xff0c;可以应用到全局&#xff0c;也可以应用到函数内部。 严格模式的好处是可以提早发现错误&#xff0c;因此可以捕获某些 ECMAScript 问题导致的编程错误。 理解严格…

前端vue实现系统拦截跳转外链并进入跳转询问界面

跳转询问界面如下图所示&#xff1a; 给自己挖坑的实现方式&#xff0c;最终解决方案请看最底下 思路&#xff1a;正常情况下我们有2种方式跳转外链 第一种非a标签&#xff0c;我们手动添加事件进行跳转 <div class"dingdan public-padding p-item" click&quo…

Python 条件语句是什么?

Python条件语句是通过一条或多条语句的执行结果&#xff08;True或者False&#xff09;来决定执行的代码块。 可以通过下图来简单了解条件语句的执行过程: Python程序语言指定任何非0和非空&#xff08;null&#xff09;值为true&#xff0c;0 或者 null为false。 Python 编程…

家政服务小程序实战教程12-详情页

我们的家政服务小程序已经完成了首页和分类展示页面的开发&#xff0c;接下来就需要开发详情页了。在详情页里我们展示我们的各项服务内容&#xff0c;让用户可以了解每项家政服务可以提供的内容。 低码开发不像传统开发&#xff0c;如果开发详情页需要考虑每个字段的类型&…

【TPC证书报错--箱码校验失败】

证书管理—>交易证书管理—>编辑&#xff0c;然后就报错了。 1.这个报错&#xff0c;一般是指一个箱码&#xff0c;【产出/报工】接口失败了&#xff0c;但是【成品入库】和【成品出口】成功了。 2.大概就是【成品出库】接口&#xff0c;会传【销售单号】和【箱码】2个…

ArcGIS与地理加权回归【三】

开 工 大 急 原址链接&#xff1a; ArcGIS与地理加权回归【三】https://mp.weixin.qq.com/s/x85EXKImSHio1IZovW9qdA 接着5个月之前.......ArcGIS与地理加权回归GWR【二】以及MGWR软件下载 在ASU下载了样例“关于影响佐治亚州受教育水平”的数据。在上一篇已简单介绍…

类和对象(下)(一)

类和对象&#xff08;下&#xff09;&#xff08;一&#xff09;1.再谈构造函数1.1构造函数体赋值1.2初始化列表1.3explicit关键字2.static成员2.1概念2.2特性3.匿名对象&#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f; &#x…

Spring框架源码(五) @configuration源码深度解析

Configuration 注解是spring-context模块提供的一个给开发者使用的配置类注解&#xff0c;开发者可以通过Configuration注解来定义配置类&#xff0c;也可以使用xml形式注入。 例如配置数据库配置&#xff0c;定义一个配置类&#xff0c;注入数据源DataSource, 事务管理器Trans…

类和对象(下)(二)

类和对象&#xff08;下&#xff09;&#xff08;二&#xff09;1.友元1.1友元函数1.2友元类2.内部类3.拷贝对象时的一些编译器优化&#xff08;vs2022&#xff09;&#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f; &#x1f680…

【c#】反射学习笔记01

c#反射学习01反射学习一、反射原理二、那么我们是如何通过metadata来实现反射呢&#xff1f;三、反射的好处四、反射创建对象&#xff08;利用配置文件简单工厂反射&#xff09;五、反射的黑科技&#xff08;多构造函数调用、破坏单例、创建泛型&#xff09;六、反射调用实例方…

环境配置完整指导——Installing C++ Distributions of PyTorch

目录一、前言二、动手开始做1. 安装cuda 11.42. 安装visual studio 2019 community3. 安装libtorch4. 安装mingw-w645. 配置环境变量6. 打开vscode开始写程序7. 运行程序8. 其他报错信息文章简介&#xff1a;这篇文章用于介绍在windows10 vscode中&#xff0c;跑通如下代码的全…

JavaScript 教程导读

JavaScript 是 Web 的编程语言。所有现代的 HTML 页面都使用 JavaScript&#xff0c;可以用于改进设计、验证表单、检测浏览器、创建cookies等。JavaScript 非常容易学。本教程将教你学习从初级到高级JavaScript知识。JavaScript 在线实例本教程包含了大量的 JavaScript 实例&a…

使用 ONLYOFFICE 转换 API 构建在线文档转换器

文档转换是非常常用、非常有价值的功能&#xff0c;可以帮助我们处理多种文档类型。ONLYOFFICE 编辑器可以轻松地将文档转换为多种格式。在这篇博文中&#xff0c;我们会向您展示&#xff0c;如何构建在 ONLYOFFICE 转换 API 上运行的在线转换器。 关于 ONLYOFFICE 转换 API 使…

傻白探索Chiplet,Design Space Exploration for Chiplet-Assembly-Based Processors(十三)

阅读了Design Space Exploration for Chiplet-Assembly-Based Processors这篇论文&#xff0c;是关于chiplet设计空间探索的&#xff0c;个人感觉核心贡献有两个&#xff1a;1.提出使用整数线性规划算法进行Chiplet的选择&#xff1b;2.基于RE和NRE提出了一个cost模型&#xff…

Map和Set(Java详解)

在开始详解之前&#xff0c;先来看看集合的框架&#xff1a; 可以看到Set实现了Collection接口&#xff0c;而Map又是一个单独存在的接口。 而最下面又分别各有两个类&#xff0c;分别是TreeSet&#xff08;Map&#xff09;和 HashSet&#xff08;Map&#xff09;。 TreeSet&…

CSS---动态向下的循环箭头动画效果

介绍 在移动端的页面中&#xff0c;经常有翻页的提示效果&#xff0c;经常使用向下的箭头动画来提示&#xff1b;一般效果如下所示&#xff1a; 使用到的图片 使用到的图片&#xff0c;就是一个向下的箭头&#xff1b;这里可以下载我的图片使用&#xff1b; 或者也可以使用…

92.【SpringCloud NetFilx】

SpringCloud(一)、这个阶段该如何学习?1.微服务介绍2.面试常见问题(二)、微服务概述1.什么是微服务?2. 微服务与微服务架构(1).微服务(2).微服务架构⭐(3). 微服务优缺点(4). 微服务技术栈有那些&#xff1f;(5). 为什么选择SpringCloud作为微服务架构(三)、SpringCloud入门概…

Python 之 Matplotlib 第一个绘图程序和基本方法

文章目录一、第一个 Matplotlib 绘图程序1. Matplotlib 绘图的基本步骤二、Matplotlib 的基本方法1. 图表名称 plt.title()2. x 轴和 y 轴名称3. 设置 x 轴和 y 轴的刻度4. 显示图表 show()5. 图例 legend()6. 图例的图例位置设置7. 显示每条数据的值 x,y 值的位置一、第一个 M…

LeetCode 61. 旋转链表

原题链接 难度&#xff1a;middle\color{orange}{middle}middle 题目描述 给你一个链表的头节点 headheadhead &#xff0c;旋转链表&#xff0c;将链表每个节点向右移动 kkk 个位置。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], k 2 输出&#xff1a;[4,5,1…

wafw00f源码及流量特征分析

wafw00f介绍 这不是本次的重点&#xff0c;相关介绍及使用方法相信大家已经了解&#xff0c;所以此处就直接引用其开发者对该工具的介绍。 To do its magic, WAFW00F does the following: Sends a normal HTTP request and analyses the response; this identifies a number o…