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

rocketmq5--(三)--broker发送消息给消费者

202412/30回过头来记录一下:之前一直找不到在哪里把消息丢给消费者(2024/11/23),找了很久都没找到,就放弃了,然后发现,我靠原来是在poplongpollingservice里。。。。还是乱打断点,偶然找到的。。。这属于运气好,好的,接下来处理consumer流程

RocketMQ主要有两种消费模式:CLUSTERING集群消费(默认)和BROADCASTING广播消费。在CLUSTERING模式下,一个ConsumerGroup中的Consumer实例根据队列分配策略算法为Consumer分配队列,平均分摊(默认)消费消息。例如,如果Topic是Test的消息发送到该主题的不同队列中,发送了有100条消息,其中一个ConsumerGroup有3个Consumer实例,那么根据队列分配算法,每个队列都会有消费者,每个消费者实例只消费自己队列上的数据,消费完的消息不能被其他消费实例消费。在BROADCASTING模式下,消息会被所有在该Topic的ConsumerGroup中的Consumer实例共同消费。消费模式在创建Consumer时指定,不同的消费模式其内部机制也不同,消息的消费方式、记录消费进度、消息的消费状态等也都各不相同。

对于长时间没有 ACK 的消息,Broker 端并非毫无办法。Pop 消费引入了消息不可见时间(invisibleTime)的机制。当 Pop 出一条消息后,这条消息对所有消费者不可见,即进入不可见时间,当它超过该时刻还没有被 ACK,Broker 将会把它放入 Pop 专门的重试 Topic(这个过程称为 Revive),这条消息重新可以被消费。https://my.oschina.net/u/4249738/blog/5609999

rocketmq5有三种消费者:默认是pop

    CONSUME_ACTIVELY("PULL")    --CONSUME_PASSIVELY("PUSH")   --CONSUME_POP("POP")          -- 对应PopLongPollingService

顺序写随机读:

发消息时,Productor的消息确实是顺序写CommitLog
订阅消息时,Consumer也是顺序读ConsumeQueue,然而根据其中的起始物理位置偏移量offset读取消息真实内容却是随机读CommitLog。

https://zhouj000.github.io/2021/05/10/rocketmq-source1/

pullRequestHoldService

ReputMessageService

源码阅读建议:因为rocketmq中有很多topic,而同一套代码流程可能同时处理我们的生产者消费者,也可能处理系统的相关消息,比如heartbeat,所以为了打断点的时候能确保是我们的代码触发的,所以建议如下修改源码,这样一旦运行到断点处就能确定是我们的代码触发的:

 @Overridepublic GetMessageResult getMessage(final String group, final String topic, final int queueId, final long offset,final int maxMsgNums, final int maxTotalMsgSize, final MessageFilter messageFilter) {if (this.shutdown) {LOGGER.warn("message store has shutdown, so getMessage is forbidden");return null;}......if (topic.equals("testz")){                    #testz是我们自己的代码中使用的topicSystem.out.println("i found you:testz");   #随便来两条语句,然后在这里打断点int a=1;                                   #这样就能确保运行到这个断点的时候topic是我们自己的topic#而不会被heartbeat等系统消息所干扰}......

消费逻辑:

client sdk中consumer有两种消息拉取模式:broker push和consumer pull 
20241230 补充:rocketmq5还有pop方式

三个where:

commitWhere表示消费者已经消费到哪了,也是另开一个线程去flush即在CommitRealTimeService.run中刷到磁盘
flushWehre表示数据已经刷盘到哪里了,flushWhere的数据也叫confirmedOffset
妈的,一大堆offset、where,都绕晕了

ReputService:

ReputService是DefaultMessageStore的一个内部类

ReputService.runwhile !this.isStopped:ReputService.doReputif ReputService.reputFromOffset < CommitLog.getMinOffset:  #commitLog的minOffset表示目前存在的消息的最小offset#因为offset是递增的,所以这就表示凡是offset小于minOffset#的消息都已经被删除了,也就是对应的文件expired了ReputService.reputFromOffset = CommitLog.getMinOffset    #所以此时需要重置reputOffset为minOffset#即第一条有效地commitLogwhile doNext:                                            ok=isCommitLogAvailable:                               #检测commitLog的offset是否有变动,new=ReputService.getReputEndOffset                   #首先获取commitLog的位置#有两个offset:maxOffset和confirmedOffset#maxOffset表示commitLog的当前位置#并且这一块数据可能还没有flush到磁盘,所以是不稳定的#confirmedOffset则是已经刷新到磁盘的数据的最大offset#所以如果开启读未提交则返回maxOffset,反之返回confirmedOffsetMessageStoreConfig.isReadUnCommitted?CommitLog.getMaxOffset():CommitLog.getConfirmOffset()return cur<new                                       #一旦检测到commitLog的offset大于记录的reputOffset#就说明有更新即本次需要doReput而不是sleepCommitLog.getData(reputFromOffset)                     #从commitLog获取新增的数据#所有数据都是存放在commitLog,consumerQueue存的是索引没有存原始数据CommitLog.checkMessageAndReturnSize                     #检查消息是否ok比如检查crc对不对,以及获取消息大小        if reputFromOffset + size > getReputEndOffset():        #如果当前reputOffset+msgSize超过了offsetdoNext = false                                        #就表示这不是一条完整的消息即所有消息都处理完了,可以结束本次循环break if dispatchRequest.isSuccess():                         #如果这条消息okif size > 0:                                          #如果size大于0ReputService.doDispatch                             #分发消息,默认有4个dispatch,分别是:#CommitLogDispatcherCalcBitMap:#DefaultMessageStore.CommitLogDispatcherBuildConsumeQueue、#DefaultMessageStore.CommitLogDispatcherBuildIndex、#CommitLogDispatcherCompaction1: 计算consumerQueue对应的bitmapFilter         CommitLogDispatcherCalcBitMap.dispatch            #1:CommitLogDispatcherCalcBitMap计算consumerQueue对应的bitmapFilter  ...略...                                        #估计是用来加快查找的,和indexFile类似#不过默认是不启用的2:把offset写入consumerQueue文件CommitLogDispatcherBuildConsumeQueue.dispatch     #2:把offset写入consumerQueue文件switch (tranType):                            case MessageSysFlag.TRANSACTION_NOT_TYPE:     #如果是非事务消息即普通消息case MessageSysFlag.TRANSACTION_COMMIT_TYPE:  #或者是COMMIT消息即已完成的事务消息#即prepare或者rollback的消息我们就不需要写入consumerQueueDefaultMessageStore.putMessagePositionInfo  #执行分发ConsumerQueueStore.putMessagePositionInfoWrapper #ConsumerQueueStoreInterface可以有多种实现:default和rocksdbConsuemrQueueStore.findOrCreateConsumeQueue    #分发时首先查找consuemrQueue对应的文件#会把msg的offset写入consumerQueue对应的文件#consumerQueue是构建在commitLog之上的逻辑队列#consumerQueue中只保存msg的offset#读取一个消息的时候要先从consuemrQueue对应的文件中#读出offset,然后拿着这个offset再去commitlog中读取消息体ConcurrentMap<Integer, ConsumeQueueInterface> map = consumeQueueTable.get(topic) #因为有很多topic而且每个topic都有多个队列#所以用一个map<topicName,topicQueuInfo>加速查找topic的queue信息#然后又用一个map<queueId,queueStore>来存放该topic的每个queue信息#所以首先根据topic获取map<topicName,topicQueuInfo>ConsumeQueueInterface logic=map.get(queueId) #然后根据queueId获取queueStoreif logic != null:                            #如果存在对应的信息则直接返回  return logic;else:newLogic = new ConsumeQueue(               #如果不存在对应的queustore,则直接创建一个新的topic,                                   #这是consuemrQueue对应的topic名queueId,                                 #对应的queueidgetStorePathConsumeQueue                 #consumerQueue路径$qpath=$storePathRootDir\\consumerqueue#完整文件路径为filePath:#$qpath\\$topicName\\$queueid\\$startOffsetthis.messageStoreConfig.getMappedFileSizeConsumeQueue(),this.messageStore);map.putIfAbsent(queueId, newLogic)         #新创建的信息放到map<queueId,queueStore>中return newLogicAbstractConsumerQueueStore.putMessagePositionInfoWrapper          #把msg的offset追加到consumerQueue文件末尾ConsumerQueueStore.putMessagePositionInfoWrapperRunningFlags.isCQWriteable()                                  #判断当前consumerQueue是否可写result=ConsuemrQueue.putMessagePositionInfo                   #把offset写入consumerQueue文件if MessageStoreConfig.isPutConsumeQueueDataByFileChannel(): #默认是fileDefaultMappedFile.appendMessageUsingFileChannel           #写入磁盘......---->flush线程会定时刷盘consumerQueue的数据刷盘并更新onfirmOffsetFlushConsumeQueueService.run                          #这里是另一个异步刷盘线程while !this.isStopped:                              #死循环FlushConsumeQueueService.doFlush                  #刷盘ConsumeQueueStore.flush                         #执行刷盘ConsumerStore.getLifeCycle                    #先获取consumerQueue对象ConsumerStore.findOrCreateConsumeQueueConsumerQueue.flush                           #然后刷盘和更新flushWhereDefaultMappedFile.flush                     #刷盘MappedFileQueue.setFlushedWhere             #更新flushWhereif messageStoreConfig.isEnableCompaction:       #如果开启了compactioncompactionStore.flush(flushConsumeQueueLeastPages) #则也要处理compactionStoreelse:DefaultMappedFile.appendMessage< - - - 回到reputService.run线程if !notifyMessageArriveInBatch:DefaultMessageStore.notifyMessageArriveIfNecessaryNotifyMessageArrivingListener.arrivingPullRequestHoldService.notifyMessageArriving...暂时略...PopMessageProcessor.notifyMessageArriving...暂时略...NotificationProcessor.notifyMessageArriving...暂时略...if !notifyMessageArriveInBatch:ReputService.notifyMessageArriveIfNecessaryelse if size == 0:                                    #如果消息体size为0,说明下一个offset是在下一个mappedFile文件中了#(因为commitlog是一个逻辑文件,实际由多个offset连续的文件组成)reputFromOffset=CommitLog.rollNextFile              #则直接更新reputFromOffset来跳过这条消息ReputService.finishCommitLogDispatch                    #空函数,什么也不做

PopLongPollingService:

这是单独的线程负责查询消息并返回给consumer,我真的郁闷死,原来rocketmq5是有3种消费方式,而默认的是pop。。。。我一直以为是pull,挂不得我就说pullMessageProcessor中怎么只有heartBeat消息,怎么就没有对应的testz主题的消息。。。。一度使我放弃。。。。还是业务不精搞的鬼。。。

PopLongPollingService.runfor (Map.Entry<String, ConcurrentSkipListSet<PopRequest>> entry : pollingMap.entrySet())PopLongPollingService.wakeupAbstractExecutorService.submitresp=PopMessageProcessor.processRequest    #核心函数RemotingCommand.decodeCommandCustomHeader    #解析消费者请求的请求头ConsumerManager.compensateBasicConsumerInfo( #如果指定的消费者的信息不存在,则创建并保存到map中requestHeader.getConsumerGroup()           #消费者组名ConsumeType.CONSUME_POP,                   #消费者类型为POPMessageModel.CLUSTERING                    #消费方式为集群即所有消费者均摊)isTimeOut:=PopMessageRequestHeader.isTimeoutTooMuch    #判断是否超时即太久没有popreturn System.currentTimeMillis() - bornTime - pollTime > 500; #默认是500msif isTimeOut:                                          #如果超时就返回失败response.setRemark("the broker[%s] pop message is timeout too much")return PermName.isReadable                          #检查该broker是否允许消费者读取,默认是可读可写的if requestHeader.getMaxMsgNums() > 32:       #一次最多只允许broker pop 32条消息给消费者response.setCode(ResponseCode.SYSTEM_ERROR);response.setRemark("the broker[%s] pop message's num is greater than 32")return response;if !MessageStoreConfig.isTimerWheelEnable():    #检查该broker是否允许pop消息response.setCode(ResponseCode.SYSTEM_ERROR);response.setRemark("the broker[%s] pop message is forbidden because timerWheelEnable is false")return response;TopicConfigManager.selectTopicConfig           #从map中获取topic对应的config对象...略去一系列对topic的检查,比如是否可读,queueid对应的读队列是否存在等...  SubscriptionGroupManager.findSubscriptionGroupConfig  #获取消费者组信息BrokerController.getBrokerConfig               #获取broker配置信息   if requestHeader.getExp() != null && !String.isEmpty(): #获取表达式,一般是TAG表达式,如果TAG不为空则处理tag#这里的逻辑是:如果请求中设置了tag,就使用请求中的tag和表达式#如果请求中没有设置tag在,则默认使用*通配符匹配所有消息即不过滤任何消息subscriptionData = FilterAPI.build            #构建订阅信息ConsumerManager.compensateSubscribeData       #保存订阅信息retryTopic = KeyBuilder.buildPopRetryTopic    #构建retryTopic名字,每个topic都有一个对应的retryTopicretrySubscriptionData = FilterAPI.build       #构建retry订阅信息ConsumerManager.compensateSubscribeData       #保存retrytopic上的订阅信息consummerFilterData=nil                       #consumerFilterData内部含有布隆过滤器产生的数据,方便过滤消息if !ExpressionType.isTagType:                 #如果不是tag类型   consumerFilterData = ConsumerFilterManager.build #则创建consumerFilter  messageFilter = new ExpressionMessageFilter   #根据consumerFilter创建msgFilter#如果用了tag则consumerFilter会为nilelse:                                           #如果请求中没有设置tag,则默认是*,也就是会匹配全部消息#elseif里面的操作几乎一模一样subscriptionData = FilterAPI.build(requestHeader.getTopic(), "*", ExpressionType.TAG) ConsumerManager.compensateSubscribeData       #保存订阅信息retryTopic = KeyBuilder.buildPopRetryTopic    #构建retryTopic名字,每个topic都有一个对应的retryTopicretrySubscriptionData = FilterAPI.build(retryTopic, "*", ExpressionType.TAG) #构建retry订阅信息ConsumerManager.compensateSubscribeData       #保存retrytopic上的订阅信息int randomQ = random.nextInt(100)               #Pop模式中每次消费者请求都会概率性重试retry队列 #就是判断randomQ< 阈值,小于则需要重试int reviveQid;                                  #reviveQueueId:pop模式会把每次对应的checkPointMessage#保存到reviveTopic的reviveQueueId对应的队列中if requestHeader.isOrder:                       #如果请求时顺序消息(不太懂)reviveQid = KeyBuilder.POP_ORDER_REVIVE_QUEUE #则reviveQid为指定queueidelse:                                           #否则轮询使用不同的reviveQueuereviveQid = ckMessageNumber.getAndIncrement() %BrokerConfig.getReviveQueueNumneedRetry = randomQ < brokerConfig.getPopFromRetryProbability() #判断本次是否消费重试队列,比例可调if (brokerConfig.isEnableRetryTopicV2() &&      #如果开启了v2版本的pop retrybrokerConfig.isRetrieveMessageFromPopRetryTopicV1())  #并且也开启了v2版本的pop retry{                          needRetryV1 = randomQ % 2 == 0;               #那么就%2来表示本次使用哪个retry策略,不过默认是v1开启,v2关闭}if needRetry && !requestHeader.isOrder():        #如果需要消费重试队列并且请求不是顺序的,那么就消费重试topicif needRetryV1:                                #v1和v2版本的retrytopic的名字构建策略不一样,这里如果是v1retryTopic = KeyBuilder.buildPopRetryTopicV1 #则构建v1版本的retryTopic名字#v1和v2的区别只有POP_RETRY_SEPARATOR_V1为_#POP_RETRY_SEPARATOP_V2+return MixAll.RETRY_GROUP_TOPIC_PREFIX + cid + POP_RETRY_SEPARATOR_V1 + topic  PopMessageProcessor.popMsgFromTopic(retryTopic, reviveQide) #从retryTopic中获取消息else:                                          #先判断是否开启v2,开启则创建v2 retryTopic,反之则创建v1 retryTopicretryTopic = KeyBuilder.buildPopRetryTopic(requestHeader.getTopic()) PopMessageProcessor.popMsgFromTopic(retryTopic, reviveQid)if requestHeader.getQueueId() < 0:               #如果请求没有指定要消费的qid,则可消费所有的队列PopMessageProcessor.popMsgFromTopic(topic,reviveQid) #popMsgFromTopic有多个重载函数,这里是reviveQid版本#就是每个队列都消费一批数据for i = 0; i < topicConfig.getReadQueueNums(); i++:queueId = (randomQ + i) % topicConfig.getReadQueueNums() #不是每次都从第一个队列开始,而是随机从第n个队列开始PopMessageProcessor.popMsgFromQueue(queueid,reviveQid)lockKey =topic+PopAckConstants.SPLIT+consumerGroup+PopAckConstants.SPLIT+queueId#创建lockkey,举个例子mytopic@myconsumer@4PopMessageProcessor.getPopOffset(reviveQid,queueid) #获取消息对应的offset,从这里开始popoffset = ConsumerOffsetManager.queryOffset(consumerGroup,topic,queueid) #先获取消费者进度if offset < 0:offset = PopMessageProcessor.getInitOffset #从已存储的且还未被清除的消息开始if checkResetOffset:                              #再检查是否其他线程已经执行了resetOffset命令#如果执行了则表明offset已经失效,需要重新设置offsetPopMessageProcessor.resetPopOffset(topic, group, queueId)  #查询并根据resetOffset设置offsetlockKey = topic + PopAckConstants.SPLIT + group + PopAckConstants.SPLIT + queueId;ConsumerOffsetManager.queryThenEraseResetOffset #查询resetOffsetMap是否存在对应的resetOffsetif resetOffset != null:                        #如果存在,则说明有人执行了resetoffsetConsumerOrderInfoManager.clearBlock(topic, group, queueId) #所以这里先清除掉该队列对应的orderInfo(不太懂)PopBufferMergeService.clearOffsetQueue(lockKey)    #popbufservice中清除该队列的信息ConsumerOffsetManager.commitOffset(group, topic, queueId, resetOffset) #重新设置该队列的offset#备注:offset就是commitOffsetif resetOffset!=nil:                            #如果设置了resetOffset,则直接返回resetOffsetreturn resetOffsetbufferOffset=PopBufferMergeService.getLatestOffset(lockKey) #看popbuf中的offset到哪了if bufferOffset < 0:                            #如果popbuf中不存在该队列return offset                                #则返回offsetelse:return Math.max(bufferOffset, offset)        #反之看哪个进度更大,就返回哪个offset                                                                                                                                                                             if !QueueLockManager.tryLock(lockKey):    #在消费一个队列的时候会先加锁,保证一个队列同一时刻只有一个消费者在消费return                                  # if isPopShouldStop:                       #判断出于inflight的消息大小是否超过了阈值,即已经pop但是还没有ackreturn                                  #是则直接返回if isOrder:                               #如果是顺序消息...待补充,因为现在还不了解啥是顺序消息...long finalOffset = offsetDefaultMessageStore.getMessageAsync        #异步获取消息......DefaultMessageStore.getMessageDefaultMessageStore.findConsumeQueue              #根据queueid获取消费队列对象  ConsumerQueueStore.findOrCreateConsumeQueue     #如果不存在会自动创建bufferConsumeQueue=ConsumeQueue.iterateFrom(nextBeginOffset, maxMsgNums)#创建迭代器,用于遍历消费者队列#一次最多允许拉取maxMsgNums条消息while (bufferConsumeQueue.hasNext()           #接下来就是遍历迭代器拉取消息了&& nextBeginOffset < maxOffset)           #maxOffset表示队列的消息的最大offset{......if messageFilter != null && !messageFilter.isMatchedByConsumeQueue: continue                                  #如果该消息被过滤掉了 就过滤掉该消息。。。废话文学   CommitLog.getMessage(offsetPy, sizePy)      #从commitLog文件读取消息  GetMessageResult.addMessage                 #保存到响应缓冲区}if diskFallRecorded:                          #记录当前队列的消费进度落后commitLog多少long fallBehind = maxOffsetPy - maxPhyOffsetPullingBrokerStatsManager.recordDiskFallBehindSize(group, topic, queueId, fallBehind) diff = maxOffsetPy - maxPhyOffsetPulling      #记算当前队列的消费进度落后commitLog多少memory =(StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE #计算可访问的内存比例* (MessageStoreConfig.getAccessMessageInMemoryMaxRatio() / 100.0));GetMessageResult.setSuggestPullingFromSlave(diff > memory) #如果落后程度超过了可访问内存区间,#则在response中告诉client从slave broker读取#这里的逻辑就是:master一般只想用40%的内存区域保存前40%的数据#你这个消费者在这个消费队列的进度太落后了,比如还只读到50%的数据#这样master就需要保存50%~40%这个区间内的数据#所以此时master就建议去slave读,估计是为了减少master内存压力if isOrder:...待补充...else:PopMessageProcessor.appendCheckPoint      #保存本次pop消息对应的checkPoint消息PopBufferMergeService.addCk             #是保存在内存中PopBufferMergeService.putOffsetQueue   PopBufferMergeService.commitOffsets.putIfAbsentPopBufferMergeService.buffer.put(pointWrapper.getMergeKey(), pointWrapper)queueLockManager.unLock(lockKey)            #解锁队列else:PopMessageProcessor.popMsgFromTopic(topic,queueid,reviveQid) #这里是queueid版本接直接处理该队列的消息if resp!=nil:NettyRemotingAbstract.writeResponse(request.getChannel())     #写回响应20241230 17:10 不想搞了,下班了。。。身体被掏空。。。

PopMessageProcessor.processRequest

相关文章:

rocketmq5--(三)--broker发送消息给消费者

202412/30回过头来记录一下&#xff1a;之前一直找不到在哪里把消息丢给消费者&#xff08;2024/11/23&#xff09;&#xff0c;找了很久都没找到&#xff0c;就放弃了&#xff0c;然后发现&#xff0c;我靠原来是在poplongpollingservice里。。。。还是乱打断点&#xff0c;偶…...

QComboBox中使用树形控件进行选择

事情是这样的&#xff0c;要在一个ComboBox中通过树形结构进行内容的选择。 默认的QComboBox展开是下拉的列表。因此需要定制一下。 效果就是这样的 实现上面效果的核心代码就是下面这样的 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { treenew…...

单片机中运行多个定时器

在单片机的裸机编程环境中&#xff0c;同时运行多个定时器是完全可行的&#xff0c;但需要注意一些关键点以确保系统的稳定性和效率。以下是一些考虑因素和实现方法&#xff1a; 1. 硬件支持 定时器数量&#xff1a;首先确认您的单片机是否具备足够的定时器资源。大多数现代…...

go 模拟TCP粘包和拆包,及解决方法

1. 什么是 TCP 粘包与拆包&#xff1f; 粘包&#xff08;Sticky Packet&#xff09; 粘包是指在发送多个小的数据包时&#xff0c;接收端会将这些数据包合并成一个数据包接收。由于 TCP 是面向流的协议&#xff0c;它并不会在每次数据发送时附加边界信息。所以当多个数据包按顺…...

论文笔记PhotoReg: Photometrically Registering 3D Gaussian Splatting Models

1.abstract 最近推出的3D高斯飞溅(3DGS)&#xff0c;它用多达数百万个原始椭球体来描述场景&#xff0c;可以实时渲染。3DGS迅速声名鹊起。然而&#xff0c;一个关键的悬而未决的问题仍然存在&#xff1a;我们如何将多个3DG融合到一个连贯的模型中&#xff1f;解决这个问题将使…...

宝塔服务器安装备份配置

1.服务器下载安装宝塔功能 if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec执行后选择y 等待下载完成会给出…...

ubuntu22 安装CUDA

在Ubuntu系统中&#xff0c;使用nvidia-smi命令可以看到当前GPU信息&#xff0c;在右上角可以看到CUDA Version&#xff0c;意思是最大支持的CUDA版本号。 安装下载 CUDA Toolkit 11.6 Downloads | NVIDIA Developer https://developer.nvidia.com/cuda-downloads?target_osL…...

LabVIEW故障诊断中的无故障数据怎么办

在使用LabVIEW进行故障诊断时&#xff0c;可能会面临“无故障数据”的情况。这种情况下&#xff0c;缺乏明确的故障参考&#xff0c;使得系统难以通过传统对比法进行故障识别。本文将介绍应对无故障数据的关键策略&#xff0c;包括数据模拟、特征提取和基于机器学习的方法&…...

开发模式选择与最佳实践指南20241230

开发模式选择与最佳实践指南 引言 在现代软件开发中&#xff0c;选择合适的开发模式直接影响项目的开发效率和质量。本文将帮助您&#xff1a; &#x1f3af; 了解三种主流开发模式的优缺点&#x1f4a1; 根据项目特点选择最适合的开发模式&#x1f527; 掌握混合开发模式的…...

超详细!一文搞定PID!嵌入式STM32-PID位置环和速度环

本文目录 一、知识点1. PID是什么&#xff1f;2. 积分限幅--用于限制无限累加的积分项3. 输出值限幅--用于任何pid的输出4. PID工程 二、各类PID1. 位置式PID&#xff08;用于位置环&#xff09;&#xff08;1&#xff09;公式&#xff08;2&#xff09;代码使用代码 2. 增量式…...

Redhat7 PCS建立无共享存储浮动地址集群

更新记录 日期版本号内容9/22/2024Ver 1.0重新排版修正 0写在前面 0.1 简述 时间有限使用VMware6.7环境使用Centos7.8最小化安装方式&#xff08;不用配置本地yum仓库&#xff09;注意查看主机名&#xff08;主机双机操作&#xff0c;部分单机操作&#xff09; 序号HostIPAd…...

爱思唯尔word模板

爱思唯尔word模板 有时候并不一定非得latex https://download.csdn.net/download/qq_38998213/90199214 参考文献书签链接...

交换机Vlan中 tagged和untagged的区别

pvid&#xff0c;tagged与untagged pvid是交换机一个端口上的id&#xff0c;一个端口只能有一个pvid&#xff0c;多个端口可以有相同的pvid。 一&#xff1a;接收数据 Untagged&#xff1a;不管收到的数据帧是否已经有VLAN标记&#xff0c;将数据帧中的vlan标记修改为自己的pvi…...

软件需求分析期末知识点整理

前言&#xff1a;本文为wk学子量身打造&#xff0c;帮助大家少挂科。主要根据ls的会议进行整理。懂得都懂。 重点还是多看看课本 第2章 需求获取的方法 第3章 3.1.2 控制需求(案例*2) 第4章 4.3 范式 第5章 5.2.3 原子功能(案例) 5.2.4 划分功能(案例)5.3.3 工作流图(画图) 第…...

PyAudio使用手册

PyAudio 是一个功能强大的 Python 库&#xff0c;用于在 Python 中进行音频输入和输出操作 1. 安装 在使用 PyAudio 之前&#xff0c;需要先安装它。可以使用 pip 进行安装&#xff1a; pip install pyaudio在某些系统&#xff08;如 Ubuntu&#xff09;上&#xff0c;可能还需…...

总结TCP/IP四层模型

总结TCP/IP四层模型 阅读目录(Content) 一、TCP/IP参考模型概述 1.1、TCP/IP参考模型的层次结构二、TCP/IP四层功能概述 2.1、主机到网络层  2.2、网络互连层  2.3、传输层  2.3、应用层 三、TCP/IP报文格式 3.1、IP报文格式3.2、TCP数据段格式3.3、UDP数据段格式3.4、套…...

《深入挖掘Python加解密:自定义加密算法的设计与实现》

利用python实现加解密 在正式编写各种加解密前&#xff0c;我们先写个小案例&#xff0c;如下。 封面在文末呦&#xff01; 基础加解密-源码 # 加密 def encode():source01 乐茵for c in source01:ascii01 ord(c)ascii01 1print(chr(ascii01), end)# 解密 def decode():…...

【前端,TypeScript】TypeScript速成(六):函数

函数 函数的定义 定义一个最简单的加法函数&#xff1a; function add(a: number, b: number): number {return a b }&#xff08;可以看到 JavaScript/TypeScript 的语法与 Golang 也非常的相似&#xff09; 调用该函数&#xff1a; console.log(add(2, 3)) // out [LOG…...

Python中元组(tuple)内置的数据类型

在Python中&#xff0c;元组&#xff08;tuple&#xff09;是一种内置的数据类型&#xff0c;用于存储不可变的有序元素集合。元组在很多方面与列表&#xff08;list&#xff09;相似&#xff0c;但它们之间存在一些关键的区别。以下是关于Python元组的详细解释&#xff1a; 定…...

AI安全的挑战:如何让人工智能变得更加可信

引言 随着人工智能&#xff08;AI&#xff09;技术在各个领域的广泛应用&#xff0c;尤其是在医疗、金融、自动驾驶和智能制造等行业&#xff0c;AI正在重塑我们的工作和生活方式。从提高生产效率到实现个性化服务&#xff0c;AI带来了前所未有的便利。然而&#xff0c;在享受这…...

redis用途都有哪些

Redis&#xff0c;作为一个开源的高性能键值对数据库&#xff0c;其用途广泛且功能强大。 1. 缓存&#xff08;Caching&#xff09;&#xff1a; • Redis常被用作缓存层&#xff0c;存储那些频繁访问但不易改变的数据&#xff0c;如用户会话、商品详情等。 • 通过将这些数据存…...

【Django篇】--动手实现路由模块化与路由反转

一、路由模块化 在一个Django项目中&#xff0c;由于功能类别不同&#xff0c;因此需要将不同功能进行模块化设计。在Django项目中模块化设计则需要将不同模块封装为对应的app模块&#xff0c;每一个模块中涉及到的路由则也需要进行模块化设计&#xff0c;才能更好的让整个项目…...

自研国产零依赖前端UI框架实战008 用户表单以及随机ID

前言 通过前面的努力,我们的组件已经越来越多了,我们的功能也越来越完善. 不过我们的新增用户的功能还没有做. 接下来, 就让我们实现新增用户的功能. 显示新增用户的表单 首先, 我们先把新增用户的表单显示出来. 我们可以复用之前的组件. <zdp_button1 text"新增…...

【数据结构-单调队列】力扣LCR 184. 设计自助结算系统

请设计一个自助结账系统&#xff0c;该系统需要通过一个队列来模拟顾客通过购物车的结算过程&#xff0c;需要实现的功能有&#xff1a; get_max()&#xff1a;获取结算商品中的最高价格&#xff0c;如果队列为空&#xff0c;则返回 -1 add(value)&#xff1a;将价格为 value …...

项目管理和协作平台Maintainer、Guest、Reporter、Owner 和 Developer 是常见的用户角色

在项目管理和协作平台上&#xff0c;Maintainer、Guest、Reporter、Owner 和 Developer 是常见的用户角色&#xff0c;每个角色有不同的权限和责任。以下是这些角色的详细区别&#xff1a; 1. Guest&#xff08;访客&#xff09; 权限&#xff1a;最低级别的权限。访问&#…...

探索电商数据:爬取不同平台商品信息的Python实践

在数字化时代&#xff0c;电商平台的商品信息成为了宝贵的数据资源。除了亚马逊&#xff0c;全球还有许多电商平台的商品信息值得爬取。本文将介绍几个值得关注的电商平台&#xff0c;并提供Python代码示例&#xff0c;展示如何爬取这些平台的商品信息。 1. 京东 (JD.com) 京…...

Autoware Universe 安装记录

前提&#xff1a; ubuntu20.04&#xff0c;英伟达显卡。 演示&#xff1a;https://www.bilibili.com/video/BV1z4CbYFEwr/?spm_id_from333.337.search-card.all.click ROS2-Galactic安装 wget http://fishros.com/install -O fishros && . fishros 选择galactic(R…...

CAT3D: Create Anything in 3D with Multi-View Diffusion Models 论文解读

24年5月的论文&#xff0c;上一版就是ReconFusion 目录 一、概述 二、相关工作 1、2D先验 2、相机条件下的2D先验 3、多视角先验 4、视频先验 5、前馈方法 三、Method 1、多视角扩散模型 2、新视角生成 3、3D重建 一、概述 该论文提出一种CAT3D方法&#xff0c;实现…...

群落生态学研究进展▌Hmsc包对于群落生态学假说的解读、Hmsc包开展单物种和多物种分析的技术细节及Hmsc包的实际应用

HMSC&#xff08;Hierarchical Species Distribution Models&#xff09;是一种用于预测物种分布的统计模型。它在群落生态学中的应用广泛&#xff0c;可以帮助科学家研究物种在不同环境条件下的分布规律&#xff0c;以及预测物种在未来环境变化下的潜在分布范围。 举例来说&a…...

C 进阶 — 程序环境和预处理

C 进阶 — 程序环境和预处理 主要内容 程序的编译和执行环境 C 程序编译和链接 预定义符号 预处理指令 #define 预处理指令 #include 预处理指令 #undef 预处理操作符 # 和 ## 宏和函数对比 命令行定义 条件编译 一 程序的编译和执行环境 ANSI C 存在两个不同环境…...

基于单片机的温湿度采集系统(论文+源码)

2.1系统的功能 本系统的研制主要包括以下几项功能&#xff1a; (1)温度检测功能&#xff1a;对所处环境的温度进行检测&#xff1b; (2)湿度检测功能&#xff1a;对所处环境的湿度进行检测&#xff1b; (3)加热和制冷功能&#xff1a;可以完成加热和制冷功能。 (4)加湿和除…...

【数据分析处理之缺失值】

文章目录 一、缺失值的影响1. 统计分析的偏差2. 机器学习模型的性能下降3. 数据质量和可信度下降4. 数据利用率降低5. 增加数据预处理的复杂度 二、识别缺失值1. 使用工具识别缺失值2. 可视化缺失数据 三、处理缺失值的策略1. 删除含缺失值的行或列2. 填充缺失值a. 用常数填充b…...

【大模型实战篇】Mac本地部署RAGFlow的踩坑史

1. 题外话 最近一篇文章还是在11月30日写的&#xff0c;好长时间没有打卡了。最近工作上的事情特别多&#xff0c;主要聚焦在大模型的预训练、微调和RAG两个方面。主要用到的框架是Megatron-DeepSpeed&#xff0c;后续会带来一些分享。今天的文章主要聚焦在RAG。 近期调研了一系…...

SQL Server实现将分组的其他字段数据拼接成一条数据

在 SQL Server 中&#xff0c;可以使用 STRING_AGG 函数&#xff08;SQL Server 2017 及更高版本支持&#xff09;将分组的其他字段数据拼接成一条数据。以下是示例代码&#xff1a; 假设有一个表 Orders&#xff0c;结构如下&#xff1a; OrderIDCustomerIDProduct1C001Appl…...

STM32 高级 物联网通讯之蓝牙通讯

目录 蓝牙基础知识 蓝牙概述 蓝牙产生背景 蓝牙发展历程 蓝牙技术类型 经典蓝牙(BR/EDR和AMP) 低功耗蓝牙(BLE) 市场上常见蓝牙架构 SOC蓝牙单芯片方案 SOC蓝牙+MCU方案 蓝牙host+controller分开方案 蓝牙协议栈 蓝牙芯片架构 BLE低功耗蓝牙协议栈框架 物理…...

堆排序基础与实践:如何在Java中实现堆排序

目录 一、堆排序的基本原理 二、堆排序的实现步骤 三、堆排序的时间复杂度和空间复杂度 四、堆排序的工作流程 五、堆排序的优缺点 六、堆排序的应用场景 堆排序&#xff08;Heap Sort&#xff09;是一种基于堆数据结构的排序算法。堆是一种特殊的完全二叉树&#xff0c;…...

你有哪些Deep Learning(RNN、CNN)调参的经验?

在深度学习的实践中&#xff0c;调参是一项既艺术又科学的工作。它不仅需要理论知识的支撑&#xff0c;还需要大量的实践经验。以下是一些在RNN和CNN模型调参中积累的经验&#xff0c;希望对正在这个领域摸索的朋友们有所帮助。 1. 从成熟的开源项目开始 对于初学者来说&…...

小程序租赁系统开发的优势与应用探索

内容概要 在如今这个数码科技飞速发展的时代&#xff0c;小程序租赁系统开发仿佛是一张神奇的魔法卡&#xff0c;能让租赁体验变得顺畅如丝。想象一下&#xff0c;无论你需要租用什么&#xff0c;从单车到房屋&#xff0c;甚至是派对用品&#xff0c;只需动动手指&#xff0c;…...

Spring Boot教程之三十九: 使用 Maven 将 Spring Boot 应用程序 Docker 化

如何使用 Maven 将 Spring Boot 应用程序 Docker 化&#xff1f; Docker是一个开源容器化工具&#xff0c;用于在隔离环境中构建、运行和管理应用程序。它方便开发人员捆绑其软件、库和配置文件。Docker 有助于将一个容器与另一个容器隔离。在本文中&#xff0c;为了将Spring B…...

Day58 图论part08

拓扑排序精讲 拓扑排序看上去很复杂,其实了解其原理之后,代码不难 代码随想录 import java.util.*;public class Main{public static void main (String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int m = sc.nextInt();List<List<Integer&…...

u3d中JSON数据处理

一.认识JSON 1.1 Json概述 JSON&#xff08;JavaScript Object Notation&#xff0c;JavaScript对象表示法&#xff09;JSON和XML是比较类似的技术&#xff0c;都是用来存储文本信息数据的&#xff1b;相对而言&#xff0c;JSON比XML体积更小巧&#xff0c;但是易读性不如XML…...

大语言模型(LLM)一般训练过程

大语言模型(LLM)一般训练过程 数据收集与预处理 收集:从多种来源收集海量文本数据,如互联网的新闻文章、博客、论坛,以及书籍、学术论文、社交媒体等,以涵盖丰富的语言表达和知识领域。例如,训练一个通用型的LLM时,可能会收集数十亿甚至上百亿字的文本数据.清洗:去除…...

第十六届蓝桥杯模拟赛(第一期)(C语言)

判断质因数 如果一个数p是个质数&#xff0c;同时又是整数a的约数&#xff0c;则p称为a的一个质因数。 请问2024有多少个质因数。 了解 约数&#xff0c;又称因数。整数a整除整数b&#xff0c;b为a的因数&#xff08;约数&#xff09;质数&#xff0c;又称素数。只有1和它本身两…...

某网站手势验证码识别深入浅出(全流程)

注意,本文只提供学习的思路,严禁违反法律以及破坏信息系统等行为,本文只提供思路 如有侵犯,请联系作者下架 本文识别已同步上线至OCR识别网站: http://yxlocr.nat300.top/ocr/other/20 本篇文章包含经验和教训总结,我采用了两种方法进行识别,两种方法都各有优劣,其中一…...

QT---------QT框架功能概述

常用Qt界面组件 Qt提供了丰富的界面组件&#xff0c;如QPushButton&#xff08;按钮&#xff09;、QLineEdit&#xff08;单行文本框&#xff09;、QTextEdit&#xff08;多行文本框&#xff09;、QLabel&#xff08;标签&#xff09;、QComboBox&#xff08;下拉框&#xff0…...

C++ 设计模式:模板方法(Template Method)

链接&#xff1a;C 设计模式 链接&#xff1a;C 设计模式 - 策略模式 链接&#xff1a;C 设计模式 - 观察者模式 模板方法&#xff08;Template Method&#xff09;是一种行为设计模式&#xff0c;它定义了一个操作中的算法的骨架&#xff0c;而将一些步骤延迟到子类中。通过这…...

下载mysql免安装版和配置

1、下载地址 点击去官网下载https://downloads.mysql.com/archives/community/ 2、解压安装mysql 解压的文件夹是没有my.ini文件和data目录&#xff0c;需要我们自己去创建 根目录下创建my.ini&#xff0c;根目录创建data [mysql] default-character-setutf8[mysqld] #端口 po…...

Web服务端技术原理及应用

前言 黄色的是考点&#xff0c;蓝色的是重点。 HTML/CSS/JS 本章会有一个7分的程序设计题&#xff0c;用到前端知识 form表单元素&#xff0c;常用表单元素 html:HTML快速上手 基础语法、css常用选择器&#xff08;ID、类&#xff09;、盒子模型 css:网页美化指南 JS …...

数据库的使用09:使用SSMS工具将SQLsever数据导出到Excel

第一步&#xff0c;新建一个空白的.csv文件 第二步&#xff0c;按步骤点击导出 第三步&#xff0c;选择数据源&#xff08;Db数据库&#xff09; 第四步&#xff0c;选择目标源&#xff08;CSV平面文件目标&#xff09; 第五步&#xff0c;指定表或SQL 一直点下一步即可&am…...

Python中__getitem__ 魔法方法

在Python中&#xff0c;__getitem__ 是一个特殊的方法&#xff0c;通常称为“魔法方法”或“双下方法”&#xff08;因为它们的名字前后都有两个下划线&#xff09;。__getitem__ 方法允许一个对象实现像序列&#xff08;如列表、元组、字符串&#xff09;一样的行为&#xff0…...