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

敲详细的springframework-amqp-rabbit源码解析

看源码时将RabbitMQ的springframework-amqp-rabbit和spring-rabbit的一套区分开,springboot是基于RabbitMQ的Java客户端建立了简便易用的框架。

springboot的框架下相对更多地使用消费者Consumer和监听器Listener的概念,这两个概念不注意区分容易混淆。默认情况下,springboot中消费者为单线程串行消费的模型,体现了队列的特性。


在springboot的框架下使用rabbitmq的一般步骤

  1. 启动rabbitmq服务器,springboot项目引入依赖
  2. 配置信息,有两种方式
    1. 配置文件配置
    2. 配置类配置SimpleMessageListenerContainer
  3. 实现消息处理类ChannelAwareMessageListener处理业务逻辑,或用@RabbitListener注解

这两种方式其实异曲同工,@RabbitListener的方式在实际使用时创建MessagingMessageListenerAdapter,这个对象是ChannelAwareMessageListener接口的实现类,实现了onMessage()方法,这个方法利用了适配器模式,能够调用注解标注的方法,而实现ChannelAwareMessageListener的方式比较直白就是实现onMessage()方法


源码解析

关于SimpleMessageListenerContainer

SimpleMessageListenerContainer是在spring项目中使用RabbitMQ关键的类,用来接收并处理消息的。阅读源码可以从这个类入手。

  1. 首先关注构造器,需要传入ConnectionFactory用于获取连接,这跟原生rabbitmq是一致的,都从Connection连接开始。

  2. 关键属性

    concurrentConsumers:指定要创建的并发消费者的数量。默认值为1。建议增加并发使用者的数量,以便扩展从队列传入的消息的消耗。但是,请注意,一旦注册了多个消费者,将无法保证顺序。一般来说,对于低容量队列,坚持使用1个消费者。同时不能超过maxConcurrentConsumers(如果设置了)。

    maxConcurrentConsumers:设置消费者数量的上限。默认为concurrentConsumers。消费者可以根据需求增加,但不会小于concurrentConsumers。

    acknowledgeMode:消息确认模式

    // 自动确认消息
    container.setAcknowledgeMode(AcknowledgeMode.NONE);
    // 根据情况确认消息
    container.setAcknowledgeMode(AcknowledgeMode.AUTO);
    // 手动确认消息
    container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
    
  3. 绑定组件:

  4. 设置消费者的Consumer_tag和Arguments:container.setConsumerTagStrategy可以设置消费者的 Consumer_tag, container.setConsumerArguments可以设置消费者的 Arguments

    container.setConsumerTagStrategy(queue -> "order_queue_"+(++count));
    //设置消费者的Arguments
    Map<String, Object> args = new HashMap<>();
    args.put("module","订单模块");
    args.put("fun","发送消息");
    container.setConsumerArguments(args);
    

    在这里插入图片描述


spring的亮点在于用注解简化了很多代码操作,其中最常用的当属@RabbitListener

@RabbitListener(queues = {BiMqConstant.BI_QUEUE_NAME}, ackMode = "MANUAL")
public void receiveMessage(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag){
}

从@RabbitListener入手

1、从spring开启RabbitMQ的注解模式,@EnableRabbit导入RabbitBootstrapConfiguration配置类。

2、这个配置类定义了RabbitListenerAnnotationBeanPostProcessor和RabbitListenerEndpointRegistry两个bean。前者用来扫描加了@RabbitListener 的类,通过反射找到带注解的类,再找到对应的方法,存为handlerMethods。后者在注册终端后用于构建ListenerContainer(继承了RabbitListener注解内的信息,包括监听的队列和注解所在的类和方法)。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(RabbitBootstrapConfiguration.class)
public @interface EnableRabbit {
}

3、RabbitListenerEndpointRegistry通过创建MethodRabbitListenerEndpoint对象和SimpleRabbitListenerContainerFactory工厂bean,生成SimpleMessageListenerContainer对象。

(RabbitListenerAnnotationBeanPostProcessor中拥有注解信息,如队列名,以及被标注注解的方法,所以endpoint的注册还是在processor类中)

(processor中有注册员成员变量registrar的registerEndpoint()注册endpoint,registrar有注册处registry成员变量注册利用registerListenerContainer()的createListenerContainer()注册container)

public class RabbitListenerEndpointRegistry  implements SmartLifecycle{private final Map<String, MessageListenerContainer> listenerContainers =new ConcurrentHashMap<String, MessageListenerContainer>();//注册终端public void registerListenerContainer(RabbitListenerEndpoint endpoint, RabbitListenerContainerFactory<?> factory,boolean startImmediately) {String id = endpoint.getId();synchronized (this.listenerContainers) {//创建 listenerContainerMessageListenerContainer container = createListenerContainer(endpoint, factory);this.listenerContainers.put(id, container);……if (startImmediately) {startIfNecessary(container);}}}protected MessageListenerContainer createListenerContainer(RabbitListenerEndpoint endpoint,RabbitListenerContainerFactory<?> factory) {//调用RabbitListener容器工厂的createListenerContainer方法获取RabbitListener容器MessageListenerContainer listenerContainer = factory.createListenerContainer(endpoint);return listenerContainer;}

4、SimpleMessageListenerContainer对象保存了要监听的队列名(可以是configuration时set的也可以是@RabbitListener中标注的),创建了用于处理消息的MessagingMessageListenerAdapter实例(实际上是一个listener)

public class MethodRabbitListenerEndpoint extends AbstractRabbitListenerEndpoint {......@Overrideprotected MessagingMessageListenerAdapter createMessageListener(MessageListenerContainer container) {Assert.state(this.messageHandlerMethodFactory != null,"Could not create message listener - MessageHandlerMethodFactory not set");MessagingMessageListenerAdapter messageListener = createMessageListenerInstance();messageListener.setHandlerMethod(configureListenerAdapter(messageListener));String replyToAddress = getDefaultReplyToAddress();if (replyToAddress != null) {messageListener.setResponseAddress(replyToAddress);}MessageConverter messageConverter = container.getMessageConverter();if (messageConverter != null) {messageListener.setMessageConverter(messageConverter);}if (getBeanResolver() != null) {messageListener.setBeanResolver(getBeanResolver());}return messageListener;}protected MessagingMessageListenerAdapter createMessageListenerInstance() {return new MessagingMessageListenerAdapter(this.bean, this.method);}......
}

5、SimpleMessageListenerContainer的内部类AsyncMessageProcessingConsumer(区分,该类封装了BlockingQueueConsumer,由于该类实现了Runnable接口,可以视为一个线程任务放入线程池中执行)有一个run()方法,调用了receiveAndExecute(),这个方法会获取BlockingQueueConsumer,阻塞读取其消息(一次获取多条),完成消息读取。

6、接着调用listener进行消息处理,这里设置了代理,最终会执行actualInvokeListener所谓实际被执行的listener,溯源最终调用了listener.onMessage(message, channelToUse)。

SimpleMessageListenerContainer {//接受并执行private boolean receiveAndExecute(final BlockingQueueConsumer consumer) throws Throwable {//do接受并执行return doReceiveAndExecute(consumer);}//do接受并执行private boolean doReceiveAndExecute(BlockingQueueConsumer consumer) throws Throwable {Channel channel = consumer.getChannel();for (int i = 0; i < this.txSize; i++) {//txSize为一次事务接受的消息个数//读取消息,这里阻塞的,但是有一个超时时间。Message message = consumer.nextMessage(this.receiveTimeout);if (message == null) {//阻塞超时break;}try {executeListener(channel, message);//消息接收已完成,现在开始处理消息。}catch (Exception e) {}}return consumer.commitIfNecessary(isChannelLocallyTransacted());}//处理消息开始。该方法在其父类中protected void executeListener(Channel channel, Message messageIn) throws Exception {try {Message message = messageIn;if (……) {//批处理信息,这个不研究}else {invokeListener(channel, message);}}catch (Exception ex) {}}//在其父类中protected void invokeListener(Channel channel, Message message) throws Exception {//这里this.proxy.invokeListener最终会调用actualInvokeListener方法。this.proxy.invokeListener(channel, message);}//在其父类中protected void actualInvokeListener(Channel channel, Message message) throws Exception {Object listener = getMessageListener();if (listener instanceof ChannelAwareMessageListener) {doInvokeListener((ChannelAwareMessageListener) listener, channel, message);}else if (listener instanceof MessageListener) {//……doInvokeListener((MessageListener) listener, message)}else{//……}}    protected void doInvokeListener(ChannelAwareMessageListener listener, Channel channel, Message message)throws Exception {Channel channelToUse = channel;try {listener.onMessage(message, channelToUse);}catch (Exception e) {throw wrapToListenerExecutionFailedExceptionIfNeeded(e, message);}}
}

7、关于第6点,根据这个listener实例的不同,有两种处理方式:

如果是前面所说的实现ChannelAwareMessageListener,就直接调用实现类的onMessage()。

如果是@RabbitListener注解,不同在于MessagingMessageListenerAdapter(ChannelAwareMessageListener的实现类,也是listen),基于适配器模式持有@RabbitListener注解的对象和方法(adapter实例中有HandlerMethod属性加入到adapter类中,HandlerMethod调用invoke()就能执行注解标注的方法)。

public class HandlerAdapter {private final InvocableHandlerMethod invokerHandlerMethod;private final DelegatingInvocableHandler delegatingHandler;public Object invoke(Message<?> message, Object... providedArgs) throws Exception                {if (this.invokerHandlerMethod != null) {//InvocableHandlerMethod不为null,就调用invokerHandlerMethod.invoke方法。return this.invokerHandlerMethod.invoke(message, providedArgs);}else if (this.delegatingHandler.hasDefaultHandler()) {//……}else {//……}}
}
public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageListener {private HandlerAdapter handlerMethod;
}

现在就能把整个过程串起来了


关于关于endpoint和register

Endpoint为终端,像电脑、手机都是终端,他们都可以接受外部信息并响应,如手机来短信了就有提示。这里也用了终端的概念,被@RabbitListener注解修饰方法也有终端的特点可以接受外部信息并响应,即接到消息就执行对应方法。

registry姑且成为注册处用Map保存endpoint的id和对应的listenerContainer,注册处registerListenerContainer()利用endpoint和factory实例创建container,实际上是用了containerfactory的createListenerContainer(RabbitListenerEndpoint endpoint)方法

public class RabbitListenerEndpointRegistry implements DisposableBean, SmartLifecycle, ApplicationContextAware,ApplicationListener<ContextRefreshedEvent> {// 检查是否被注册过,注册过就不能注册第二次// 调用createListenerContainer创建消息监听// 关于分组消费的,我们不关心// 是否立即启动,是的话,同步调用startIfNecessary方法public void registerListenerContainer(RabbitListenerEndpoint endpoint, RabbitListenerContainerFactory<?> factory,boolean startImmediately) {Assert.notNull(endpoint, "Endpoint must not be null");Assert.notNull(factory, "Factory must not be null");String id = endpoint.getId();Assert.hasText(id, "Endpoint id must not be empty");synchronized (this.listenerContainers) {Assert.state(!this.listenerContainers.containsKey(id),"Another endpoint is already registered with id '" + id + "'");MessageListenerContainer container = createListenerContainer(endpoint, factory);this.listenerContainers.put(id, container);if (StringUtils.hasText(endpoint.getGroup()) && this.applicationContext != null) {List<MessageListenerContainer> containerGroup;if (this.applicationContext.containsBean(endpoint.getGroup())) {containerGroup = this.applicationContext.getBean(endpoint.getGroup(), List.class);}else {containerGroup = new ArrayList<MessageListenerContainer>();this.applicationContext.getBeanFactory().registerSingleton(endpoint.getGroup(), containerGroup);}containerGroup.add(container);}if (startImmediately) {startIfNecessary(container);}}// 其实就是调用了RabbitListenerContainerFactory的createListenerContainer生成了一个MessageListenerContainer对象protected MessageListenerContainer createListenerContainer(RabbitListenerEndpoint endpoint,RabbitListenerContainerFactory<?> factory) {MessageListenerContainer listenerContainer = factory.createListenerContainer(endpoint);if (listenerContainer instanceof InitializingBean) {try {((InitializingBean) listenerContainer).afterPropertiesSet();}catch (Exception ex) {throw new BeanInitializationException("Failed to initialize message listener container", ex);}}int containerPhase = listenerContainer.getPhase();if (containerPhase < Integer.MAX_VALUE) {  // a custom phase valueif (this.phase < Integer.MAX_VALUE && this.phase != containerPhase) {throw new IllegalStateException("Encountered phase mismatch between container factory definitions: " +this.phase + " vs " + containerPhase);}this.phase = listenerContainer.getPhase();}return listenerContainer;}
}

把endpoint内的信息全部注入到container里。

@Override
public C createListenerContainer(RabbitListenerEndpoint endpoint) {C instance = createContainerInstance();if (this.connectionFactory != null) {instance.setConnectionFactory(this.connectionFactory);}if (this.errorHandler != null) {instance.setErrorHandler(this.errorHandler);}if (this.messageConverter != null) {instance.setMessageConverter(this.messageConverter);}if (this.acknowledgeMode != null) {instance.setAcknowledgeMode(this.acknowledgeMode);}if (this.channelTransacted != null) {instance.setChannelTransacted(this.channelTransacted);}if (this.autoStartup != null) {instance.setAutoStartup(this.autoStartup);}if (this.phase != null) {instance.setPhase(this.phase);}instance.setListenerId(endpoint.getId());// 最重要的一行!!!endpoint.setupListenerContainer(instance);initializeContainer(instance);return instance;
}

关于container和containerFactory

containerFactory也能配置并发消费者等参数。

@Configuration
@EnableAsync
public class ThreadPoolConfig { @Bean("customContainerFactory") public SimpleRabbitListenerContainerFactory containerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();factory.setConcurrentConsumers(10); //设置线程数factory.setMaxConcurrentConsumers(10); //最大线程数configurer.configure(factory, connectionFactory);return factory; }
}

配置containerFactory能够创建container,但一般不在配置类中手动创建。一般是在注解中标记,然后让spring来生产container。

@RabbitListener(queues="demo.queue",containerFactory = "customContainerFactory")

直接配置container效果是相同的,同样可以设置队列,并发消费者等。


细说上面第5步container内的操作。

  1. container的启动入口是star()方法,然后进入doStart(),在该方法中会初始化consumer(BlockingQueueConsumer),每一个并发需要对应一个consumer,consumer的数量是根据前面所说的concurrentConsumers确定

    consumer = new BlockingQueueConsumer(getConnectionFactory(), getMessagePropertiesConverter(),this.cancellationLock, getAcknowledgeMode(), isChannelTransacted(), actualPrefetchCount,isDefaultRequeueRejected(), getConsumerArguments(), isNoLocal(), isExclusive(), queues);
    // 带有连接信息,数据转换器,确认模式,预取值,consumerArgs,监听的队列(可多个)等信息传入
    

区分一下consumer和listener,consumer是接收消息的消费者,listener是实际处理业务的执行者,consumer接收的每个消息都需要调用listener内的onMessage()方法来处理实际业务。

int newConsumers = initializeConsumers();
  1. 然后将consumer封装成AsyncMessageProcessingConsumer线程任务类型,然后就可以放入线程池中执行。
AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer);
processors.add(processor);
getTaskExecutor().execute(processor);
  1. 这里的线程池是SimpleAsyncTaskExecutor(也可以自定义传入),默认是不限制并发量的。每个container都有一个线程池,线程不足以支持consumer并发时就会超时报错。

    private Executor taskExecutor = new SimpleAsyncTaskExecutor();
    
  2. 进入AsyncMessageProcessingConsumer这个Runnable类的run()方法,如果consumer有监听的队列,就初始化initialize并开启mainloop()

    if (this.consumer.getQueueCount() < 1) {...
    }
    try {initialize();while (isActive(this.consumer) || this.consumer.hasDelivery() || !this.consumer.cancelled()) {mainLoop();}
    }
    
  3. initialize()会创建exchangequeuebindings等实例,设置Qos,实现consumer与broker之间的对接,完成消息的订阅,并且会根据tag不同在每个BlockingQueueConsumer中再划分出internalConsumer,再放入BlockingQueueConsumer的queue中逐一处理。

    说明Qos流控指令包括prefetch-sizeprefetch-count参数。

    //该参数是设置在channel上的
    int prefetchCount = 1;
    channel.basicQos(prefetchCount);
    

    broker的delivery指令在客户端会先打包成一个Envelope,所以consumertag是对应consumer一个,而deliveryTag是对应broker中的一条消息一个。

    Envelope envelope = new Envelope(m.getDeliveryTag(),m.getRedelivered(),m.getExchange(),m.getRoutingKey());
    

    当然在broker执行delivery指令将消息推送到客户端Consumer之前还有channel,一个BlockingQueueConsumer对应一个channel,对应一个线程的调用。内部的consumer共用channel,channel会根据tag在dispatcher将消息推送至对应的consumer。

    一个channel对应了多个consumer

    在这里插入图片描述

    多个AsyncMessageProcessingConsumer对应不同的线程来处理

    在这里插入图片描述

    一个container可能监听多个队列。

    在这里插入图片描述

  4. mainLoop()相较于如何利用consumer接收消息,更侧重于最终的listener来进行业务处理。前面已经知道客户端会将消息存到Consume的queue中,简单来说,mainloop就是只要客户端正常启动就会无限循环来处理业务的,它主要就是完成从queue中提取消息数据然后经过一系列操作最终传递给业务逻辑处理MessageListener中。

    mainLoop()方法中就会从queue中提取消息,根据**batchSize**确定每次提取消息数量,最后回调MessageListener,实现将消息传递到业务逻辑进行处理;

    多个AsyncMessageProcessingConsumer对应一个listener(一个container对应一个listener即是一套处理业务,共用一个线程池,因为它们只是对应不同的并发, 处理的业务逻辑应是相同的。

    在这里插入图片描述

增加RabbitMQ并发的方法

  1. 增加并发消费者数量。并保障能提供充足的线程资源,虽然默认的线程池不设线程并发上线。示例:Redis与RabbitMQ配合使用多线程(多消费者)处理消息_多线程 处理 rabbitmq消息-CSDN博客

  2. 在listener方法上加上@Async(),这样会在异步的子线程下执行,如果提供线程池,就能实现并发。示例:线程池解决RabbitMQ消息堆积_rabbitmq线程池-CSDN博客

  3. 增大prefetchCount,prefetchCount是BlockingQueueConsumer内部维护的一个阻塞队列LinkedBlockingQueue的大小,其作用就是如果某个消费者队列阻塞,就无法接收新的消息

  4. 配置container的自定义线程池,但这个方法不推荐,示例:【RabbitMQ-9】自定义配置线程池(线程池资源不足-MQ初始化队列&&MQ动态扩容影响) - 简书 (jianshu.com)

  5. 当并发量确实无法短时间内提高时,也应尽可能提高消息队列的容量,并开启持久化。如设置惰性队列

    RabbitMQ 从 3.6.0 版本开始引入了惰性队列的概念。惰性队列会尽可能的将消息存入磁盘,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储。当消费者由于各种各样的原因(比如消费者下线宕机亦或者是由于维护而关闭等)而致使长时间内不能消费消息造成消息堆积时,惰性队列就很有必要了。

    正常的队列会尽可能存储在内存中。

相关文章:

敲详细的springframework-amqp-rabbit源码解析

看源码时将RabbitMQ的springframework-amqp-rabbit和spring-rabbit的一套区分开&#xff0c;springboot是基于RabbitMQ的Java客户端建立了简便易用的框架。 springboot的框架下相对更多地使用消费者Consumer和监听器Listener的概念&#xff0c;这两个概念不注意区分容易混淆。…...

Telegram Bot、小程序开发(三)Mini Apps小程序

文章目录 一、Telegram Mini Apps小程序二、小程序启动方式三、小程序开发小程序调试模式初始化小程序Keyboard Button Mini Apps 键盘按钮小程序【依赖具体用户信息场景,推荐】**Inline Button Mini Apps内联按钮小程序**initData 的自动传递使用内联菜单时候哪些参数会默认传…...

Django F()函数

F()函数的作用 F()函数在Django中是一个非常强大的工具&#xff0c;主要用于在查询表达式中引用模型的字段。它允许你在数据库层面执行各种操作&#xff0c;而无需将数据加载到Python内存中。这不仅提高了性能&#xff0c;还允许你利用数据库的优化功能。 字段引用 在查询表达…...

GraphRAG的实践

好久没有体验新技术了&#xff0c;今天来玩一下GraphRAG 顾名思义&#xff0c;一种检索增强的方法&#xff0c;利用图谱来实现RAG 1.配置环境 conda create -n GraphRAG python3.11 conda activate GraphRAG pip install graphrag 2.构建GraphRAG mkdir -p ./ragtest/i…...

自动驾驶三维车道线检测系列—LATR: 3D Lane Detection from Monocular Images with Transformer

文章目录 1. 概述2. 背景介绍3. 方法3.1 整体结构3.2 车道感知查询生成器3.3 动态3D地面位置嵌入3.4 预测头和损失 4. 实验评测4.1 数据集和评估指标4.2 实验设置4.3 主要结果 5. 讨论和总结 1. 概述 3D 车道线检测是自动驾驶中的一个基础但具有挑战性的任务。最近的进展主要依…...

守护动物乐园:视频AI智能监管方案助力动物园安全与秩序管理

一、背景分析 近日&#xff0c;某大熊猫参观基地通报了4位游客在参观时&#xff0c;向大熊猫室外活动场内吐口水的不文明行为。这几位游客的行为违反了入园参观规定并可能对大熊猫造成严重危害&#xff0c;已经被该熊猫基地终身禁止再次进入参观。而在此前&#xff0c;另一熊猫…...

FairGuard游戏加固入选《嘶吼2024网络安全产业图谱》

2024年7月16日&#xff0c;国内网络安全专业媒体——嘶吼安全产业研究院正式发布《嘶吼2024网络安全产业图谱》(以下简称“产业图谱”)。 本次发布的产业图谱&#xff0c;共涉及七大类别&#xff0c;127个细分领域。全面展现了网络安全产业的构成和重要组成部分&#xff0c;探…...

数据仓库事实表

数据仓库中的三种常见事实表类型&#xff1a;事务事实表、周期快照事实表和累积快照事实表 事务事实表&#xff1a; 事务事实表是记录事务级别数据的事实表。它记录了每个事务发生的具体度量指标&#xff0c;如销售金额、数量等。事务事实表的优势在于能够提供详细的事务级别…...

LeetCode题练习与总结:两数之和Ⅱ-输入有序数组--167

一、题目描述 给你一个下标从 1 开始的整数数组 numbers &#xff0c;该数组已按 非递减顺序排列 &#xff0c;请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] &#xff0c;则 1 < index1 < index…...

在 Java 中,怎样设计一个可扩展且易于维护的微服务架构?

在Java中设计一个可扩展且易于维护的微服务架构&#xff0c;可以考虑以下几个方面&#xff1a; 模块化设计&#xff1a;将应用拆分为多个小的、独立的模块&#xff0c;每个模块负责处理特定的业务逻辑。每个模块可以独立开发、测试和部署&#xff0c;增加或替换模块时不会影响其…...

零基础入门鸿蒙开发 HarmonyOS NEXT星河版开发学习

今天开始带大家零基础入门鸿蒙开发&#xff0c;也就是你没有任何编程基础的情况下就可以跟着石头哥零基础学习鸿蒙开发。 目录 一&#xff0c;为什么要学习鸿蒙 1-1&#xff0c;鸿蒙介绍 1-2&#xff0c;为什么要学习鸿蒙 1-3&#xff0c;鸿蒙各个版本介绍 1-4&#xff0…...

Chromium CI/CD 之Jenkins实用指南2024-在Windows节点上创建任务(九)

1. 引言 在现代软件开发流程中&#xff0c;持续集成&#xff08;CI&#xff09;和持续交付&#xff08;CD&#xff09;已成为确保代码质量和加速发布周期的关键实践。Jenkins作为一款广泛应用的开源自动化服务器&#xff0c;通过其强大的插件生态系统和灵活的配置选项&#xf…...

ceph进程网卡绑定逻辑

main() //如osd进程&#xff0c;是ceph_osd.cc文件的main函数&#xff1b;mon进程&#xff0c;是ceph_mon.cc文件的main函数 -->pick_addresses() // 会读取"cluster_network_interface"和"public_network_interface"这两个配置项来过滤ip ---->fill…...

学习opencv

初步学习可以参考&#xff1a; OpenCV学习之路&#xff08;附加资料分享&#xff09;_opencv资料-CSDN博客 【OpenCV】OpenCV常用函数合集【持续更新】_opencv函数手册-CSDN博客 整体框架可以参考&#xff1a; OpenCV学习指南&#xff1a;从零基础到全面掌握&#xff08;零…...

利用双端队列 实现二叉树的非递归的中序遍历

双端队列&#xff1a;双向队列&#xff1a;支持插入删除元素的线性集合。 java官方文档推荐用deque实现栈&#xff08;stack&#xff09;。 pop(): 弹出栈中元素&#xff0c;也就是返回并移除队头元素&#xff0c;等价于removeFirst()&#xff0c;如果队列无元素&#xff0c;则…...

昇思25天学习打卡营第18天 | 基于MindSpore的GPT2文本摘要

昇思25天学习打卡营第18天 | 基于MindSpore的GPT2文本摘要 文章目录 昇思25天学习打卡营第18天 | 基于MindSpore的GPT2文本摘要数据集创建数据集数据预处理Tokenizer 模型构建构建GPT2ForSummarization模型动态学习率 模型训练模型推理总结打卡 数据集 实验使用nlpcc2017摘要数…...

科研绘图系列:R语言circos图(circos plot)

介绍 Circos图是一种数据可视化工具,它以圆形布局展示数据,通常用于显示数据之间的关系和模式。这种图表特别适合于展示分层数据或网络关系。Circos图的一些关键特点包括: 圆形布局:数据被组织在一个或多个同心圆中,每个圆可以代表不同的数据维度或层次。扇区:每个圆被划…...

追踪Conda包的踪迹:深入探索依赖关系与管理

追踪Conda包的踪迹&#xff1a;深入探索依赖关系与管理 Conda作为Python和其他科学计算语言的包管理器&#xff0c;不仅提供了安装、更新和卸载包的功能&#xff0c;还有一个强大的包跟踪功能&#xff0c;帮助用户理解包之间的依赖关系和管理环境。本文将详细解释如何在Conda中…...

苹果电脑pdf合并软件 苹果电脑合并pdf 苹果电脑pdf怎么合并

在数字化办公日益普及的今天&#xff0c;pdf文件因其跨平台兼容性强、格式稳定等特点&#xff0c;已经成为工作、学习和生活中不可或缺的文件格式。然而&#xff0c;我们常常面临一个问题&#xff1a;如何将多个pdf文件合并为一个&#xff1f;这不仅有助于文件的整理和管理&…...

axios(ajax请求库)

json-server(搭建http服务) json-server用来快速搭建模拟的REST API的工具包 使用json-server 下载&#xff1a;npm install -g json-server创建数据库json文件&#xff1a;db.json开启服务&#xff1a;json-srver --watch db.json axios的基本使用 <!doctype html>…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成

一个面向 Java 开发者的 Sring-Ai 示例工程项目&#xff0c;该项目是一个 Spring AI 快速入门的样例工程项目&#xff0c;旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计&#xff0c;每个模块都专注于特定的功能领域&#xff0c;便于学习和…...

离线语音识别方案分析

随着人工智能技术的不断发展&#xff0c;语音识别技术也得到了广泛的应用&#xff0c;从智能家居到车载系统&#xff0c;语音识别正在改变我们与设备的交互方式。尤其是离线语音识别&#xff0c;由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力&#xff0c;广…...

es6+和css3新增的特性有哪些

一&#xff1a;ECMAScript 新特性&#xff08;ES6&#xff09; ES6 (2015) - 革命性更新 1&#xff0c;记住的方法&#xff0c;从一个方法里面用到了哪些技术 1&#xff0c;let /const块级作用域声明2&#xff0c;**默认参数**&#xff1a;函数参数可以设置默认值。3&#x…...