34.RocketMQ之Broker端消息存储流程详解


highlight: arduino-light

Broker消息存储概要设计

RocketMQ主要存储的文件包括Commitlog文件,ConsumeQueue文件,IndexFile文件。

RMQ把所有主题的消息存储在同一个文件中,确保消息发送时顺序写文件。

为了提高消费效率引入了ConsumeQueue消息队列文件,每个消息主题包含多个消息消费队列,每一个消息队列有一个消息文件。

IndexFile索引文件,其主要设计理念就是为了加速消息的检索性能,根据消息的属性快速从Commitlog文件中检索消息。

初识Broker消息存储

消息存储实现类:org.apache.rocketmq.store.DefaultMessageStore,它是存储模块里面最重要的一个类,包含了很多对存储文件的操作API。 java // 消息存储配置属性     private final MessageStoreConfig messageStoreConfig; // CommitLog 文件的存储实现类 private final CommitLog commitLog; // 消息队列存储缓存表,按消息主题分组 private final ConcurrentMap<String/* topic */, ConcurrentMap<Integer/* queueId */, ConsumeQueue>> consumeQueueTable; // ConsumeQueue刷盘线程 private final FlushConsumeQueueService flushConsumeQueueService; // 清除CommitLog文件服务 private final CleanCommitLogService cleanCommitLogService; // 清除ConsumeQueue文件服务 private final CleanConsumeQueueService cleanConsumeQueueService; // 索引文件实现类 private final IndexService indexService; // MappedFile分配服务 private final AllocateMappedFileService allocateMappedFileService; // CommitLog消息分发,根据CommitLog文件构建ConsumeQueue,IndexFile文件 private final ReputMessageService reputMessageService; // 存储HA机制 private final HAService haService; // 消息堆内存缓存 private final TransientStorePool transientStorePool; // 消息拉取长轮询模式消息达到监听器 private final MessageArrivingListener messageArrivingListener; // Broker属性配置类 private final BrokerConfig brokerConfig; // 文件刷盘监测点 private StoreCheckpoint storeCheckpoint; // CommitLog文件转发请求 private final LinkedList<CommitLogDispatcher> dispatcherList;

消息发送存储流程

Step1:如果Broker停止工作或者为Slave,则拒绝消息写入。

如果消息长度超过256个字符,消息属性长度超过65536个字符将拒绝改消息的写入。

Step2:如果消息的延迟级别大于0,将消息的原主题名称与原消息队列ID存入消息属性中,用延迟消息主题SCHEDULE_TOPIC、消息队列ID更新原先消息的主题与队列,这是并发消息消费重试关键的一步。

Step3:获取当前可以写入的Commitlog文件。每一个文件默认1G,一个文件写满后再创建另外一个,以该文件中第一个偏移量为文件名,偏移量小于20位用0补齐。第一个文件初始偏移量为0,第二个文件的偏移量是1073741824,代表该文件中的第一条消息的物理偏移量为1073741824,这样就能快速定位到消息。

Step4:对于rocketmq来说,存储消息的主要文件被称为CommitLog,因此就从该类入手。处理存储请求的入口方法是asyncPutMessage,主要流程如下: java public CompletableFuture<PutMessageResult> asyncPutMessage(final MessageExtBrokerInner msg) { //可能会有多个线程并发请求,虽然支持集群,但是对于每个单独的broker都是本地存储,所以内存锁就足够了 putMessageLock.lock(); try { //获取最新的文件 MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(); ... //如果文件为空,或者已经存满,则创建一个新的commitlog文件 if (null == mappedFile || mappedFile.isFull()) { mappedFile = this.mappedFileQueue.getLastMappedFile(0); } ... //调用底层的mappedFile进行出处,但是注意此时还没有刷盘 result = mappedFile.appendMessage(msg, this.appendMessageCallback, putMessageContext); ... } finally { putMessageLock.unlock(); } PutMessageResult putMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, result); } 在写入CommitLog之前,先申请putMessageLock,也就是将消息存储到CommitLog文件中是串行的。

可能会有多个线程并发请求,虽然支持集群,但是对于每个单独的broker都是本地存储,所以单机内存锁就足够。

接口PutMessageLock java public interface PutMessageLock { void lock(); void unlock(); } 重入实现PutMessageReentrantLock ```java public class PutMessageReentrantLock implements PutMessageLock { private ReentrantLock putMessageNormalLock = new ReentrantLock(); // NonfairSync

@Override
    public void lock() {
        putMessageNormalLock.lock();
    }

    @Override
    public void unlock() {
        putMessageNormalLock.unlock();
    }
}

**自旋实现PutMessageSpinLock** java public class PutMessageSpinLock implements PutMessageLock { //true: Can lock, false : in lock. private AtomicBoolean putMessageSpinLock = new AtomicBoolean(true);

@Override
    public void lock() {
        boolean flag;
        do {
            flag = this.putMessageSpinLock.compareAndSet(true, false);
        }
        while (!flag);
    }

    @Override
    public void unlock() {
        this.putMessageSpinLock.compareAndSet(false, true);
    }
}

``` 异步刷盘建议用自旋锁,同步刷盘建议用重入锁:useReentrantLockWhenPutMessage默认false使用自旋锁;

异步刷盘建议开启TransientStorePoolEnable;建议关闭transferMsgByHeap,提高拉消息效率;

同步刷盘建议适当增大sendMessageThreadPoolNums,具体配置需要经过压测。

自旋锁

互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给Java虚拟机的并发性能带来了很大的压力,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。

如果物理机器有一个以上的处理器或者处理器核心,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一会”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只须让线程执行一个循环(自旋),这项技术就是所谓的自旋锁。

自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,所以如果锁被占用的时间很短,自旋等待的效果就会非常好,反之如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有价值的工作,这就会带来性能的浪费。

也就是说我们要权衡自旋等待、线程的用户态与内核态切换的开销,哪个更大?

重入锁

当一个线程要获取一个被其他线程持有的独占锁时,该线程会被阻塞,那么当一个线程再次获取它自己己经获取的锁时是否会被阻塞呢?如果不被阻塞,那么我们说该锁是可重入的。

了解了自旋锁和可重入锁,我们在看看如何回答:为什么建议“异步刷盘建议使用自旋锁,同步刷盘建议使用重入锁”?

同步刷盘时,锁竞争激烈,会有较多的线程处于等待阻塞等待锁的状态,如果采用自旋锁会浪费很多的CPU时间,所以“同步刷盘建议使用重入锁”。

异步刷盘是间隔一定的时间刷一次盘,锁竞争不激烈,不会存在大量阻塞等待锁的线程,偶尔锁等待就自旋等待一下很短的时间,不要进行上下文切换了,所以采用自旋锁更合适。

Step5:设置消息的存储时间,如果mappedFile为空,表明 /commitlog 目录下不存在任何文件,说明本次消息是第一次消息发送,用偏移量0创建第一个 commit 文件。 java messageExtBatch.setStoreTimestamp(beginLockTimestamp); if (null == mappedFile || mappedFile.isFull()) { mappedFile = this.mappedFileQueue.getLastMappedFile(0); } if (null == mappedFile) { log.error("Create mapped file1 error"); beginTimeInLock = 0; return new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, null); } Step6:将消息追加到 MappedFile 中。首先先获取 MappedFile 当前写指针,如果currentPos大于或等于文件大小则表明文件已写满,抛出UNKNOWN_ERROR。如果currentPos小于文件大小,通过 slice() 方法创建一个与MappedFile的贡献内存区,并设置position为当前指针。 ```java int currentPos = this.wrotePosition.get();

if (currentPos < this.fileSize) { ByteBuffer byteBuffer = writeBuffer != null ? writeBuffer.slice() : this.mappedByteBuffer.slice(); byteBuffer.position(currentPos); AppendMessageResult result; if (messageExt instanceof MessageExtBrokerInner) { result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBrokerInner) messageExt); } else if (messageExt instanceof MessageExtBatch) { result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBatch) messageExt); } else { return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } this.wrotePosition.addAndGet(result.getWroteBytes()); this.storeTimestamp = result.getStoreTimestamp(); return result; } **Step7:创建全局唯一消息ID,消息ID有16字节,消息ID前4字节是IP,中间4字节是端口号,最后8字节是消息偏移量。** java long wroteOffset = fileFromOffset + byteBuffer.position(); this.resetByteBuffer(storeHostHolder, storeHostLength); String msgId = MessageDecoder.createMessageId(this.msgIdMemory, msgInner.getStoreHostBytes(storeHostHolder), wroteOffset); ``` 但是为了消息ID可读性,返回给应用程序的msgId为字符类型,可以通过UtilAll.byte2string方法将msgId字节数组转换成字符串,通过UtilAll.string2bytes方法将msgId字符串还原成16个字节的字节数组,从而根据提取消息偏移量,可以快速通过msgId找到消息内容。

Step8:获取该消息在消息队列的偏移量,CommitLog中保存了当前所有消息队列的当前待写入偏移量 java keyBuilder.setLength(0); keyBuilder.append(msgInner.getTopic()); keyBuilder.append('-'); keyBuilder.append(msgInner.getQueueId()); String key = keyBuilder.toString(); Long queueOffset = CommitLog.this.topicQueueTable.get(key); if (null == queueOffset) { queueOffset = 0L; CommitLog.this.topicQueueTable.put(key, queueOffset); } Step9:根据消息体的长度、主题的长度、属性的长度结合消息存储格式计算消息的总长度。 ```java protected static int calMsgLength(int sysFlag, int bodyLength, int topicLength, int propertiesLength) {

final int msgLen =
            4   //TOTALSIZE:该消息条目总长度,4字节
            + 4 //MAGICCODE:魔数,4字节。固定值0xdaa320a7
            + 4 //BODYCRC:消息体crc校验码,4字节
            + 4 //QUEUEID:消息消息队列ID,4字节
            + 4 //FLAG:消息FLAG,RMQ不做处理,供应用程序使用,默认4字节
            + 8 //QUEUEOFFSET:消息在消息消费队列的偏移量,8字节
            + 8 //PHYSICALOFFSET:消息在CommitLog文件中偏移量,8字节
            + 4 //SYSFLAG:消息系统Flag,例如是否压缩,是否是事务消息等,4字节
            + 8 //BORNTIMESTAMP:消息生产者调用消息发送API的时间戳,8字节
            + bornhostLength //BORNHOST:发送者IP,端口,8字节
            + 8 //STORETIMESTAMP:消息存储时间戳,8字节
            + storehostAddressLength //STOREHOSTADDRESS:BrokerIP和端口,8字节
            + 4 //RECONSUMETIMES:消息重试次数,4字节
            + 8 //Prepared Transaction Offset:事务消息物理偏移量,8字节
            + 4 + (bodyLength > 0 ? bodyLength : 0) //BODY:消息体长度,4字节
            //TOPIC:主题存储长度,1字节
            + 1 + topicLength
             //propertiesLength:消息属性,长度为PropertiesLength中存储的值
            + 2 + (propertiesLength > 0 ? propertiesLength : 0)
            + 0;
        return msgLen;
    }

``` 上述表明CommitLog条目是不定长的,每一个条目的长度存储在前4个字节中。

Step10:如果消息长度 + END_FILE_MIN_BLANK_LENGTH 大于 CommitLog文件的空闲空间,则返回AppendMessageStatus.END_OF_FILE,Broker会创建一个新的CommitLog文件来存储该消息。

从这里可以看出,每个CommitLog文件最少会空闲8字节,高4字节存储当前文件剩余空间,低四字节存储魔数:CommitLog.BLANK_MAGIC_CODE。 ```java final long beginTimeMills = CommitLog.this.defaultMessageStore.now(); // Write messages to the queue buffer byteBuffer.put(this.msgStoreItemMemory.array(), 0, msgLen);

AppendMessageResult result = new AppendMessageResult(AppendMessageStatus.PUT_OK, wroteOffset, msgLen, msgId, msgInner.getStoreTimestamp(), queueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills); Step11:将消息内容存储到ByteBuffer中,然后创建AppendMessageResult。把消息存储在MappedFile对应的内存映射Buffer中,并没有刷写到磁盘。 java // Return code private AppendMessageStatus status; // Where to start writing private long wroteOffset; // Write Bytes private int wroteBytes; // Message ID private String msgId; // Message storage timestamp private long storeTimestamp; // Consume queue's offset(step by one) private long logicsOffset; private long pagecacheRT = 0; private int msgNum = 1; //批量发送消息条数

/**
 * When write a message to the commit log, returns code
 */
public enum AppendMessageStatus {
    PUT_OK,
    END_OF_FILE, //超过文件大小
    MESSAGE_SIZE_EXCEEDED, //消息长度超过最大允许长度
    PROPERTIES_SIZE_EXCEEDED, //消息属性超过最大允许长度
    UNKNOWN_ERROR,
}

``` Step12:更新消息队列逻辑偏移量

Step13:处理完消息追加逻辑后将释放putMessageLock锁

Step14:DefaultAppendMessageCallback#doAppend只是将消息追加在内存中,需要根据同步刷盘还是异步刷盘方式,将内存中的数据持久化道磁盘,然后执行HA主从同步复制。

存储文件组织与内存映射 RMQ通过使用内存映射文件来提高IO访问性能,无论是CommitLog、ConsumeQueue还是IndexFile,单个文件都被设计成固定长度,如果一个文件写满后再创建一个新文件,文件名就是第一条消息的偏移量。

RocketMQ使用MappedFile、MappedFileQueue来封装存储文件,一个MappedFileQueue可以包含多个MappedFile。

Broker收到消息之后的处理流程:简单来说,roker通过Netty网络服务器获取到条消息,接着就会把这条消息写入到一个Commitlog文件里去,一个Broker机器上就只有一个CommitLog文件,所有Topic的消息都会写入到一个文件里去。

然后还会以异步的方式把消息写入到ConsumeQueue文件里去,因为一个Topic有多个MessageQueue,任何条消息都是写入一个MessageQueue的,那个MessaqeQueue其实就是对应了一个ConsumeQueue文件 所以一条写入MessageQueue的消息,必然会异步进入对应的ConsumeQueue文件。

同时还会异步把消息写入一个ndexFile里,在里面主要就是把每条消息的key和消息在CommitLog中的offset偏移量做一个索引,这样后续如果要根据消息key从CommitLog文件里查询消息,就可以根据IndexFile的索引来了。

接着我们来一步一步分析一下这里写入这几个文件的一个流程。

首先Broker收到一个消息之后,必然是先写入CommitLog文件的,那么这个CommitLog文件在磁盘上的目录结构大致如何呢? 看下面 CommitLog文件的存储目录是在SROCKETMQ HOME/store/commitlog下的,里面会有很多的CommitLog文件,每个文件默认是1GB大小,一个文件写满了就创建一个新的文件,文件名的话,就是文件中的第一个偏移量,如人面所示。文件名如果不足20位的话,就用0来补齐就可以了。 000000000000000000000 000000000003052631924 在把消息写入CommitLog文件的时候,会申请 个putMessageLock锁 也就是说,在Broker上写入消息到CommitLog文件的时候,都是事行的,不会让你并发的写入,并发写入文件必然会有数据错乱的问题,下面是源码片段 java protected final PutMessageLock putMessageLock; putMessageLock .lock()

接着其实会对消息做出一通处理,包括设置消息的存储时间、创建全局唯一的消息D、计算消息的总长度,然后会走段很关键的源码,把消息写入到MappedFile里去,这个其实我们之前还讲解过里面的黑科技,看下面的源码。

java if (messageExt instanceof MessapeExtBrokerInner) { result = cb.doAppend(this.getFileFromoffset(), byteBuffer, this.filesize - currentpos, (MessapeExtBrokerInner) messageExt); }

上面源码片段中,其实最关键的是cb.doAppend这行代码,这行代码其实是把消息追加到MappedFile映射的一块内存里去,并没有直接刷入磁盘中.

至于具体什么时候才会把内存里的数据刷入磁盘,其实要看我们配置的刷盘策略,另外就是不管是同步刷盘还是异步刷盘,假设你配置了主从同步,一旦你写入完消息到CommitLog之后,接下来都会进行主从同步复制的。

Broker收到一条消息之后,其实就会直接把消息写入到Commitlod里去,但是他写入刚开始仅仅是写入到MappedFile映射的一块内存里去,后续是根据刷盘策略去决定是否立即把数据从内存刷入磁盘的.

这个消息写入CommitLog之后,然后消息是如何进入ConsumeQueue和IndexFile的。 实际上,Broker启动的时候会开启一个线程,ReputMessageService,他会把CommitLog更新事件转发出去,然后让任务处理器去更新ConsumeQueue和IndexFile.

我们看下面的源码片段,在DefaultMessageStore的start方法里,在里面就是启动了这个ReputMessageService线程。 这个DefaultMessageStore的start方法就是在Broker启动的时候调用的,所以相当于是Broker启动就会启动这个线程.

image.png

下面我们看这个ReputlMessageService线程的运行逻辑,源码片段如下所示

image.png

也就是说,在这个线程里,每隔1毫秒,就会把最近写入CommitLog的消息进行一次转发,转发到ConsumeQueue和ndexFile里去,通过的是doReput0方法来实现的,我们再看doReput方法里的实现逻辑,先看下面源码片段.

image.png

这段代码意思非常的清晰明了,就是从commitLog中去获取到一DispatchRequest拿到了一份需要进行转发的消息,也就是从CommitLog中读取的。

接着他就会通过下面的代码,调用doDispatch0方法去把消息进行转发,一个是转发到ConsumeQueue里去,一个是转发到IndexFile里去 大家看下面的源码片段,里面走了CommitLogDispatcher的循环

image.png

实际上F常来说这个CommitLoaDispatcher的实现类有两,分别是CommitLoaDispatcherBuildConsumeQueue和CommitLoaDispatcherBuildlindex,他们俩分别会负责把消息转发到ConsumeQueue和IndexFile。

接着我们看一下ConsumeQueueDispatche的源码实现逻辑,其实非常的简单,就是找到当前Topic的messageQueueld对应的一个ConsumeQueue文件 个MessageQueue会对应多个ConsumeQueue文件,找到一个即可,然后消息写入其中

image.png

再来看看indexFile的写入逻辑,其实也很简单,无非就是在ndexFile里去构建对应的索引罢了,如下面的源码片段

image.png

因此到这里为止,我想大家基本就看明白了,当我们把消息写入到CommitLog之后,有一个后台线程每隔1毫秒就会去拉取CommitLog中最新更新的一批消息,然后分别转发到ConsumeQueue和ndexFile里去,这就是他底层的实现原理

ConsumerQueue 消息消费队列是专门为消息订阅构建的索引文件,提高根据主题与消息队列检索消息的速度。

IndexFile主要用于根据Message Key和Unique Key检索消息

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

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

相关文章

云原生TDengine-v3.0部署手册

云原生TDengine-v3.0部署手册 一、管理namespace1.1 创建namespace1.2 namespaces列表 二、配置3份yaml文件2.1 tdengine3-storage-class.yaml2.2 taosd-service.yaml2.3 taosd-tdengine.yaml 三、服务部署3.1 部署StorageClass3.2 部署Service3.3 部署StatefulSet3.4 查看启动…

Flask新手教程

Flask简介 Flask是一个轻量级的可定制框架&#xff0c;使用Python语言编写&#xff0c;较其他同类型框架更为灵活、轻便、安全且容易上手。 Flask 可以很好地结合MVC模式进行开发&#xff0c;开发人员分工合作&#xff0c;小型团队在短时间内就可以完成功能丰富的中小型网站或…

微服务 云原生:微服务相关技术简要概述

后端架构演进 单体架构 所谓单体架构&#xff0c;就是只有一台服务器&#xff0c;所有的系统、程序、服务、应用都安装在这一台服务器上。比如一个 bbs 系统&#xff0c;它用到的数据库&#xff0c;它需要存储的图片和文件等&#xff0c;统统都部署在同一台服务器上。 单体架…

有PMP有没有必要换cspm?未来的发展前景如何?

最近 CSPM 证书很热门&#xff0c;CSPM 相关问题大家都很关心&#xff0c;今天本橘座就给大家全面解答一下 CSPM到底是何方神圣&#xff1f; 文章主要是解答下面几个常见问题&#xff0c;其他问题可以留言或者私信咨询我哦~ 一、什么是 CSPM证书&#xff1f;跟PMP是什么关系&a…

STM32F103驱动VL53L0X激光测距模块

STM32F103驱动VL53L0X激光测距模块 简介引脚定义STM32F103ZET6开发板与VL53L0X模块接线测试代码实验结果 简介 TOF 是飞行时间&#xff08;Tlme of Flight&#xff09;技术的缩写&#xff0c;即传感器发出经过调制的近红外光&#xff0c;遇到物体后反射&#xff0c;传感器通过…

「提高你的CSS技能」:15个重要的CSS属性详解

这篇文章介绍了15个重要的CSS属性&#xff0c;旨在提高读者的CSS知识和技能。文章以清晰的方式解释了每个属性的作用和用法&#xff0c;并提供了相应的示例代码。通过这篇文章&#xff0c;读者可以了解到一些有趣且实用的CSS属性。 1:in-range 和:out-of-range 伪类 CSS的:in…

[C++] C++特殊类设计 以及 单例模式:设计无法拷贝、只能在堆上创建、只能在栈上创建、不能继承的类, 单例模式以及饿汉与懒汉的场景...

特殊类 1. 不能被拷贝的类 注意, 是不能被拷贝的类, 不是不能拷贝构造的类. 思路就是 了解什么时候 会以什么途径 发生拷贝, 然后将路堵死. 拷贝发生一般发生在 拷贝构造 和 赋值重载 所以, 只要把类的这两个成员函数堵死, 此类就不能拷贝了 C98 在C11之前, 可以通过这种方…

python-计算两个矩阵的相似度。

余弦相似度 在pytorch中&#xff0c;有一个专门的函数用于计算相似度&#xff1a;torch.cosine_similarity() https://pytorch.org/docs/stable/nn.functional.html#cosine-similarity import torch import torch.nn.functional as F input1 torch.randn(100, 128) input2 t…

技术讨论:我心中TOP1的编程语言

欢迎关注博主 六月暴雪飞梨花 或加入【六月暴雪飞梨花】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术…

使用STM32 再实现感应开关盖垃圾桶

硬件介绍 SG90舵机 如上图所示的舵机SG90&#xff0c;橙线对应PWM信号&#xff0c;而PWM波的频率不能太高&#xff0c;大约50Hz&#xff0c;即周期0.02s&#xff0c;20ms左右。 在20ms的周期内&#xff0c;高电平占多少秒和舵机转到多少度的关系如下&#xff1a; 0.5ms-----0度…

msvcr110.dll丢失的修复教程,找不到msvcr110.dll解决办法哪个更推荐

msvcr110.dll是微软的Visual C运行库文件之一。它是Microsoft Visual Studio 2012的一部分&#xff0c;用于支持运行在Windows操作系统上使用Visual C编写的应用程序。在Windows系统中非常重要&#xff0c;如果丢失或是损坏就会造成很多程序无法启动运行。 会出现以下的报错提…

【云原生】k8s之存储卷

容器磁盘上的文件的生命周期是短暂的&#xff0c;这就使得在容器中运行重要应用时会出现一些问题。首先&#xff0c;当容器崩溃时&#xff0c;kubelet 会重启它&#xff0c;但是容器中的文件将丢失——容器以干净的状态&#xff08;镜像最初的状态&#xff09;重新启动。其次&a…

简单版本视频播放服务器V1

一直想做个家用版本的视频播放器&#xff0c;通过这个可以实现简单的电脑&#xff0c;通过浏览器就是可以访问电脑里面的视频&#xff0c;通过手机&#xff0c;平板等都是可以访问自己的视频服务了 后端代码&#xff1a; package mainimport ("fmt""io/iouti…

JMeter 中 3 种参数值的传递

目录 前言&#xff1a; (一) 从 CSV 文件读取要批量输入的变量 (二) 利用 Cookie 进行值的传递 (三) 利用正则匹配提取上一个接口的返回数据作为下个请求的输入 前言&#xff1a; 在JMeter中&#xff0c;参数值的传递是非常重要的&#xff0c;因为它允许你在测试过程中动态…

Proxy-Reflect使用详解

1 监听对象的操作 2 Proxy类基本使用 3 Proxy常见捕获器 4 Reflect介绍和作用 5 Reflect的基本使用 6 Reflect的receiver Proxy-监听对象属性的操作(ES5) 通过es5的defineProperty来给对象中的某个参数添加修改和获取时的响应式。 单独设置defineProperty是只能一次设置一…

Spring专家课程Day01_Spring-IOC

​ 文章目录 基础配置1)基础文件结构(Maven项目创建) 一、01_Spring概述_IOC_HelloWorld1.Spring框架的两个核心功能1.1) IOC/DI ,控制反转依赖注入!1.2) AOP,面向切面编程 2.IOC的两种模式2.1)配置文件中配置 Bean2.2)配置文件,组件扫描注解类注解Component 二、02_JavaBean_J…

Ajax简介和实例

目录 什么是 AJAX &#xff1f; AJAX实例 ajax-get无参 ajax-get有参 对象和查询字符串的互转 ajax-post ajax-post 表单 AJAX 是一种在无需重新加载整个网页的情况下&#xff0c;能够更新部分网页的技术。 什么是 AJAX &#xff1f; 菜鸟教程是这样介绍的&#xff1a…

本地Linux 部署 Dashy 并远程访问

文章目录 简介1. 安装Dashy2. 安装cpolar3.配置公网访问地址4. 固定域名访问 转载自cpolar极点云文章&#xff1a;本地Linux 部署 Dashy 并远程访问 简介 Dashy 是一个开源的自托管的导航页配置服务&#xff0c;具有易于使用的可视化编辑器、状态检查、小工具和主题等功能。你…

Gradle下载和配置教程:Windows、Mac和Linux系统安装指南

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

扒开 TCP 的外衣,看清 TCP 的本质

TCP 非常重要&#xff0c;它的内容很多&#xff0c;今天只能讲解其中的一部分&#xff0c;但足以让你超越 80 % 的编程开发人员对于 TCP 的认知。 本篇内容非常多&#xff0c;非常干&#xff0c;希望你花点时间仔细研究&#xff0c;我相信会对你有所帮助。 1. TCP 协议是什么…