RocketMQ5.0.0消息存储<二>_消息存储流程
目录
一、消息存储概览
二、Broker接收消息
三、消息存储流程
1. DefaultMessageStore类
2. 存储流程
1):同步与异步存储
2):CommitLog异步存储消息
3):提交消息(Commit)
四、参考资料
一、消息存储概览
如下图所示,是消息从生产者发送消息到消费者消费消息的大致流程。
- step1:生产者发送消息到消息存储Broker端;
- step2:单一文件Commitlog存储所有主题消息,确保顺序写入,提高吞吐量;
- step3:消息通过堆外缓存,Commit消息写入文件内存映射,然后Flush写入磁盘;
- step4:消息Flush磁盘后,把消息转发到ConsumeQueue、IndexFile供消费者消费;
- step5:主题下消费队列内容相同,但是一个消费队列在同一时刻只能被一个消费者消费;
- step6:消费者根据集群/广播模式、PUSH/PULL模式来消费消息。
如何实现顺序存储的呢?通过org.apache.rocketmq.store.PutMessageLock接口,在消息追加文件内存映射时,加锁实现存储消息串行。
消息存储模式:同步、异步。默认异步存储,但是无论同步还是异步,最终执行存储方法是org.apache.rocketmq.store.CommitLog#asyncPutMessage(异步执行,提高存储效率),而同步需要等待存储结果才能返回。
本章主要介绍生产者发送消息,Broker如何接收消息,如何Commit写入文件内存映射,并没有介绍如何刷盘、转发到ConsumeQueue和IndexFile、HA主从同步等内容。
二、Broker接收消息
org.apache.rocketmq.broker.processor.SendMessageProcessor是生产者发送消息后,Broker接收消息的核心实现类。 发送消息请求码是RequestCode.SEND_MESSAGE。发送消息参考《RocketMQ5.0.0消息发送》。
org.apache.rocketmq.broker.processor.SendMessageProcessor#processRequest不仅处理生产者发来的消息,同时还是处理消费端消费ACK的处理请求。其核心逻辑是sendMessage或sendBatchMessage处理方法,都是Broker端存储消息。
以下代码是org.apache.rocketmq.broker.processor.SendMessageProcessor#sendMessage存储之前对消息的预处理。 核心逻辑如下:
- randomQueueId():发送时是否指定消费队列,若没有指定,则随机选择;
- handleRetryAndDLQ():消息是否延迟或重试消息,并处理;
- sendTransactionPrepareMessage变量:判定是否事务消息,true事务消息;
- 异步存储消息(默认):
事务消息存储:TransactionalMessageServiceImpl#asyncPrepareMessage
普通消息存储:DefaultMessageStore#asyncPutMessage
- 同步存储消息:
事务消息存储:TransactionalMessageServiceImpl#prepareMessage
普通消息存储:DefaultMessageStore#putMessage
/*** 存储之前,对消息的处理* step1:预发送处理,如:检查消息、主题是否符合规范* step2:发送消息时,是否指定消费队列,若没有则随机选择* step3:消息是否进入重试或延迟队列中(重试次数失败)* step4:消息是否是事务消息,若是则存储为prepare消息* step5:BrokerConfig#asyncSendEnable是否开启异步存储,默认开启true* (异步存储、同步存储)*/
public RemotingCommand sendMessage(final ChannelHandlerContext ctx,final RemotingCommand request,final SendMessageContext sendMessageContext,final SendMessageRequestHeader requestHeader,final TopicQueueMappingContext mappingContext,final SendMessageCallback sendMessageCallback) throws RemotingCommandException {// 预发送处理,如:检查消息、主题是否符合规范final RemotingCommand response = preSend(ctx, request, requestHeader);if (response.getCode() != -1) {return response;}final SendMessageResponseHeader responseHeader = (SendMessageResponseHeader) response.readCustomHeader();// 获取消息体final byte[] body = request.getBody();// 发送消息时,是否指定消费队列,若没有则随机选择int queueIdInt = requestHeader.getQueueId();// 获取主题配置属性TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());if (queueIdInt < 0) { // 队列ID不符合,则在写队列随机找个queueIdInt = randomQueueId(topicConfig.getWriteQueueNums());}MessageExtBrokerInner msgInner = new MessageExtBrokerInner();msgInner.setTopic(requestHeader.getTopic());msgInner.setQueueId(queueIdInt);// 消息扩展属性Map<String, String> oriProps = MessageDecoder.string2messageProperties(requestHeader.getProperties());// 消息是否进入重试或延迟队列中(重试次数失败)if (!handleRetryAndDLQ(requestHeader, response, request, msgInner, topicConfig, oriProps)) {return response;}msgInner.setBody(body);msgInner.setFlag(requestHeader.getFlag());String uniqKey = oriProps.get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);if (uniqKey == null || uniqKey.length() <= 0) {uniqKey = MessageClientIDSetter.createUniqID();oriProps.put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, uniqKey);}MessageAccessor.setProperties(msgInner, oriProps);msgInner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(topicConfig.getTopicFilterType(), msgInner.getTags()));msgInner.setBornTimestamp(requestHeader.getBornTimestamp());msgInner.setBornHost(ctx.channel().remoteAddress());msgInner.setStoreHost(this.getStoreHost());msgInner.setReconsumeTimes(requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes());String clusterName = this.brokerController.getBrokerConfig().getBrokerClusterName();MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_CLUSTER, clusterName);msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));// Map<String, String> oriProps = MessageDecoder.string2messageProperties(requestHeader.getProperties());// 事务标签String traFlag = oriProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED);boolean sendTransactionPrepareMessage = false;if (Boolean.parseBoolean(traFlag)&& !(msgInner.getReconsumeTimes() > 0 && msgInner.getDelayTimeLevel() > 0)) { //For client under version 4.6.1// Broker禁止事务消息存储if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) {response.setCode(ResponseCode.NO_PERMISSION);response.setRemark("the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1()+ "] sending transaction message is forbidden");return response;}sendTransactionPrepareMessage = true;}long beginTimeMillis = this.brokerController.getMessageStore().now();// 消息是否异步存储if (brokerController.getBrokerConfig().isAsyncSendEnable()) {CompletableFuture<PutMessageResult> asyncPutMessageFuture;if (sendTransactionPrepareMessage) { // 事务prepare操作asyncPutMessageFuture = this.brokerController.getTransactionalMessageService().asyncPrepareMessage(msgInner);} else {asyncPutMessageFuture = this.brokerController.getMessageStore().asyncPutMessage(msgInner);}final int finalQueueIdInt = queueIdInt;final MessageExtBrokerInner finalMsgInner = msgInner;asyncPutMessageFuture.thenAcceptAsync(putMessageResult -> {RemotingCommand responseFuture =handlePutMessageResult(putMessageResult, response, request, finalMsgInner, responseHeader, sendMessageContext,ctx, finalQueueIdInt, beginTimeMillis, mappingContext);if (responseFuture != null) {doResponse(ctx, request, responseFuture);}sendMessageCallback.onComplete(sendMessageContext, response);}, this.brokerController.getPutMessageFutureExecutor());// Returns null to release the send message threadreturn null;}// 消息同步存储else {PutMessageResult putMessageResult = null;// 事务消息存储if (sendTransactionPrepareMessage) {putMessageResult = this.brokerController.getTransactionalMessageService().prepareMessage(msgInner);} else {// 同步存储消息putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);}handlePutMessageResult(putMessageResult, response, request, msgInner, responseHeader, sendMessageContext, ctx, queueIdInt, beginTimeMillis, mappingContext);sendMessageCallback.onComplete(sendMessageContext, response);return response;}
}
三、消息存储流程
1. DefaultMessageStore类
org.apache.rocketmq.store.DefaultMessageStore是消息存储实现类,也是存储模块最重要的一个类,其UML如下。
其关键属性如下代码所示。同步与异步存储的方法:
- 同步消息:单个消息putMessage()、批量消息putMessages()
- 异步消息:单个消息asyncPutMessage()、批量消息asyncPutMessages()
// Commitlog引用次数
public final PerfCounter.Ticks perfs = new PerfCounter.Ticks(LOGGER);// 存储配置属性
private final MessageStoreConfig messageStoreConfig;
// CommitLog(Commitlog文件存储实现类)
private final CommitLog commitLog;
// ConsumeQueue文件存储实现类
private final ConsumeQueueStore consumeQueueStore;
// 刷盘线程
private final FlushConsumeQueueService flushConsumeQueueService;
// 删除过期Commitlog文件服务
private final CleanCommitLogService cleanCommitLogService;
// 删除过期ConsumeQueue文件服务
private final CleanConsumeQueueService cleanConsumeQueueService;
// 矫正逻辑偏移量服务
private final CorrectLogicOffsetService correctLogicOffsetService;
// index文件实现类
private final IndexService indexService;
// MappedFile分配服务
private final AllocateMappedFileService allocateMappedFileService;
// 消息提交到Commitlog时消息转发,构建ConsumeQueue、index文件服务
private ReputMessageService reputMessageService;
// HA服务(主从同步服务)
private HAService haService;
// 存储状态服务
private final StoreStatsService storeStatsService;
// 堆内存缓存
private final TransientStorePool transientStorePool;// Broker状态管理
private final BrokerStatsManager brokerStatsManager;
// 消息达到监听器(消息拉取长轮询模式)
private final MessageArrivingListener messageArrivingListener;
// Broker配置属性
private final BrokerConfig brokerConfig;
// 存储刷盘检查点
private StoreCheckpoint storeCheckpoint;
// 定时消息存储实现类
private TimerMessageStore timerMessageStore;
// 日志打印次数
private AtomicLong printTimes = new AtomicLong(0);
// Commitlog文件转发请求
private final LinkedList<CommitLogDispatcher> dispatcherList;// 延迟消息的延迟级别
private final ConcurrentMap<Integer /* level */, Long/* delay timeMillis */> delayLevelTable =new ConcurrentHashMap<Integer, Long>(32);// 最大延迟级别
private int maxDelayLevel;
2. 存储流程
1):同步与异步存储
如下图所示是同步与异步存储的实现方法调用链,它们之间区别与联系:
联系:a. 同步存储实际是调用异步存储方法,即:DefaultMessageStore#asyncPutMessage;
b. 最终执行存储方法是org.apache.rocketmq.store.CommitLog#asyncPutMessage;
区别:同步存储需要等待存储结果waitForPutResult()
2):CommitLog异步存储消息
org.apache.rocketmq.store.CommitLog#asyncPutMessage是异步执行存储消息。如下代码所示,关键步骤如下:
- step1:mappedFileQueue队列中,获取可写入的Commitlog,即:从commitlog目录下获取当前写入的Commitlog;
- step2:获取当前写入Commitlog的偏移量 = 文件偏移量 + 该文件写入位置;
- step3:是否需要HA(主从Broker同步数据);
- step4:CommitLog.calMsgLength获取该消息的总长度(不定长,总长度存储在前4个字节);
- step5:加锁后(串行写),判定再次Commitlog文件没有或已被写满,则创建新的Commitlog文件;
- step6:Commit操作,即:消息缓存或直接(是否开启堆外内存池)追加到Commitlog文件内存映射buffer,追加当前消息,并没有刷写到磁盘,则返回结果,方法:{@link DefaultMappedFile#appendMessage};
- step7:执行同步或异步刷盘、HA主从同步复制等,方法: {@link CommitLog#handleDiskFlushAndHA}。
/*** 执行存储消息* step1:mappedFileQueue队列中,获取可写入的Commitlog,即:从commitlog目录下获取当前写入的Commitlog;* step2:获取当前写入Commitlog的偏移量 = 文件偏移量 + 该文件写入位置;* step3:是否需要HA(主从Broker同步数据);* step4:CommitLog.calMsgLength获取该消息的总长度(不定长,总长度存储在前4个字节);* step5:加锁后(串行写),判定再次Commitlog文件没有或已被写满,则创建新的Commitlog文件;* step6:Commit操作,即:消息缓存或直接(是否开启堆外内存池)追加到Commitlog文件内存映射buffer,追加当前消息,并没有刷写到磁盘,则返回结果* {@link DefaultMappedFile#appendMessage}* step7:执行同步或异步刷盘、HA主从同步复制等* {@link CommitLog#handleDiskFlushAndHA}* @param msg 消息* @return 存储结果*/
public CompletableFuture<PutMessageResult> asyncPutMessage(final MessageExtBrokerInner msg) {// Set the storage timeif (!defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) {msg.setStoreTimestamp(System.currentTimeMillis());}// Set the message body CRC (consider the most appropriate setting on the client)msg.setBodyCRC(UtilAll.crc32(msg.getBody()));// Back to ResultsAppendMessageResult result = null;StoreStatsService storeStatsService = this.defaultMessageStore.getStoreStatsService();String topic = msg.getTopic();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);String topicQueueKey = generateKey(putMessageThreadLocal.getKeyBuilder(), msg);long elapsedTimeInLock = 0;// mappedFileQueue队列中,获取可写入的Commitlog,即:从commitlog目录下获取当前写入的CommitlogMappedFile unlockMappedFile = null;MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile();// 当前写入Commitlog的偏移量long currOffset;if (mappedFile == null) {currOffset = 0;} else {// 当前写入Commitlog的偏移量 = 文件偏移量 + 该文件写入位置currOffset = mappedFile.getFileFromOffset() + mappedFile.getWrotePosition();}// 是否需要HAint needAckNums = this.defaultMessageStore.getMessageStoreConfig().getInSyncReplicas();boolean needHandleHA = needHandleHA(msg);if (needHandleHA && this.defaultMessageStore.getBrokerConfig().isEnableControllerMode()) {if (this.defaultMessageStore.getHaService().inSyncReplicasNums(currOffset) < this.defaultMessageStore.getMessageStoreConfig().getMinInSyncReplicas()) {return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH, null));}if (this.defaultMessageStore.getMessageStoreConfig().isAllAckInSyncStateSet()) {// -1 means all ack in SyncStateSetneedAckNums = MixAll.ALL_ACK_IN_SYNC_STATE_SET;}} else if (needHandleHA && this.defaultMessageStore.getBrokerConfig().isEnableSlaveActingMaster()) {int inSyncReplicas = Math.min(this.defaultMessageStore.getAliveReplicaNumInGroup(),this.defaultMessageStore.getHaService().inSyncReplicasNums(currOffset));needAckNums = calcNeedAckNums(inSyncReplicas);if (needAckNums > inSyncReplicas) {// Tell the producer, don't have enough slaves to handle the send requestreturn CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH, null));}}topicQueueLock.lock(topicQueueKey);try {boolean needAssignOffset = true;if (defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()&& defaultMessageStore.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE) {needAssignOffset = false;}if (needAssignOffset) {defaultMessageStore.assignOffset(msg, getMessageNum(msg));}/*当前消息编码,计算byteBuf(字节缓存)长度, null则正常处理CommitLog.calMsgLength获取该消息的总长度(不定长,总长度存储在前4个字节)*/PutMessageResult encodeResult = putMessageThreadLocal.getEncoder().encode(msg);if (encodeResult != null) {return CompletableFuture.completedFuture(encodeResult);}msg.setEncodedBuff(putMessageThreadLocal.getEncoder().getEncoderBuffer());PutMessageContext putMessageContext = new PutMessageContext(topicQueueKey);putMessageLock.lock(); //spin or ReentrantLock ,depending on store configtry {long beginLockTimestamp = this.defaultMessageStore.getSystemClock().now();this.beginTimeInLock = beginLockTimestamp;// Here settings are stored timestamp, in order to ensure an orderly// globalif (!defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) {msg.setStoreTimestamp(beginLockTimestamp); // 消息存储时间戳,确保消息存储有序}// Commitlog文件没有或已被写满,则创建新的Commitlog文件if (null == mappedFile || mappedFile.isFull()) {// 创建新的Commitlog文件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());beginTimeInLock = 0;return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPPED_FILE_FAILED, null));}// Commitlog文件写入消息(Commitlog文件内存映射buffer,追加当前消息,并没有刷写到磁盘,则返回结果)result = mappedFile.appendMessage(msg, this.appendMessageCallback, putMessageContext);switch (result.getStatus()) {case PUT_OK:onCommitLogAppend(msg, result, mappedFile);break;case END_OF_FILE:onCommitLogAppend(msg, result, mappedFile);unlockMappedFile = mappedFile;// Create a new file, re-write the messagemappedFile = this.mappedFileQueue.getLastMappedFile(0);if (null == mappedFile) {// XXX: warn and notify melog.error("create mapped file2 error, topic: " + msg.getTopic() + " clientAddr: " + msg.getBornHostString());beginTimeInLock = 0;return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPPED_FILE_FAILED, result));}result = mappedFile.appendMessage(msg, this.appendMessageCallback, putMessageContext);if (AppendMessageStatus.PUT_OK.equals(result.getStatus())) {onCommitLogAppend(msg, result, mappedFile);}break;case MESSAGE_SIZE_EXCEEDED:case PROPERTIES_SIZE_EXCEEDED:beginTimeInLock = 0;return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, result));case UNKNOWN_ERROR:beginTimeInLock = 0;return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result));default:beginTimeInLock = 0;return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result));}elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginLockTimestamp;beginTimeInLock = 0;} finally {putMessageLock.unlock();}} finally {topicQueueLock.unlock(topicQueueKey);}// putMessage加锁超时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);// StatisticsstoreStatsService.getSinglePutMessageTopicTimesTotal(msg.getTopic()).add(result.getMsgNum());storeStatsService.getSinglePutMessageTopicSizeTotal(topic).add(result.getWroteBytes());/*执行同步或异步刷盘、HA主从同步复制等注意:MappedFile.appendMessage只是将消息追加到Commitlog文件内存映射buffer中,并没有刷写到磁盘,则返回结果*/return handleDiskFlushAndHA(putMessageResult, msg, needAckNums, needHandleHA);
}
3):提交消息(Commit)
org.apache.rocketmq.store.logfile.DefaultMappedFile#appendMessage是文件内存映射追加消息方法,目的是把堆外缓存池消息或直接Commit到文件内存映射,其调用链如下。
org.apache.rocketmq.store.logfile.DefaultMappedFile#appendMessagesInner是追加消息到文件内存映射的核心方法,如下代码所示。
/*** Commit操作,即:消息缓存或直接(是否开启堆外内存池)追加到Commitlog文件内存映射buffer,追加当前消息,并没有刷写到磁盘,则返回结果* step1:当前Commitlog的写指针,判断文件是否写满;* step2:slice():创建与原ByteBuffer共享的内存区,拥有独立的position、limit、capacity等指针,并设置position当前写指针* step3:判断是否是批量消息,并追加消息到Commitlog文件内存映射buffer,并没有刷写到磁盘,则返回结果*/
public AppendMessageResult appendMessagesInner(final MessageExt messageExt, final AppendMessageCallback cb,PutMessageContext putMessageContext) {assert messageExt != null;assert cb != null;// 当前Commitlog的写指针int currentPos = WROTE_POSITION_UPDATER.get(this);// 未写满,则追加if (currentPos < this.fileSize) {/*slice():创建与原ByteBuffer共享的内存区,拥有独立的position、limit、capacity等指针并设置position当前写指针*/ByteBuffer byteBuffer = appendMessageBuffer().slice();byteBuffer.position(currentPos);AppendMessageResult result;// 批量消息if (messageExt instanceof MessageExtBatch && !((MessageExtBatch) messageExt).isInnerBatch()) {// traditional batch message// Commitlog文件内存映射buffer,追加当前消息,并没有刷写到磁盘,则返回结果result = 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// Commitlog文件内存映射buffer,追加当前消息,并没有刷写到磁盘,则返回结果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);
}
需要注意的是:appendMessageBuffer().slice(),创建与原ByteBuffer共享的内存区,拥有独立的position、limit、capacity等指针,并设置position当前写指针。创建的内存追加消息。
四、参考资料
Rocket Mq消息持久化_飞科-程序人生的博客-CSDN博客
百度安全验证
【RocketMQ】同一个项目中,同一个topic,可以存在多个消费者么? - N!CE波 - 博客园
RocketMQ5.0.0消息发送_爱我所爱0505的博客-CSDN博客
RocketMQ5.0.0路由中心NameServer_爱我所爱0505的博客-CSDN博客
RocketMQ5.0.0消息存储<一>_存储文件及内存映射_爱我所爱0505的博客-CSDN博客
相关文章:

RocketMQ5.0.0消息存储<二>_消息存储流程
目录 一、消息存储概览 二、Broker接收消息 三、消息存储流程 1. DefaultMessageStore类 2. 存储流程 1):同步与异步存储 2):CommitLog异步存储消息 3):提交消息(Commit) 四、参考资料 一、消息存储概览 如下图所…...
【单片机方案】蓝牙体温计方案介绍
蓝牙体温计方案的工作原理利用了温度传感器输出电信号,直接输出数字信号或者再将电流信号(模拟信号)转换成能够被内部集成的电路识别的数字信号,然后通过显示器(如液晶、数码管、LED矩阵等)显示以数字形式的温度,能记录、读取被测温度的最高值…...
React 的受控组件和非受控组件有什么不同
大家好,我是前端西瓜哥,今天我们来看看 React 的受控组件和非受控组件有什么不同。 受控组件 受控组件,指的是将表单元素的值交给组件的 state 来保存。 例子: import ./styles.css import { useState } from reactconst App …...

【逐步剖C】-第六章-结构体初阶
一、结构体的声明 1. 结构体的基本概念 结构体是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。结构体使得C语言有能力描述复杂类型。 如学生,有姓名、学号、性别等;如书,有作者,出版日期…...

Java 并发在项目中的使用场景
1、并发编程的三个核心问题:(1)分工:所谓分工指的是如何高效地拆解任务并分配给线程(2)同步:而同步指的是线程之间如何协作(3)互斥:互斥则是保证同一时刻只允…...

15.面向对象程序设计
文章目录面向对象程序设计15.1OOP:概述继承动态绑定15.2定义基类和派生类15.2.1定义基类成员函数与继承访问控制与继承15.2.2定义派生类派生类对象及派生类向基类的类型转换派生类构造函数派生类使用基类的成员继承与静态成员派生类的声明被用作基类的类防止继承的发…...

Element UI框架学习篇(一)
Element UI框架学习篇(一) 1.准备工作 1.1 下载好ElementUI所需要的文件 ElementUI官网 1.2 插件的安装 1.2.1 更改标签的时实现自动修改 1.2.2 element UI提示插件 1.3 使用ElementUI需要引入的文件 <link rel"stylesheet" href"../elementUI/element…...
【算法】【C语言】
差分算法力扣1094题目描述学习代码思考力扣1094 题目描述 车上最初有 capacity 个空座位。车 只能 向一个方向行驶(也就是说,不允许掉头或改变方向) 给定整数 capacity 和一个数组 trips , trip[i] [numPassengersi, fromi, toi] 表示第 …...

【✨十五天搞定电工基础】基本放大电路
本章要求1. 理解放大电路的放大作用和共发射极放大电路的性能特点; 2. 掌握静态工作点的估算方法和放大电路的微变等效电路分析法; 3. 了解放大电路输入、输出电阻和电压放大倍数的计算方法,了解放大电路的频率特性、 互补功率放大…...

MyBatis 入门教程详解
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...

shiro、springboot、vue、elementUI CDN模式前后端分离的权限管理demo 附源码
shiro、springboot、vue、elementUI CDN模式前后端分离的权限管理demo 附源码 源码下载地址 https://github.com/Aizhuxueliang/springboot_shiro.git 前提你电脑的安装好这些工具:jdk8、idea、maven、git、mysql; shiro的主要概念 Shiro是一个强大…...

智能优化算法——粒子群优化算法(PSO)(小白也能看懂)
前言: 暑假期间,因科研需要,经常在论文中看到各种优化算法,所以自己学习了一些智能优化的算法,做了一些相关的纸质性笔记,寒假一看感觉又有点遗忘了,并且笔记不方便随时查看,所以希…...

Lesson 6.4 逻辑回归手动调参实验
文章目录一、数据准备与评估器构造1. 数据准备2. 构建机器学习流二、评估器训练与过拟合实验三、评估器的手动调参在补充了一系列关于正则化的基础理论以及 sklearn 中逻辑回归评估器的参数解释之后,接下来,我们尝试借助 sklearn 中的逻辑回归评估器&…...
Oracle数据库入门大全
oracle数据库 Oracle 数据库、实例、用户、表空间、表之间的关系 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pSv0SArH-1675906973035)(vx_images/573695710268888.png 676x)] 数据库 数据库是数据集合。Oracle是一种数据库管理系统ÿ…...

C语言操作符详解(下)
提示:本篇内容是C语言操作符详解下篇 文章目录前言八、条件表达式九、逗号表达式十、 下标引用、函数调用和结构成员1. [ ] 下标引用操作符2. ( ) 函数调用操作符3.结构成员访问操作符十一、表达式求值1. 隐式类型转换举例说明1举例说明2举例说明32.算数转换3.操作…...

【五六七人口普查】我国省市两级家庭户住房状况
人口数据是我们在各项研究中最常使用的数据!之前我们分享过第七次人口普查(简称七普)的数据!很多小伙伴拿到数据后都反馈数据非常好用,同时很多小伙伴咨询有没有前面几次人口普查的数据,这样方便做人口变化…...

大数据框架之Hadoop:入门(二)从Hadoop框架讨论大数据生态
第2章 从Hadoop框架讨论大数据生态 2.1 Hadoop是什么 Hadoop是一个由Apache基金会所开发的分布式系统基础架构。主要解决,海量数据的存储和海量数据的分析计算问题。广义上来说,Hadoop通常是指一个更广泛的概念-Hadoop生态圈。 2.2 Hadoop发展历史 1&…...

负载均衡反向代理下的webshell上传+apache漏洞
目录一、负载均衡反向代理下的webshell上传1、nginx 负载均衡2、搭建环境3、负载均衡下的 WebShell连接的难点总结难点一、需要在每一台节点的相同位置都上传相同内容的 WebShell难点二、无法预测下次的请求交给哪台机器去执行。难点三、下载文件时,可能会出现飘逸&…...

打造安全可信的通信服务,阿里云云通信发布《短信服务安全白皮书》
随着数字化经济的发展,信息保护和数据安全成为企业、个人关注的焦点。近日,阿里云云通信发布《短信服务安全白皮书》,该白皮书包含安全责任共担、安全合规、安全架构三大板块,呈现了阿里云云通信在信息安全保护方面的技术能力、安…...

Python项目实战——外汇牌价(附源码)
前言 几乎每个人都在使用银行卡,今天我们就来爬取某行外汇牌价,获取我们想要的数据。 环境使用 python 3.9pycharm 模块使用 requests 模块介绍 requestsrequests是一个很实用的Python HTTP客户端库,爬虫和测试服务器响应数据时经常会用到&…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...

Linux部署私有文件管理系统MinIO
最近需要用到一个文件管理服务,但是又不想花钱,所以就想着自己搭建一个,刚好我们用的一个开源框架已经集成了MinIO,所以就选了这个 我这边对文件服务性能要求不是太高,单机版就可以 安装非常简单,几个命令就…...