当前位置: 首页 > news >正文

深度解析RocketMq源码-持久化组件(一) MappedFile

1. 绪论

rocketmq之所以能够有如此大的吞吐量,离不开两个组件,一个是利用netty实现的高性能网络通信组件;另一个就是利用mmap技术实现的存储组件。而在rocketmq的存储组件中主要有三个组件,分别是持久化文件commitLog,让消费直接消费信息的文件consumerqueue,同时我们需要根据msgKey进行检索,所以还有一个indexFile。这三个文件是rocketmq存储组件的核心。本文将要介绍的是持久化组件commitLog中与磁盘交互的组件mappedFile。

2. mmap

在介绍commitLog之前,我们来看看它所使用的核心技术,mmap。

2.1 传统读写

我们来看看传统读操作有什么局限?如果是利用传统的读写操作从磁盘文件中读取一块数据的话,需要经过如下几步:

1.用户线程通过调用操作系统的read方法,发起读取磁盘文件的请求。

2.操作系统通过需要读取文件的inode信息,在页缓存中查询是否找到页内容,如果找到,便直接返回文件页内容。

3.如果未找到,便通过inode信息定位到磁盘文件地址,并且通过计算机的dma组件,将磁盘文件复制到页缓存中。

4.cpu再将页缓存数据返回给用户进程的缓冲区中。

通过上面的步骤看出,如果采用普通的读写操作,需要经过磁盘->内核空间页缓存->用户空间缓冲区->内核空间缓冲区->网卡这四个步骤。

2.2 零拷贝技术

2.2.1 物理地址和虚拟地址

1.什么是物理地址?

物理地址就是实际的物理内存的地址

2.什么是虚拟地址

在早期的计算机系统中,cpu其实操作的是物理地址,后来发现内存不够过后,便发明了swap技术,即将内存中的不经常访问的数据放到磁盘中去,这样内存空间可以存储更多的数据,给用户营造了一种内存很大的假象。而这些复杂的操作是操作系统完成的,而操作系统给用户提供的是一片连续的地址空间,这些地址就是虚拟地址。并且通过内存管理单元(mmu)实现虚拟地址和物理地址的转换。

2.2.2 mmap映射步骤

1.进程启动映射,并且在进程的虚拟地址空间中创建映射区域

其实就是进程会在用户空间中的一块专门的虚拟地址空间(直接内存中)划分一块区域用来存储映射磁盘文件的虚拟地址。

2.建立物理内存地址和虚拟内存地址的映射关系

即建立地址虚拟空间和磁盘文件的地址之间的映射关系

3.对映射空间进行访问,发生缺页异常,实现磁盘到主存的拷贝

在通过对虚拟地址空间进行读写时,会通过mmu转换成物理地址,操作系统发现对应的物理地址缺失,产生缺页异常,从而将磁盘文件读取到虚拟空间的这片内存中来。

2.2.3 mmap的优点

mmap其实就是零拷贝,通过上面传统操作的读写,可以看出从磁盘需要拷贝到内核空间中转后才能到用户空间。有没有办法减少中间这次拷贝呢?那就是利用mmap技术。

通过mmap技术可以将磁盘文件地址可以和进程虚拟地址空间中的一段虚拟地址映射,在对虚拟地址进行读写操作时,就相当于对磁盘文件进行读写操作。减少了两次用户缓冲区和内核空间页表复制的过程。

3.mappedFile

mappedfile是rocketmq真正将数据写入到磁盘的组件。接下来,我们看看mappedfile是如何存储数据的。

3.1.1 mappedFile的组成

public class DefaultMappedFile extends AbstractMappedFile {//一个page页的大小为4kb,即mappedFile隔4kb便写入一个byte值实现文件预热,前面说过mmap技术是利用缺页中断将磁盘中的数据加载到内存中的,而一个内存页的大小为4kb//如果不采用文件预热机制的话,1gb的commitLog需要发生26w次缺页中断,所以在初始化commitLog的时候,每隔4kb就写入一个假0,这样就一次性的将所以的文件页加载到了内存中。public static final int OS_PAGE_SIZE = 1024 * 4;public static final Unsafe UNSAFE = getUnsafe();private static final Method IS_LOADED_METHOD;public static final int UNSAFE_PAGE_SIZE = UNSAFE == null ? OS_PAGE_SIZE : UNSAFE.pageSize();protected static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);//加载过的总的虚拟内存大小protected static final AtomicLong TOTAL_MAPPED_VIRTUAL_MEMORY = new AtomicLong(0);//加载过的总的虚拟内存数量protected static final AtomicInteger TOTAL_MAPPED_FILES = new AtomicInteger(0);protected static final AtomicIntegerFieldUpdater<DefaultMappedFile> WROTE_POSITION_UPDATER;protected static final AtomicIntegerFieldUpdater<DefaultMappedFile> COMMITTED_POSITION_UPDATER;protected static final AtomicIntegerFieldUpdater<DefaultMappedFile> FLUSHED_POSITION_UPDATER;//当前虚拟buffer的写指针protected volatile int wrotePosition;//虚拟buffer的读指针protected volatile int committedPosition;//flush指针protected volatile int flushedPosition;//文件大小protected int fileSize;protected FileChannel fileChannel;/*** Message will put to here first, and then reput to FileChannel if writeBuffer is not null.*/protected ByteBuffer writeBuffer = null;//在高并发场景,直接内存可能也抵挡不住,所以可以先将数据写入到堆内存中,然后再commiit到直接内存中,最后通过flush到磁盘中protected TransientStorePool transientStorePool = null;//文件名,mappedfile的文件名protected String fileName;//当前文件写入offsetprotected long fileFromOffset;protected File file;//这个是对应的虚拟bufferprotected MappedByteBuffer mappedByteBuffer;//存储的时间protected volatile long storeTimestamp = 0;protected boolean firstCreateInQueue = false;//最后一次flush的时间private long lastFlushTime = -1L;protected MappedByteBuffer mappedByteBufferWaitToClean = null;protected long swapMapTime = 0L;protected long mappedByteBufferAccessCountSinceLastSwap = 0L;
}

3.3.2 mmapedFile的写入的两种模式-是否开启瞬时缓存技术

1.如果不开启瞬时缓存技术

在写入的时候,直接写入到直接内存中(MapedFileBuffer),然后flush到磁盘

2.如果开启瞬时缓存技术

如果开启瞬时缓存技术的话,数据会先写入bytebuffer中,然后commit到MappedBytebuffer,最后再flush到磁盘中去。其实commit和flush都是采用异步线程刷入来实现的,所以增加了吞吐量。

3. 瞬时缓存技术TransientStorePool

初始化:

public class TransientStorePool {private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);//有多少内存页private final int poolSize;private final int fileSize;//核心:就是多个ByteBuffer队列private final Deque<ByteBuffer> availableBuffers;private volatile boolean isRealCommit = true;
}

借内存页

其实就是从内存队列中取出第一个buffer。

 public ByteBuffer borrowBuffer() {ByteBuffer buffer = availableBuffers.pollFirst();if (availableBuffers.size() < poolSize * 0.4) {log.warn("TransientStorePool only remain {} sheets.", availableBuffers.size());}return buffer;}

归还内存页

其实就是清空buffer,放入到内存队列中。

  public void returnBuffer(ByteBuffer byteBuffer) {byteBuffer.position(0);byteBuffer.limit(fileSize);this.availableBuffers.offerFirst(byteBuffer);}

3.3.3 mappedFile的初始化

在介绍完上面mappedFile的两种写入方式过后,mappedFile的初始化就很清晰了。

    public void init(final String fileName, final int fileSize,final TransientStorePool transientStorePool) throws IOException {init(fileName, fileSize);//这个是开启瞬时存储技术的话,需要从transientStorePool获取到一个bufferthis.writeBuffer = transientStorePool.borrowBuffer();this.transientStorePool = transientStorePool;}private void init(final String fileName, final int fileSize) throws IOException {this.fileName = fileName;this.fileSize = fileSize;//构造文件this.file = new File(fileName);//可以看出,mappedfile的文件名就是他的offsetthis.fileFromOffset = Long.parseLong(this.file.getName());boolean ok = false;//确保文件夹初始化完成UtilAll.ensureDirOK(this.file.getParent());try {this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();//核心:就是将mappedByteBuffer映射到对于的磁盘文件上this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(fileSize);TOTAL_MAPPED_FILES.incrementAndGet();ok = true;} catch (FileNotFoundException e) {log.error("Failed to create file " + this.fileName, e);throw e;} catch (IOException e) {log.error("Failed to map file " + this.fileName, e);throw e;} finally {if (!ok && this.fileChannel != null) {this.fileChannel.close();}}}

至此,mappedfile中的直接内存缓冲区映射完成,堆内存buffer也初始化完成。

3.3.4 mappedFile的数据写入

分析这段逻辑其实我们可以发现,本质上是调用的AppendMessageCallback方法,写入数据封装到了MessageExt中,并且返回了PutMessageContext这一写入结果。

    public AppendMessageResult appendMessagesInner(final MessageExt messageExt, final AppendMessageCallback cb,PutMessageContext putMessageContext) {assert messageExt != null;assert cb != null;//获取当前的写指针int currentPos = WROTE_POSITION_UPDATER.get(this);//判断写指针大小是否超过了文件大小,如果超过便抛出异常if (currentPos < this.fileSize) {ByteBuffer byteBuffer = appendMessageBuffer().slice();byteBuffer.position(currentPos);AppendMessageResult result;if (messageExt instanceof MessageExtBatch && !((MessageExtBatch) messageExt).isInnerBatch()) {// traditional batch messageresult = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos,(MessageExtBatch) messageExt, putMessageContext);} else if (messageExt instanceof MessageExtBrokerInner) {// traditional single message or newly introduced inner-batch message//本质上是调用的AppendMessageCallback的doAppend方法result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos,(MessageExtBrokerInner) messageExt, putMessageContext);} else {return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR);}//更新写指针WROTE_POSITION_UPDATER.addAndGet(this, result.getWroteBytes());//更新时间戳this.storeTimestamp = result.getStoreTimestamp();return result;}log.error("MappedFile.appendMessage return null, wrotePosition: {} fileSize: {}", currentPos, this.fileSize);return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR);}
1. MappedFile的写入数据格式-MessageExt

MessageExt包含了消息的一些元信息和它的实际内容。

public class Message implements Serializable {private static final long serialVersionUID = 8445773977080406428L;//消息属于哪个topicprivate String  topic;private int flag;//额外的附加属性private Map<String, String> properties;//真正的消息内容private byte[] body;//如果是事务消息,有事务idprivate String transactionId;
}public class MessageExt extends Message {private static final long serialVersionUID = 5720810158625748049L;//当前broker的名称private String brokerName;//消息属于哪个queueIdprivate int queueId;//消息大小private int storeSize;//消息在queue中的偏移量是多少private long queueOffset;private int sysFlag;//消息产生时间private long bornTimestamp;//消息是诞生主机的ip地址private SocketAddress bornHost;private long storeTimestamp;//消息存储主机的ip地址private SocketAddress storeHost;//消息idprivate String msgId;//消息在commitLog中的offset是多少private long commitLogOffset;private int bodyCRC;private int reconsumeTimes;private long preparedTransactionOffset;}

2.mappedFile的核心写入逻辑-AppendMessageCallback

其实就是在MessageExt的基础上,补充上写入commitLog的一些信息,并且刷新到buffer中

    //byteBuffer - 消息写入到直接内存的buffer//preEncodeBuffer - 消息内容public AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer byteBuffer, final int maxBlank,final MessageExtBrokerInner msgInner, PutMessageContext putMessageContext) {// STORETIMESTAMP + STOREHOSTADDRESS + OFFSET <br>//取出消息的内容preEncodeBuffer里面ByteBuffer preEncodeBuffer = msgInner.getEncodedBuff();boolean isMultiDispatchMsg = messageStoreConfig.isEnableMultiDispatch() && CommitLog.isMultiDispatchMsg(msgInner);if (isMultiDispatchMsg) {AppendMessageResult appendMessageResult = handlePropertiesForLmqMsg(preEncodeBuffer, msgInner);if (appendMessageResult != null) {return appendMessageResult;}}//获取消息的总长度final int msgLen = preEncodeBuffer.getInt(0);preEncodeBuffer.position(0);preEncodeBuffer.limit(msgLen);// PHY OFFSET//当前文件的位置+消息长度 = 实际开始写入的位置long wroteOffset = fileFromOffset + byteBuffer.position();//构建消息id 机器ip+端口号+偏移量Supplier<String> msgIdSupplier = () -> {int sysflag = msgInner.getSysFlag();int msgIdLen = (sysflag & MessageSysFlag.STOREHOSTADDRESS_V6_FLAG) == 0 ? 4 + 4 + 8 : 16 + 4 + 8;ByteBuffer msgIdBuffer = ByteBuffer.allocate(msgIdLen);MessageExt.socketAddress2ByteBuffer(msgInner.getStoreHost(), msgIdBuffer);msgIdBuffer.clear();//because socketAddress2ByteBuffer flip the buffermsgIdBuffer.putLong(msgIdLen - 8, wroteOffset);return UtilAll.bytes2string(msgIdBuffer.array());};// Record ConsumeQueue information//记录consumerqueue中的偏移量Long queueOffset = msgInner.getQueueOffset();// this msg maybe an inner-batch msg.short messageNum = getMessageNum(msgInner);// Transaction messages that require special handlingfinal int tranType = MessageSysFlag.getTransactionValue(msgInner.getSysFlag());switch (tranType) {// Prepared and Rollback message is not consumed, will not enter the consume queuecase MessageSysFlag.TRANSACTION_PREPARED_TYPE:case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:queueOffset = 0L;break;case MessageSysFlag.TRANSACTION_NOT_TYPE:case MessageSysFlag.TRANSACTION_COMMIT_TYPE:default:break;}//如果写入文件大小超过了磁盘大小,抛出END_OF_FILE的异常// Determines whether there is sufficient free spaceif ((msgLen + END_FILE_MIN_BLANK_LENGTH) > maxBlank) {this.msgStoreItemMemory.clear();// 1 TOTALSIZEthis.msgStoreItemMemory.putInt(maxBlank);// 2 MAGICCODEthis.msgStoreItemMemory.putInt(CommitLog.BLANK_MAGIC_CODE);// 3 The remaining space may be any value// Here the length of the specially set maxBlankfinal long beginTimeMills = CommitLog.this.defaultMessageStore.now();byteBuffer.put(this.msgStoreItemMemory.array(), 0, 8);return new AppendMessageResult(AppendMessageStatus.END_OF_FILE, wroteOffset,maxBlank, /* only wrote 8 bytes, but declare wrote maxBlank for compute write position */msgIdSupplier, msgInner.getStoreTimestamp(),queueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills);}//更新queue中偏移量、物理地址、产生消息的时间戳、机器地址等,int pos = 4 + 4 + 4 + 4 + 4;// 6 QUEUEOFFSETpreEncodeBuffer.putLong(pos, queueOffset);pos += 8;// 7 PHYSICALOFFSETpreEncodeBuffer.putLong(pos, fileFromOffset + byteBuffer.position());int ipLen = (msgInner.getSysFlag() & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 4 + 4 : 16 + 4;// 8 SYSFLAG, 9 BORNTIMESTAMP, 10 BORNHOST, 11 STORETIMESTAMPpos += 8 + 4 + 8 + ipLen;// refresh store time stamp in lockpreEncodeBuffer.putLong(pos, msgInner.getStoreTimestamp());if (enabledAppendPropCRC) {// 18 CRC32int checkSize = msgLen - crc32ReservedLength;ByteBuffer tmpBuffer = preEncodeBuffer.duplicate();tmpBuffer.limit(tmpBuffer.position() + checkSize);int crc32 = UtilAll.crc32(tmpBuffer);tmpBuffer.limit(tmpBuffer.position() + crc32ReservedLength);MessageDecoder.createCrc32(tmpBuffer, crc32);}final long beginTimeMills = CommitLog.this.defaultMessageStore.now();CommitLog.this.getMessageStore().getPerfCounter().startTick("WRITE_MEMORY_TIME_MS");// Write messages to the queue buffer//写入文件到直接内存中byteBuffer.put(preEncodeBuffer);CommitLog.this.getMessageStore().getPerfCounter().endTick("WRITE_MEMORY_TIME_MS");msgInner.setEncodedBuff(null);if (isMultiDispatchMsg) {CommitLog.this.multiDispatch.updateMultiQueueOffset(msgInner);}//返回写入成功return new AppendMessageResult(AppendMessageStatus.PUT_OK, wroteOffset, msgLen, msgIdSupplier,msgInner.getStoreTimestamp(), queueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills, messageNum);}
3.,mappedFile的写入结果-AppendMessageResult

主要是返回了写入的物理地址+消息的MsgId+consumerqueue中的逻辑地址。可以看出MsgId其实是在写入到commitLog后生成的,因为里面需要包含写入的物理地址。

public class AppendMessageResult {// Return codeprivate AppendMessageStatus status;// Where to start writingprivate long wroteOffset;// Write Bytesprivate int wroteBytes;// Message IDprivate String msgId;private Supplier<String> msgIdSupplier;// Message storage timestampprivate long storeTimestamp;// Consume queue's offset(step by one)private long logicsOffset;private long pagecacheRT = 0;
}

3.3.5 mappedFile的数据commit操作

其实就是将数据写入到bytebuffer中.

  public int commit(final int commitLeastPages) {//如果没有开启瞬时缓存技术,直接返回写指针if (writeBuffer == null) {//no need to commit data to file channel, so just regard wrotePosition as committedPosition.return WROTE_POSITION_UPDATER.get(this);}//no need to commit data to file channel, so just set committedPosition to wrotePosition.if (transientStorePool != null && !transientStorePool.isRealCommit()) {COMMITTED_POSITION_UPDATER.set(this, WROTE_POSITION_UPDATER.get(this));} else if (this.isAbleToCommit(commitLeastPages)) {if (this.hold()) {commit0();this.release();} else {log.warn("in commit, hold failed, commit offset = " + COMMITTED_POSITION_UPDATER.get(this));}
}
 protected void commit0() {int writePos = WROTE_POSITION_UPDATER.get(this);int lastCommittedPosition = COMMITTED_POSITION_UPDATER.get(this);if (writePos - lastCommittedPosition > 0) {try {ByteBuffer byteBuffer = writeBuffer.slice();byteBuffer.position(lastCommittedPosition);byteBuffer.limit(writePos);//其实就是将当前数据写入到对内存buffer中this.fileChannel.position(lastCommittedPosition);this.fileChannel.write(byteBuffer);//更新commit指针COMMITTED_POSITION_UPDATER.set(this, writePos);} catch (Throwable e) {log.error("Error occurred when commit data to FileChannel.", e);}}}

3.3.6 mappedFile数据的flush操作

 public int flush(final int flushLeastPages) {if (this.isAbleToFlush(flushLeastPages)) {if (this.hold()) {int value = getReadPosition();try {this.mappedByteBufferAccessCountSinceLastSwap++;//We only append data to fileChannel or mappedByteBuffer, never both.if (writeBuffer != null || this.fileChannel.position() != 0) {this.fileChannel.force(false);} else {//利用force方法,将缓存中数据刷入到磁盘中this.mappedByteBuffer.force();}this.lastFlushTime = System.currentTimeMillis();} catch (Throwable e) {log.error("Error occurred when force data to disk.", e);}FLUSHED_POSITION_UPDATER.set(this, value);this.release();} else {log.warn("in flush, hold failed, flush offset = " + FLUSHED_POSITION_UPDATER.get(this));FLUSHED_POSITION_UPDATER.set(this, getReadPosition());}}return this.getFlushedPosition();}

3.3.7 mappedFile数据的内存预热

    public void warmMappedFile(FlushDiskType type, int pages) {this.mappedByteBufferAccessCountSinceLastSwap++;long beginTime = System.currentTimeMillis();ByteBuffer byteBuffer = this.mappedByteBuffer.slice();long flush = 0;// long time = System.currentTimeMillis();//fileSize:总的mappedfile大小,即每隔4kb,便向mappedByteBuffer写入一个零,否者的话,会产生频繁的页中断,导致磁盘和数据频繁交互。for (long i = 0, j = 0; i < this.fileSize; i += DefaultMappedFile.OS_PAGE_SIZE, j++) {byteBuffer.put((int) i, (byte) 0);// 如果是同步刷盘的话,便会进行缓存预热,因为同步刷盘可能一次数据量很小,造成频繁的os//与buffer的交互if (type == FlushDiskType.SYNC_FLUSH) {if ((i / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE) >= pages) {flush = i;mappedByteBuffer.force();}}}

4.总结

mappedFile本质上是利用mapp技术来提高读写效率的,而mappedFile的核心本质上就是mappedFileBuffer,默认大小为1G,可以由mappedFileSizeCommitLog来进行控制。

相关文章:

深度解析RocketMq源码-持久化组件(一) MappedFile

1. 绪论 rocketmq之所以能够有如此大的吞吐量&#xff0c;离不开两个组件&#xff0c;一个是利用netty实现的高性能网络通信组件&#xff1b;另一个就是利用mmap技术实现的存储组件。而在rocketmq的存储组件中主要有三个组件&#xff0c;分别是持久化文件commitLog&#xff0c…...

贝壳APP渗透测试WP

前期配置 环境说明 使用PIXEL 4手机&#xff0c;为Android 12系统 APP名为贝壳找房&#xff0c;包名com.lianjia.beike&#xff0c;版本号3.01.10&#xff0c;截至2024/05/07为最新版&#xff0c;小米应用市场下载 绕过反Frida机制 可以参考往期推送&#xff0c;《绕过最新…...

IDEA快速入门02-快速入门

二、快速入门 2.1 打开IDEA,点击New一个项目 入口&#xff0c;依次打开 File -> New -> Project。 2.2 使用Spring Initializr方式构建Spring Boot项目 2.3 设置项目所属组、项目名称、java版本等 2.4 选择SpringBoot版本及依赖组件 点击Create进行创建。 2.6 创建成…...

快速构建本地RAG聊天机器人:使用LangFlow和Ollama实现无代码开发

基于LangChain的快速RAG应用原型制作方法 还记得构建智能聊天机器人需要数月编码的日子吗&#xff1f; LangChain这样的框架确实简化了开发流程&#xff0c;但对非程序员来说&#xff0c;数百行代码仍然是一道门槛。 有没有更简单的方法呢&#xff1f; 图片由 Ravi Palwe 在…...

关于使用pycharm中控制台运行代码错误之FileNotFoundError: [Errno 2] No such file or directory:

在使用pycharm环境下复现《python编程&#xff1a;从入门到实践》这本书第16.1.1内容中分析csv文件头一节的代码时出现如下问题&#xff1a; 1、文章中使用的数据来源问题 直接参考本站Kenny C同学的文章提供内容即可。 https://github.com/kenidi8215/Hello-World 打开网页&a…...

【SpringBoot】深入分析 SpringApplication 源码:彻底理解 SpringBoot 启动流程

在黄昏的余晖里&#xff0c;梦境渐浓&#xff0c;如烟如雾。心随星辰&#xff0c;徜徉远方&#xff0c;岁月静好&#xff0c;愿如此刻般绵长。 文章目录 前言一、SpringBoot 应用二、SpringApplication2.1 SpringApplication 中的属性2.2 SpringApplication 的构造器2.3 Sprin…...

边界内聚和耦合

内聚 功能内聚 功能内聚是软件工程中一个重要的概念&#xff0c;它描述了一个模块内部各个元素之间的紧密程度。一个具有高功能内聚的模块意味着其内部的各个组件都共同完成一个具体的、明确的功能&#xff0c;并且这些组件之间的联系不是偶然的&#xff0c;而是因为它们共同服…...

单调栈——AcWing.830单调栈

单调栈 定义 单调栈是一种特殊的数据结构&#xff0c;栈内元素保持某种单调性&#xff08;通常是单调递增或单调递减&#xff09;。 运用情况 求解下一个更大元素或下一个更小元素。计算每个元素左边或右边第一个比它大或小的元素。 注意事项 要明确单调栈是递增还是递减…...

手机上安装AI模型是一种什么体验?

昨天参加微软的AI DAY活动&#xff0c;看到微软的技术大佬分享了一个场景&#xff0c;就是坐飞机从上海到北京&#xff0c;机长广播因为天气原因&#xff0c;飞机需要盲降&#xff0c;他说当时听到盲降第一反应感觉有点恐慌&#xff0c;但是因为飞机上受限于网络环境&#xff0…...

【MySQL】主从复制

https://www.bilibili.com/video/BV1Kr4y1i7ru/?p161​ https://blog.csdn.net/qq_47959003/article/details/126058710 主从复制是指将数据库的DDL和DML操作通过二进制日志传到从库服务器中&#xff0c;然后在从库上对这些日志重新执行&#xff08;也叫重做&#xff09;&…...

vscode插件开发之 - menu配置

上一遍博客介绍了如何从0到1搭建vscode插件开发的base code&#xff0c;这遍博客将重点介绍如何配置menu。通常&#xff0c;开发一款插件&#xff0c;会将插件显示在VSCode 左侧的活动栏&#xff08;Activity Bar&#xff09;&#xff0c;那么如何配置让插件显示在Activity Bar…...

自学C语言-9

** 第9章 函数 ** 大型程序一般会被分为若干个程序模块&#xff0c;每个模块实现一个特定功能 。C语言中&#xff0c;由函数实现子程序&#xff0c;由子程序实现模块功能。本章致力于使读者了解函数的概念&#xff0c;掌握函数的定义及调用方式&#xff1b;了解内部函数和外部…...

NVIDIA Triton系列01-应用概论

NVIDIA Triton系列01-应用概论 推理识别是人工智能最重要的落地应用&#xff0c;其他与深度学习相关的数据收集、标注、模型训练等工作&#xff0c;都是为了得到更好的最终推理性能与效果。 几乎每一种深度学习框架都能执行个别的推理工作&#xff0c;包括 Tensorflow、Pytorc…...

LIMS(实验室)信息管理系统源码、有哪些应用领域?采用C# ASP.NET dotnet 3.5 开发的一套实验室信息系统源码

LIMS&#xff08;实验室&#xff09;信息管理系统源码、有哪些应用领域&#xff1f;采用C# ASP.NET dotnet 3.5 开发的一套实验室信息系统源码 LIMS实验室信息管理系统&#xff0c;是一种基于计算机硬件和数据库技术&#xff0c;集多个功能模块为一体的信息管理系统。该系统主…...

Web前端进国企:挑战与机遇并存

Web前端进国企&#xff1a;挑战与机遇并存 随着互联网的飞速发展&#xff0c;Web前端技术已经成为企业信息化建设的重要组成部分。对于许多热衷于前端技术的年轻人来说&#xff0c;进入国企工作既是一种挑战&#xff0c;也是一种机遇。本文将从四个方面、五个方面、六个方面和…...

快速上手SpringBoot

黑马程序员Spring Boot2 文章目录 1、SpringBoot 入门程序开发1.1 创建一个新的项目 2、浅谈入门程序工作原理2.1 parent2.2 starter2.3 引导类2.4 内嵌tomcat 1、SpringBoot 入门程序开发 1.1 创建一个新的项目 file > new > project > empty Project 创建新模块&a…...

SQL 快速参考

SQL 快速参考 SQL&#xff08;Structured Query Language&#xff09;是一种用于管理关系数据库管理系统&#xff08;RDBMS&#xff09;的标准编程语言。它用于执行各种操作&#xff0c;如查询、更新、插入和删除数据库中的数据。本快速参考将提供SQL的基本语法和常用命令&…...

Cask ‘oraclexxx‘ is unavailable: No Cask with this name exists.

brew search oracle-jdk或brew search --cask oracle-jdk 原因&#xff1a;Homebrew官方仓库不再维护多个旧版本的OracleJDK 不推荐使用Homebrew环境安装JDK //指定版本安装 brew install --cask temurin17 //设置 JAVA_HOME 环境变量 //找到安装的JDK 版本的路径 /usr/lib…...

2024年武汉市中级、高级职称水测考试开卷方法分享

2024年武汉市&#xff08;除开东湖高新区外&#xff09;职称首次组织全员水测&#xff0c;先考水测后报名&#xff0c;水测报名在5月16号截止。 武汉市水测组织形式&#xff1a; 武汉市2024年专业技术职务水平能力测试分为笔试和面试&#xff0c;面试答辩有关事项另行通知&…...

计算机网络(6) ICMP协议

ICMP&#xff08;Internet Control Message Protocol&#xff0c;互联网控制消息协议&#xff09;是一种用于在IP网络中传递控制消息和错误报告的协议。ICMP是IP协议族的一部分&#xff0c;尽管它并不用于传输用户数据&#xff0c;但它在网络诊断和管理中起着关键作用。以下是关…...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

MinIO Docker 部署:仅开放一个端口

MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...

6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙

Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...

sshd代码修改banner

sshd服务连接之后会收到字符串&#xff1a; SSH-2.0-OpenSSH_9.5 容易被hacker识别此服务为sshd服务。 是否可以通过修改此banner达到让人无法识别此服务的目的呢&#xff1f; 不能。因为这是写的SSH的协议中的。 也就是协议规定了banner必须这么写。 SSH- 开头&#xff0c…...