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

[RocketMQ] Consumer 负载均衡服务 RebalanceService入口源码 (十五)

  • RocketMQ一个消费者组中可以有多个消费者, 在集群模式下他们共同消费topic下的所有消息, RocketMQ规定一个消息队列仅能被一个消费者消费, 但是一个消费者可以同时消费多个消息队列。
  • 需要负载均衡服务RebalanceService来进行消息队列分配的重平衡。
  • 使用负载均衡服务RebalanceService来专门处理多个消息队列和消费者的对应关系, 如何分配消息队列给这些消费者。

RocketMQ消费者负载均衡服务RebalanceService的入口代码

文章目录

      • 1.负载均衡或者重平衡
        • 1.1 RebalanceService自动重平衡
        • 1.2 Consumer启动重平衡
        • 1.3 Broker请求重平衡
        • 1.4 Broker处理心跳请求
          • 1.4.1 registerConsumer注册消费者
          • 1.4.2 客户端处理重平衡请求

1.负载均衡或者重平衡

有三种情况会触发Consumer进行负载均衡或者说重平衡。

  1. RebalanceService服务是一个线程任务, 由MQClientInstance启动, 每隔20s自动进行一次自动负载均衡。
  2. Broker触发的重平衡:
    1. Broker收到心跳请求之后如果发现消息中有新的consumer连接或者consumer订阅了新的topic或者移除了topic的订阅, Broker发送Code为NOTIFY_CONSUMER_IDS_CHANGED的请求给该group下面的所有Consumer, 要求进行一次负载均衡。
    2. 如果某个客户端连接出现连接异常事件EXCEPTION、连接断开事件CLOSE、或者连接闲置事件IDLE, 则Broker同样会发送重平衡请求给消费者组下面的所有消费者。
  3. 新的Consumer服务启动的时候, 主动调用rebalanceImmediately唤醒负载均衡服务rebalanceService。

1.1 RebalanceService自动重平衡

RebalanceService#run方法, 最多每隔20s执行一次重平衡。

/*** RebalanceServicede 方法*/
@Override
public void run() {log.info(this.getServiceName() + " service started");/** 运行时逻辑* 如果服务没有停止,则在死循环中执行负载均衡*/while (!this.isStopped()) {//等待运行,默认最多等待20s,可以被唤醒this.waitForRunning(waitInterval);//执行重平衡操作this.mqClientFactory.doRebalance();}log.info(this.getServiceName() + " service end");
}

1.2 Consumer启动重平衡

新的Consumer服务启动的时候, 主动调用rebalanceImmediately唤醒负载均衡服务rebalanceService。

/*** MQClientInstance的方法* 立即重平衡*/
public void rebalanceImmediately() {//唤醒重平衡服务,立即重平衡this.rebalanceService.wakeup();
}

1.3 Broker请求重平衡

  1. Broker收到心跳请求之后如果发现消息中有新的consumer连接或者consumer订阅了新的topic或者移除了topic的订阅, Broker发送Code为NOTIFY_CONSUMER_IDS_CHANGED的请求给该group下面的所有Consumer, 要求进行一次负载均衡。
  2. 如果某个客户端连接出现连接异常事件EXCEPTION、连接断开事件CLOSE、或者连接闲置事件IDLE, 则Broker同样会发送重平衡请求给消费者组下面的所有消费者。处理入口方法为ClientHousekeepingService# doChannelCloseEvent方法。

新的Consumer和Producer启动的时候, 发送心跳给Broker, MQClientInstance的内部的服务也会定时30s发送心跳信息给Broker。

心跳请求的Code为HEART_BEAT, 该请求最终被Broker的ClientManageProcessor处理器处理。

/*** ClientManageProcessor的方法, Broker处理心跳方法*/
@Override
public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request)throws RemotingCommandException {switch (request.getCode()) {//客户端心跳请求case RequestCode.HEART_BEAT://客户端心跳请求return this.heartBeat(ctx, request);case RequestCode.UNREGISTER_CLIENT:return this.unregisterClient(ctx, request);case RequestCode.CHECK_CLIENT_CONFIG:return this.checkClientConfig(ctx, request);default:break;}return null;
}

1.4 Broker处理心跳请求

Broker的ClientManageProcessor#heartBeat该方法用于Broker处理来自客户端, consumer和producer的请求。

  1. 解码消息中的信息成为HeartbeatData对象。
  2. 循环遍历处理consumerDataSet集合, 对ConsumerData信息进行注册或者更改, 如果consumer信息修改的话, Broker会发送NOTIFY_CONSUMER_IDS_CHANGED请求给同组的所有consumer客户端, 进行重平衡操作。
  3. 循环遍历处理consumerDataSet集合, 对ProducerData信息注册或者更改。
/*** ClientManageProcessor的方法* <p>* 处理客户端心跳请求*/
public RemotingCommand heartBeat(ChannelHandlerContext ctx, RemotingCommand request) {//构建响应命令对象RemotingCommand response = RemotingCommand.createResponseCommand(null);//解码HeartbeatData heartbeatData = HeartbeatData.decode(request.getBody(), HeartbeatData.class);//构建客户端连接信息对象ClientChannelInfo clientChannelInfo = new ClientChannelInfo(ctx.channel(),heartbeatData.getClientID(),request.getLanguage(),request.getVersion());/** 1 循环遍历处理consumerDataSet,即处理consumer的心跳信息*/for (ConsumerData data : heartbeatData.getConsumerDataSet()) {//查找broker缓存的当前消费者组的订阅组配置SubscriptionGroupConfig subscriptionGroupConfig =this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(data.getGroupName());boolean isNotifyConsumerIdsChangedEnable = true;//如果已存在订阅组if (null != subscriptionGroupConfig) {//当consumer发生改变的时候是否支持通知同组的所有consumer,默认true,即支持isNotifyConsumerIdsChangedEnable = subscriptionGroupConfig.isNotifyConsumerIdsChangedEnable();int topicSysFlag = 0;if (data.isUnitMode()) {topicSysFlag = TopicSysFlag.buildSysFlag(false, true);}//尝试创建重试topicString newTopic = MixAll.getRetryTopic(data.getGroupName());this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(newTopic,subscriptionGroupConfig.getRetryQueueNums(),PermName.PERM_WRITE | PermName.PERM_READ, topicSysFlag);}/** 注册consumer,返回consumer信息是否已发生改变* 如果发生了改变,Broker会发送NOTIFY_CONSUMER_IDS_CHANGED请求给同组的所有consumer客户端,要求进行重平衡操作*/boolean changed = this.brokerController.getConsumerManager().registerConsumer(data.getGroupName(),clientChannelInfo,data.getConsumeType(),data.getMessageModel(),data.getConsumeFromWhere(),data.getSubscriptionDataSet(),isNotifyConsumerIdsChangedEnable);if (changed) {//如果consumer信息发生了改变,打印日志log.info("registerConsumer info changed {} {}",data.toString(),RemotingHelper.parseChannelRemoteAddr(ctx.channel()));}}/** 2 循环遍历处理producerDataSet,即处理producer的心跳信息*/for (ProducerData data : heartbeatData.getProducerDataSet()) {/** 注册producer*/this.brokerController.getProducerManager().registerProducer(data.getGroupName(),clientChannelInfo);}//返回响应response.setCode(ResponseCode.SUCCESS);response.setRemark(null);return response;
}
1.4.1 registerConsumer注册消费者

循环遍历处理consumerDataSet集合, 对ConsumerData信息进行注册或者更改, 如果consumer信息修改的话, Broker会发送NOTIFY_CONSUMER_IDS_CHANGED请求给同组的所有consumer客户端, 进行重平衡操作。

/*** ConsumerManager的方法* <p>* 注册consumer,返回consumer信息是否已发生改变* 如果发生了改变,Broker会发送NOTIFY_CONSUMER_IDS_CHANGED请求给同组的所有consumer客户端,要求进行重平衡操作** @param group                            消费者组* @param clientChannelInfo                客户端连接信息* @param consumeType                      消费类型,PULL or PUSH* @param messageModel                     消息模式,集群 or 广播* @param consumeFromWhere                 启动消费位置* @param subList                          订阅信息数据* @param isNotifyConsumerIdsChangedEnable 一个consumer改变时是否通知该consumergroup中的所有consumer进行重平衡* @return 是否重平衡*/
public boolean registerConsumer(final String group, final ClientChannelInfo clientChannelInfo,ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere,final Set<SubscriptionData> subList, boolean isNotifyConsumerIdsChangedEnable) {//获取当前group对应的ConsumerGroupInfoConsumerGroupInfo consumerGroupInfo = this.consumerTable.get(group);//如果为null,那么新建一个ConsumerGroupInfo并存入consumerTableif (null == consumerGroupInfo) {ConsumerGroupInfo tmp = new ConsumerGroupInfo(group, consumeType, messageModel, consumeFromWhere);ConsumerGroupInfo prev = this.consumerTable.putIfAbsent(group, tmp);consumerGroupInfo = prev != null ? prev : tmp;}/** 1 更新连接*/boolean r1 =consumerGroupInfo.updateChannel(clientChannelInfo, consumeType, messageModel,consumeFromWhere);/** 2 更新订阅信息*/boolean r2 = consumerGroupInfo.updateSubscription(subList);/** 2 如果连接或者订阅信息有更新,并且允许通知,那么通知该consumergroup中的所有consumer进行重平衡*/if (r1 || r2) {if (isNotifyConsumerIdsChangedEnable) {//CHANGE事件this.consumerIdsChangeListener.handle(ConsumerGroupEvent.CHANGE, group, consumerGroupInfo.getAllChannel());}}// 3.注册订阅信息到ConsumerFilterManagerthis.consumerIdsChangeListener.handle(ConsumerGroupEvent.REGISTER, group, subList);return r1 || r2;
}

1.updateChannel更新连接

更新ConsumerGroup组对应的ConsumerGroupInfo的一些属性, 还会判断当前连接是否是新连接, 如果Broker此前没有该连接的信息, 那么表示有新的consumer连接到此broker, 需要通知当前ConsumerGroup的所有consumer进行重平衡。

/*** ConsumerGroupInfo的方法* <p>* 更新连接** @param infoNew          新连接信息* @param consumeType      消费类型,PULL or PUSH* @param messageModel     消息模式,集群 or 广播* @param consumeFromWhere 启动消费位置* @return 是否通知*/
public boolean updateChannel(final ClientChannelInfo infoNew, ConsumeType consumeType,MessageModel messageModel, ConsumeFromWhere consumeFromWhere) {boolean updated = false;//更新信息this.consumeType = consumeType;this.messageModel = messageModel;this.consumeFromWhere = consumeFromWhere;//根据当前连接获取channelInfoTable缓存中的连接信息ClientChannelInfo infoOld = this.channelInfoTable.get(infoNew.getChannel());//如果缓存中的连接信息为null,说明当前连接是一个新连接if (null == infoOld) {//存入缓存ClientChannelInfo prev = this.channelInfoTable.put(infoNew.getChannel(), infoNew);//长期按没有该连接信息,那么表示有新的consumer连接到此broekr,那么需要通知if (null == prev) {log.info("new consumer connected, group: {} {} {} channel: {}", this.groupName, consumeType,messageModel, infoNew.toString());updated = true;}infoOld = infoNew;} else {//异常情况if (!infoOld.getClientId().equals(infoNew.getClientId())) {log.error("[BUG] consumer channel exist in broker, but clientId not equal. GROUP: {} OLD: {} NEW: {} ",this.groupName,infoOld.toString(),infoNew.toString());this.channelInfoTable.put(infoNew.getChannel(), infoNew);}}//更新更新时间this.lastUpdateTimestamp = System.currentTimeMillis();infoOld.setLastUpdateTimestamp(this.lastUpdateTimestamp);return updated;
}

2.updateSubscription更新订阅信息

更新此ConsumerGroup组对应的订阅信息集合, 如果存在新增订阅的topic, 或者移除某个topic的订阅, 那么需要通知当前ConsumerGroup的所有consumer进行重平衡。

RocketMQ需要保证组内的所有消费者订阅的topic都必须一致。

  1. 方法首先遍历当前请求传递的订阅信息集合, 然后对于每个订阅的topic从subscriptionTable缓存中尝试获取, 获取不到则表示新增了topic订阅信息, 存入subscriptionTable。
  2. 遍历subscriptionTable集合, 判断每一个topic是否存在于当前请求传递的订阅信息集合中, 如果不存在, 表示consumer移除了topic的订阅, 那么当前topic的订阅信息会从subscriptionTable集合中被移除。
/*** ConsumerGroupInfo的方法* 更新订阅信息** @param subList 订阅信息集合*/
public boolean updateSubscription(final Set<SubscriptionData> subList) {boolean updated = false;//遍历订阅信息集合for (SubscriptionData sub : subList) {//根据订阅的topic在ConsumerGroup的subscriptionTable缓存中此前的订阅信息SubscriptionData old = this.subscriptionTable.get(sub.getTopic());//如果此前没有关于该topic的订阅信息,那么表示此topic为新增订阅if (old == null) {//存入subscriptionTableSubscriptionData prev = this.subscriptionTable.putIfAbsent(sub.getTopic(), sub);//此前没有关于该topic的订阅信息,那么表示此topic为新增订阅,那么需要通知if (null == prev) {updated = true;log.info("subscription changed, add new topic, group: {} {}",this.groupName,sub.toString());}} else if (sub.getSubVersion() > old.getSubVersion()) {//更新数据if (this.consumeType == ConsumeType.CONSUME_PASSIVELY) {log.info("subscription changed, group: {} OLD: {} NEW: {}",this.groupName,old.toString(),sub.toString());}this.subscriptionTable.put(sub.getTopic(), sub);}}/** 遍历ConsumerGroup的subscriptionTable缓存*/Iterator<Entry<String, SubscriptionData>> it = this.subscriptionTable.entrySet().iterator();while (it.hasNext()) {Entry<String, SubscriptionData> next = it.next();//获取此前订阅的topicString oldTopic = next.getKey();boolean exist = false;//判断当前的subList是否存在该topic的订阅信息for (SubscriptionData sub : subList) {//如果存在,则退出循环if (sub.getTopic().equals(oldTopic)) {exist = true;break;}}//当前的subList不存在该topic的订阅信息,说明consumer移除了对于该topic的订阅if (!exist) {log.warn("subscription changed, group: {} remove topic {} {}",this.groupName,oldTopic,next.getValue().toString());//移除数据it.remove();//那么需要通知updated = true;}}this.lastUpdateTimestamp = System.currentTimeMillis();return updated;
}

3.consumerIdsChangeListener.handle监听器通知

通知监听器处理对应的事件, 事件为ConsumerGroupEvent.CHANGE。

  1. 如果运行通知, 则遍历该ConsumerGroup的连接集合, 然后对每个连接调用notifyConsumerIdsChanged方法通知对应的客户端消费者执行负载均衡。

  2. notifyConsumerIdsChanged: broker发送客户端一个重平衡请求, Code为NOTIFY_CONSUMER_IDS_CHANGED。

/*** DefaultConsumerIdsChangeListener的方法* * 处理监听到的事件* @param event 事件* @param group 消费者组* @param args 参数*/
@Override
public void handle(ConsumerGroupEvent event, String group, Object... args) {if (event == null) {return;}switch (event) {//改变事件,需要通知该消费者组的每一个消费者case CHANGE:if (args == null || args.length < 1) {return;}//获取参数List<Channel> channels = (List<Channel>) args[0];//如果允许通知if (channels != null && brokerController.getBrokerConfig().isNotifyConsumerIdsChangedEnable()) {//遍历连接集合for (Channel chl : channels) {//通知该消费者客户端执行负载均衡this.brokerController.getBroker2Client().notifyConsumerIdsChanged(chl, group);}}break;case UNREGISTER:this.brokerController.getConsumerFilterManager().unRegister(group);break;case REGISTER:if (args == null || args.length < 1) {return;}Collection<SubscriptionData> subscriptionDataList = (Collection<SubscriptionData>) args[0];this.brokerController.getConsumerFilterManager().register(group, subscriptionDataList);break;default:throw new RuntimeException("Unknown event " + event);}
}/*** Broker2Client的方法* <p>* 通知消费者变更** @param channel       连接* @param consumerGroup 消费者组*/
public void notifyConsumerIdsChanged(final Channel channel,final String consumerGroup) {if (null == consumerGroup) {log.error("notifyConsumerIdsChanged consumerGroup is null");return;}//构建请求头NotifyConsumerIdsChangedRequestHeader requestHeader = new NotifyConsumerIdsChangedRequestHeader();requestHeader.setConsumerGroup(consumerGroup);//构建远程命令对象,请求code为NOTIFY_CONSUMER_IDS_CHANGEDRemotingCommand request =RemotingCommand.createRequestCommand(RequestCode.NOTIFY_CONSUMER_IDS_CHANGED, requestHeader);try {//发送单向请求,无需等待客户端回应this.brokerController.getRemotingServer().invokeOneway(channel, request, 10);} catch (Exception e) {log.error("notifyConsumerIdsChanged exception. group={}, error={}", consumerGroup, e.toString());}
}
1.4.2 客户端处理重平衡请求

broker的请求在客户端是通过ClientRemotingProcessor#processRequest处理的。

NOTIFY_CONSUMER_IDS_CHANGED请求通过客户端的ClientRemotingProcessor#notifyConsumerIdsChanged方法处理。

/*** ClientRemotingProcessor的方法* <p>* 处理来自远程服务端的请求*/
@Override
public RemotingCommand processRequest(ChannelHandlerContext ctx,RemotingCommand request) throws RemotingCommandException {switch (request.getCode()) {case RequestCode.CHECK_TRANSACTION_STATE:return this.checkTransactionState(ctx, request);case RequestCode.NOTIFY_CONSUMER_IDS_CHANGED://处理NOTIFY_CONSUMER_IDS_CHANGED请求return this.notifyConsumerIdsChanged(ctx, request);case RequestCode.RESET_CONSUMER_CLIENT_OFFSET:return this.resetOffset(ctx, request);case RequestCode.GET_CONSUMER_STATUS_FROM_CLIENT:return this.getConsumeStatus(ctx, request);case RequestCode.GET_CONSUMER_RUNNING_INFO:return this.getConsumerRunningInfo(ctx, request);case RequestCode.CONSUME_MESSAGE_DIRECTLY:return this.consumeMessageDirectly(ctx, request);case RequestCode.PUSH_REPLY_MESSAGE_TO_CLIENT:return this.receiveReplyMessage(ctx, request);default:break;}return null;
}

notifyConsumerIdsChanged客户端重平衡

客户端接口到broker的重平衡请求之后, 调用这个方法。内部仅仅是调用我们之前讲的rebalanceImmediately 方法唤醒负载均衡服务rebalanceService, 进行重平衡。

public RemotingCommand notifyConsumerIdsChanged(ChannelHandlerContext ctx,RemotingCommand request) throws RemotingCommandException {try {//解析请求头final NotifyConsumerIdsChangedRequestHeader requestHeader =(NotifyConsumerIdsChangedRequestHeader) request.decodeCommandCustomHeader(NotifyConsumerIdsChangedRequestHeader.class);//打印日志log.info("receive broker's notification[{}], the consumer group: {} changed, rebalance immediately",RemotingHelper.parseChannelRemoteAddr(ctx.channel()),requestHeader.getConsumerGroup());//熟悉的方法,立即进行重平衡this.mqClientFactory.rebalanceImmediately();} catch (Exception e) {log.error("notifyConsumerIdsChanged exception", RemotingHelper.exceptionSimpleDesc(e));}return null;
}

相关文章:

[RocketMQ] Consumer 负载均衡服务 RebalanceService入口源码 (十五)

RocketMQ一个消费者组中可以有多个消费者, 在集群模式下他们共同消费topic下的所有消息, RocketMQ规定一个消息队列仅能被一个消费者消费, 但是一个消费者可以同时消费多个消息队列。需要负载均衡服务RebalanceService来进行消息队列分配的重平衡。使用负载均衡服务RebalanceSe…...

【K210】K210学习笔记六——MaixHub在线模型训练识别数字

【K210】K210学习笔记六——MaixHub在线模型训练识别数字 前言K210准备工作数据的获取MaixHub如何在线训练模型训练模型在K210上的测试小结 前言 本人大四学生&#xff0c;电赛生涯已经走到尽头&#xff0c;一路上踩过不少坑&#xff0c;但运气也不错拿了两年省一&#xff0c;…...

142. 环形链表 II

142. 环形链表 II 中等 2.2K 相关企业 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定…...

Flutter系列文章-Flutter进阶2

这一节我将再详细地为您介绍 Flutter 进阶主题&#xff0c;包括导航和路由、状态管理、异步处理、HTTP请求和Rest API&#xff0c;以及数据持久化。让我们逐个介绍这些主题。 1.导航和路由 在 Flutter 中&#xff0c;导航和路由是构建多页面应用的关键概念。导航是指从一个页…...

css实现鼠标滑动左下角弹框带动画效果

代码 <div classNamekuang></div> css代码 .kuang {height: 500px;width: 400px;// background-color: #fff;position: absolute;z-index: 10;bottom: 0;transform: translateX(-390px)}.kuang:hover {animation: myanimation 3s linear 1;animation-fill-mode:f…...

【Spring Cloud Alibaba】限流--Sentinel

文章目录 概述一、Sentinel 是啥&#xff1f;二、Sentinel 的生态环境三、Sentinel 核心概念3.1、资源3.2、规则 四、Sentinel 限流4.1、单机限流4.1.1、引入依赖4.1.2、定义限流规则4.1.3、定义限流资源4.1.4、运行结果 4.2、控制台限流4.2.1、客户端接入控制台4.2.2、引入依赖…...

ARM将常数加载到寄存器方法之LDR伪指令

一、是什么&#xff1f; LDR Rd,const伪指令可在单个指令中构造任何32位数字常数,使用伪指令可以生成超过MOV和MVN指令 允许范围的常数. 实现原理: (1)如果可以用MOV或MVN指令构造该常数,则汇编程序会生成适当的指令 (2)如果不能用MOV或MVN指令构造该常数,则汇编程序会执行下列…...

深入理解Gradle构建系统的工作原理

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…...

STM32F030C8T6输出3路PWM

1,常规套餐 上电初始化 SystemInit(); TIM1_Init(); 2,TIMI1初始化 TIM_TimeBaseInitTypeDef TIM1_TimeBaseStructure; TIM_OCInitTypeDef TIM1_OCInitStructure; NVIC_InitTypeDef NVIC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; //记得打开时钟 RCC_AHBPeriphClo…...

如何理解原型及原型链?js的继承方式

原型与原型链 原型 在js中&#xff0c;每个对象都有一个原型&#xff08;prototype&#xff09;。原型是一个对象&#xff0c;其他对象可以通过原型来共享属性和方法。当我们创建一个对象时&#xff0c;它会自动关联到一个原型对象。 例如&#xff1a;function Person(name, a…...

C# 按表格中的某列排序/查询

using System; using System.Data; using System.Linq;class Program {static void Main(){// 创建一个示例的 DataTable 对象DataTable table new DataTable();table.Columns.Add("ID", typeof(int));table.Columns.Add("Name", typeof(string));table.R…...

【Vue】】img使用 :src 动态绑定图片地址,但是加载图片不成功

问题复现&#xff1a; img标签直接动态绑定图片的相对路径的时候&#xff0c;图片不能正常显示。代码如下所示 <view style"margin: 20rpx" v-for"(item, index) in showSampleImage" :key"index"><u-image :src"item.src"…...

list模拟

之前模拟了string,vector&#xff0c;再到现在的list&#xff0c;list的迭代器封装最让我影响深刻。本次模拟的list是双向带头节点的循环链表&#xff0c;该结构虽然看起来比较复杂&#xff0c;但是却非常有利于我们做删除节点的操作&#xff0c;结构图如下。 由于其节点结构特…...

python字典:怎么取出key对应的值

目录 python中的字典是什么 怎么判断key是否在字典中 怎么取出key对应的值 总结 python中的字典是什么 在Python中&#xff0c;字典&#xff08;Dictionary&#xff09;是一种无序且可变的数据类型&#xff0c;用于存储键-值&#xff08;Key-Value&#xff09;对。字典通过…...

okvis

论文 Keyframe-Based Visual-Inertial SLAM Using Nonlinear Optimization 摘要 由于两种感知模式的互补性&#xff0c;视觉和惯性线索的融合在机器人中变得很流行。虽然迄今为止大多数融合策略都依赖于过滤方案&#xff0c;但视觉机器人界最近转向了非线性优化方法&#x…...

fabric js双击弹出菜单, 双击弹出输入框 修改文字 群组对象

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>测试1</title><style type"text/css">body {background-color: #ccc;float: left;}#main {background-color: #fff;border: 1px…...

路由器工作原理

路由器原理 路由概述 路由&#xff1a;跨越从源主机到目标主机的一个互联网络来转发数据包的过程。&#xff08;为数据包选择路径的过程&#xff09; 作用&#xff1a;路由器是连接不同网段的。 转发依据&#xff1a; 路由表&#xff1a;路径选择全看路由表&#xff0c;根…...

在centos 7系统docker上构建mysql 5.7

一、VM上已经安装centos 7.9&#xff0c;且已完成docker的构建 二、安装mysql5.7 安装镜像&#xff1a;[rootlocalhost lll]# docker pull mysql:5.7 查看镜像[rootlocalhost lll]# docker images 根据镜像id构建mysql容器&#xff0c;且分配端口号[rootlocalhost lll]# dock…...

数据库的介绍和分类

目录 一、数据库的介绍和分类 二、命令行客户端 三、数据操作 四、查询的基本操作 五、条件查询 六、分组和聚合 资料获取方法 一、数据库的介绍和分类 数据库&#xff1a;长期存储在计算机内、有组织的数据集合 数据库的分类&#xff1a; 关系型数据库 以表格的形式…...

职责链模式——请求的链式处理

1、简介 1.1、概述 很多情况下&#xff0c;在一个软件系统中可以处理某个请求的对象不止一个。例如SCM系统中的采购单审批&#xff0c;主任、副董事长、董事长和董事会都可以处理采购单&#xff0c;他们可以构成一条处理采购单的链式结构。采购单沿着这条链进行传递&#xff…...

docker中涉及的挂载点总结

文章目录 1.场景描述2. 容器信息在主机上位置3. 通过docker run 命令4、通过Dockerfile创建挂载点5、容器共享卷&#xff08;挂载点&#xff09;6、最佳实践&#xff1a;数据容器 1.场景描述 在介绍VOLUME指令之前&#xff0c;我们来看下如下场景需求&#xff1a; 1&#xff…...

elasticsearch 官方优化建议

.一般建议 a.不要返回过大的结果集。这个建议对一般数据库都是适用的&#xff0c;如果要获取大量结果&#xff0c;可以使用search_after api&#xff0c;或者scroll &#xff08;新版本中已经不推荐&#xff09;。 b.避免大的文档。 2. 如何提高索引速度 a.使用批量请求。为了…...

Kubernetes(K8s)从入门到精通系列之五:K8s的基本概念和术语之应用类

Kubernetes K8s从入门到精通系列之五:K8s的基本概念和术语之应用类 一、Service与Pod二、Label与标签选择器三、Pod与Deployment四、Service的ClusterIP地址五、Service的外网访问问题六、有状态的应用集群七、批处理应用八、应用配置问题九、应用的运维一、Service与Pod Ser…...

DevOps(四)

CD(二) 1. CDStep 1 - 上传代码Step 2 - 下载代码Step 3 - 检查代码Step 4 - 编译代码Step 5 - 上传仓库Step 6 - 下载软件Step 7 - 制作镜像Step 8 - 上传镜像Step 9 - 部署服务2. 整体预览2.1 预览1. 修改代码2. 查看sonarqube检查结果3. 查看nexus仓库4. 查看harbor仓库5.…...

Element-plus侧边栏踩坑

问题描述 el-menu直接嵌套el-menu-item菜单&#xff0c;折叠时不会出现文字显示和小箭头无法隐藏的问题&#xff0c;但是实际开发需求中难免需要把el-menu-item封装为组件 解决 vue3项目中嵌套两层template <template><template v-for"item in list" :k…...

支持多种通信方式和协议方便接入第三方服务器或云平台

2路RS485串口是一种常用的通信接口&#xff0c;可以支持Modbus Slave协议&#xff0c;并可接入SCADA、HMI、DSC、PLC等上位机。它还支持Modbus RTU Master协议&#xff0c;可用于扩展多达48个Modbus Slave设备&#xff0c;如Modbus RTU远程数据采集模块、电表、水表、柴油发电机…...

使用 OpenCV 进行图像模糊度检测(拉普拉斯方差方法)

写在前面 工作中遇到&#xff0c;简单整理人脸识别中&#xff0c;对于模糊程度较高的图像数据&#xff0c;识别率低&#xff0c;错误率高。虽然使用 AdaFace 模型&#xff0c;对低质量人脸表现尤为突出。但是还是需要对 模糊程度高的图像进行丢弃处理当前通过阈值分类&#xff…...

神经网络简单介绍

人工神经网络(artififial neural network) 简称神经网络&#xff0c;它是一种模仿生物神经网络结构和功能的非线性数学模型。 神经网络通过输入层接受原始特征信息&#xff0c;再通过隐藏层进行特征信息的加工和提取&#xff0c;最后通过输出层输出结果。 根据需要神经网络可以…...

16位S912ZVML32F3MKH、S912ZVML31F1WKF、S912ZVML31F1MKH混合信号MCU,适用于汽车和工业电机控制应用。

S12 MagniV微控制器是易于使用且高度集成的混合信号MCU&#xff0c;非常适合用于汽车和工业应用。S12 MagniV MCU提供单芯片解决方案&#xff0c;是基于成熟的S12技术的完整系统级封装 (SiP) 解决方案&#xff0c;在整个产品组合内软件和工具都兼容。 S12 MagniV系统级封装 (S…...

力扣 509. 斐波那契数

题目来源&#xff1a;https://leetcode.cn/problems/fibonacci-number/description/ C题解1&#xff1a;根据题意&#xff0c;直接用递归函数。 class Solution { public:int fib(int n) {if(n 0) return 0;else if(n 1) return 1;else return(fib(n-1) fib(n-2));} }; C题…...