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

【RabbitMQ】可靠性传输

概述

作为消息中间件来说,最重要的任务就是收发消息。因此我们在收发消息的过程中,就要考虑消息是否会丢失的问题。结果是必然的,假设我们没有采取任何措施,那么消息一定会丢失。对于一些不那么重要的业务来说,消息丢失几条是无所谓的,例如使用消息中间件来做一个注册成功的业务,那丢失几条是无所谓的;但是,如果是一些比较重要的业务来说,消息一条也不能丢失。所以,我们就要考虑消息是在哪个阶段丢失的,应如何避免消息丢失。

如上图,消息丢失大概分为三种情况: 

1. 生产者到Broker的过程产生问题:由于应用程序故障、网络抖动等原因,生产者并没有成功向Broker发送消息。

2. Broker本身的问题:生产者成功将消息发送给了Broker,但是Broker没有把消息保存好,导致消息丢失。

3. Broker到消费者的过程产生问题:Broker发送消息到消费者,消费者在消费消息时,由于没有处理好,导致Broker将消费失败的消息从队列中删除了。

RabbitMQ针对这三种消息可能丢失的情形进行考虑,做出了不同的应对:

1. 针对生产者到Broker的问题,RabbitMQ推出了发送方确认机制,或者说是发布确认模式。

2. 针对Broker自身的问题,RabbitMQ推出了持久化的机制,例如针对交换机、队列以及消息的持久化。

3. 针对Broker到消费者的问题,RabbitMQ推出了消息确认机制。

发送方确认

当消息从生产者发送出去之后,消息有没有成功的到达Broker之中,这是第一个消息可能丢失的情况。并且,由于Broker内部也分成了Exchange和Queue两部分,即使消息成功到达了交换机,但是有没有到达队列之中,这也是需要考虑的点。

对于该问题,RabbitMQ提出了两种解决方案:

  • 事务
  • 发送方确认机制

在该篇文章中,主要来介绍发送方确认机制。原因则是因为使用事务比较消耗性能,因此日常开发中使用的并不多。针对刚才提到了交换机和队列之间的问题,RabbitMQ也是全部考虑到了,所以有两个方式来控制消息的可靠性传递:

  • confirm确认模式
  • return退回模式

confirm确认模式

spring:rabbitmq:host: 43.138.108.125port: 5672username: adminpassword: adminvirtual-host: mq-springboot-testpublisher-confirm-type: correlated # 表示发送方确认模式的confirm机制,correlated表示异步确认,还有一种同步确认,不管同步确认可能造成阻塞
// 可靠性传输的发送方确认
@Configuration
public class ConfirmConfig {@Bean("confirmQueue")public Queue confirmQueue() {return QueueBuilder.durable(Constants.CONFIRM_QUEUE).build();}@Bean("confirmExchange")public Exchange confirmExchange() {return ExchangeBuilder.directExchange(Constants.CONFIRM_EXCHANGE).durable(true).build();}@Bean("confirmQueueBind")public Binding confirmQueueBind(@Qualifier("confirmExchange") Exchange exchange,@Qualifier("confirmQueue") Queue queue) {return BindingBuilder.bind(queue).to(exchange).with("confirm").noargs();}}
@RestController
@RequestMapping("/confirm")
public class ConfirmController {@Resourceprivate RabbitTemplate rabbitTemplate;@RequestMappingpublic void confirmQueue() {// 异步调用方法this.rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {@Overridepublic void confirm(CorrelationData correlationData, boolean b, String s) {System.out.println("执行了消息确认机制中的confirm机制");if(b) {System.out.println("交换机接收到了消息,消息id为:" + correlationData.getId());} else {System.out.println("交换机没有接收到消息,原因为:" + s);System.out.println("处理具体业务,选择重发或者其他");}}});// 定义一个全局id,区分不同消息,防止ack时出现错误String uuid = UUID.randomUUID().toString().replace("-", "");CorrelationData correlationData = new CorrelationData(uuid);this.rabbitTemplate.convertAndSend(Constants.CONFIRM_EXCHANGE,"confirm", "hello 发送确认模式", correlationData);}}
@Configuration
public class ConfirmListener {@RabbitListener(queues = Constants.CONFIRM_QUEUE)public void confirmListener(String msg) {System.out.println("接收到消息:" + msg);}}

 启动程序之后,当生产者发送消息之后,就会出现如下内容:

但是,如果再次发送消息,就会直接报错:

 原因是因为Spring的Bean默认是单例,而RabbitTemplate对象同样支持一个回调,所以就出现了如上错误。想要解决上述办法的话,可以将Bean的作用域设置成多例模式。

@Component
public class RabbitTemplateConfig {@Bean// 这样做是有问题的,但是并不知道问题出在哪里,后续进行解决
//    @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {return new RabbitTemplate(connectionFactory);}}

 由于使用上述方法并没有跑通程序,因此我又使用了如下方法:

@Component
public class RabbitTemplateConfig {@Bean("rabbitTemplate")// 这样做是有问题的,但是并不知道问题出在哪里,后续进行解决
//    @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {return new RabbitTemplate(connectionFactory);}@Bean("rabbitTemplateConfirm")public RabbitTemplate rabbitTemplateConfirm(ConnectionFactory connectionFactory) {RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);// 执行回调方法rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {@Overridepublic void confirm(CorrelationData correlationData, boolean b, String s) {System.out.println("执行了消息确认机制中的confirm机制");if(b) {System.out.println("交换机接收到了消息,消息id为:" + correlationData.getId());} else {System.out.println("交换机没有接收到消息,原因为:" + s);System.out.println("处理具体业务,选择重发或者其他");}}});return rabbitTemplate;}}
@RestController
@RequestMapping("/confirm")
public class ConfirmController {@Resource(name = "rabbitTemplateConfirm")private RabbitTemplate rabbitTemplate;@RequestMappingpublic void confirmQueue() {// 定义一个全局id,区分不同消息,防止ack时出现错误String uuid = UUID.randomUUID().toString().replace("-", "");CorrelationData correlationData = new CorrelationData(uuid);this.rabbitTemplate.convertAndSend(Constants.CONFIRM_EXCHANGE,"confirm", "hello 发送确认模式", correlationData);}}

使用如上代码之后,成功跑通程序,获取到如下结果:

在confirm模式中,只要是消息没有到达交换机,那就会出现错误结果。但是,如果正确到达交换机,即使没有正确到达队列,回调函数也会成功执行到正确结果那部分。测试如下:

1. 当给出交换机的名称错误时,不会到达交换机,返回错误结果

交换机名称故意写错,原本是confirm.exchange​

 2. 交换机正确,但是给出的路由键错误,导致到不了相应的交换机

路由键故意写错,原本是confirm

在上述两个例子中证明了即使使用了confirm确认模式,消息也有可能在从交换机到队列的过程中出错。因此我们也需要在交换机和队列的传输过程中增加一个保障,那就是return退回模式。 

return退回模式 

如上描述,消息到达交换机之后,会根据路由规则进行匹配,把消息放到队列中。交换机到队列的过程,如果消息无法被任何队列消费(可能是队列不存在,也可能是路由键没有匹配的队列),可以选择把消息退回给发送方。

当我们写一个回调方法,对消息进行处理,就是return退回模式。

spring:rabbitmq:host: 43.138.108.125port: 5672username: adminpassword: adminvirtual-host: mq-springboot-testpublisher-confirm-type: correlated # 表示发送方确认模式的confirm机制,correlated表示异步确认,还有一种同步确认,不管同步确认可能造成阻塞publisher-returns: true # 表示发送方确认模式的return模式,true表示开启template:mandatory: true # true表示交换机无法进行路由消息时,会将消息返回给生产者,false表示无法进行路由时,直接丢弃
@Component
public class RabbitTemplateConfig {@Bean("rabbitTemplate")// 这样做是有问题的,但是并不知道问题出在哪里,后续进行解决
//    @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {return new RabbitTemplate(connectionFactory);}@Bean("rabbitTemplateConfirm")public RabbitTemplate rabbitTemplateConfirm(ConnectionFactory connectionFactory) {RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);// 执行confirm机制回调方法rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {@Overridepublic void confirm(CorrelationData correlationData, boolean b, String s) {System.out.println("执行了消息确认机制中的confirm机制");if(b) {System.out.println("交换机接收到了消息,消息id为:" + correlationData.getId());} else {System.out.println("交换机没有接收到消息,原因为:" + s);System.out.println("处理具体业务,选择重发或者其他");}}});// 执行了return机制回调方法rabbitTemplate.setMandatory(true);rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {@Overridepublic void returnedMessage(ReturnedMessage returnedMessage) {System.out.println("执行了消息确认机制中的return模式");System.out.println("消息进行退回,退回的消息是:" + new String(returnedMessage.getMessage().getBody()));}});return rabbitTemplate;}}

将发送方确认机制的两种模式完成之后,再写一个交换机正确,路由键不正确的例子,就会出现如下结果: 

 综上所述,将生产者到Broker的消息丢失问题给解决了。总的来说,包含了两个维度,一个是从生产者到交换机,一个是从交换机到队列。

持久化

持久化是RabbitMQ的可靠性保证机制之一,它保证的是RabbitMQ内部的可靠性。

持久化分为三个部分:交换机的持久化、队列的持久化、消息的持久化。

交换机的持久化

其实,交换机的持久化早就在使用了,只不过是没有进行介绍而已。在JavaSDK中,通过声明交换机时的一个参数来实现;在SpringBoot中,也是通过声明交换机时的一个参数来实现(durable)。交换机设置成持久化之后,交换机的属性就会在服务器内部保存,当MQ的服务器发生意外宕机之后,重启服务器后不需要重新去建立交换机,持久化后的交换机会自动建立。

如果交换机不设置持久化,那么MQ服务器在重启之后,相关的交换机元数据就会消失。因此,对于一个长期使用的交换机来说,必然是要将其设置成持久化的。

队列的持久化

和交换机相同,队列的持久化也早就在使用了。同样,也是通过设置参数durable实现的。

不同的是,队列的持久化比交换机的持久化还稍微重要些。设想,队列不进行持久化,当重启队列之后,队列元数据就会被删除。既然队列都被删除了,那消息肯定也都没了。这对于生产环境的机子来说是比较难搞的事情,因此队列要进行持久化。

消息的持久化

队列持久化之后,消息不进行持久化,那重启之后,照样没数据,还不如不持久化队列呢。所以,继交换机持久化、队列持久化之后,消息也要进行持久化。

在SpringBoot中,消息的持久化就是设置MessageProperties中的deliveyMode为PERSISTENT即可,如下述代码:

@Configuration
public class DurableConfig {@Bean("durableQueue")public Queue durableQueue() {return QueueBuilder.durable(Constants.DURABLE_QUEUE).build(); // 队列持久化}@Bean("durableExchange")public Exchange durableExchange() {return ExchangeBuilder.directExchange(Constants.DURABLE_EXCHANGE).durable(true).build(); // 交换机持久化}@Bean("durableQueueBind")public Binding durableQueueBind(@Qualifier("durableExchange") Exchange exchange,@Qualifier("durableQueue") Queue queue) {return BindingBuilder.bind(queue).to(exchange).with("durable").noargs();}}
@RestController
@RequestMapping("/durable")
public class DurableController {@Resourcepublic RabbitTemplate rabbitTemplate;@RequestMappingpublic void durableQueue() {String body = "hello 持久化";Message msg = new Message(body.getBytes(StandardCharsets.UTF_8), new MessageProperties());msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 消息持久化this.rabbitTemplate.convertAndSend(Constants.DURABLE_EXCHANGE, "durable", msg);System.out.println("持久化发送消息成功!");}}
@Configuration
public class DurableLister {@RabbitListener(queues = Constants.DURABLE_QUEUE)public void durableListener(String msg) {System.out.println("持久化消息消费成功:" + msg);}}

消息确认

消息确认机制是保证可靠性传输的最后一个机制,保障的是从Broker到消费者的整个过程。

RabbitMQ向消费者发送消息之后,就会把这条消息删除。但是,如果消费者处理消息异常,就会造成消息丢失。为了保障消息从队列顺利到达消费者,RabbitMQ提出了消息确认机制。

消费者在订阅队列是,可以指定autoAck参数,根据这个参数设置,消息确认机制可以分成以下两种:

  • 手动确认:当autoAck等于false时,RabbitMQ会等待消费者显示地调用Basic.Ack命令,恢复确认信号后才能从内存(或者磁盘中)删除消息。这种模式适合于消息可靠性要求较高的场景。
  • 自动确认:当autoAck等于true时,RabbitMQ会自动把发送出去的消息设置为确认,然后从内存(或者硬盘中)删除消息,而不管消费者是否真正消费了这条消息。这种模式适合于消息可靠性要求不高的场景。

当消费者指定需要手动确认时,队列中的消息就分成了两个部分:

  • 等待投递给消费者的消息
  • 已经投递给消费者,但是还没有收到消费者确认信号的消息

如果RabbitMQ一直没有收到消费者的确认信号,并且消费此消息的消费者已经断开连接,则RabbitMQ会安排该消息重新进入队列,等待投递给下一个消费者,当然也有可能还是原来那个消费者。

手动确认

消费者在收到消息之后,可以选择确认,也可以选择直接拒绝或者跳过。RabbitMQ也提供了不同的确认应答方式,消费者可以调用与其对应的channel相关方法,共有三种:

肯定确认

channel,basiAck(long deliveryTag, boolean multiple),表示RabbitMQ已经知道该消息处理成功,可以将其丢弃了。

deliveryTag表示消息的唯一标识,是一个单调递增的64位的长整型值。deliveryTag是每个信道独立维护的,所以在每个信道上都是唯一的。当消费者确认一条消息时,必须使用对应的信道上进行确认。

multiple表示是否批量确认,在某些情况下,为了减少网络流量,可以对一系列的deliveryTag进行批量确认。值为true则会一次性确认所有小于或等于指定deliveryTag的消息。值为false时则只会确认当前指定的deliveryTag的消息。

deliveryTagRabbitMQ中消息确认机制的一个重要组成部分,他确保了消息传递的可靠性和顺序性。

否定确认

channle.basicReject(long deliveryTag, boolean requeue),消费者可以调用该消息告诉RabbbitMQ拒绝该消息。

requeue表示拒绝后这条消息如何处理。值为true时,会重新将该消息存入队列,以便可以发送给下一个订阅的消费者。值为false时,则会把消息从队列中删除,而不会把他发送给新的消费者。

否定确认

channel.basicNack(long deliveryTag, boolean multiple, boolean requeue),该方法和第二个方法含义相同,唯一的区别是该方法可以批量处理。

代码案例

主要展示SpringBoot模式下的代码书写。

Spring-AMQP对于消息确认机制提供了三种策略:

  1. AcknowledgeMode.NONE:表示消息一旦投递给消费者,不管消费者是否处理成功该消息,RabbitMQ都会自动确认,从队列中移除该消息。如果消费者处理消息失败,消息可能会丢失。
  2. AcknowledgeMode.AUTO(默认):表示消息投递给消费者,如果处理过程中抛出了异常,则不会确认该消息;但是如果没有发生异常,该消息就会自动确认。
  3. AcknowledgeMode.MANUAL:表示手动确认模式,消费者必须在成功处理消息之后调用basicAck来确认消息。如果消息未被确认,RabbitMQ会认为消息未处理成功,并且会在消费者可用时重新投递该消息,这种模式提高了消息处理的可靠性,因为即使消费者处理消息后失败,消息也不会丢失,而是可以被重新处理。
spring:rabbitmq:host: 43.138.108.125port: 5672username: adminpassword: adminvirtual-host: mq-springboot-testpublisher-confirm-type: correlated # 表示发送方确认模式的confirm机制,correlated表示异步确认,还有一种同步确认,不管同步确认可能造成阻塞publisher-returns: true # 表示发送方确认模式的return模式,true表示开启template:mandatory: true # true表示交换机无法进行路由消息时,会将消息返回给生产者,false表示无法进行路由时,直接丢弃listener:simple:acknowledge-mode: auto # 消息确认机制,三种策略可选
@Configuration
public class AckConfig {@Bean("ackQueue")public Queue ackQueue() {return QueueBuilder.durable(Constants.ACK_QUEUE).build();}@Bean("ackExchange")public Exchange ackExchange() {return ExchangeBuilder.directExchange(Constants.ACK_EXCHANGE).durable(true).build();}@Bean("ackQueueBind")public Binding ackQueueBind(@Qualifier("ackExchange") Exchange exchange,@Qualifier("ackQueue") Queue queue) {return BindingBuilder.bind(queue).to(exchange).with("ack").noargs();}}
@RestController
@RequestMapping("/ack")
public class AckController {@Resourcepublic RabbitTemplate rabbitTemplate;@RequestMappingpublic void ackQueue() {this.rabbitTemplate.convertAndSend(Constants.ACK_EXCHANGE, "ack", "hello ack");System.out.println("消息确认机制生产者发送成功");}}

当使用策略为NONE时,发现即使出现异常,消息也会从消费者中删除:

@Configuration
@RabbitListener(queues = Constants.ACK_QUEUE) // 当类中所有的消费者都指向一个队列时,就可以放在类上
public class AckListener {@RabbitHandler // RabbitListener注解放在类上时,就需要使用该注解放在方法上public void ackListener(String msg) {System.out.println("接收到消息:" + msg);int a = 3 / 0;System.out.println("自制异常,用来感受auto");}}

当使用策略为AUTO时,发现出现异常之后,会一直重试,并且在开源界面中也会出现未确认消息一条:

@Configuration
@RabbitListener(queues = Constants.ACK_QUEUE) // 当类中所有的消费者都指向一个队列时,就可以放在类上
public class AckListener {@RabbitHandler // RabbitListener注解放在类上时,就需要使用该注解放在方法上public void ackListener(String msg) {System.out.println("接收到消息:" + msg);int a = 3 / 0;System.out.println("自制异常,用来感受auto");}}

当使用策略为MANUAL,就需要在消费者中修改代码:

@Configuration
@RabbitListener(queues = Constants.ACK_QUEUE) // 当类中所有的消费者都指向一个队列时,就可以放在类上
public class AckListener {@RabbitHandler // RabbitListener注解放在类上时,就需要使用该注解放在方法上public void ackListener(Message msg, Channel channel) throws IOException {try {System.out.println("接收到消息为:" + msg);/*** 第一个参数是deliveryTag* 第二个参数是是否批量处理*/channel.basicAck(msg.getMessageProperties().getDeliveryTag(), true);} catch (Exception e) {/*** 第一个参数是deliveryTag* 第二个参数是requeue*/channel.basicReject(msg.getMessageProperties().getDeliveryTag(), true);/*** 第一个参数是deliveryTag* 第二个参数是是否批量处理* 第三个参数是requeue*/// channel.basicNack(msg.getMessageProperties().getDeliveryTag(), true, true);}}}

总结

在该篇文章中,主要描述了RabbitMQ的保障可靠性传输的三个策略,这三个策略保障了消息从生产者产生到消费者消费整个过程的可靠性,使得RabbitMQ的性能更好。但是,并不能完全保障消息不丢失,或者说,没有一个消息队列可以保障消息不丢失,例如持久化时,是不会直接持久化到硬盘,而是持久化到缓存中,经过几条消息的沉淀之后,再持久化到硬盘,所以在这个过程中一点宕机,那么消息也是会丢失的。总的来说,这三条策略还是尽可能的保障了消息传输的可靠性。

相关文章:

【RabbitMQ】可靠性传输

概述 作为消息中间件来说,最重要的任务就是收发消息。因此我们在收发消息的过程中,就要考虑消息是否会丢失的问题。结果是必然的,假设我们没有采取任何措施,那么消息一定会丢失。对于一些不那么重要的业务来说,消息丢失…...

【论文阅读】PERCEIVER-ACTOR: A Multi-Task Transformer for Robotic Manipulation

Abstract transformers凭借其对大型数据集的扩展能力,彻底改变了视觉和自然语言处理。但在机器人操作中,数据既有限又昂贵。通过正确的问题表述,操纵仍然可以从变形金刚中受益吗?我们使用peract来研究这个问题,peract…...

Linux 常用指令

Linux 常用指令 这是本人在备战 CSP 初赛做 Linux 指令题时,心血来潮整理的,希望对大家有帮助。如有错误或有补充,麻烦私信或评论指出。 表格按字母顺序排列 命令作用alias对命令重命名cal显示日历的指令cat查看文本文件的内容cd改变当前工…...

使用 PHPstudy 建立ThinkPHP8 本地集成环境

安装Composer 下载地址:https://getcomposer.org/Composer-Setup.exehttps://getcomposer.org/Composer-Setup.exe 打开PHPstudy创建网站: cmd终端进入PHPstudy www根目录下: 执行代码:cd phpstudy www 根目录地址 cd C:\phpst…...

【系统架构设计】软件的知识产权保护+标准化概论+应用数学+云计算

【系统架构设计】软件的知识产权保护标准化概论应用数学云计算 软件的知识产权保护标准化概论应用数学云计算 软件的知识产权保护 在该部分内容中,以下几点需要注意: 如果作品是委托创作的,著作权的归属应通过委托人和受托人之间的合同来确…...

解决使用阿里云DataV Geo在线地图路径访问403问题

文章目录 1. DataV Geo在线地图路径访问403问题2. 解决方法3. 重启生效 1. DataV Geo在线地图路径访问403问题 最近在写一个省市下钻的demo,用到的是 阿里云DataV Geo在线地图 去动态获取GeoJSON 省市的数据,如下代码 axios.get("https://geo.dat…...

linux 使用SSH密钥配置免密登录

需求:多台主机SSH免密登录,需要使用同一个密钥对 操作: 在Linux中,使用SSH密钥对来在多台主机之间配置免密登录。以下是配置步骤: 在你的本地机器上生成一个SSH密钥对。如果你已经有一个,你可以跳过这一…...

python教程(二):python数据结构大全(附代码)

Python 中数据结构的重要性不言而喻,它们是构建高效、可维护代码的基础。数据结构决定了如何存储、组织和操作数据。理解和使用合适的数据结构能够极大地提升程序的性能、简洁性以及代码的可读性。 Python 的基础数据结构有 4 种,分别是 列表 (list)、元…...

MySQL基于GTID同步模式搭建主从复制

系列文章目录 rpmbuild构建mysql5.7.42版本的rpm包 文章目录 系列文章目录一、mysql-5.7.42RPM包构建二、同步模式分类介绍1.异步同步模式2.半同步模式2.1.实现半同步操作流程2.2.半同步问题总结2.3.半同步一致性2.4.异步与半同步对比 3.GTID同步 三、GTID同步介绍1.gtid介绍2…...

RecyclerView的子项长按选择功能

在Android开发中,实现RecyclerView的子项长按选择功能通常涉及到几个关键步骤:设置RecyclerView的ItemTouchListener来监听长按事件,管理选中状态,以及更新UI以反映选中状态。以下是一个基本的实现步骤和示例代码。 1. 定义数据模…...

mongoDB-1

文章目录 一、疑似坑1.11.2 mongo ops manager1.3 mongo features视图固定大小集合(有点类似ringbuffer数据结构,capped collections)(聚簇集合)clustered collection(类比到Mysql的聚簇索引)聚合管道 aggregation pipelineWiredTiger (默认存…...

iKuai使用及设置流程

iKuai使用及设置流程 iKuai安装步骤 一、配置主机 1.电脑连接ETH0网口 2.ETH1网口连接猫上面的千兆口 3.手动配置pc的IP地址和192.168.1.1./24在同一网段 3.浏览器输入192.168.1.1 admin admin 二、外网设置 1.直接联通电信网络设置 2.点击 网络设置-内外网设置-点击接…...

【乐企-业务篇】销项开票接口声明(主要是业务对接)

我们系统销项对接了四家,所以抽象出来一个接口 专门用来定义销项相关的接口声明 代码如下 import java.util.List;/*** User: yanjun.hou* Date: 2024/9/4 10:07* Description:开票策略*/ public interface InvoiceStrategy {/*** 开票** @param order...

Pytest配置文件pytest.ini如何编写生成日志文件?

1、新建pytest.ini文件 [pytest] log_clitrue log_leveLNOTSET log_format %(asctime)s %(levelname)s %(message)s %(filename)s %(funcName)s %(lineno)d log_date_format %Y-%m-%d %H:%M:%Slog_file ./logdata/log.log log_file_level info log_file_format %(asctime…...

rust快速创建Tauri App ——基于create-tauri-app

Tauri App Tauri是一个工具包,可以帮助开发人员使用现有的几乎任何前端框架为主要桌面平台制作应用程序。核心是用Rust构建的,CLI利用Node.js使Tauri成为创建和维护优秀应用程序的真正多语言方法。 cargo install create-tauri-appcreate-tauri-app&am…...

【MySQL】MySQL中JDBC编程——MySQL驱动包安装——(超详解)

前言: 🌟🌟本期讲解Java中JDBC编程,希望能帮到屏幕前的你。 🌈上期博客在这里:【MySQL】MySQL索引与事务的透析——(超详解)-CSDN博客 🌈感兴趣的小伙伴看一看小编主页&a…...

电脑安装OpenWRT系统

通过网盘分享的文件:OpenWRT 链接: https://pan.baidu.com/s/1nrRBeKgGviD31Omji480qA?pwd9900 提取码: 9900 下面开始教程: 1.先把普通U盘制作成一个PE启动盘,我用的是微PE工具箱,直接安装PE到U盘。 2.把写盘工具和openWRT系统…...

说说几款耳机

从前,大约在戴森推出他们那款奇特的发明——戴森耳机与空气净化器组合一年后,人们仍对这个奇怪的产品感到困惑。这款穿戴式空气净化耳机更像是一个实验,缺乏实际用途。回想起那时的评测,大家一致认为这是有史以来最无意义的产品之…...

Excel爬虫使用实例-百度热搜

原来excel也能爬虫抓取数据,而且简单好用 目标网址: https://top.baidu.com/board?tabrealtime 下面是一个excel爬虫的小小例子,爬取了百度热搜的前50(还有一个置顶的热搜没有1,2,3编号) 实现…...

arcgisPro地理配准

1、添加图像 2、在【影像】选项卡中,点击【地理配准】 3、 点击添加控制点 4、选择影像左上角格点,然后右击填入目标点的投影坐标 5、依次输入四个格角点的坐标 6、点击【变换】按钮,选择【一阶多项式(仿射)】变换 7…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;&#xff0c;为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展&#xff0c;机器人仍难以胜任复杂的长时程任务&#xff08;如家具装配&#xff09;&#xff0c;主要受限于人…...

嵌入式面试常问问题

以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…...