首页 > 其他分享 >【RocketMQ】数据的清理机制

【RocketMQ】数据的清理机制

时间:2023-10-23 09:11:05浏览次数:29  
标签:文件 清理 DefaultMessageStore 磁盘 机制 true public RocketMQ

Broker在启动的时候会注册定时任务,定时清理过期的数据,默认是每10s执行一次,分别清理CommitLog文件和ConsumeQueue文件:

public class DefaultMessageStore implements MessageStore {

    // CommitLog清理类
    private final CleanCommitLogService cleanCommitLogService;
    // ConsumeQueue清理类
    private final CleanConsumeQueueService cleanConsumeQueueService;

    public void start() throws Exception {
        // ...

        // 添加定时任务
        this.addScheduleTask();

        //...
    }

    private void addScheduleTask() {
        // ...
        // 注册定时定理任务,默认10s执行一次
        this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                // 清理数据
                DefaultMessageStore.this.cleanFilesPeriodically();
            }
        }, 1000 * 60, this.messageStoreConfig.getCleanResourceInterval(), TimeUnit.MILLISECONDS);

        // ...
    }

    private void cleanFilesPeriodically() {
        // 启动CommitLog清理
        this.cleanCommitLogService.run();
        // 启动ConsumeQueue清理
        this.cleanConsumeQueueService.run();
    }
}

CommitLog文件清理

CommitLog文件清理的逻辑主要在CleanCommitLogService中,它是DefaultMessageStore的内部类,调用了deleteExpiredFiles方法删除过期文件:

public class DefaultMessageStore implements MessageStore {
    class CleanCommitLogService {
        public void run() {
            try {
                // 删除过期文件
                this.deleteExpiredFiles();
                this.redeleteHangedFile();
            } catch (Throwable e) {
                DefaultMessageStore.log.warn(this.getServiceName() + " service has exception. ", e);
            }
        }
    }
}

在执行清理任务之前,首先会获取一些配置参数,然后通过isTimeToDelete方法判断是否到了清理文件的时间,通过isSpaceToDelete方法计算磁盘是否还有足够的空间,处于以下三种情况之一,将会执行文件清理操作:

  1. 已经到了清理文件的时间,默认是4点;
  2. 磁盘使用已经超过了设定的阈值;
  3. 手动删除;
public class DefaultMessageStore implements MessageStore {
    class CleanCommitLogService {
        private void deleteExpiredFiles() {
            int deleteCount = 0;
            // 获取文件保留时间
            long fileReservedTime = DefaultMessageStore.this.getMessageStoreConfig().getFileReservedTime();
            // 删除文件的间隔时间
            int deletePhysicFilesInterval = DefaultMessageStore.this.getMessageStoreConfig().getDeleteCommitLogFilesInterval();
            int destroyMapedFileIntervalForcibly = DefaultMessageStore.this.getMessageStoreConfig().getDestroyMapedFileIntervalForcibly();
            // 计算是否到了清理文件的时间,默认是4点
            boolean timeup = this.isTimeToDelete();
            // 计算磁盘是否有足够空间
            boolean spacefull = this.isSpaceToDelete();
            // 是否手动删除
            boolean manualDelete = this.manualDeleteFileSeveralTimes > 0;
            // 如果到了时间或者磁盘使用率超过阈值或者是手动删除
            if (timeup || spacefull || manualDelete) {

                if (manualDelete)
                    this.manualDeleteFileSeveralTimes--;
                // 是否立刻清理
                boolean cleanAtOnce = DefaultMessageStore.this.getMessageStoreConfig().isCleanFileForciblyEnable() && this.cleanImmediately;

                log.info("begin to delete before {} hours file. timeup: {} spacefull: {} manualDeleteFileSeveralTimes: {} cleanAtOnce: {}",
                    fileReservedTime, timeup, spacefull, manualDeleteFileSeveralTimes, cleanAtOnce);
                // fileReservedTime默认是72小时,这里转换为毫秒数
                fileReservedTime *= 60 * 60 * 1000;
                // 删除超过72小时的文件
                deleteCount = DefaultMessageStore.this.commitLog.deleteExpiredFile(fileReservedTime, deletePhysicFilesInterval,
                    destroyMapedFileIntervalForcibly, cleanAtOnce);
                if (deleteCount > 0) {
                } else if (spacefull) {
                    log.warn("disk space will be full soon, but delete file failed.");
                }
            }
        }
    }
}

清理时间判断

deleteWhen:清理时间点,默认是凌晨四点:

public class MessageStoreConfig {

     // 触发文件删除的时间,默认凌晨四点
    @ImportantField
    private String deleteWhen = "04";
}

isTimeToDelete中首先会获取配置的清理时间,默认是早上4点,然后判断当前时间是否已经到达了这个时间点:

        private boolean isTimeToDelete() {
            // 获取配置的清理时间,默认是早上4点
            String when = DefaultMessageStore.this.getMessageStoreConfig().getDeleteWhen();
            // 如果到达了时间,返回true
            if (UtilAll.isItTimeToDo(when)) {
                DefaultMessageStore.log.info("it's time to reclaim disk space, " + when);
                return true;
            }

            return false;
        }

磁盘空间使用判断

RocketMQ设定了两个阈值,如果磁盘使用率超过了这些阈值,需要立刻进行清理:

  • diskSpaceWarningLevelRatio:磁盘使用率警戒阈值,默认0.90;
  • diskSpaceCleanForciblyRatio:强制进行清理的磁盘使用比例阈值,默认0.85;
    class CleanCommitLogService {
        // 磁盘使用率警戒阈值,默认0.9
        private final double diskSpaceWarningLevelRatio =
            Double.parseDouble(System.getProperty("rocketmq.broker.diskSpaceWarningLevelRatio", "0.90"));
        // 强制进行清理的磁盘使用比例阈值,默认0.85
        private final double diskSpaceCleanForciblyRatio =
            Double.parseDouble(System.getProperty("rocketmq.broker.diskSpaceCleanForciblyRatio", "0.85"));
    }

计算磁盘使用率

  1. 获取Commitlog存储目录;
  2. 计算每个目录下磁盘分区的使用率;
  3. 记录这些目录中,磁盘使用率最小的那个值,记在minPhysicRatio中;

接下来对磁盘使用率最小的那个值minPhysicRatio进行判断:

  1. 如果使用率最小的那个分区都已经大于警告阈值(默认0.90)说明需要立刻清理,将立即执行清理状态cleanImmediately置为true;
  2. 如果使用率最小的那个分区大于强制进行清理的磁盘使用比例阈值(默认0.85),说明需要立刻清理,将立即执行清理状态cleanImmediately置为true;
  3. 其他情况,表示磁盘使用率正常;
        private boolean isSpaceToDelete() {
            // 获取磁盘最大使用比率
            double ratio = DefaultMessageStore.this.getMessageStoreConfig().getDiskMaxUsedSpaceRatio() / 100.0;
            cleanImmediately = false;

            {
                // 获取Commitlog存储目录
                String commitLogStorePath = DefaultMessageStore.this.getStorePathPhysic();
                String[] storePaths = commitLogStorePath.trim().split(MessageStoreConfig.MULTI_PATH_SPLITTER);
                Set<String> fullStorePath = new HashSet<>();
                double minPhysicRatio = 100;
                String minStorePath = null;
                for (String storePathPhysic : storePaths) {
                    // 计算磁盘分区使用率
                    double physicRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathPhysic);
                    if (minPhysicRatio > physicRatio) {
                        minPhysicRatio =  physicRatio; // 更新最小使用率,主要是记录所有目录分区中使用率最小的值
                        minStorePath = storePathPhysic;
                    }
                    // 如果大于设定的最大磁盘使用比例
                    if (physicRatio > diskSpaceCleanForciblyRatio) {
                        // 将对应的目录加入到fullStorePath
                        fullStorePath.add(storePathPhysic);
                    }
                }
                DefaultMessageStore.this.commitLog.setFullStorePaths(fullStorePath);
                if (minPhysicRatio > diskSpaceWarningLevelRatio) { // 如果最小的那个磁盘使用率大于警告线
                    boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskFull();
                    if (diskok) {
                        DefaultMessageStore.log.error("physic disk maybe full soon " + minPhysicRatio +
                                ", so mark disk full, storePathPhysic=" + minStorePath);
                    }
                    // 立即执行清理置为true
                    cleanImmediately = true;
                } else if (minPhysicRatio > diskSpaceCleanForciblyRatio) {// 如果最小的那个磁盘使用率大于警告线
                    cleanImmediately = true;
                } else {
                    // 其他情况,表示磁盘使用率正常
                    boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskOK();
                    if (!diskok) {
                        DefaultMessageStore.log.info("physic disk space OK " + minPhysicRatio +
                                ", so mark disk ok, storePathPhysic=" + minStorePath);
                    }
                }

                // 路径为空或者文件不存在的时候minPhysicRatio值会变成-1,
                if (minPhysicRatio < 0 || minPhysicRatio > ratio) {
                    DefaultMessageStore.log.info("physic disk maybe full soon, so reclaim space, "
                            + minPhysicRatio + ", storePathPhysic=" + minStorePath);
                    return true;
                }
            }

            // ...

            // 其他情况,表示磁盘使用率正常
            return false;
        }

磁盘分区使用率计算方式

  1. 获取磁盘总空间大小totalSpace;
  2. 计算已使用空间大小(usedSpace) = 磁盘总空间(totalSpace) - 剩余空间(剩余空间不一定全部是可用的);
  3. 计算可用的剩余空间usableSpace;
  4. 计算可用空间总大小(entireSpace) = 已使用空间大小(usedSpace) + 可用的剩余空间(usableSpace);
  5. 计算了一个roundNum;
  6. 使用usedSpace * 100 / entireSpace + roundNum计算磁盘使用率;
public class UtilAll {
    public static double getDiskPartitionSpaceUsedPercent(final String path) {
        // 如果路径为空,返回-1
        if (null == path || path.isEmpty()) {
            log.error("Error when measuring disk space usage, path is null or empty, path : {}", path);
            return -1;
        }

        try {
            File file = new File(path);
            // 如果文件不存在
            if (!file.exists()) {
                log.error("Error when measuring disk space usage, file doesn't exist on this path: {}", path);
                return -1;
            }
            // 获取总空间
            long totalSpace = file.getTotalSpace();
            if (totalSpace > 0) {
                // 已使用空间 = 磁盘总空间 - 剩余空间(剩余空间不一定全部是可用的)
                long usedSpace = totalSpace - file.getFreeSpace();
                // 可用剩余空间
                long usableSpace = file.getUsableSpace();
                // 可用空间总大小
                long entireSpace = usedSpace + usableSpace;
                long roundNum = 0;
                if (usedSpace * 100 % entireSpace != 0) {
                    roundNum = 1;
                }
                long result = usedSpace * 100 / entireSpace + roundNum;
                return result / 100.0;
            }
        } catch (Exception e) {
            log.error("Error when measuring disk space usage, got exception: :", e);
            return -1;
        }
        // 异常情况返回-1
        return -1;
    }
}

清理文件

先来看一些配置参数:

public class MessageStoreConfig {
    // 文件的保留时间,默认72小时
    @ImportantField
    private int fileReservedTime = 72;

    // CommitLog文件删除间隔时间,默认100ms,删除过期文件后会休眠一段时间(这个间隔时间),在进行下一个
    private int deleteCommitLogFilesInterval = 100;

    // 主要用于在调用shuntdown关闭文件后,如果到达这个间隔时间之后依旧有线程引用该文件时,会强制将文件的引用数置为负数,表示不再有线程引用
    private int destroyMapedFileIntervalForcibly = 1000 * 120;
}

回到删除方法,继续看到达清理条件的代码:

  1. 判断是否需要立刻清理,cleanImmediately为true并且开启了允许强制清理;
  2. fileReservedTime默认是72小时,将其转换为毫秒数;
  3. 调用CommitLog的deleteExpiredFile方法,将文件的过期时间、文件删除间隔、是否立刻清理等参数传入,开始删除过期文件;
public class DefaultMessageStore implements MessageStore {
    class CleanCommitLogService {
        private void deleteExpiredFiles() {
            // ...
            // 如果到了时间或者磁盘使用率超过阈值或者是手动删除
            if (timeup || spacefull || manualDelete) {
                // ...
                // 是否立刻清理
                boolean cleanAtOnce = DefaultMessageStore.this.getMessageStoreConfig().isCleanFileForciblyEnable() && this.cleanImmediately;

                log.info("begin to delete before {} hours file. timeup: {} spacefull: {} manualDeleteFileSeveralTimes: {} cleanAtOnce: {}",
                    fileReservedTime, timeup, spacefull, manualDeleteFileSeveralTimes, cleanAtOnce);
                // fileReservedTime默认是72小时,这里转换为毫秒数
                fileReservedTime *= 60 * 60 * 1000;
                // 删除超过72小时的文件
                deleteCount = DefaultMessageStore.this.commitLog.deleteExpiredFile(fileReservedTime, deletePhysicFilesInterval,
                    destroyMapedFileIntervalForcibly, cleanAtOnce);
                if (deleteCount > 0) {
                } else if (spacefull) {
                    log.warn("disk space will be full soon, but delete file failed.");
                }
            }
        }
    }
}

在CommitLog的deleteExpiredFile删除文件的方法中,又调用了MappedFileQueue的deleteExpiredFileByTime开始删除过期文件:

public class CommitLog {
   public int deleteExpiredFile(
        final long expiredTime,
        final int deleteFilesInterval,
        final long intervalForcibly,
        final boolean cleanImmediately
    ) {
        return this.mappedFileQueue.deleteExpiredFileByTime(expiredTime, deleteFilesInterval, intervalForcibly, cleanImmediately);
    }
}

删除过期文件

MappedFileQueue的deleteExpiredFileByTime处理逻辑如下:

  1. 计算文件的存活时间:文件最后一次修改的时间 + 过期时间(默认72小时);
  2. 如果当前时间大于文件的存活时间,或者cleanImmediately为true,这两个条件都表示需要清理文件,则调用MappedFile的destroy方法删除文件;
  3. 删除当前文件后,如果设置了文件删除间隔,并且当前文件不是最后一个,则休眠一段时间(设置的删除间隔时间)后再删除下一个过期文件;
public class MappedFileQueue {
    public int deleteExpiredFileByTime(final long expiredTime,
        final int deleteFilesInterval,
        final long intervalForcibly,
        final boolean cleanImmediately) {
        Object[] mfs = this.copyMappedFiles(0);
        if (null == mfs)
            return 0;
        int mfsLength = mfs.length - 1;
        int deleteCount = 0;
        List<MappedFile> files = new ArrayList<MappedFile>();
        if (null != mfs) {
            for (int i = 0; i < mfsLength; i++) {
                MappedFile mappedFile = (MappedFile) mfs[i];
                // 文件最后一次修改的时间 + 过期时间
                long liveMaxTimestamp = mappedFile.getLastModifiedTimestamp() + expiredTime;
                // 如果当前时间大于文件的存活时间获取 cleanImmediately为true
                if (System.currentTimeMillis() >= liveMaxTimestamp || cleanImmediately) {
                    // 清理文件
                    if (mappedFile.destroy(intervalForcibly)) {
                        files.add(mappedFile);
                        deleteCount++;

                        //...
                        // 如果设置了文件删除间隔,并且不是最后一个文件,休眠一段时间后再删除下一个过期文件
                        if (deleteFilesInterval > 0 && (i + 1) < mfsLength) {
                            try {
                                Thread.sleep(deleteFilesInterval);
                            } catch (InterruptedException e) {
                            }
                        }
                    } else {
                        break;
                    }
                } else {
                    //avoid deleting files in the middle
                    break;
                }
            }
        }
        deleteExpiredFile(files);
        return deleteCount;
    }
}

由于文件可能被其他线程占用,所以在删除文件前需要保证没有其他线程使用文件,可以看到调用了shutdown方法关闭文件,然后调用isCleanupOver方法判断是否没有线程占用文件并且已经关闭文件相关的资源,只有这两个条件满足时才可以删除文件:

public class MappedFile extends ReferenceResource {
    public boolean destroy(final long intervalForcibly) {
        // 需要先关闭文件
        this.shutdown(intervalForcibly);
        // 是否关闭
        if (this.isCleanupOver()) {
            try {
                // 关闭filechannel
                this.fileChannel.close();
                log.info("close file channel " + this.fileName + " OK");
                long beginTime = System.currentTimeMillis();
                // 删除文件
                boolean result = this.file.delete();
                log.info("delete file[REF:" + this.getRefCount() + "] " + this.fileName
                    + (result ? " OK, " : " Failed, ") + "W:" + this.getWrotePosition() + " M:"
                    + this.getFlushedPosition() + ", "
                    + UtilAll.computeElapsedTimeMilliseconds(beginTime));
            } catch (Exception e) {
                log.warn("close file channel " + this.fileName + " Failed. ", e);
            }

            return true;
        } else {
            log.warn("destroy mapped file[REF:" + this.getRefCount() + "] " + this.fileName
                + " Failed. cleanupOver: " + this.cleanupOver);
        }

        return false;
    }
}

shutdown关闭文件

ReferenceResource中有两个成员变量需要关注:

  • available:表示文件是否可用,默认为true,表示文件可用,当调用shutdown方法关闭文件的时候,会置为false;
  • refCount:当前文件的引用数量,初始化为1,表示有一个线程在使用文件,对文件进行操作前,会先申请占用(hold),申请占用成功时refCount会增1表示新增一个引用,使用完毕之后会释放(release方法),将refCount的值再减1,通过refCount的值可以判断是否有其他线程在使用文件;
  • cleanupOver:文件的清理状态,如果文件已经被关闭、没有线程引用并且文件占用的相关资源已经释放,此时会被置为true,否则为false,表示文件的相关资源还未清理完毕;
public abstract class ReferenceResource {
    protected final AtomicLong refCount = new AtomicLong(1);
    protected volatile boolean available = true;
    protected volatile boolean cleanupOver = false;
}

shutdown方法的处理逻辑如下:

  1. 首先判断文件是否可用状态(available)是否为true,如果为true,将available为置为false,表示该文件不再提供使用,并记录本次关闭文件的时间戳,然后调用release方法关闭文件相关的资源;
  2. 如果available为false但是该文件的引用数量大于0,表示文件已经不再提供使用但是还有其他线程在引用,此时判断当前时间距离上次关闭文件的时间是否大于强制清理间隔,如果大于等于表示到了强制清理的时间,强制将文件的引用数量置为负数,再调用release方法关闭文件相关的资源;
public abstract class ReferenceResource {
   public void shutdown(final long intervalForcibly) {
        // 文件是否可用状态为true
        if (this.available) {
            // 置为false表示不再提供使用
            this.available = false;
            // 记录时间
            this.firstShutdownTimestamp = System.currentTimeMillis();
            // 开始释放资源
            this.release();
        } else if (this.getRefCount() > 0) { // 如果文件的引用数量大于0,表示文件被占用
            // 计算当前时间 - 上次关闭文件的时间,是否大于强制清理间隔
            if ((System.currentTimeMillis() - this.firstShutdownTimestamp) >= intervalForcibly) {
                // 引用数量设置为负值
                this.refCount.set(-1000 - this.getRefCount());
                // 释放资源
                this.release();
            }
        }
    }
}

以commit方法为例,在使用文件进行操作的时候,通常会先调用hold方法申请占用,使用完毕之后再调用release方法释放占用:

public class MappedFile extends ReferenceResource {
    public int commit(final int commitLeastPages) {
        // ...
        if (this.isAbleToCommit(commitLeastPages)) {
            // 申请文件的占用
            if (this.hold()) {
                commit0();
                // 释放文件的占用
                this.release();
            } else {
                log.warn("in commit, hold failed, commit offset = " + this.committedPosition.get());
            }
        }
    }
}

在hold方法中可以看到,先判断当前文件是否可用(通过available的值判断),如果可用再进行如下操作:

  1. 调用getAndIncrement先让refCount自增,如果refCount大于0表示文件占用成功;
  2. 如果上一步refCount自增之后小于等于0,表示未成功,由于上面进行了一次自增操作,所以这里要再减回去;
public abstract class ReferenceResource {
    public synchronized boolean hold() {
        // 判断文件是否可用
        if (this.isAvailable()) {
            // 调用getAndIncrement先让refCount自增,如果refCount大于0表示获取成功
            if (this.refCount.getAndIncrement() > 0) {
                return true;
            } else {
                // 如果上一步refCount自增之后小于等于0,表示未获取成功,由于上面进行了一次自增操作,所以这里要再减回去
                this.refCount.getAndDecrement();
            }
        }

        return false;
    }
}

在release方法中,首先可以看到对refCount进行了自减操作,释放文件的占用数,然后进行如下判断:

  1. 如果此时refCount的值依旧大于0,表示有其他线程还在引用,直接返回即可;
  2. 如果此时refCount的值小于等于0,表示没有其他线程使用,加锁调用cleanup方法开始清理文件的使用资源,然后返回清理状态,这一步主要是在文件没有其他线程使用的时候,关闭文件占用相关资源;
public abstract class ReferenceResource {

    public void release() {
        // 自减操作,释放文件的占用数
        long value = this.refCount.decrementAndGet();
        // 如果文件的引用数量大于0,表示文件被占用
        if (value > 0)
            return;
        // 加锁
        synchronized (this) {
            // 清理文件的使用资源,返回清理状态,true表示清理完毕,false表示未完成
            this.cleanupOver = this.cleanup(value);
        }
    }
}

cleanup关闭文件占用资源

cleanup的处理逻辑如下:

  1. 如果文件的可用状态vailable还为true,表示文件未关闭,不能清理,返回false;
  2. 如果isCleanupOver已经为true,表示已经清理完毕,返回true;
  3. 开始清理文件占用的资源,MappedByteBuffer涉及到堆外内存,所以关闭文件前需要清理相关内存,防止内存泄露;
  4. 清理完毕,返回true赋值给cleanupOver,表示文件相关占用资源都已清理完毕;
public class MappedFile extends ReferenceResource {
    @Override
    public boolean cleanup(final long currentRef) {
        // 如果文件的可用状态还为true,表示文件未关闭,不能清理,返回false
        if (this.isAvailable()) {
            log.error("this file[REF:" + currentRef + "] " + this.fileName
                + " have not shutdown, stop unmapping.");
            return false;
        }
        // 如果isCleanupOver已经为true,表示已经清理完毕,返回true
        if (this.isCleanupOver()) {
            log.error("this file[REF:" + currentRef + "] " + this.fileName
                + " have cleanup, do not do it again.");
            return true;
        }
        // 开始清理文件的使用资源
        clean(this.mappedByteBuffer);
        TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(this.fileSize * (-1));
        TOTAL_MAPPED_FILES.decrementAndGet();
        log.info("unmap file[REF:" + currentRef + "] " + this.fileName + " OK");
        // 返回true,之后cleanupOver为true,表示已经清理了文件的相关占用资源
        return true;
    }
}

总结
RocketMQ会注册定时任务,定时执行清理任务,删除过期文件(默认72小时),在清理任务执行时,会先判断是否达到了清理的条件,主要有以下三个条件:

  1. 已经到了清理文件的时间,默认是4点;
  2. 磁盘使用已经超过了设定的阈值;
  3. 手动删除;

如果满足以上条件之一,开始执行清理。在删除过期文件之前,需要保证文件已经关闭以及没有其他线程在引用该文件,所以会调用关闭文件的方法修改文件可用状态,将其改为不可用并清理相关资源,接着会调用isCleanupOver方法判断该文件是否没有线程引用并且文件占用的相关资源已经释放(MappedByteBuffer涉及到堆外内存,关闭文件前需要清理相关内存,防止内存泄露),之后才可以对文件进行删除。

标签:文件,清理,DefaultMessageStore,磁盘,机制,true,public,RocketMQ
From: https://www.cnblogs.com/shanml/p/17755529.html

相关文章

  • 小程序底层技术机制解读 - 运行环境隔离
    小程序作为一种流行的移动应用形式,其底层技术机制在保障用户隐私安全和提供流畅体验方面起着关键作用。其中之一是小程序的运行环境隔离技术,本文将深入解读这一机制,并提供一个简单的代码演示,以帮助读者更好地理解。什么是运行环境隔离?运行环境隔离是指将一个应用程序的运行环境与主......
  • 注意力机制
    如果你想使用PyTorch来实现这段代码,你可以按照以下步骤进行操作:导入所需的库和模块,包括NumPy和PyTorch。importnumpyasnpimporttorch定义输入矩阵A和B,并获取它们的维度信息。A=np.array(...)#输入矩阵AB=np.array(...)#输入矩阵B#转换为Py......
  • Objective-C内存管理机制概述
    Objective-C管理的是分配在堆上的NSObject对象的内存,对其他非对象的C语言数据类型(int、char、float、double、struct、enum等)无效。有以下3种方式:手工引用计数和自动释放池(MRC,ManualReferenceCounting),又称手动保留释放(MRR,ManualRetain-Release)垃圾收集(GC,GarbageCollecti......
  • C++中的RTTI机制、多继承中的虚函数
    C++中的RTTI机制基类有虚函数时才能实现RTTI机制:基类无虚函数时,typeid(*pA)返回的是pA声明时的类型。基类有虚函数时,typeid(*pA)返回的是pA指向对象的类型。比较两个带有虚函数的类的对象是否相等if(typeid(*a)==typeid(B))if(dynamic_cast<B*>(a)):如果能够成功向......
  • RocketMQ5.0 搭建 Name Server And Broker+Proxy 同进程部署、搭建RocketMQ控制台图形
    前言RocketMQ5.0中的几个角色NameServer、Broker和Proxy,它们的作用如下:NameServer:NameServer是RocketMQ的名称服务器,负责管理消息队列和消费者组。Broker:Broker是RocketMQ的消息代理服务器,负责接收、处理和存储消息。Proxy:Proxy是RocketMQ的代理服务器,用于扩展消息......
  • 小程序底层技术机制解读 - JavaScript编程语言
    JavaScript是小程序的核心编程语言之一,它在小程序中起着至关重要的作用。本文将深入探讨JavaScript在小程序底层技术机制中的作用,以及如何利用JavaScript来构建小程序应用。同时,我们还将提供一个简单的代码演示,以帮助读者更好地理解JavaScript在小程序中的应用。JavaScript在小程序......
  • make clean命令清理在不同目录中编译的对象
    gnu-makemakefile UsingMakefiletocleansubdirectories是否可以从父目录执行makeclean,而该父目录又递归清除所有子目录,而不必在每个子目录中都包含makefile?例如,当前在我的Makefile中,我有类似以下内容:123456789SUBDIRS=src,src1.PHONY:cleansubdirs$(S......
  • postgres消息机制
    聊聊Postgres中的IPC之SIMessageQueue 在PostgreSQL中,每一个进程都有属于自己的共享缓存(sharedcache)。例如,同一个系统表在不同的进程中都有对应的Cache来缓存它的元组(对于RelCache来说缓存的是一个RelationData结构)。同一个系统表的元组可能同时被多个进程的Cache所缓......
  • async函数执行机制
    fn()console.log(1);setTimeout(()=>{console.log(4);},100);Promise.resolve().then(()=>{console.log(2);})console.log(3);functionfnPromise(){......
  • 16、实现Client远程调用的重试机制
    由于远程程序服务健壮性和网络的波动等因素,可能造成接口调用失败,因此有必要实现Client远程调用的重试机制一、基于异常捕捉的重试机制:publicStringgetDetailFromClient(){//重试次数intretryCount=3;//重试时长(单位:ms)intretryTi......