当前位置: 首页 > 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;但它在网络诊断和管理中起着关键作用。以下是关…...

FuTalk设计周刊-Vol.036

&#x1f525;AI漫谈 热点捕手 1、Stable Zero123:从单张图像生成高质量 3D 对象 Stable Zero123 可以生成物体的新颖视图&#xff0c;展示从各个角度对物体外观的 3D 理解&#xff0c;由于训练数据集和高程条件的改进&#xff0c;其质量比 Zero1-to-3 或 Zero123-XL 显著提高…...

Java——面向对象进阶(三)

前言&#xff1a; 抽象类&#xff0c;接口&#xff0c;内部类 文章目录 一、抽象类1.1 抽象方法1.2 抽象类1.3 抽象类的使用 二、 接口2.1 接口的定义和实现2.2 default 关键字2.3 实现接口时遇到的问题 三、内部类3.1 成员内部类3.2 静态内部类3.3 成员内部类3.4 匿名内部类&a…...

鸿蒙开发电话服务:【@ohos.telephony.observer (observer)】

observer 说明&#xff1a; 本模块首批接口从API version 6开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import observer from ohos.telephony.observerobserver.on(‘networkStateChange’) on(type: ‘networkStateChange’, ca…...

希亦、追觅、云鲸洗地机:究竟有何不同?选择哪款更合适

最近收到很多私信里&#xff0c;要求洗地机测评的呼声特别高&#xff0c;作为宠粉的测评博主&#xff0c;当然是马上安排起来&#xff0c;满足大家对想看洗地机的愿望。这次洗地机测评&#xff0c;我挑选了三款热门的品牌型号&#xff0c;并从多个维度对它们进行使用测评&#…...

代码随想录算法训练营第二十六天

题目&#xff1a;455. 分发饼干 贪心第一题 这里的局部最优就是大饼干喂给胃口大的&#xff0c;充分利用饼干尺寸喂饱一个&#xff0c;全局最优就是喂饱尽可能多的小孩。或者小饼干先喂饱小胃口 首先要对 g 和 s进行排序这样才能知道最大的胃口和最大的饼干然后进行遍历即可…...

[面试题]Java【并发】

[面试题]Java【基础】[面试题]Java【虚拟机】[面试题]Java【并发】[面试题]Java【集合】[面试题]MySQL 因为 Java 并发涉及到的内容会非常多&#xff0c;本面试题可能很难覆盖到所有的知识点&#xff0c;所以推荐 《Java并发编程的艺术》 。 Java 线程 线程 通知 等待 线…...

基于VSCode和MinGW-w64搭建LVGL模拟开发环境

目录 概述 1 运行环境 1.1 版本信息 1.2 软件安装 1.2.1 下载安装VS Code 1.2.1.1 下载软件 1.2.1.1 安装软件 1.2.2 下载安装MinGW-w64 1.2.2.1 下载软件 1.2.2.2 安装软件 1.2.3 下载安装SDL 1.2.3.1 下载软件 ​1.2.3.2 安装软件 1.2.4 下载安装CMake 1.2.4.…...

H5112B 降压恒流芯片12V24V36V48V60V72V100V 1.2ALED 调光无频闪光滑细腻

H5112B多功能LED恒流驱动器是一款具有良好性能与高度集成度的驱动芯片。以下是该产品的主要优点及应用领域的详细分析&#xff1a; 产品优点&#xff1a; 宽电压输入范围&#xff1a;H5112B支持5V至90V的宽电压输入范围&#xff0c;使其能够适应多种不同的电源环境&#xff0…...

真心建议大家冲一冲新兴领域,工资高前景好【大模型NLP开发篇】

前言 从ChatGPT到新近的GPT-4&#xff0c;GPT模型的发展表明&#xff0c;AI正在向着“类⼈化”⽅向迅速发展。 GPT-4具备深度阅读和识图能⼒&#xff0c;能够出⾊地通过专业考试并完成复杂指令&#xff0c;向⼈类引以为傲的“创造⼒”发起挑战。 现有的就业结构即将发⽣重⼤变…...

深度剖析淘宝扭蛋机源码:打造趣味性电商活动的秘诀

在当今电商市场中&#xff0c;如何吸引用户的注意力、提升用户的参与度成为了各大电商平台竞相追求的目标。淘宝扭蛋机作为一种新型的电商活动形式&#xff0c;以其趣味性和互动性深受用户喜爱。本文将深度剖析淘宝扭蛋机源码&#xff0c;探讨其如何打造趣味性与互动性并存的电…...