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

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)&#xff1a;同步与异步存储 2)&#xff1a;CommitLog异步存储消息 3)&#xff1a;提交消息&#xff08;Commit&#xff09; 四、参考资料 一、消息存储概览 如下图所…...

【单片机方案】蓝牙体温计方案介绍

蓝牙体温计方案的工作原理利用了温度传感器输出电信号&#xff0c;直接输出数字信号或者再将电流信号(模拟信号)转换成能够被内部集成的电路识别的数字信号&#xff0c;然后通过显示器(如液晶、数码管、LED矩阵等)显示以数字形式的温度&#xff0c;能记录、读取被测温度的最高值…...

React 的受控组件和非受控组件有什么不同

大家好&#xff0c;我是前端西瓜哥&#xff0c;今天我们来看看 React 的受控组件和非受控组件有什么不同。 受控组件 受控组件&#xff0c;指的是将表单元素的值交给组件的 state 来保存。 例子&#xff1a; import ./styles.css import { useState } from reactconst App …...

【逐步剖C】-第六章-结构体初阶

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

Java 并发在项目中的使用场景

1、并发编程的三个核心问题&#xff1a;&#xff08;1&#xff09;分工&#xff1a;所谓分工指的是如何高效地拆解任务并分配给线程&#xff08;2&#xff09;同步&#xff1a;而同步指的是线程之间如何协作&#xff08;3&#xff09;互斥&#xff1a;互斥则是保证同一时刻只允…...

15.面向对象程序设计

文章目录面向对象程序设计15.1OOP&#xff1a;概述继承动态绑定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 个空座位。车 只能 向一个方向行驶&#xff08;也就是说&#xff0c;不允许掉头或改变方向&#xff09; 给定整数 capacity 和一个数组 trips , trip[i] [numPassengersi, fromi, toi] 表示第 …...

【✨十五天搞定电工基础】基本放大电路

本章要求1. 理解放大电路的放大作用和共发射极放大电路的性能特点&#xff1b; 2. 掌握静态工作点的估算方法和放大电路的微变等效电路分析法&#xff1b; 3. 了解放大电路输入、输出电阻和电压放大倍数的计算方法&#xff0c;了解放大电路的频率特性、 互补功率放大…...

MyBatis 入门教程详解

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…...

shiro、springboot、vue、elementUI CDN模式前后端分离的权限管理demo 附源码

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

智能优化算法——粒子群优化算法(PSO)(小白也能看懂)

前言&#xff1a; 暑假期间&#xff0c;因科研需要&#xff0c;经常在论文中看到各种优化算法&#xff0c;所以自己学习了一些智能优化的算法&#xff0c;做了一些相关的纸质性笔记&#xff0c;寒假一看感觉又有点遗忘了&#xff0c;并且笔记不方便随时查看&#xff0c;所以希…...

Lesson 6.4 逻辑回归手动调参实验

文章目录一、数据准备与评估器构造1. 数据准备2. 构建机器学习流二、评估器训练与过拟合实验三、评估器的手动调参在补充了一系列关于正则化的基础理论以及 sklearn 中逻辑回归评估器的参数解释之后&#xff0c;接下来&#xff0c;我们尝试借助 sklearn 中的逻辑回归评估器&…...

Oracle数据库入门大全

oracle数据库 Oracle 数据库、实例、用户、表空间、表之间的关系 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pSv0SArH-1675906973035)(vx_images/573695710268888.png 676x)] 数据库 数据库是数据集合。Oracle是一种数据库管理系统&#xff…...

C语言操作符详解(下)

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

【五六七人口普查】我国省市两级家庭户住房状况

人口数据是我们在各项研究中最常使用的数据&#xff01;之前我们分享过第七次人口普查&#xff08;简称七普&#xff09;的数据&#xff01;很多小伙伴拿到数据后都反馈数据非常好用&#xff0c;同时很多小伙伴咨询有没有前面几次人口普查的数据&#xff0c;这样方便做人口变化…...

大数据框架之Hadoop:入门(二)从Hadoop框架讨论大数据生态

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

负载均衡反向代理下的webshell上传+apache漏洞

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

打造安全可信的通信服务,阿里云云通信发布《短信服务安全白皮书》

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

Python项目实战——外汇牌价(附源码)

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

String、StringBuffer、StringBuilder有什么区别?

第5讲 | String、StringBuffer、StringBuilder有什么区别&#xff1f; 今天我会聊聊日常使用的字符串&#xff0c;别看它似乎很简单&#xff0c;但其实字符串几乎在所有编程语言里都是个特殊的存在&#xff0c;因为不管是数量还是体积&#xff0c;字符串都是大多数应用中的重要…...

python基于django+vue的高铁地铁火车订票管理系统

目录 1 绪论 1 1.1课题背景 1 1.2课题研究现状 1 1.3初步设计方法与实施方案 2 1.4本文研究内容 2 2 系统开发环境 4 2.1 使用工具简介 4 2.2 环境配置 4 2.4 MySQL数据库 5 2.5 框架介绍 5 3 系统分析 6 3.1系统可行性分析 6 3.1.1经济可行性 6 3.1.2技术可行性 6 3.1.3运行可…...

全栈自动化测试技术笔记(一):前期调研怎么做

昨天下午在家整理书架&#xff0c;把很多看完的书清理打包好&#xff0c;预约了公益捐赠机构上门回收。 整理的过程中无意翻出了几年前的工作记事本&#xff0c;里面记录了很多我刚开始做自动化和性能测试时的笔记。 虽然站在现在的角度来看&#xff0c;那个时候无论是技术细…...

专家培养计划

1、先知道一百个关键词 进入一个行业&#xff0c;如果能快速掌握其行业关键词&#xff0c;你会发现&#xff0c;你和专家的距离在迅速缩短。 若不然&#xff0c;可能同事间的日常交流&#xff0c;你都会听得云里雾里&#xff0c;不知所云。 比如做零售&#xff0c;就要了解零售…...

583. 两个字符串的删除操作 72. 编辑距离

583. 两个字符串的删除操作 dp[i][j]:以i-1结尾的word1和j-1结尾的word2 变成相同字符串最少的步骤为dp[i][j] 初始化dp[i][0],dp[0][j]为空字符串和第一个字符匹配的最少步骤&#xff0c;即i/j&#xff0c;删除对应的字符个数。dp[i][0]i,dp[0][j]j; 遍历两个字符串。 若word1…...

[多线程进阶] 常见锁策略

专栏简介: JavaEE从入门到进阶 题目来源: leetcode,牛客,剑指offer. 创作目标: 记录学习JavaEE学习历程 希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长. 学历代表过去,能力代表现在,学习能力代表未来! 目录: 1. 常见的锁策略 1.1 乐观锁 vs 悲观锁 1.2 读写…...

Scala - Idea 项目报错 Cannot resolve symbol XXX

一.引言 Idea 编译 Scala 项目大面积报错 Cannot resolve symbol xxx。 二.Cannot resolve symbol xxx 1.问题描述 Idea 内的 Scala 工程打开后显示下述异常&#xff1a; 即 Scala 常规语法全部失效&#xff0c;代码出现大面积红色报错。 2.尝试解决方法 A.设置 Main Sourc…...

信息化发展与应用的新特点

一、信息化发展与应用二、国家信息化发展战略三、电子政务※四、电子商务五、两化融合&#xff08;工业和信息化&#xff09;六、智慧城市 一、信息化发展与应用 我国在“十三五”规划纲要中&#xff0c;将培育人工智能、移动智能终端、第五代移动通信(5G)先进传感器等作为新…...

软件测试】测试时间不够了,我很慌?项目马上发布了......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 常见的几种情况&…...

MapReduce编程规范

MapReduce编程规范 MapReduce的开发一共有八个步骤,其中Map阶段分为2个步骤&#xff0c;Shuffle阶段4个步骤&#xff0c;Reduce阶段分为2个步骤。 Map阶段2个步骤 设置InputFormat类,将数据切分为Key-Value(K1和V1)对,输入到第二步。 自定义Map逻辑,将第一步的结果转换成另外的…...