深度解析RocketMq源码-持久化组件(四) CommitLog

1.绪论

commitLog是rocketmq存储的核心,前面我们介绍了mappedfile、mappedfilequeue、刷盘策略,其实commitlog的核心组件我们基本上已经介绍完成。

2.commitLog的组成

commitLog的核心其实就是MqppedFilequeue,它本质上就是多个mappedFile的queue,所以可以看出commitLog和mappedFile是一对多的关系。

2.1 commitLog的基本组件

下面是commitLog的具体组成的一些组件:

public class CommitLog implements Swappable {
    // Message's MAGIC CODE daa320a7
    //commitLog的魔数
    public final static int MESSAGE_MAGIC_CODE = -626843481;
    protected static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);
    // End of file empty MAGIC CODE cbd43194
    public final static int BLANK_MAGIC_CODE = -875286124;
    /**
     * CRC32 Format: [PROPERTY_CRC32 + NAME_VALUE_SEPARATOR + 10-digit fixed-length string + PROPERTY_SEPARATOR]
     */
    public static final int CRC32_RESERVED_LEN = MessageConst.PROPERTY_CRC32.length() + 1 + 10 + 1;
    //核心:即MqppedFilequeue是真正存储消息的地方,可以看出是由多个MappedFile组成
    protected final MappedFileQueue mappedFileQueue;
    //当前commitLog所属的MessageStore组件
    protected final DefaultMessageStore defaultMessageStore;
    //进行flush的组件
    private final FlushManager flushManager;
    //不常用的数据的检查组件
    private final ColdDataCheckService coldDataCheckService;
    //真正写入数据的组件
    private final AppendMessageCallback appendMessageCallback;
    private final ThreadLocal<PutMessageThreadLocal> putMessageThreadLocal;

    protected volatile long confirmOffset = -1L;

    private volatile long beginTimeInLock = 0;

    protected final PutMessageLock putMessageLock;

    protected final TopicQueueLock topicQueueLock;

    private volatile Set<String> fullStorePaths = Collections.emptySet();
    //刷新磁盘的监视器
    private final FlushDiskWatcher flushDiskWatcher;
    //commitlog大小,默认为1gb
    protected int commitLogSize;

    private final boolean enabledAppendPropCRC;
    //多路分发器,rocketmq为了支持mutt协议的组件
    protected final MultiDispatch multiDispatch;
}

构造函数:

public CommitLog(final DefaultMessageStore defaultMessageStore) {
        String storePath = defaultMessageStore.getMessageStoreConfig().getStorePathCommitLog();
        //初始化messagequeue,这个时候文件会与直接内存建立映射关系,并且完成文件预热
        if (storePath.contains(MessageStoreConfig.MULTI_PATH_SPLITTER)) {
            this.mappedFileQueue = new MultiPathMappedFileQueue(defaultMessageStore.getMessageStoreConfig(),
                    defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(),
                    defaultMessageStore.getAllocateMappedFileService(), this::getFullStorePaths);
        } else {
            this.mappedFileQueue = new MappedFileQueue(storePath,
                    defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(),
                    defaultMessageStore.getAllocateMappedFileService());
        }

        this.defaultMessageStore = defaultMessageStore;

        //如果是同步的时候,采用GroupCommitService进行flush
        if (FlushDiskType.SYNC_FLUSH == defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
            this.flushCommitLogService = new GroupCommitService();
        } else {
            //如果是异步刷盘,采用FlushRealTimeService进行flush
            this.flushCommitLogService = new FlushRealTimeService();
        }
        //开启瞬时缓存池技术的话,采用CommitRealTimeService进行commit
        this.commitLogService = new CommitRealTimeService();

        this.appendMessageCallback = new DefaultAppendMessageCallback();
        //每个线程都有一个消息的编码器,MessageExtEncoder负责将消息进行编码到bytebuffer中
        putMessageThreadLocal = new ThreadLocal<PutMessageThreadLocal>() {
            @Override
            protected PutMessageThreadLocal initialValue() {
                return new PutMessageThreadLocal(defaultMessageStore.getMessageStoreConfig().getMaxMessageSize());
            }
        };
        this.putMessageLock = defaultMessageStore.getMessageStoreConfig().isUseReentrantLockWhenPutMessage() ? new PutMessageReentrantLock() : new PutMessageSpinLock();
        //rocketmq实现,mutt协议的组件,它可以让消息只有一个commitlog但是分发到多个queue中
        this.multiDispatch = new MultiDispatch(defaultMessageStore, this);
        //当采用同步刷盘的时候,调用线程会阻塞等待刷盘结果,FlushDiskWatcher会检测每一个flush请求,如果超过完成时间
        //便会自动的唤醒调用线程,防止调用线程阻塞过久的场景出现
        flushDiskWatcher = new FlushDiskWatcher();
    }

2.2 commtLog真正存储消息的地方-MappedFileQueue

mappedfilequeue是commitLog的核心,在前面mappedfilequeue中已经仔细分析过,这里不再赘述。详情请看:深度解析RocketMq源码-持久化组件(二) MappedFileQueue-CSDN博客

2.3 commitLog是如何落盘的-FlushCommitLogService

flushmanager包含了是对commotlog罗盘进行处理的组件,它包含了commit和flush两个逻辑。在前面刷盘策略中已经仔细分析过,这里不再赘述。详情请看:

深度解析RocketMq源码-持久化组件(三) 刷盘策略-CSDN博客

2.4 同步刷盘失败怎么办 -FlushDiskWatcher

当采用同步刷盘的时候,调用线程会阻塞等待刷盘结果,FlushDiskWatcher会检测每一个flush请求,如果超过完成时间便会自动的唤醒调用线程,防止调用线程阻塞过久的场景出现。由此可以看出,就算是同步刷盘策略,也有可能因为超过3s没有刷盘成功导致数据丢失。
 @Override
    public void run() {
        while (!isStopped()) {
            GroupCommitRequest request = null;
            try {
                //获取提交的flush请求
                request = commitRequests.take();
            } catch (InterruptedException e) {
                log.warn("take flush disk commit request, but interrupted, this may caused by shutdown");
                continue;
            }
            //
            while (!request.future().isDone()) {
                long now = System.nanoTime();
                //如果超过了请求的超时时间
                if (now - request.getDeadLine() >= 0) {
                    //返回刷盘超时
                    request.wakeupCustomer(PutMessageStatus.FLUSH_DISK_TIMEOUT);
                    break;
                }
                // To avoid frequent thread switching, replace future.get with sleep here,
                long sleepTime = (request.getDeadLine() - now) / 1_000_000;
                sleepTime = Math.min(10, sleepTime);
                if (sleepTime == 0) {
                    request.wakeupCustomer(PutMessageStatus.FLUSH_DISK_TIMEOUT);
                    break;
                }
                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                    log.warn(
                            "An exception occurred while waiting for flushing disk to complete. this may caused by shutdown");
                    break;
                }
            }
        }
    }

3. commitLog的常见方法

3.1 commitLog是如何写入消息的 - asyncPutMessage

commitLog的核心方法就是写入消息,本质上就是在写入消息前对消息设置一些参数,比如存储时间戳,或者顺序消息的话,会转移topic,会把消息真正的topic覆盖原来的topic。然后获取到最后一个commitLog,并调用commitLog的appendMessage方法写入消息。所以commitLog其实是将消息顺序写入的。

  public CompletableFuture<PutMessageResult> asyncPutMessage(final MessageExtBrokerInner msg) {
        // Set the storage time
        //设置存储时间戳
        msg.setStoreTimestamp(System.currentTimeMillis());
        // Set the message body BODY CRC (consider the most appropriate setting
        // on the client)
        //将消息体进行crc编码,并存储到bodyCrc中
        msg.setBodyCRC(UtilAll.crc32(msg.getBody()));
        // Back to Results
        AppendMessageResult result = null;

        StoreStatsService storeStatsService = this.defaultMessageStore.getStoreStatsService();
        //获取到对应的topic
        String topic = msg.getTopic();
//        int queueId msg.getQueueId();
        //如果是事务消息,获取到事务类型prepared或者commit或者rollback
        final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag());
        //如果是非事务消息或者commit消息
        if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE
                || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {
            // Delay Delivery
            if (msg.getDelayTimeLevel() > 0) {
                //如果是延迟消息的话,需要校验延迟消息的最大延迟是否在mq的要求的范围内
                if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) {
                    msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel());
                }
            
                topic = TopicValidator.RMQ_SYS_SCHEDULE_TOPIC;
                //并且将消息的延迟级别获取到延迟消息需要放入的queueid
                int queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());

                // Backup real topic, queueId
                //并且设置消息真正放入的地方也就是SCHEDULE_TOPIC_XXXX中
                MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());
                MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));
                msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));

                msg.setTopic(topic);
                msg.setQueueId(queueId);
            }
        }
        //设置存储的时间戳
        InetSocketAddress bornSocketAddress = (InetSocketAddress) msg.getBornHost();
        if (bornSocketAddress.getAddress() instanceof Inet6Address) {
            msg.setBornHostV6Flag();
        }

        InetSocketAddress storeSocketAddress = (InetSocketAddress) msg.getStoreHost();
        if (storeSocketAddress.getAddress() instanceof Inet6Address) {
            msg.setStoreHostAddressV6Flag();
        }

        PutMessageThreadLocal putMessageThreadLocal = this.putMessageThreadLocal.get();
        updateMaxMessageSize(putMessageThreadLocal);
        if (!multiDispatch.isMultiDispatchMsg(msg)) {
            //获取到消息的编码器,并且编码,因为encode为一个公共类,为了防止锁竞争,所以放入到threadLocal中
            PutMessageResult encodeResult = putMessageThreadLocal.getEncoder().encode(msg);
            if (encodeResult != null) {
                return CompletableFuture.completedFuture(encodeResult);
            }
            msg.setEncodedBuff(putMessageThreadLocal.getEncoder().getEncoderBuffer());
        }
        //构建消息上下文中
        PutMessageContext putMessageContext = new PutMessageContext(generateKey(putMessageThreadLocal.getKeyBuilder(), msg));

        long elapsedTimeInLock = 0;
        MappedFile unlockMappedFile = null;
        //开始写入消息
        putMessageLock.lock(); //spin or ReentrantLock ,depending on store config
        try {
            MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile();
            long beginLockTimestamp = this.defaultMessageStore.getSystemClock().now();
            this.beginTimeInLock = beginLockTimestamp;

            // Here settings are stored timestamp, in order to ensure an orderly
            // global
            msg.setStoreTimestamp(beginLockTimestamp);

            if (null == mappedFile || mappedFile.isFull()) {
                //获取到最后一个mappedfile
                mappedFile = this.mappedFileQueue.getLastMappedFile(0); // Mark: NewFile may be cause noise
            }
            if (null == mappedFile) {
                log.error("create mapped file1 error, topic: " + msg.getTopic() + " clientAddr: " + msg.getBornHostString());
                return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, null));
            }
            //调用mappedfile的appendMessage方法写入消息
            result = mappedFile.appendMessage(msg, this.appendMessageCallback, putMessageContext);
            switch (result.getStatus()) {
                case PUT_OK:
                    break;
                case END_OF_FILE:
                    unlockMappedFile = mappedFile;
                    // Create a new file, re-write the message
                    mappedFile = this.mappedFileQueue.getLastMappedFile(0);
                    if (null == mappedFile) {
                        // XXX: warn and notify me
                        log.error("create mapped file2 error, topic: " + msg.getTopic() + " clientAddr: " + msg.getBornHostString());
                        return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, result));
                    }
                    result = mappedFile.appendMessage(msg, this.appendMessageCallback, putMessageContext);
                    break;
                case MESSAGE_SIZE_EXCEEDED:
                case PROPERTIES_SIZE_EXCEEDED:
                    return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, result));
                case UNKNOWN_ERROR:
                    return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result));
                default:
                    return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result));
            }

            elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginLockTimestamp;
        } finally {
            beginTimeInLock = 0;
            putMessageLock.unlock();
        }

        if (elapsedTimeInLock > 500) {
            log.warn("[NOTIFYME]putMessage in lock cost time(ms)={}, bodyLength={} AppendMessageResult={}", elapsedTimeInLock, msg.getBody().length, result);
        }

        if (null != unlockMappedFile && this.defaultMessageStore.getMessageStoreConfig().isWarmMapedFileEnable()) {
            this.defaultMessageStore.unlockMappedFile(unlockMappedFile);
        }

        PutMessageResult putMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, result);

        // Statistics
        storeStatsService.getSinglePutMessageTopicTimesTotal(msg.getTopic()).add(1);
        storeStatsService.getSinglePutMessageTopicSizeTotal(topic).add(result.getWroteBytes());

        CompletableFuture<PutMessageStatus> flushResultFuture = submitFlushRequest(result, msg);
        //设置返回结果
        CompletableFuture<PutMessageStatus> replicaResultFuture = submitReplicaRequest(result, msg);
        return flushResultFuture.thenCombine(replicaResultFuture, (flushStatus, replicaStatus) -> {
            if (flushStatus != PutMessageStatus.PUT_OK) {
                putMessageResult.setPutMessageStatus(flushStatus);
            }
            if (replicaStatus != PutMessageStatus.PUT_OK) {
                putMessageResult.setPutMessageStatus(replicaStatus);
            }
            return putMessageResult;
        });
    }

3.2 commitLog是如何flush消息的-submitFlushRequest

commitLog如果要flush磁盘的话,其实是提交一个flush请求,然后根据同步刷盘还是异步刷盘,最后交由FlushCommitLogService来消费请求,最后调用mappedFile的flush方法,将数据从buffer中flush到磁盘中去的。

  /**
     * @param result 向mappedfile中追加消息的结果
     * @param messageExt 具体的消息内容已经协议头等
     * @return
     */
    public CompletableFuture<PutMessageStatus> submitFlushRequest(AppendMessageResult result, MessageExt messageExt) {
        // Synchronization flush
        //如果采用同步刷盘
        if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
            //采用的是GroupCommitService进行刷盘
            final GroupCommitService service = (GroupCommitService) this.flushCommitLogService;
            //默认是需要等待消息刷盘成功的
            if (messageExt.isWaitStoreMsgOK()) {
                //构建刷盘请求,包括当前消息的在buffer中的写指针
                GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(),
                        this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
                flushDiskWatcher.add(request);
                //调用GroupCommitService的putRequest推送刷盘请求
                service.putRequest(request);
                //当前线程阻塞等待刷盘结果
                return request.future();
            } else {
                //如果不需要等待刷盘线程成功,便直接唤醒刷盘线程,并且返回成功(此时也可能有丢失数据的风险)
                service.wakeup();
                return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
            }
        }
        // Asynchronous flush
        else {
            //如果采用异步刷盘策略
            if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
                //如果未采用了瞬时缓存池技术,便唤醒flush服务线程
                flushCommitLogService.wakeup();
            } else  {
                //如果未采用了瞬时缓存池技术,便唤醒commit服务线程
                commitLogService.wakeup();
            }
            //返回成功
            return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
        }
    }

3.3 rocketMq是如何实现主从同步的

rocketmq以前采用的方式是通过主从的方式,主节点负责写入,然后并且commitLog同步到从节点的方式实现高可用的,但是这样有个问题,就是主节点宕机过后,需要手动的修改某个从节点为主节点。现在这种架构基本上没有使用,而换用的是后面要讲解的用raft协议实现dledger高可用架构。我们接下来大致了解一下主从架构是如何同步的。

3.3.1 提交复制请求 -submitReplicaRequest

public CompletableFuture<PutMessageStatus> submitReplicaRequest(AppendMessageResult result, MessageExt messageExt) {
        //如果节点为主节点,并且为同步复制
        if (BrokerRole.SYNC_MASTER == this.defaultMessageStore.getMessageStoreConfig().getBrokerRole()) {
            HAService service = this.defaultMessageStore.getHaService();
            //如果消息已经同步完毕
            if (messageExt.isWaitStoreMsgOK()) {
                if (service.isSlaveOK(result.getWroteBytes() + result.getWroteOffset())) {
                    GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(),
                            this.defaultMessageStore.getMessageStoreConfig().getSlaveTimeout());
                    //会调用HAserver的putRequest向里面发送复制请求
                    service.putRequest(request);
                    service.getWaitNotifyObject().wakeupAll();
                    return request.future();
                }
                else {
                    return CompletableFuture.completedFuture(PutMessageStatus.SLAVE_NOT_AVAILABLE);
                }
            }
        }
        return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
    }

3.3.2 rocketmq的主从同步组件-HAService

1.主节点监听从节点的连接请求,并且建立socket连接

下面的代码是nio的监听并且建立连接的标准写法。

   public void run() {
            log.info(this.getServiceName() + " service started");
            
            while (!this.isStopped()) {
                try {
                    this.selector.select(1000);
                    Set<SelectionKey> selected = this.selector.selectedKeys();

                    if (selected != null) {
                        for (SelectionKey k : selected) {
                            if ((k.readyOps() & SelectionKey.OP_ACCEPT) != 0) {
                                //监听多路复用的请求
                                SocketChannel sc = ((ServerSocketChannel) k.channel()).accept();

                                if (sc != null) {
                                    HAService.log.info("HAService receive new connection, "
                                        + sc.socket().getRemoteSocketAddress());

                                    try {
                                        //拿到与从节点的channel过后,并且凑早HAconnection
                                        HAConnection conn = new HAConnection(HAService.this, sc);
                                        conn.start();
                                        //加入到链接池connectionList中
                                        HAService.this.addConnection(conn);
                                    } catch (Exception e) {
                                        log.error("new HAConnection exception", e);
                                        sc.close();
                                    }
                                }
                            } else {
                                log.warn("Unexpected ops in select " + k.readyOps());
                            }
                        }

                        selected.clear();
                    }
                } catch (Exception e) {
                    log.error(this.getServiceName() + " service has exception.", e);
                }
            }

            log.info(this.getServiceName() + " service end");
        }
2.将commitLog通过网络连接发送至从节点-GroupTransferService
 public void run() {
            log.info(this.getServiceName() + " service started");

            while (!this.isStopped()) {
                try {
                    this.waitForRunning(10);
                    this.doWaitTransfer();
                } catch (Exception e) {
                    log.warn(this.getServiceName() + " service has exception. ", e);
                }
            }

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

3.从节点读取消息并且将其加入到自己的commitLog中-HAClient

 private boolean processReadEvent() {
            
            int readSizeZeroTimes = 0;
            while (this.byteBufferRead.hasRemaining()) {
                try {
                    //从网络中读取消息到byteBufferRead中
                    int readSize = this.socketChannel.read(this.byteBufferRead);
                    if (readSize > 0) {
                        readSizeZeroTimes = 0;
                        //将bytebuffer中的数据写入到磁盘中
                        boolean result = this.dispatchReadRequest();
                        if (!result) {
                            log.error("HAClient, dispatchReadRequest error");
                            return false;
                        }
                    } else if (readSize == 0) {
                        if (++readSizeZeroTimes >= 3) {
                            break;
                        }
                    } else {
                        log.info("HAClient, processReadEvent read socket < 0");
                        return false;
                    }
                } catch (IOException e) {
                    log.info("HAClient, processReadEvent read socket exception", e);
                    return false;
                }
            }

            return true;
        }
 private boolean dispatchReadRequest() {
            //获取到消息头大小
            final int msgHeaderSize = 8 + 4; // phyoffset + size

            while (true) {
                int diff = this.byteBufferRead.position() - this.dispatchPosition;
                //获取到完整的消息内容
                if (diff >= msgHeaderSize) {
                    //获取到master的物理偏移量
                    long masterPhyOffset = this.byteBufferRead.getLong(this.dispatchPosition);
                    //获取到消息体
                    int bodySize = this.byteBufferRead.getInt(this.dispatchPosition + 8);
                    //当前同步的最大偏移量
                    long slavePhyOffset = HAService.this.defaultMessageStore.getMaxPhyOffset();

                    if (slavePhyOffset != 0) {
                        //如果从节点偏移量和主节点偏移量不想当便直接返回
                        if (slavePhyOffset != masterPhyOffset) {
                            log.error("master pushed offset not equal the max phy offset in slave, SLAVE: "
                                + slavePhyOffset + " MASTER: " + masterPhyOffset);
                            return false;
                        }
                    }

                    if (diff >= (msgHeaderSize + bodySize)) {
                        byte[] bodyData = byteBufferRead.array();
                        //获取到消息体开始的指针
                        int dataStart = this.dispatchPosition + msgHeaderSize;
                        //调用mappedfile的appendToCommitLog方法直接写入待commitLog中
                        HAService.this.defaultMessageStore.appendToCommitLog(
                                masterPhyOffset, bodyData, dataStart, bodySize);
                        //更新同步偏移量
                        this.dispatchPosition += msgHeaderSize + bodySize;

                        if (!reportSlaveMaxOffsetPlus()) {
                            return false;
                        }

                        continue;
                    }
                }

                if (!this.byteBufferRead.hasRemaining()) {
                    this.reallocateByteBuffer();
                }

                break;
            }

            return true;
        }

4.总结

至此,rocketmq的核心持久化主键commitLog我们便已经全部分析完成,简单而言,commitLog持有一个messageFileQueue,而mappedFileQueue对应不同的mappedFile文件,而mappedFile通过mmap技术与磁盘中的文件建立映射。而磁盘中的物理文件的每个文件的名称都是以它当前的文件的起始偏移量命名的,比如第一个文件为0.log,1个mappedfile的大小为1gb,假设第一个文件的内容刚好全部写满,所以其第二个文件的名称为1024*1024*1024.log。一有消息到来,就会向commitLog中写入文件,进行持久化,其实就是获取到最后一个mappedFile,并且调用他的appendMessage方法完成消息的写入。

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

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

相关文章

SCI一区TOP|局部强化优化算法(PRO)原理及实现【免费获取Matlab代码】

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;A Taheri受到部分强化效应(PRE)理论启发&#xff0c;提出了局部强化优化算法&#xff08;Partial Reinforcement Optimizer, PRO&#xff09;。 2.算法原理 2.1算法思…

Python数据科学 | 是时候跟Conda说再见了

本文来源公众号“Python数据科学”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;是时候跟Conda说再见了 1 简介 conda作为Python数据科学领域的常用软件&#xff0c;是对Python环境及相关依赖进行管理的经典工具&#xff0c;通…

【漏洞复现】红帆iOffice.net wssRtSyn接口处存在SQL注入

【产品&&漏洞简述】 红帆iOffice.net从最早满足医院行政办公需求&#xff08;传统OA&#xff09;&#xff0c;到目前融合了卫生主管部门的管理规范和众多行业特色应用&#xff0c;是目前唯一定位于解决医院综合业务管理的软件&#xff0c;是最符合医院行业特点的医院综…

SVN学习(001 svn安装)

尚硅谷SVN高级教程(svn操作详解) 总时长 4:53:00 共72P 此文章包含第1p-第p19的内容 介绍 为什么使用版本控制工具 版本控制工具的功能 版本控制简介 客户端服务器结构 c/s结构 服务端的结构&#xff1a; 服务程序 、版本库(存放我们上传的文件) 客户端的三个基本操作&#…

记录一次递归查询导致的 java.lang.StackOverflowError: null

问题截图&#xff1a; 由于作者使用递归统计信息&#xff0c;刚开始这个接口运行得正常&#xff0c;但是上线运行一段时间后接口就出现了&#xff0c;如图的栈溢出错误。可以看出确实是堆栈溢出了&#xff0c;解决栈溢出目前只有两种方式&#xff1a; 第一种调大栈的大小&…

小程序开发的技术难点

小程序开发是一项技术难度较高的工作&#xff0c;需要开发者具备多方面的知识和技能&#xff0c;小程序开发的技术难点主要体现在以下几个方面。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1. 多端兼容 小程序需要在微信、支付宝…

Flow Matching For Generative Modeling

Flow Matching For Generative Modeling 一、基于流的&#xff08;Flow based&#xff09;生成模型 生成模型 我们先回顾一下所谓的生成任务&#xff0c;究竟是想要做什么事情。我们认为&#xff0c;世界上所有的图片&#xff0c;是符合某种分布 p d a t a ( x ) p_{data}(…

MES管理系统如何设计生产质量管理功能

在现代制造业中&#xff0c;MES管理系统作为连接企业计划层与车间操作层的关键桥梁&#xff0c;其生产者计量管理功能的设计显得尤为重要。一个完善的MES管理系统生产质量管理模块&#xff0c;不仅要求能够实时、准确地采集和分析生产过程中的质量数据&#xff0c;还需要能够与…

Unity3d 游戏暂停(timeScale=0)引起的deltaTime关联的系列问题解决

问题描述 游戏暂停的功能是通过设置timeScale0实现的&#xff0c;不过在暂停游戏的时候&#xff0c;需要对角色进行预览和设置&#xff0c;为了实现这个功能&#xff0c;是通过鼠标控制相机的操作&#xff0c;为了使相机的操作丝滑&#xff0c;获取鼠标操作系数乘以Time.delta…

如何在React中使用CSS模块,并解释为什么使用它们比传统CSS更有益?

在React中使用CSS模块是一种将CSS类名局部化到单个组件的方法&#xff0c;从而避免了全局作用域中的类名冲突。CSS模块允许你为组件编写样式&#xff0c;并确保这些样式只应用于该组件&#xff0c;而不会影响到其他组件。 以下是在React中使用CSS模块的步骤&#xff1a; 安装C…

Excel 识别数据层次后转换成表格

某列数据可分为 3 层&#xff0c;第 1 层是字符串&#xff0c;第 2 层是日期&#xff0c;第 3 层是时间&#xff1a; A1NAME122024-06-03304:06:12404:09:23508:09:23612:09:23717:02:2382024-06-02904:06:121004:09:231108:09:2312NAME2132024-06-031404:06:121504:09:231620…

FreeBSD在zfs挂接第二块ssd 硬盘

为FreeBSD机器新增加了一块ssd硬盘&#xff1a;骑尘 256G 先格式化分区硬盘 进入bsdconfig 选Disk Management 选择ada1 &#xff0c;也就是新增加的硬盘 选择auto 然后选择Entire Disk 提示信息 The existing partition scheme on this disk (MBR) │ …

如何解决windows自动更新,释放C盘更新内存

第一步&#xff1a;首先关闭windows自动更新组件 没有更新windows需求&#xff0c;为了防止windows自动更新&#xff0c;挤占C盘空间&#xff0c;所以我们要采取停止Windows Update服务。按下WinR打开运行对话框&#xff0c;输入services.msc&#xff0c; 然后按Enter。在服务…

传输大文件之镭速自动清理过期文件

电子文档的普及无疑极大地便利了我们的工作与生活&#xff0c;但随之而来的是如何有效管理这些日益增多的文件。企业面临着存储空间紧张、文件传输复杂、敏感信息泄露等挑战。自动化文件清理的需求日益凸显&#xff0c;这不仅关乎个人对高效工作环境的追求&#xff0c;更是企业…

绘唐3工具—让创作触手可及

绘唐AIGC&#xff08;Artificial Intelligence Generated Creativity&#xff09;是一种新兴的技术&#xff0c;通过人工智能生成创意&#xff0c;让创作更加触手可及。 绘唐3下载地址https://qvfbz6lhqnd.feishu.cn/wiki/KnRawcWQxiFrj5kC8lVcCEypnZc 绘唐AIGC结合了人工智能…

element-plus的form表单组件之checkbox组件

单个checkbox 绑定的响应式的值类型为bool类型&#xff0c;同一个组的checkbox多选其值对应值的数组&#xff0c;类型根据checkbox的value值而来。 label只用来显示具体的值&#xff0c;根据value属性来设置。 element-plus的checkbox提供多种特性。 如单选&#xff0c;多选…

B站广告开户投流是什么政策?要哪些资质?

B站&#xff08;哔哩哔哩&#xff09;作为年轻人喜爱的视频分享社区&#xff0c;其广告价值也日益凸显。为了更好地服务广告主&#xff0c;B站近日对广告开户投流政策进行了更新&#xff0c;云衔科技作为专业的数字营销服务商&#xff0c;也积极响应&#xff0c;为广告主提供一…

【java分布式计算】控制反转和依赖注入(DI IOC AOP)

考试要求&#xff1a;了解控制反转的基本模式&#xff0c;用依赖注入编写程序 目录 控制反转&#xff08;Inversion of Control, IOC&#xff09;&#xff1a; 依赖注入&#xff08;Dependency Injection, DI&#xff09;&#xff1a; 依赖注入的三种实现方式 具体的例子 …

C#——装箱与拆箱详情

装箱与拆箱 装箱: 将值类型转换成引用类型的过程&#xff1b; 拆箱: 把引用类型转为值类型的过程&#xff0c;就是拆箱 装箱 拆箱

韩顺平0基础学Java——第27天

p548-568 明天开始坦克大战 Entry 昨天没搞明白的Map、Entry、EntrySet&#xff1a;//GPT教的 Map 和 Entry 的关系 1.Map 接口&#xff1a;它定义了一些方法来操作键值对集合。常用的实现类有 HashMap、TreeMap 等。 2. Entry接口&#xff1a;Entry 是 Map 接口的一个嵌…