[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进行负载均衡或者说重平衡。
- RebalanceService服务是一个线程任务, 由MQClientInstance启动, 每隔20s自动进行一次自动负载均衡。
- Broker触发的重平衡:
- Broker收到心跳请求之后如果发现消息中有新的consumer连接或者consumer订阅了新的topic或者移除了topic的订阅, Broker发送Code为NOTIFY_CONSUMER_IDS_CHANGED的请求给该group下面的所有Consumer, 要求进行一次负载均衡。
- 如果某个客户端连接出现连接异常事件EXCEPTION、连接断开事件CLOSE、或者连接闲置事件IDLE, 则Broker同样会发送重平衡请求给消费者组下面的所有消费者。
- 新的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请求重平衡
- Broker收到心跳请求之后如果发现消息中有新的consumer连接或者consumer订阅了新的topic或者移除了topic的订阅, Broker发送Code为NOTIFY_CONSUMER_IDS_CHANGED的请求给该group下面的所有Consumer, 要求进行一次负载均衡。
- 如果某个客户端连接出现连接异常事件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的请求。
- 解码消息中的信息成为HeartbeatData对象。
- 循环遍历处理consumerDataSet集合, 对ConsumerData信息进行注册或者更改, 如果consumer信息修改的话, Broker会发送NOTIFY_CONSUMER_IDS_CHANGED请求给同组的所有consumer客户端, 进行重平衡操作。
- 循环遍历处理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都必须一致。
- 方法首先遍历当前请求传递的订阅信息集合, 然后对于每个订阅的topic从subscriptionTable缓存中尝试获取, 获取不到则表示新增了topic订阅信息, 存入subscriptionTable。
- 遍历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。
-
如果运行通知, 则遍历该ConsumerGroup的连接集合, 然后对每个连接调用notifyConsumerIdsChanged方法通知对应的客户端消费者执行负载均衡。
-
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上的测试小结 前言 本人大四学生,电赛生涯已经走到尽头,一路上踩过不少坑,但运气也不错拿了两年省一,…...

142. 环形链表 II
142. 环形链表 II 中等 2.2K 相关企业 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定…...
Flutter系列文章-Flutter进阶2
这一节我将再详细地为您介绍 Flutter 进阶主题,包括导航和路由、状态管理、异步处理、HTTP请求和Rest API,以及数据持久化。让我们逐个介绍这些主题。 1.导航和路由 在 Flutter 中,导航和路由是构建多页面应用的关键概念。导航是指从一个页…...

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

深入理解Gradle构建系统的工作原理
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &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中,每个对象都有一个原型(prototype)。原型是一个对象,其他对象可以通过原型来共享属性和方法。当我们创建一个对象时,它会自动关联到一个原型对象。 例如: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 动态绑定图片地址,但是加载图片不成功
问题复现: img标签直接动态绑定图片的相对路径的时候,图片不能正常显示。代码如下所示 <view style"margin: 20rpx" v-for"(item, index) in showSampleImage" :key"index"><u-image :src"item.src"…...

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

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

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

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

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

数据库的介绍和分类
目录 一、数据库的介绍和分类 二、命令行客户端 三、数据操作 四、查询的基本操作 五、条件查询 六、分组和聚合 资料获取方法 一、数据库的介绍和分类 数据库:长期存储在计算机内、有组织的数据集合 数据库的分类: 关系型数据库 以表格的形式…...
职责链模式——请求的链式处理
1、简介 1.1、概述 很多情况下,在一个软件系统中可以处理某个请求的对象不止一个。例如SCM系统中的采购单审批,主任、副董事长、董事长和董事会都可以处理采购单,他们可以构成一条处理采购单的链式结构。采购单沿着这条链进行传递ÿ…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
全面解析各类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…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...