从零开始读RocketMq源码(五)Consumer消费Message流程解析

news2024/9/23 1:26:25

目录

前言

准备

拉取服务和重平衡服务启动

初识PullRequest

重平衡服务

对重平衡资源进行排序

MessageQueue消息队列集合来源

Consumer消费者集合数据来源

确实分配资源策略

执行分配策略

初始化ProcessQueue

初始化PullRequest

内存队列填充PullRequest

消息拉取服务

调用消息核心处理方法

调用远程Broker服务拉取消息

提交消费消息请求

初始化ConsumeRequest

消息监听器消费消息

总结


前言

上一篇我们对Consumer的启动流程就进行了解析,有了消费者那么消费的Message从何而来呢,这就是本篇学习的重点。本篇会讲到MessageQueue的分配、Message的拉取以及消费等,让我们一起来学习吧!

准备

源码地址:https://github.com/apache/rocketmq

目前最新版本为:5.2.0

那么我们在idea上切换分支为 release-5.2.0

拉取服务和重平衡服务启动

//源码位置
//包名:org.apache.rocketmq.client.impl.factory
//文件名:MQClientInstance
//行数:315
// Start pull service
this.pullMessageService.start();
// Start rebalance service
this.rebalanceService.start();
  • 拉取服务重平衡服务的启动其实就是在上一篇《从零开始读RocketMq源码(四)Consumer启动流程解析》中就已经执行了
  • 这里就是触发ConsumerBroker获取Message的源头,进入启动源码会发现其实也就是分别开启了两个独立的线程来运行的。消息的获取也是需要这两个线程相互合作才能完成。

为什么我们消息选择的是Push推模式但是这里服务启动的却是PullMessageService呢?

因为实际上,推模式下的实现还是基于消费者主动拉取的方式,推模式是通过一个长轮询的机制来实现的 , 消费者向 Broker 注册一个消息监听器,消费者内部维护一个线程,不断向 Broker 拉取消息,如果没有消息,则保持连接并等待消息到来。(下面会详细讲到)

初识PullRequest

我们按照启动顺序先对pullMessageService源码进行了解,进入线程的run()方法中

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:PullMessageService
//行数:131
MessageRequest messageRequest = this.messageRequestQueue.take();
if (messageRequest.getMessageRequestMode() == MessageRequestMode.POP) {
    this.popMessage((PopRequest) messageRequest);
} else {
    this.pullMessage((PullRequest) messageRequest);
}
  • 这里调用了内存队列take()方法,这个方法看着是否眼熟,因为我们在《Broker存储Message流程解析》中也使用过
  • take()方法的作用就是取出元素并从队列中删除,如果队列为空则会阻塞, 直到队列中有可用的消息请求为止。由此可见pullMessageService相当于内存队列中的消费者角色
  • 但是这次的内存队列使用的是LinkedBlockingQueue类型,Broker存储Message中却使用的是PriorityBlockingQueue类型
  • 内存队列获取出来的元素最终被转化为 PullRequest ,该对象在 RocketMQ 中用于封装消费者向 Broker 拉取消息时所需的所有信息。顾名思义这个对象就是向Broker发起拉取Message的请求对象。

扩展:LinkedBlockingQueue与PriorityBlockingQueue内存队列有什么区别吗?

LinkedBlockingQueue是一个 基于链表实现的阻塞队列按 FIFO(先进先出)顺序存储元素。插入元素时,会添加到队列的尾部,移除元素时,会从队列的头部取出 。 如果队列满了,插入操作会阻塞;如果队列空了,移除操作会阻塞。

PriorityBlockingQueue 基于优先级堆实现的阻塞队列元素按优先级顺序排列。默认情况下,使用元素的自然顺序(即元素需要实现 Comparable 接口) , 插入元素时,会根据优先级排序,移除元素时,总是移除优先级最高的元素 ,由于是无界队列,所以插入操作不会阻塞;如果队列空了,移除操作会阻塞。

因为项目刚初始化,所以messageRequestQueue队列中一定是空的,那么调用task()方法后,pullMessageService线程一直会是阻塞状态不能向下执行,那么什么地方会新增内存队列元素呢,那就只有先把另一个主角rebalanceService服务请出场了。

重平衡服务

rebalanceService的核心作用就是 负责定期执行消费者的负载均衡操作当消费者实例数量或者消息队列数量发送变更,确保消息队列均匀分布在多个消费者实例上,然后将 PullRequest 塞到messageRequestQueue内存队列中。

Producer生产者也使用到了该服务,作用则是将Message负载均衡到不同的MessageQueue中

进入run()方法

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:RebalanceService
//行数:51
boolean balanced = this.mqClientFactory.doRebalance();
realWaitInterval = balanced ? waitInterval : minInterval;
  • 结合源码上下文会发现这是包裹在一个while()循环中,相当于一个定时任务
  • 当调用doRebalance()方法重平衡成功后,设置waitInterval= 20s后再次执行
  • 如果调用doRebalance()方法重平衡执行失败,设置minInterval= 1s后再次重试
  • 只要当前线程启动成功就会按照上面的逻辑周而复始

对重平衡资源进行排序

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:RebalanceImpl
//行数:342
Collections.sort(mqAll);
Collections.sort(cidAll);
  1. mqAll为当前Topic中所有消息队列集合,然后进行排序
  2. cidAll为所有订阅了当前Topic的消费者实例id集合,然后进行排序

看到这里一头雾水,为什么要进行排序呢?

  • 因为重平衡操作都是单独在每一个客户端进行的,而不是统一在Broker服务上进行分配的,那么为了保证消费者实例与消息队列能够合理的负载均衡,并且让每个消费者拿到互不相同的MessageQueue,那么就需要进行排序
  • 又因为订阅相同的topic,那么他们获取的总的消息队列和消费者实例都是完全相同的,然后每个客户端进行相同的排序,那么排序结果都是一样的,后续会使用到这些排序后的结果来对每个客户端进行分配MessageQueue,因为每个客户端id都不一样,从而经过后面的分配算法保证买个消费者分配的MessageQueue都不一样。后续分配算法会具体讲到。

MessageQueue消息队列集合来源

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:RebalanceImpl
//行数:325
Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
//...
List<MessageQueue> mqAll = new ArrayList<>();
mqAll.addAll(mqSet);

mqAll集合最终是从Map集合中通过Topic获取:ConcurrentMap<String/* topic */, Set<MessageQueue>> topicSubscribeInfoTable;

秉承着所以数据都有迹可循的原则,不禁要问topicSubscribeInfoTable中的数据从哪里来的呢?

其实就在上一篇讲到的Consumer启动流程中的填入的数据,就在下面方法中

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:DefaultMQPushConsumerImpl
//行数:1001
this.updateTopicSubscribeInfoWhenSubscriptionChanged();

查看上方方法中的逻辑,会发现数据源又来自topic的订阅信息map:ConcurrentMap<String /* topic */, SubscriptionData> subscriptionInner通过subscriptionInner的循环处理来分别填充topic对应的MessageQueue集合的

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:DefaultMQPushConsumerImpl
//行数:1235
Map<String, SubscriptionData> subTable = this.getSubscriptionInner();

那么subscriptionInner中的数据又是什么时候填充的呢?

其实也是在最开始的Consumer启动的main方法设置订阅topic时中填充的。

//源码位置
//包名:org.apache.rocketmq.example.simple
//文件名:PushConsumer
//行数:39
consumer.subscribe(TOPIC, "*");

深入subscribe()方法,你就会发现填充数据的源码

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:DefaultMQPushConsumerImpl
//行数:1250
SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, subExpression);
this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData);

这样我们就理清楚了整个MessageQueue集合的来源和去向,有头也有尾,也不会再一知半解了

Consumer消费者集合数据来源

//源码位置
//包名:org.apache.rocketmq.client.impl
//文件名:MQClientAPIImpl
//行数:1349
RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr),
                                                          request, timeoutMillis);

由源码可知,消费者客户端集合cidAll是直接调用Broker服务来获取的

确实分配资源策略

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:RebalanceImpl
//行数:345
AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;
List<MessageQueue> allocateResult = null;
try {
    allocateResult = strategy.allocate(
        this.consumerGroup,
        this.mQClientFactory.getClientId(),
        mqAll,
        cidAll);
} catch (Throwable e) {
    log.error("allocate message queue exception. strategy name: {}, ex: {}", strategy.getName(), e);
    return false;
}
  • 调用 strategy.allocate()方法对当前消费者实例分配消息队列,并返回一个集合allocateResult

进入分配方法中,会发现官方实现了多种分配策略

那么我们使用的是哪一种呢?其实在我们启动消费者服务实例化消费者对象时就已经设置了默认策略了

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:DefaultMQPushConsumer
//行数:308
public DefaultMQPushConsumer(final String consumerGroup) {
    this(consumerGroup, null, new AllocateMessageQueueAveragely());
}

new AllocateMessageQueueAveragely() 就是为我们默认指定的分配策略,即平均哈希队列算法

执行分配策略

//源码位置
//包名:org.apache.rocketmq.client.consumer.rebalance
//文件名:AllocateMessageQueueAveragely
//行数:32
List<MessageQueue> result = new ArrayList<>();
if (!check(consumerGroup, currentCID, mqAll, cidAll)) {
    return result;
}
int index = cidAll.indexOf(currentCID);
int mod = mqAll.size() % cidAll.size();
int averageSize =
mqAll.size() <= cidAll.size() ? 1 : (mod > 0 && index < mod ? mqAll.size() / cidAll.size()
                                     + 1 : mqAll.size() / cidAll.size());
int startIndex = (mod > 0 && index < mod) ? index * averageSize : index * averageSize + mod;
int range = Math.min(averageSize, mqAll.size() - startIndex);
for (int i = 0; i < range; i++) {
    result.add(mqAll.get((startIndex + i) % mqAll.size()));
}

这就是给当前消费者分配消息队列的算法逻辑

  • index表示当前消费者实例所有排序后的消费者实例集合cidAll中的下标位置,每个currentCID都是唯一的。
  • mod:表示mqAll集合中的MessageQueue能否被cidAll集合中的消费者实例均匀分配。
  • averageSize:表示当前消费者平均能被分配到的MessageQueue数量。
  • startIndex:表了当前这个 Consumer 从 MessageQueue 数组的哪个位置开始取。
  • range: 代表当前这个 Consumer 获取到了多少个 MessageQueue

总结最终分配逻辑可以理解为两步所有消费者客户端都会分配到相同数量的MessageQueue,对剩余无法平均分配的MessageQueue按照cidAll集合的顺序进行分配。但其实源码中是一次性算出分配结果的。

初始化ProcessQueue

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:RebalanceImpl
//行数:528
ProcessQueue pq = createProcessQueue(topic);
pq.setLocked(true);
long nextOffset = this.computePullFromWhere(mq);
if (nextOffset >= 0) {
    ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
    //...
}
  • 第一步初始化创建ProcessQueue处理队列
  • 最后将队列设置到processQueueTable的Map中,ConcurrentMap<MessageQueue, ProcessQueue> processQueueTable主要用于消费者负载均衡和消息消费管理,确保消息队列能够被正确地分配和处理
  • 该Map在上一篇消费者启动中讲到过,数据用于处理定时任务清除过期的消息
  • putIfAbsent()方法为如果存在相同的Key,就将原来的Value返回,不存在则返回null,同时put数据

初始化PullRequest

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:RebalanceImpl
//行数:532
if (pre != null) {
    log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq);
} else {
    log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
    PullRequest pullRequest = new PullRequest();
    pullRequest.setConsumerGroup(consumerGroup);
    pullRequest.setNextOffset(nextOffset);
    pullRequest.setMessageQueue(mq);
    pullRequest.setProcessQueue(pq);
    pullRequestList.add(pullRequest);
    changed = true;
}
  • 到这里就正式封装PullRequest拉取请求对象了,紧接着上面初始化ProcessQueue代码可知,只有当该MessageQueue是一个全新并且之前不存在的消息队列时才会进行拉取请求
  • 初始化PullRequest从而和本篇前面讲到的pullMessageService有了关联

内存队列填充PullRequest

循环处理PullRequest对象

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:RebalancePushImpl
//行数:266
for (PullRequest pullRequest : pullRequestList) {
    if (delay <= 0) {
        this.defaultMQPushConsumerImpl.executePullRequestImmediately(pullRequest);
    } else {
        this.defaultMQPushConsumerImpl.executePullRequestLater(pullRequest, delay);
    }
}

循环逻辑处理中,对每次PullRequest对象的处理延迟5s,可以看到这里已经进入了PullMessageService服务中

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:PullMessageService
//行数:45
this.scheduledExecutorService.schedule(new Runnable() {
    @Override
    public void run() {
        PullMessageService.this.executePullRequestImmediately(pullRequest);
    }
}, timeDelay, TimeUnit.MILLISECONDS);

最后就是将PullRequest对象put进内存队列LinkedBlockingQueue<MessageRequest> messageRequestQueue中,从而激活PullMessageService服务,结束阻塞状态开始执行拉取逻辑。

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:PullMessageService
//行数:58
this.messageRequestQueue.put(pullRequest);

消息拉取服务

上面RebalanceService服务完成内存队列PullRequest入栈后,那么紧接着PullMessageService服务开始处理PullRequest。

调用消息核心处理方法

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:DefaultMQPushConsumerImpl
//行数:480
this.pullAPIWrapper.pullKernelImpl(
    pullRequest.getMessageQueue(),
    subExpression,
    subscriptionData.getExpressionType(),
    subscriptionData.getSubVersion(),
    pullRequest.getNextOffset(),
    this.defaultMQPushConsumer.getPullBatchSize(),
    this.defaultMQPushConsumer.getPullBatchSizeInBytes(),
    sysFlag,
    commitOffsetValue,
    BROKER_SUSPEND_MAX_TIME_MILLIS,
    CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND,
    CommunicationMode.ASYNC,
    pullCallback
);

我们这里关注两个点:

  • CommunicationMode.ASYNC:表示向Broker服务拉取消息是一个异步的操作
  • pullCallback:异步回调后处理的逻辑就封装在该对象中

调用远程Broker服务拉取消息

//源码位置
//包名:org.apache.rocketmq.client.impl
//文件名:MQClientAPIImpl
//行数:1024
 this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
     //...
    public void operationSucceed(RemotingCommand response) {
        try {
            PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response, addr);
            pullCallback.onSuccess(pullResult);
        } catch (Exception e) {
            pullCallback.onException(e);
        }
    }
     //...
 }

由源码我们可知,pullCallback回调对象提供了

  • pullCallback.onSuccess(pullResult):请求成功后处理逻辑方法
  • pullCallback.onException(e):请求异常处理逻辑方法

提交消费消息请求

在调用this.pullAPIWrapper.pullKernelImpl核心方法之前,就已经重写了pullCallback的回调方法

并在onSuccess()实现中进行提交消费请求操作

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:DefaultMQPushConsumerImpl
//行数:370
DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest(
    pullResult.getMsgFoundList(),
    processQueue,
    pullRequest.getMessageQueue(),
    dispatchToConsume);

初始化ConsumeRequest

ConsumeRequest消费请求是PullRequest拉取请求之后的又一请求对象

顾名思义这也是消息最终被消费的请求了

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:ConsumeMessageConcurrentlyService
//行数:211
ConsumeRequest consumeRequest = new ConsumeRequest(msgThis, processQueue, messageQueue);
try {
    this.consumeExecutor.submit(consumeRequest);
} catch (RejectedExecutionException e) {
    for (; total < msgs.size(); total++) {
        msgThis.add(msgs.get(total));
    }
    this.submitConsumeRequestLater(consumeRequest);
}
  • ConsumeRequest加入到线程池 ThreadPoolExecutor consumeExecutor;相当于就是为当前消费请求单独创建一个线程来异步处理
  • 如果线程池加入异常,则会延迟5s后再次重试一次

消息监听器消费消息

直接进入ConsumeRequest线程的run()方法中

//源码位置
//包名:org.apache.rocketmq.client.impl.consumer
//文件名:ConsumeMessageConcurrentlyService
//行数:211
MessageListenerConcurrently listener = ConsumeMessageConcurrentlyService.this.messageListener;
ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(messageQueue);
ConsumeConcurrentlyStatus status = null;
//...
status = listener.consumeMessage(Collections.unmodifiableList(msgs), context);
  • 这里就是消息消费的最后一步,将Message放入监听器MessageListenerConcurrentlyconsumeMessage()方法中。

Message最终在消费者启动中main()方法的注册监听器的地方打印出来,最后返回消费成功状态ConsumeConcurrentlyStatus.CONSUME_SUCCESS

//源码位置
//包名:org.apache.rocketmq.example.simple
//文件名:PushConsumer
//行数:37
consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
        System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

Consumer端消费Message简易流程图如下

总结

根据上一篇消费者的启动到本篇消息的拉取与消费,完成了一个Message在消费者端的闭环。本篇我们也学到了一个新的内存队列LinkedBlockingQueue,也讲到了与PriorityBlockingQueue的区别,至此消费者端的源码基本学习完了,希望从源码中大家都有所收获!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1934246.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

hackmyvm--Decode

环境 靶机&#xff1a;ip未知 攻击机kali&#xff1a;192.168.233.128 192.168.56.101 主机探测 锁定靶机ip为108 端口扫描 nmap -p- -T4 -A 192.168.56.108 常规套路80和22 web打点 dirsearch -u http://192.168.56.108/ 访问robots,txt文件 访问/decode 发现其自动添加了/,怀…

Chromium源码阅读(9):了解Log模块

Chromium许多日志被TraceEvent代替了&#xff0c;因此TraceEvent出现的频率要比Log高很多。 但是也有不少场景使用Log。 在blink&#xff0c;Log的实现由base提供&#xff0c;而blink/render/core/base/logging.h进行了二次封装。 日志系统的设计细节 错误对话框处理 错误消…

Qt第十二章 样式表

样式表 文章目录 样式表1.样式表盒子模型 2.选择器选择器类型伪状态选择器Pseudo-State 3.控件示例4继承自QWidget的类&#xff0c;设置qss样式表没有效果&#xff0c;需要重写paintEvent 1.样式表 盒子模型 2.选择器 样式表语法&#xff0c;选择器{属性1:值;属性2:值;}如果只…

韦东山嵌入式linux系列-驱动进化之路:设备树的引入及简明教程

1 设备树的引入与作用 以 LED 驱动为例&#xff0c;如果你要更换LED所用的GPIO引脚&#xff0c;需要修改驱动程序源码、重新编译驱动、重新加载驱动。 在内核中&#xff0c;使用同一个芯片的板子&#xff0c;它们所用的外设资源不一样&#xff0c;比如A板用 GPIO A&#xff0c…

鸿蒙仓颉语言【类型class】

类与结构&#xff08;class & struct&#xff09; 面向对象的编程语言&#xff0c;必不可少的基础元素&#xff0c;类或者叫类型&#xff0c;在仓颉中类可以抽象(abstract)、继承&#xff08;<:&#xff09;&#xff0c;公开&#xff08;Public&#xff09;或者私有&am…

在jsPsych中使用Vue

jspsych 介绍 jsPsych是一个非常好用的心理学实验插件&#xff0c;可以用来构建心理学实验。具体的就不多介绍了&#xff0c;大家可以去看官网&#xff1a;https://www.jspsych.org/latest/ 但是大家在使用时就会发现&#xff0c;这个插件只能使用js绘制界面&#xff0c;或者…

【算法专题】归并排序

目录 1. 排序数组 2. 交易逆序对的总数 3. 计算右侧小于当前元素的个数 4. 翻转对 总结 1. 排序数组 912. 排序数组 - 力扣&#xff08;LeetCode&#xff09; 今天我们使用归并排序来对数组进行排序&#xff0c;实际上&#xff0c;归并排序和快速排序是有一定相似之处的&a…

什么是蓝牙芯片?蓝牙芯片和蓝牙模块的区别

蓝牙芯片&#xff0c;是一种集成了蓝牙无线通信技术的微型电子元件。它如同一个微小的通信枢纽&#xff0c;能够在各种电子设备之间建立无线连接&#xff0c;实现数据的传输与共享。蓝牙芯片的设计精妙而复杂&#xff0c;内部集成了射频前端、数字基带、协议栈等多个功能模块&a…

Linux中nohup(no hang up)不挂起,用于在系统后台不挂断地运行命令,即使退出终端也不会影响程序的运行。

nohup的英文全称是 no hang up&#xff0c;即“不挂起”。这个命令在Linux或Unix系统中非常有用&#xff0c;主要用于在系统后台不挂断地运行命令&#xff0c;即使退出终端也不会影响程序的运行。默认情况下&#xff08;非重定向时&#xff09;&#xff0c;nohup会将输出写入一…

linux中常见的协议、服务端口整理汇总

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 ​ &#x1f3c5;阿里云ACE认证高级工程师 ​ &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社…

排序系列 之 插入排序

&#xff01;&#xff01;&#xff01;排序仅针对于数组哦本次排序是按照升序来的哦 介绍 插入排序英文名为InsertSort 基本思路 1、认为数组当中的第一个数值已经排好序了2、定义一个游标从第二个数值开始不断地向后进行遍历3、游标指向的数据插入已经排好序的数组中 代码…

力扣刷题之2959.关闭分部的可行集合数目

题干描述 一个公司在全国有 n 个分部&#xff0c;它们之间有的有道路连接。一开始&#xff0c;所有分部通过这些道路两两之间互相可以到达。 公司意识到在分部之间旅行花费了太多时间&#xff0c;所以它们决定关闭一些分部&#xff08;也可能不关闭任何分部&#xff09;&…

AV1 编码标准屏幕内容编码技术概述

AV1 屏幕内容编码 为了提高屏幕捕获内容的压缩性能&#xff0c;AV1采用了几种编码工具&#xff0c;例如用于处理屏幕画面中重复模式的内帧内块复制&#xff08;IntraBC&#xff09;&#xff0c;以及用于处理颜色数量有限的屏幕块的调色板模式。 帧内块拷贝 AV1 编码中的 Intra …

【Vue】快速入门:构建你的第一个Vue 3应用

文章目录 一、Vue简介二、环境搭建1. 安装Node.js和npm2. 安装Vue CLI 三、创建Vue项目四、项目结构介绍五、组件基础创建一个组件使用组件 六、模板语法插值指令v-bindv-ifv-for 七、事件处理八、状态管理安装Vuex创建Store使用Store 九、路由基础安装Vue Router配置路由使用路…

FFmpeg播放视频

VS2017+FFmpeg6.2.r113110+SDL2.30.5 1.下载 ShiftMediaProject/FFmpeg 2.下载SDL2 3.新建VC++控制台应用 3.配置include和lib 4.把FFmpeg和SDL的dll 复制到工程Debug目录下,并设置调试命令

24年Hvv准备,6大方向,33篇技战法

进去不少小伙伴后台留言说需要技战法&#xff0c;因此小编对市面上的技战法进行了收集和总结&#xff0c;并对收集来的技战法进行了分类&#xff0c;总共分了6大类&#xff0c;共计33篇&#xff1a; 有需要的小伙伴关注我&#xff0c;点击在看&#xff0c;并私信回复“技战法”…

IO、进程、线程03

第一题&#xff1a;预习 opendir 和 readdir函数 opendir 和 readdir 是两个在C语言&#xff08;特别是使用POSIX标准的系统&#xff0c;如Linux和UNIX&#xff09;中用于目录遍历的函数。这两个函数属于标准的C库中的目录操作部分&#xff0c;通常与<dirent.h>头文件一…

MySQL学习记录 —— 이십일 MySQL服务器文件系统(1)

文章目录 1、配置和默认值2、系统变量和选项1、介绍2、常用选项3、如何使用系统变量 3、常用服务器配置4、查看状态变量5、MySQL数据目录 mysql的服务端就是mysqld&#xff0c;d就是daemon&#xff0c;守护进程的意思。 配置文件中[mysqld]部分时服务器支持的启动选项。服务器…

某航空制造业集团IT信息化总体规划方案

获取完整方案见下图 更多有关华为研发管理/IPD、MBSE、PLM、ERP、MES、数据治理、数字样机等方面免费解决方案、资料获取&#xff0c;请见下图

开放式耳机哪个品牌好?开放式耳机实用推荐

开放式耳机是一种耳机类型&#xff0c;其外壳是开放的&#xff0c;发声单元的背面和外界相通。这种耳机的外壳上通常有许多小孔或者直接能够看到内部的发声单元。 而且开放式耳机几乎摒弃了传统耳机几乎所有缺点&#xff01;&#xff01; ❌有线耳机&#xff1a;运动的时候特别…