消息队列应用场景

image-20200716220330316

image-20200716220349030

消息中间件概述

image-20200716220452119

image-20200716220508446

image-20200716220524410

image-20200716220538166

RabbitMQ 概念

image-20200716220548181

image-20200716220600626

image-20200716220610376

image-20200716220646276

一个客户端只与消息中间件建立一条链接(长连接),一条链接里面有多个channel(虚拟链接,复用一条TCP连接)

虚拟主机是rabbitmq为了隔离不同类型客户端出现的概念。我们在开发过程中可以一个客户端一个虚拟主机。即一个rabbitmq里面可以有多个虚拟主机

虚拟主机:多个交换机构成虚拟主机,主要目的是隔离客户端的消息。比如当前消息队列的生产者有java端和php端,为了隔离开我们可以划分出两个虚拟主机。虚拟主机互相隔离,一台虚拟主机出现问题,不会影响到别的虚拟主机

虚拟主机是按照路径来划分的。

流程:无论是生产者往mq发送消息,还是消费者接受mq的消息,都会建立一条连接,所有的数据都会在连接里面开辟信道来进行收发。收发的消息分成两部分,消息头和消息体。消息头就是消息的属性信息,消息体就是消息的真正内容,消息里面还需要定义发给那个交换机,那个虚拟主机,消息头里面最重要就是route-key。发消息时,消息会来到rabbitmq里面指定的虚拟主机中,然后到达指定的交换机。交换机通过消息里面的route-key和绑定的队列的路由键,决定将消息发送给与交换机绑定的哪些队列中。如果队列放入的消息,监听队列的消费者就会得到队列里面的数据。一个客户端建立一条长连接的好处有,mq能实时的知道那个消费端断线了,从而及时的将队列里面的数据保存起来(停止出队),防止消息丢失。

docker 安装rabbitmq

docker run -d \
-p 5671:5671 \
-p 5672:5672 \
-p 4369:4369 \
-p 25672:25672 \
-p 15671:15671 \
-p 15672:15672 \
--restart=always --name rabbitmq rabbitmq:3-management默认账户密码是guest
4369, 25672 (Erlang发现&集群端口)
5672, 5671 (AMQP端口)
15672 (web管理后台端口)
61613, 61614 (STOMP协议端口)
1883, 8883 (MQTT协议端口)
https://www.rabbitmq.com/networking.html

RabbitMQ的运行机制

image-20200716220703348 image-20200716220722776 image-20200716220746057

一个交换机可以绑定多个队列,一个队列可以绑定多个交换机。交换机决定要按照什么绑定关系路由给哪个消息队列。

交换机的类型:direct、fanout(扇出)、topic、headers。direct和headers 是点对点的实现、fanout和topic是发布订阅模式的实现。而header性能低下,现在都只用direct、fanout、topic这三种不同类型的交换机。

发消息是将消息发送给虚拟主机里面的交换机,消费消息是监听队列。

direct:直接类型交换机。按照路由键精确匹配到一个队列。

fanout:广播类型交换机。消息会发送给交换机下面绑定的所有队列,不管路由键。

topic:发布订阅模式交换机。可根据路由键进行模糊匹配。#匹配0个或多个单词,* 匹配一个单词。

注意:路由键是有单词组成的,单词之间使用 . 分割,这一点是理解topic模糊匹配的关键。

测试一下三种不同类型的exchange

image-20200716162659015

导出的配置(是json文件,xxx.json 导入即可)

{"rabbit_version":"3.8.5","rabbitmq_version":"3.8.5","product_name":"RabbitMQ","product_version":"3.8.5","users":[{"name":"guest","password_hash":"VLq+EgZQzd1z32LOZ83Onxc/MFCJcoMPf4jynGCdC9Aqvc6B","hashing_algorithm":"rabbit_password_hashing_sha256","tags":"administrator"}],"vhosts":[{"name":"/"}],"permissions":[{"user":"guest","vhost":"/","configure":".*","write":".*","read":".*"}],"topic_permissions":[],"parameters":[],"global_parameters":[{"name":"cluster_name","value":"rabbit@7072bb66c978"},{"name":"internal_cluster_id","value":"rabbitmq-cluster-id-9wl1WHjPlMGESsZsgSTH_g"}],"policies":[],"queues":[{"name":"atguigu.news","vhost":"/","durable":true,"auto_delete":false,"arguments":{"x-queue-type":"classic"}},{"name":"atguigu","vhost":"/","durable":true,"auto_delete":false,"arguments":{"x-queue-type":"classic"}},{"name":"gulixueyuan.news","vhost":"/","durable":true,"auto_delete":false,"arguments":{"x-queue-type":"classic"}},{"name":"atguigu.emps","vhost":"/","durable":true,"auto_delete":false,"arguments":{"x-queue-type":"classic"}}],"exchanges":[{"name":"exchange.fanout","vhost":"/","type":"fanout","durable":true,"auto_delete":false,"internal":false,"arguments":{}},{"name":"exchange.topic","vhost":"/","type":"topic","durable":true,"auto_delete":false,"internal":false,"arguments":{}},{"name":"my.exchange.direct","vhost":"/","type":"direct","durable":true,"auto_delete":false,"internal":false,"arguments":{}},{"name":"exchange.direct","vhost":"/","type":"direct","durable":true,"auto_delete":false,"internal":false,"arguments":{}}],"bindings":[{"source":"exchange.direct","vhost":"/","destination":"atguigu","destination_type":"queue","routing_key":"atguigu","arguments":{}},{"source":"exchange.direct","vhost":"/","destination":"atguigu.emps","destination_type":"queue","routing_key":"atguigu.emps","arguments":{}},{"source":"exchange.direct","vhost":"/","destination":"atguigu.news","destination_type":"queue","routing_key":"atguigu.news","arguments":{}},{"source":"exchange.direct","vhost":"/","destination":"gulixueyuan.news","destination_type":"queue","routing_key":"gulixueyuan.news","arguments":{}},{"source":"exchange.fanout","vhost":"/","destination":"atguigu","destination_type":"queue","routing_key":"atguigu","arguments":{}},{"source":"exchange.fanout","vhost":"/","destination":"atguigu.emps","destination_type":"queue","routing_key":"atguigu.emps","arguments":{}},{"source":"exchange.fanout","vhost":"/","destination":"atguigu.news","destination_type":"queue","routing_key":"atguigu.news","arguments":{}},{"source":"exchange.fanout","vhost":"/","destination":"gulixueyuan.news","destination_type":"queue","routing_key":"gulixueyuan.news","arguments":{}},{"source":"exchange.topic","vhost":"/","destination":"atguigu.news","destination_type":"queue","routing_key":"*.news","arguments":{}},{"source":"exchange.topic","vhost":"/","destination":"gulixueyuan.news","destination_type":"queue","routing_key":"*.news","arguments":{}},{"source":"exchange.topic","vhost":"/","destination":"atguigu","destination_type":"queue","routing_key":"atguigu.#","arguments":{}},{"source":"exchange.topic","vhost":"/","destination":"atguigu.emps","destination_type":"queue","routing_key":"atguigu.#","arguments":{}},{"source":"my.exchange.direct","vhost":"/","destination":"atguigu","destination_type":"queue","routing_key":"atguigu","arguments":{}}]}

SpringBoot 整合 RabbitMQ

依赖&API

  1. 引入spring-boot-starter-amqp(场景启动器)
  2. 配置application.yml
  3. 测试RabbitMQ
    1. AmqpAdmin:管理组件
    2. RabbitTemplate:消息发送处理组件

代码

依赖

<!-- amqp -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置文件

# rabbit 配置信息
spring.rabbitmq.host=192.168.1.10
spring.rabbitmq.virtual-host=/
spring.rabbitmq.port=5672

api

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class GulimallOrderApplicationTests {@AutowiredAmqpAdmin amqpAdmin;@AutowiredRabbitTemplate rabbitTemplate;/*** 1、如何创建Exchange、Queue、Binding(AmqpAdmin)* 2、如何收发消息(RabbitTemplate)*/@Testpublic void contextLoads() {/*String name, boolean durable, boolean autoDelete, Map<String, Object> arguments*/DirectExchange directExchange = new DirectExchange("hello-java-exchange", true, false);amqpAdmin.declareExchange(directExchange);log.info("Exchange创建成功【{}】", directExchange.getName());}@Testpublic void testCreateQueue() {// exclusive true 就是只有一个消费者能连接Queue queue = new Queue("hello-java-queue", true, false, false);amqpAdmin.declareQueue(queue);log.info("Queue创建成功【{}】", queue.getName());}@Testpublic void testCreateBinding() {/*String destination, DestinationType destinationType, String exchange, String routingKey,Map<String, Object> arguments*/// 将exchange指定的交换机和destination目的地进行绑定,目的地的类型是什么,使用作为指定的路由键routingKeyBinding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE, "hello-java-exchange", "hello-java-queue", null);amqpAdmin.declareBinding(binding);log.info("Binding创建成功");}@Testpublic void testSendMessage() {// 发送消息,如果发送的消息是个对象,我们会使用序列化机制,将对象写出去。对象必须实现Serializable接口String msg = "hello world";// 发送对象类型的消息 可以是json个格式的,自定义MessageConverter即可// rabbitTemplate.convertAndSend("hello-java-exchange", "hello-java-queue", msg);for (int i = 0; i < 10; i++) {if (i % 2 == 0) {OrderReturnReasonEntity orderReturnReasonEntity = new OrderReturnReasonEntity();orderReturnReasonEntity.setId(1L);orderReturnReasonEntity.setCreateTime(new Date());orderReturnReasonEntity.setName("哈哈" + i);rabbitTemplate.convertAndSend("hello-java-exchange", "hello-java-queue", orderReturnReasonEntity);}else{OrderEntity orderEntity = new OrderEntity();orderEntity.setOrderSn(UUID.randomUUID().toString());rabbitTemplate.convertAndSend("hello-java-exchange", "hello-java-queue", orderEntity);}}log.info("消息发送完成:{}");}}

接受消息的注解

  • 使用接听消息的注解,必须先启用 @EnableRabbit
监听消息:使用@RabbitListener,@RabbitHandler;必须有@EnableRabbit;* @RabbitListener: 类 + 方法(绑定消息,也可以直接放在方法上接受消息)
* @RabbitHandler:方法(重载区分不同的消息)
@Service("orderItemService")
@RabbitListener(queues = {"hello-java-queue"})
public class OrderItemServiceImpl{		/*** queues:声明需要监听的所有队列* org.springframework.amqp.core.Message* 参数可以有以下(都是可选参数,可以一个不行也可以写全参)* 1、Message message:原生消息详细信息。头+体* 2、T<发送的消息的类型> OrderReturnReasonEntity content* 3、Channel channel:当前传输数据的通道* * Queue:可以很多人都来监听。只要收到消息,队列删除消息,而且只能有一个人收到消息* 场景:* 1、 订单服务启动多个:同一个消息,只能有一个客户端能收到* 2、 只有一个消息完全处理完,方法运行结束,我们才可以接受下一个消息** @param message*/// @RabbitListener(queues = {"hello-java-queue"})@RabbitHandlerpublic void receiveMessage(Message message, OrderReturnReasonEntity content,Channel channel) throws InterruptedException {}@RabbitHandlerpublic void receiveMessage2(OrderEntity content) throws InterruptedException {}
}

自定义发送消息时的序列化器

@Configuration
public class MyRabbitConfig {@Beanpublic MessageConverter Jackson2JsonMessageConverter() {return new Jackson2JsonMessageConverter();}
}

自动配置原理

添加场景启动器spring-boot-starter-amqp

​ ==》导入RabbitAutoConfiguration

​ ==》注入属性文件@EnableConfigurationProperties(RabbitProperties.class)

​ ==》注入RabbitTemplate

​ ==》RabbitTemplate 有一个属性并有默认值private MessageConverter messageConverter = new SimpleMessageConverter();

​ ==> 在自动注入RabbitTemplate会从IOC容器中获取MessageConverter,获取得到就给属性重新赋值,实现替换消息的序列化器

​ ==》注入AmqpAdmin

RabbitMQ消息确认机制-可靠抵达

概述

image-20200716181810540

可靠抵达-ConfirmCallback

  • spring.rabbitmq.publisher-confirms=true
    • 在创建connectionFactory的时候设置PublisherConfirms(true)选项,开启confirmcallback。
    • CorrelationData:用来表示当前消息唯一性。
    • 消息只要被broker接收到就会执行confirmCallback,如果是cluster模式,需要所有broker接收到才会调用confirmCallback。
    • 被broker接收到只能表示message已经到达服务器,并不能保证消息一定会被投递到目标 queue里。所以需要用到接下来的returnCallback。
  • 调用时机:broker成功收到生产者的消息

可靠抵达-returnCallback

  • spring.rabbitmq.publisher-returns=true
  • spring.rabbitmq.template.mandatory=true
    • confrim模式只能保证消息到达broker,不能保证消息准确投递到目标queue里。在有些业务场景下,我们需要保证消息一定要投递到目标queue里,此时就需要用到return退回模式。
    • 这样如果未能投递到目标queue里将调用returnCallback,可以记录下详细到投递数据,定期的巡检或者自动纠错都需要这些数据。
  • 调用时机:比如我们发送的消息的route-key 匹配不到exchange下面的所有queue的时候会触发这个回调

可靠抵达一 Ack消息确认机制

  • spring.rabbitmq.listener.simple.acknowledge-mode=manual

  • 消费者获取到消息,成功处理,可以回复Ack给Broker

    • basic.ack用于肯定确认; broker将移除此消息
    • basic.nack用于否定确认;可以指定broker是否丢弃此消息,可以批量
    • basic.reject用于否定确认;同上,但不能批量
  • 默认自动ack,消息被消费者收到,就会从broker的queue中移除

  • queue无消费者,消息依然会被存储,直到消费者消费

  • 消费者收到消息,默认会自动ack。但是如果无法确定此消息是否被处理完成,或者成功处理。我们可以开启手动ack模式

    • 消息处理成功,ack(), 接受下一个消息,此消息broker就会移除
    • 消息处理失败,nack()/reject(), 重新发送给其他人进行处理,或者容错处理后ack
    • 消息一直没有调用ack/nack方法, broker认为此消息正在被处理,不会投递给别人,此时客户端断开,消息不会被broker移除,会投递给别人

confirmCallback 和 returnCallback是发送端的回调,ack是消费端的回调。消息中间件成功接受生产者的消息会执行confirmCallback,交换机的信息为成功投递到queue会执行returnCallback,消费者成功收到消息会执行ack告诉broker将消息删除。

实操

配置文件

# rabbit 配置信息
spring.rabbitmq.host=192.168.1.10
spring.rabbitmq.virtual-host=/
spring.rabbitmq.port=5672# 开启发送端确认消息抵达Broker
spring.rabbitmq.publisher-confirms=true# 开启发送端消息抵达队列的确认
spring.rabbitmq.publisher-returns=true
# 只要消息不能抵达队列,会异步发送优先回调我们这个returnConfirm
spring.rabbitmq.template.mandatory=true# 手动ack消息(acknowledgement)(如果只想开启手动ack配置这个即可)
spring.rabbitmq.listener.simple.acknowledge-mode=manual

自定义RabbitTemplate

@Configuration
@EnableRabbit
public class MyRabbitConfig {@AutowiredRabbitTemplate rabbitTemplate;// 发送消息时的序列化机制@Beanpublic MessageConverter Jackson2JsonMessageConverter() {return new Jackson2JsonMessageConverter();}/*** 定制RabbitTemplate* 1、服务端收到消息就回调*  1、spring.rabbitmq.publisher-confirms=true*  2、设置确认回调ConfirmCallback** 2、消息没有正确抵达队列进行回调*  1、spring.rabbitmq.publisher-returns=true、spring.rabbitmq.template.mandatory=true*  2、设置失败回调ReturnCallback**  3、消费端确认(保证每个消息被正确消费,此时才可以broker删除这个消息)*  spring.rabbitmq.listener.simple.acknowledge-mode=manual 开启手动签收*  1、默认是自动确认的,只要消息接受到,消费端端会自动确认,服务端就会移除这个消息。*      问题:*          我们收到很多消息,自动回复给服务端ack。只有一个消息处理成功,宕机了。发生消息丢失。(通道一打开就回复ack)*          手动确认模式。只要我们没有明确告诉MQ,消息就一直是unacked转态。即使consumer宕机。队列里面的消息也不会丢失,consumer宕机则队列里面的消息会变为ready,*          下次有新的consumer连接进来就发给他(队列的消息变成unacked)。队列消息的转态是根据有没有消费者连接上队列*  2、如何签收:*       channel.basicAck(deliveryTag, false);签收:业务成功完成就应该签收*       channel.basicNack(deliveryTag,false,true); 拒签:业务失败,拒签。*/@PostConstruct // MyRabbitConfig 构造器创建完成以后,执行这个方法public void initRabbitTemplate() {// 设置确认回调rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {/*** 1、 只要消息抵达Broker就ack=true* @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)* @param ack 消息是否成功收到* @param cause 失败的原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {System.out.println("confirm...correlationData[" + correlationData + "]==>ack[" + ack + "]==>[cause[" + cause + "]");}});// 设置消息没有抵达队列的失败回调rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {/*** 只要消息没有投递给指定的队列,就触发这个失败回调* @param message 投递失败的消息详细信息* @param replyCode 回复的状态码* @param replyText 回复的文本内容* @param exchange 当时这个消息发给哪个交换机* @param routingKey 当时这个消息用哪个路由键*/@Overridepublic void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {System.out.println("Fail Message[" + message + "]==>replyCode[" + replyCode + "]==>replyText[" + replyText + "]==>exchange[" + exchange + "]==>routingKey[" + routingKey + "]");}});}
}

手动确认消息的api

void basicAck(long deliveryTag, boolean multiple) throws IOException; // 签收
void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException; // 拒签,可以批量
void basicReject(long deliveryTag, boolean requeue) throws IOException; // 拒签
@RabbitHandlerpublic void receiveMessage(Message message, OrderReturnReasonEntity content,Channel channel) throws InterruptedException {// {"id":1,"name":"哈哈","sort":null,"status":null,"createTime":1594892543985}System.out.println("接收到消息...内容:" + message + "===>内容:" + content);byte[] body = message.getBody();// 消息头属性信息MessageProperties messageProperties = message.getMessageProperties();
//        Thread.sleep(3000);System.out.println("消息处理完成=》" + content.getName());// channel内按顺序自增的long deliveryTag = message.getMessageProperties().getDeliveryTag();System.out.println("deliveryTag = " + deliveryTag);try {if (deliveryTag % 2 == 0) {//收货// 签收货物,非批量模式。批量模式就是将当前通道内里面的消息都ack,// 因为一个客户端只会与mq建立一条链接,客户端连接多个队列一个队列就是一个通道channel.basicAck(deliveryTag, false);System.out.println("签收了.." + deliveryTag);} else {// 退货// long deliveryTag, boolean multiple 批量处理 , boolean requeue 拒收之后是否重新发回队列// requeue=false 丢弃,requeue=true 发回服务器,服务器重新如入队 (重新入队的依据是我们的消息又发送了且在后面发送)
//                channel.basicReject(deliveryTag,true); // 不可批量channel.basicNack(deliveryTag,false,true); // 可批量System.out.println("没有签收了.." + deliveryTag);}} catch (Exception e) {// e.printStackTrace();// 网络中断} finally {}}

RabbitMQ延时队列(实现定时任务)

概述

场景:比如未付款订单,超过一定时间后,系统自动取消订单并释放占有物品。

常用解决方案:spring的schedule定时任务轮询数据库

缺点:消耗系统内存、增加了数据库的压力、存在较大的时间误差

解决: rabbitmq的消息TTL和死信Exchange结合

定时任务和rabbitmq延时队列的比较

image-20200721220700089

image-20200721220817549

延时队列的实现

延时队列的实现以一(推荐):设置队列过期时间

image-20200721150152967

流程:生产者发送消息到一个普通的交换机中,该交换机会将消息发送给一个设置过期时间的队列中。这个队列设置了ttl、路由键和死信交换机。当队列过期后,里面的消息就会发送到死信交换机中,然后死信交换机会根据消息的路由键发送给对应的队列。我们消费端就监听这个队列即可。

延时队列的实现以二:设置消息过期时间

image-20200721150526574

流程:和上一个差不过。

总结

总结:推荐给队列设置过期时间实现延时队列。因为rabbitmq采用的是惰性检查机制,比如消息队列放入了3条消息,第一条是5分钟过过期、第二条是3分钟分钟过过期、第三条是1分钟过期。rabbitmq检测到第一个入队的消息过期时间是5分钟,就会等到5分钟之后在查看队列里面的消息。而不是消息一入队就检测。这就导致后进入的消息不能及时的过期。 所以最好的办法是设置队列有效期。

消息的TTL (Time To Live)

  • 消息的TTL就是消息的存活时间
  • RabbitMQ可以对队列消息分别设置TTL
    • 对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信
    • 如果队列设置了,消息也设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。可以通过设置消息的expiration字段或者x-message-ttl属性来设置时间,两者是一样的效果

Dead Letter Exchanges (DLX)

  • 一个消息在满足如下条件下,会进死信路由,记住这里是路由而不是队列,一个路由可以对应很多队列。(什么是死信)
    • 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。 也就是说不会
      被再次放在队列里,被其他消费者使用。(basic.reject/ basic.nack) requeue=false
    • 上面的消息的TTL到了,消息过期了。
    • 一队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上
  • Dead Letter Exchange其实就是一种普通的exchange, 和创建其他exchange没有两样。只是在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。
  • 我们既可以控制消息在一段时间后变成死信,又可以控制变成死信的消息被路由到某一个指定的交换机, 结合二者,其实就可以实现一个延时队列

设计延时队列

总体设计思路

image-20200721221039958

初步设计思路:两个交换机

image-20200721221250832

最终设计方案:一个交换机

image-20200721221355473

延时队列代码编写

流程

  1. 在spring-boot环境下使用rabbitmq做定时任务(通过延时队列实现)

  2. 导入依赖:

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    
  3. 配置rabbitmq的主机地址、端口、虚拟主机地址

    spring.rabbitmq.host=192.168.1.10
    spring.rabbitmq.virtual-host=/
    spring.rabbitmq.port=5672
    
  4. 通过@Bean的方式在rabbitmq中创建出exchange、queue、binding

代码

有效期队列的设置参数:x-dead-letter-exchange: order-event-exchange,x-dead-letter-routing-key: order.release.order,x-message-ttl: 60000(1000毫秒=1秒)

注意点:只有在连接上rabbitmq的时候我们@Bean注入的exchange、queue、binding才会在rabbitmq服务器上被创建出来。连接的方式可以采用@RabbitListener 通过消费者的方式建立一条连接。

@Configuration
public class MyMQConfig { @Beanpublic Queue orderDelayQueue() {// String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments/*x-dead-letter-exchange: order-event-exchangex-dead-letter-routing-key: order.release.order x-message-ttl: 60000*/Map<String, Object> arguments = new HashMap<>();arguments.put("x-dead-letter-exchange", "order-event-exchange");arguments.put("x-dead-letter-routing-key", "order.release.order");arguments.put("x-message-ttl", 60000);return new Queue("order.delay.queue", true, false, false, arguments);}@Beanpublic Exchange orderEventExchange() {// String name, boolean durable, boolean autoDelete, Map<String, Object> argumentsreturn new TopicExchange("order-event-exchange", true, false, null);}@Beanpublic Binding orderCreateOrderBinding() {// String destination, DestinationType destinationType, String exchange, String routingKey,Map<String, Object> argumentsreturn new Binding("order.delay.queue",Binding.DestinationType.QUEUE,"order-event-exchange","order.create.order",null);}
}

如何保证消息可靠性

消息丢失问题

  • 消息发送出去,由于网络问题没有抵达服务器
    • 做好容错方法(trycatch) ,发送消息可能会网络失败,失败后要有重试机制,可记录到数据库,采用定期扫描重发的方式
    • 做好日志记录,每个消息状态是否都被服务器收到都应该记录
    • 做好定期重发,如果消息没有发送成功,定期去数据库扫描未成功的消息进行重发
  • 消息抵达Broker, Broker要将消息写入磁盘(持久化)才算成功。避免出现Broker尚未持久化完成,宕机。
    • publisher必须加入确认回调机制,确认成功的消息,修改数据库消息状态。(修改RabbitTemplate)
    • 通过confirmCallback 确认broker是否持久化了数据
  • 自动ACK的状态下。消费者收到消息,但没来得及消息然后宕机
    • 一定开启手动ACK,消费成功才移除,失败或者没来得及处理就noAck并重新入队

消息重复

  • 消息消费成功,事务已经提交, ack时,机器宕机。导致没有ack成功,Broker的消息重新由unack变为ready,并发送给其他消费者
  • 消息消费失败,由于重试机制,自动又将消息发送出去(这个是允许的
  • 成功消费,ack时宕机,消息由unack变为ready, Broker又重新发送
    • 消费者的业务消费接口应该设计为幂等性的。比如扣库存有工作单的状态标志
    • 使用防重表(redis/mysql) ,发送消息每一个都有业务的唯一 标识,处理过就不用处理
    • rabbitMQ的每一个消息都有redelivered字段,可以获取是否是被重新投递过来的,而不是第一次投递过来的

消息积压

  • 消费者宕机积压,
  • 消费者消费能力不足积压
  • 发送者发送流量太大
    • 上线更多的消费者,进行正常消费
    • 上线专门的队列消费服务,将消息先批量取出来,记录数据库,离线慢慢处理

代码实现

消息丢失问题

// 1.业务逻辑 try catch
try {// TODO 保证消息一定会发送出去,每一个消息都可以做好日记记录(给数据库保存每一个消息的详细信息)// TODO 定期扫描数据库将失败的消息在发送一遍rabbitTemplate.convertAndSend("order-event-exchange", "stock.release.other", orderTo);} catch (Exception e) {// TODO 重新发送(意义不大,万一是服务器宕机了)// while
}// 2.建表进行日志记录
DROP TABLE IF EXISTS `mq_message`;
CREATE TABLE `mq_message` (`message_id` char(32) NOT NULL,`content` text,`to_exchane` varchar(255) DEFAULT NULL,`routing_key` varchar(255) DEFAULT NULL,`class_type` varchar(255) DEFAULT NULL,`message_status` int(1) DEFAULT '0' COMMENT '0-新建 1-已发送 2-错误抵达 3-已抵达',`create_time` datetime DEFAULT NULL,`update_time` datetime DEFAULT NULL,PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;// 3.确保服务器已经将消息持久化
# 开启发送端确认消息抵达Broker
spring.rabbitmq.publisher-confirms=true@Configuration
@EnableRabbit
public class MyRabbitConfig {@PostConstruct // MyRabbitConfig 构造器创建完成以后,执行这个方法public void initRabbitTemplate() {// 设置确认回调rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {/*** 1、 只要消息抵达Broker就ack=true* @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)* @param ack 消息是否成功收到* @param cause 失败的原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {/*1、做好消息确认机制(publisher, consumer 【手动ack】)2、每一个发送的消息都在数据库做好记录。定期将失败的消息再次发送一遍*/// 服务器收到了// 修改消息的转态System.out.println("confirm...correlationData[" + correlationData + "]==>ack[" + ack + "]==>[cause[" + cause + "]");}});}// 4. 开启手动ack(使用 @RabbitListener、 @RabbitHandler() 需要@EnableRabbit)# 手动ack消息(acknowledgement)spring.rabbitmq.listener.simple.acknowledge-mode=manual@RabbitListener(queues = {"order.release.order.queue"})public class OrderCloseListener {@AutowiredOrderService orderService;@RabbitHandler()public void listener(Message message, OrderEntity entity, Channel channel) throws IOException {System.out.println("收到过期的订单信息,准备关闭订单" + entity.toString());try {orderService.closeOrder(entity);// 手动签收消息channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);} catch (Exception e) {// 消息消费失败,重新入队channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);}}
}

消息重复问题

// 1.业务操作设计成幂等性(多次操作结果是一样的)
// 先确定记录的转态,在修改
wareOrderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>().eq("task_id", id).eq("lock_status", 1));
update wms_ware_sku set stock_locked = stock_locked - #{num} where sku_id = #{skuId} and ware_id = #{wareId};// 2. 利用rabbitmq message的属性判断
Boolean redelivered = message.getMessageProperties().getRedelivered(); // 当前消息是否被第二次及以后(重新)派发过来

消息挤压:多开服务器呗

总结

  1. 最重要的问题是防止消息丢失

    1. 做好消息确认机制(publisher, consumer 【手动ack】)
    2. 每一个发送的消息都在数据库做好记录。定期将失败的消息再次发送一遍
  2. 消息重复问题:将业务设计成幂等操作(最简单就这么做)

  3. 消息挤压:多开服务器呗

最终代码

依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置文件

# ==== rabbit start ======
# rabbit 配置信息
spring.rabbitmq.host=192.168.1.10
spring.rabbitmq.virtual-host=/
spring.rabbitmq.port=5672# 开启发送端确认消息抵达Broker
spring.rabbitmq.publisher-confirms=true# 开启发送端消息抵达队列的确认
spring.rabbitmq.publisher-returns=true
# 只要消息抵达队列,会异步发送优先回调我们这个returnConfirm
spring.rabbitmq.template.mandatory=true# 手动ack消息(acknowledgement)
spring.rabbitmq.listener.simple.acknowledge-mode=manual
# ==== rabbit end ======

配置类(初始化rabbitTemplate)

package com.atguigu.gulimall.order.config;import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.annotation.PostConstruct;/*** @author: haitao* @email: haitaoss@aliyun.com* @date: 2020/7/16 17:25:57*/
@Configuration
@EnableRabbit
public class MyRabbitConfig {@AutowiredRabbitTemplate rabbitTemplate;@Beanpublic MessageConverter Jackson2JsonMessageConverter() {return new Jackson2JsonMessageConverter();}/*** 定制RabbitTemplate* 1、服务端收到消息就回调*  1、spring.rabbitmq.publisher-confirms=true*  2、设置确认回调ConfirmCallback** 2、消息没有正确抵达队列进行回调*  1、spring.rabbitmq.publisher-returns=true、spring.rabbitmq.template.mandatory=true*  2、设置失败回调ReturnCallback**  3、消费端确认(保证每个消息被正确消费,此时才可以broker删除这个消息)*  spring.rabbitmq.listener.simple.acknowledge-mode=manual 开启手动签收*  1、默认是自动确认的,只要消息接受到,消费端端会自动确认,服务端就会移除这个消息。*      问题:*          我们收到很多消息,自动回复给服务端ack。只有一个消息处理成功,宕机了。发生消息丢失。(通道一打开就回复ack)*          手动确认模式。只要我们没有明确告诉MQ,消息就一直是unacked转态。即使consumer宕机。队列里面的消息也不会丢失,consumer宕机则队列里面的消息会变为ready,*          下次有新的consumer连接进来就发给他(队列的消息变成unacked)。队列消息的转态是根据有没有消费者连接上队列*  2、如何签收:*       channel.basicAck(deliveryTag, false);签收:业务成功完成就应该签收*       channel.basicNack(deliveryTag,false,true); 拒签:业务失败,拒签。*/@PostConstruct // MyRabbitConfig 构造器创建完成以后,执行这个方法public void initRabbitTemplate() {// 设置确认回调rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {/*** 1、 只要消息抵达Broker就ack=true* @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)* @param ack 消息是否成功收到* @param cause 失败的原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {/*1、做好消息确认机制(publisher, consumer 【手动ack】)2、每一个发送的消息都在数据库做好记录。定期将失败的消息再次发送一遍*/// 服务器收到了// 修改消息的转态System.out.println("confirm...correlationData[" + correlationData + "]==>ack[" + ack + "]==>[cause[" + cause + "]");}});// 设置消息没有抵达队列的失败回调rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {/*** 只要消息没有投递给指定的队列,就触发这个失败回调* @param message 投递失败的消息详细信息* @param replyCode 回复的状态码* @param replyText 回复的文本内容* @param exchange 当时这个消息发给哪个交换机* @param routingKey 当时这个消息用哪个路由键*/@Overridepublic void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {//报错误了 。修改数据库当前消息的状态->错误。System.out.println("Fail Message[" + message + "]==>replyCode[" + replyCode + "]==>replyText[" + replyText + "]==>exchange[" + exchange + "]==>routingKey[" + routingKey + "]");}});}
}

通过@Bean创建exchange、queue、binding

package com.atguigu.gulimall.order.config;import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.HashMap;
import java.util.Map;/*** @author: haitao* @email: haitaoss@aliyun.com* @date: 2020/7/21 15:36:58* @Description*/
@Configuration
public class MyMQConfig {// spring boot 支持通过@Bean的方式创建出Exchange、Queue、Binding。他会自动使用amqpAdmin帮我创建到指定的消息中间件中/*** 容器中的Binding, Queue, Exchange 都会自动创建(RabbitMQ没有的情况)* RabbitMQ只要有。@Bean声明的属性发生变化也不会覆盖** @return*/@Beanpublic Queue orderDelayQueue() {// String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments/*x-dead-letter-exchange: order-event-exchangex-dead-letter-routing-key: order.release.orderx-message-ttl: 60000*/Map<String, Object> arguments = new HashMap<>();arguments.put("x-dead-letter-exchange", "order-event-exchange");arguments.put("x-dead-letter-routing-key", "order.release.order");arguments.put("x-message-ttl", 60000);return new Queue("order.delay.queue", true, false, false, arguments);}@Beanpublic Queue orderReleaseQueue() {return new Queue("order.release.order.queue", true, false, false, null);}@Beanpublic Exchange orderEventExchange() {// String name, boolean durable, boolean autoDelete, Map<String, Object> argumentsreturn new TopicExchange("order-event-exchange", true, false, null);}@Beanpublic Binding orderCreateOrderBinding() {// String destination, DestinationType destinationType, String exchange, String routingKey,Map<String, Object> argumentsreturn new Binding("order.delay.queue",Binding.DestinationType.QUEUE,"order-event-exchange","order.create.order",null);}@Beanpublic Binding orderReleaseOrderBinding() {// String destination, DestinationType destinationType, String exchange, String routingKey,Map<String, Object> argumentsreturn new Binding("order.release.order.queue",Binding.DestinationType.QUEUE,"order-event-exchange","order.release.order",null);}/*** 订单释放直接和库存释放进行绑定* (因为订单解锁消息的发送可能由于网络问题,比库存解锁消息发出的慢。导致库存释放解锁了。* 库存释放解锁逻辑是获取订单的转态不是创建就解锁库存,然后只要不报错就把消息消费掉。)** @return*/@Beanpublic Binding orderReleaseOtherBinding() {// String destination, DestinationType destinationType, String exchange, String routingKey,Map<String, Object> argumentsreturn new Binding("stock.release.stock.queue",Binding.DestinationType.QUEUE,"order-event-exchange","stock.release.other.#",null);}
}

通过api创建exchange、queue、binding

public class GulimallOrderApplicationTests {@AutowiredAmqpAdmin amqpAdmin;@AutowiredRabbitTemplate rabbitTemplate;/*** 1、如何创建Exchange、Queue、Binding(AmqpAdmin)* 2、如何收发消息(RabbitTemplate)*/@Testpublic void contextLoads() {/*String name, boolean durable, boolean autoDelete, Map<String, Object> arguments*/DirectExchange directExchange = new DirectExchange("hello-java-exchange", true, false);amqpAdmin.declareExchange(directExchange);log.info("Exchange创建成功【{}】", directExchange.getName());}@Testpublic void testCreateQueue() {// exclusive true 就是只有一个消费者能连接Queue queue = new Queue("hello-java-queue", true, false, false);amqpAdmin.declareQueue(queue);log.info("Queue创建成功【{}】", queue.getName());}@Testpublic void testCreateBinding() {/*String destination, DestinationType destinationType, String exchange, String routingKey,Map<String, Object> arguments*/// 将exchange指定的交换机和destination目的地进行绑定,目的地的类型是什么,使用作为指定的路由键routingKeyBinding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE, "hello-java-exchange", "hello-java-queue", null);amqpAdmin.declareBinding(binding);log.info("Binding创建成功");}@Testpublic void testSendMessage() {// 发送消息,如果发送的消息是个对象,我们会使用序列化机制,将对象写出去。对象必须实现Serializable接口String msg = "hello world";// 发送对象类型的消息 可以是json个格式的,自定义MessageConverter即可// rabbitTemplate.convertAndSend("hello-java-exchange", "hello-java-queue", msg);for (int i = 0; i < 10; i++) {if (i % 2 == 0) {OrderReturnReasonEntity orderReturnReasonEntity = new OrderReturnReasonEntity();orderReturnReasonEntity.setId(1L);orderReturnReasonEntity.setCreateTime(new Date());orderReturnReasonEntity.setName("哈哈" + i);rabbitTemplate.convertAndSend("hello-java-exchange", "hello-java-queue", orderReturnReasonEntity);}else{OrderEntity orderEntity = new OrderEntity();orderEntity.setOrderSn(UUID.randomUUID().toString());rabbitTemplate.convertAndSend("hello-java-exchange", "hello-java-queue", orderEntity);}}log.info("消息发送完成:{}");}}

建表解决消息的丢失问题

// 2.建表进行日志记录
DROP TABLE IF EXISTS `mq_message`;
CREATE TABLE `mq_message` (`message_id` char(32) NOT NULL,`content` text,`to_exchane` varchar(255) DEFAULT NULL,`routing_key` varchar(255) DEFAULT NULL,`class_type` varchar(255) DEFAULT NULL,`message_status` int(1) DEFAULT '0' COMMENT '0-新建 1-已发送 2-错误抵达 3-已抵达',`create_time` datetime DEFAULT NULL,`update_time` datetime DEFAULT NULL,PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

监听队列获取消息

@Service
@RabbitListener(queues = {"order.release.order.queue"})
public class OrderCloseListener {@AutowiredOrderService orderService;@RabbitHandler()public void listener(Message message, OrderEntity entity, Channel channel) throws IOException {System.out.println("收到过期的订单信息,准备关闭订单" + entity.toString());try {orderService.closeOrder(entity);// 手动签收消息channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);} catch (Exception e) {// 消息消费失败,重新入队channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);}}
}
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 精通Git

    精通Git下载地址: https://pan.baidu.com/s/12kJefrDUhvsPhvfBug2VEA扫码下面二维码关注公众号回复100012 获取分享码本书目录结构如下:ProGit. . . . . . . . . . . . . . . . . . . . . . 1Scott Chacon 序 . . . . . . . . . . . . . . . . . . . . . 2Ben Straub 序 . .…...

    2024/4/30 2:26:18
  2. 整理使用RecyclerView控件在长按删除事件中的用法(使用Kotlin)

    一、前言: 在最近的学习中,使用Recyclerview控件时,遇到需要长按删除的场景,在测试过程中,遇到各种崩溃,在这里总结一下: 在本博客汇中,使用了一个统计学生信息的demo,并且用到了SQLite,这样保证长按删除的同时数据库也能同步数据,先上一下长按删除的效果图:二、代…...

    2024/4/30 2:26:26
  3. SV并发线程

    写在前面:冲冲冲Verilog典型的并发语句集合initial语句:在整个仿真时间内只执行一次, initial语句都是并发的always语句:可以对组合电路和时序电路进行建模, always语句都是并发的assign语句:可以对组合电路进行建模, assign语句都是并发的beginend:语句从上往下,顺序…...

    2024/4/30 2:26:09
  4. HANA数据库恢复体验

    刚才一个开发人员告诉我,他删除了一个SAP标准功能。 评估后,我开始恢复开发环境到今天早上8:00,只用了5分钟不到,他再次进入系统,SAP标准功能回来了。这就是HANA,试问DB2,ORACLE管理有这么方便吗?...

    2024/4/30 2:26:08
  5. Vue X入门

    Vue X入门安装使用导入项目在项目中使用 安装 使用NPM安装 npm install vuex --save使用Yarn安装 yarn add vuex使用 导入项目 在项目src目录下创建store文件夹,并在该文件夹中创建index.js打开index.js 导入vuex 并用vue使用它 import Vue from vue import Vuex from vuexVue…...

    2024/4/30 2:26:02
  6. 前端-ES6

    一、简介 ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版。 ES6 主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念,但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功…...

    2024/4/30 2:26:06
  7. js中[]、{}、()区别

    一、{ } 大括号,表示定义一个对象,大部分情况下要有成对的属性和值,或是函数体{}表示对象、[]表示对象的属性、方法,()如果用在方法名后面,代表调用如:var LangShen = {"Name":"Langshen","AGE":”28”}; 上面声明了一个名为“LangShen”的…...

    2024/4/30 2:25:52
  8. 揭秘快手直播带货网红KOL/大V们满满的赚钱套路!

    今年是直播带货的元年,风口流量特别大,尤其是快手直播在电商带货发展趋势也是不断发展壮大。你羡慕薇娅们、辛有志为首的818家族、韩国媳妇大璐璐们的那么,让我们一起来揭秘这些快手直播带货大V们成功的秘诀是什么吧!一、新玩法去年大家都在谈私域流量,今年则是直播带货,…...

    2024/4/30 2:25:54
  9. 合成模糊图像

    参考:https://www.cnblogs.com/arkenstone/p/8480759.html1) 运动模糊图像一般来说,运动模糊的图像都是朝同一方向运动的,那么就可以利用cv2.filter2D函数。import numpy as npdef motion_blur(image, degree=10, angle=20):image = np.array(image)# 这里生成任意角度的运…...

    2024/4/29 6:31:10
  10. JDK动态代理

    JDK动态代理是Java官方的代理 使用JDK官方的Proxy类创建代理对象需要通过Proxy类创建代理对象 创建代理对象必须要一个代理处理类JDK动态代理API分析 1、java.lang.reflect.Proxy 类 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成…...

    2024/4/30 2:25:42
  11. Docker OCI runtime exec failed: exec failed: container_linux.go:344: starting container process caus

    docker执行命令:docker exec -it 1e33b26152e1 /bin/bash 在进入容器报错:OCI runtime exec failed: exec failed: container_linux.go:344: starting container process caused "exec: \"/bin/bash\": stat /bin/bash: no such file or directory": unkn…...

    2024/4/29 1:01:18
  12. 排序与搜索

    1、排序算法 排序算法(Sorting algorithm) 是一种能将一串数据依照特定顺序进行排列的一种算法。 2、排序算法的稳定性 稳定性: 稳定排序算法会让原本有相等键值的记录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的记录 R 和 S ,且在原本的列表中 R 出…...

    2024/4/30 2:25:34
  13. 设计模式10——原型模式(protype)

    “对象创建模式” 通过“对象创建”模式绕开new,来避免对象创建(new)过程中所导致的紧耦合,从而支持对象创建的稳定。它是接口抽象之后的第一部工作。 动机 在软件系统中,有时候面临着“一个复杂对象“的创建工作其通常由各个部分的子对象用一定的算法构成;由于需求的变化…...

    2024/4/30 2:25:33
  14. Layui+ajax+Springboot

    之前写了一个demo,用ajax异步发送请求,后端一直无法接收到参数。排查了很久,少加了请求头。前两天在一个交流群也发现群友遇到同样的问题。所以想记录一下Layui的这个坑。遇到过两次了。 前端:Layui 后端:Springboot 直接上代码,Ajax请求,最重要的就是这一行 contentTyp…...

    2024/4/30 2:25:30
  15. 密码学之md5还原大师

    我们得到了一串神秘字符串:TASC?O3RJMV?WDJKX?ZM,问号部分是未知大写字母,为了确定这个神秘字符串,我们通过了其他途径获得了这个字串的32位MD5码。但是我们获得它的32位MD5码也是残缺不全,E903???4DAB????08?????51?80??8A?,请猜出神秘字符串的原本模样…...

    2024/5/3 10:42:14
  16. 20200817git相关

    Git的使用 一.使用前配置 需要告诉git你是谁,在向git仓库中提交时需要用到 1.备置提交人姓名:git config --global user.name 提交人姓名 2.备置提交人邮箱:git config --global user.email 提交人邮箱 3.查看git配置信息:git config --list 注意: 1.如果需要修改配置信息…...

    2024/4/30 2:25:19
  17. 操作系统作业1-虚拟机中安装Ubuntu 20.04操作系统

    题目:在虚拟机中安装Ubuntu 20.04操作系统,体验Linux操作系统。要求:把操作过程的关键步骤截图保存,自选两张截图中写入学号和姓名,并用文字记录整个安装过程,写成文档发布在https://blog.csdn.net/。每位同学提交自己csdn博客地址。资源地址(复制地址到迅雷或浏览器即可…...

    2024/4/30 2:25:17
  18. python代码的中断调试操作

    如果python的集成软件上,可以通过软件的自动调试程序对代码进行调试,比如VSCode. 但是如果没有调试按键,该如何调试? 可以是使用代码: import pdb pdb.set_trace() #--------------设置断点,可用于在终端的调试只需要把pdb写在需要断点的位置即可,代码执行到这里将自动…...

    2024/5/3 18:28:07
  19. redis设计与实现学习---(四)独立功能的实现

    一 发布与订阅 1 简介 Redis的发布与订阅功能由PUBLISH、SUBSCRIBE、PSUBSCRIBE等命令组成。 SUBSCRIBE “news.it” PUBLISH “news.it” “hello” PSUBSCRIBE命令订阅一个或多个模式,从 而成为这些模式的订阅者:每当有其他客户端向某个频道发送消息时,消息不仅会被发送给…...

    2024/4/30 2:25:12
  20. 如何使用memcpy向容器里拷贝数据

    无疑容器(std::vector<>std::vector<>)在开发的过程中作为动态数组的首选,但是直接使用push和pop存取的话在很多时候显得很无力。 例如在图形处理的时候如果想用std::vector bitmap来存放图像数据,当向bitmap里拷贝数据的时候如果一个字节一个字节的压,我想谁…...

    2024/4/29 2:44:27

最新文章

  1. sql-server--索引

    SQL Server提供了五种索引类型&#xff0c;它们分别是 唯一索引 主键索引 聚集索引 &#xff08; Clustered Index &#xff09; 非聚集索引 &#xff08; Nonclustered Index &#xff09; 视图索引 --tb_student表的sno列创建索引 create index ix_sno on tb_student(…...

    2024/5/4 15:35:47
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 基于springboot实现影城管理系统项目【项目源码+论文说明】

    基于springboot实现影城管理系统演示 摘要 随着现在网络的快速发展&#xff0c;网上管理系统也逐渐快速发展起来&#xff0c;网上管理模式很快融入到了许多生活之中&#xff0c;随之就产生了“小徐影城管理系统”&#xff0c;这样就让小徐影城管理系统更加方便简单。 对于本小…...

    2024/5/3 2:03:48
  4. axios拦截器:每次请求自动带上 token

    Step 1&#xff1a;创建Axios实例并添加拦截器 在你的Vue项目中&#xff0c;一般我们会先导入axios&#xff0c;然后创建一个axios实例。这样做是为了方便统一管理和配置。 import axios from axios; // 引入axios// 创建一个axios实例 const service axios.create();// 添加请…...

    2024/4/30 17:13:59
  5. 416. 分割等和子集问题(动态规划)

    题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义&#xff1a;dp[i][j]表示当背包容量为j&#xff0c;用前i个物品是否正好可以将背包填满&#xff…...

    2024/5/4 12:05:22
  6. 【Java】ExcelWriter自适应宽度工具类(支持中文)

    工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...

    2024/5/4 11:23:32
  7. Spring cloud负载均衡@LoadBalanced LoadBalancerClient

    LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon&#xff0c;直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件&#xff0c;我们讨论Spring负载均衡以Spring Cloud2020之后版本为主&#xff0c;学习Spring Cloud LoadBalance&#xff0c;暂不讨论Ribbon…...

    2024/5/4 14:46:16
  8. TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案

    一、背景需求分析 在工业产业园、化工园或生产制造园区中&#xff0c;周界防范意义重大&#xff0c;对园区的安全起到重要的作用。常规的安防方式是采用人员巡查&#xff0c;人力投入成本大而且效率低。周界一旦被破坏或入侵&#xff0c;会影响园区人员和资产安全&#xff0c;…...

    2024/5/3 16:00:51
  9. VB.net WebBrowser网页元素抓取分析方法

    在用WebBrowser编程实现网页操作自动化时&#xff0c;常要分析网页Html&#xff0c;例如网页在加载数据时&#xff0c;常会显示“系统处理中&#xff0c;请稍候..”&#xff0c;我们需要在数据加载完成后才能继续下一步操作&#xff0c;如何抓取这个信息的网页html元素变化&…...

    2024/5/4 12:10:13
  10. 【Objective-C】Objective-C汇总

    方法定义 参考&#xff1a;https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...

    2024/5/3 21:22:01
  11. 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】

    &#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格…...

    2024/5/3 23:17:01
  12. 【ES6.0】- 扩展运算符(...)

    【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数&#xff0…...

    2024/5/4 14:46:12
  13. 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?

    文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕&#xff0c;各大品牌纷纷晒出优异的成绩单&#xff0c;摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称&#xff0c;在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁&#xff0c;多个平台数据都表现出极度异常…...

    2024/5/4 14:46:11
  14. Go语言常用命令详解(二)

    文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令&#xff0c;这些命令可以帮助您在Go开发中进行编译、测试、运行和…...

    2024/5/4 14:46:11
  15. 用欧拉路径判断图同构推出reverse合法性:1116T4

    http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b&#xff0c;我们在 a i a_i ai​ 和 a i 1 a_{i1} ai1​ 之间连边&#xff0c; b b b 同理&#xff0c;则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然&#xff0…...

    2024/5/4 2:14:16
  16. 【NGINX--1】基础知识

    1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息&#xff0c;并安装一些有助于配置官方 NGINX 软件包仓库的软件包&#xff1a; apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...

    2024/5/3 16:23:03
  17. Hive默认分割符、存储格式与数据压缩

    目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限&#xff08;ROW FORMAT&#xff09;配置标准HQL为&#xff1a; ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...

    2024/5/4 12:39:12
  18. 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法

    文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中&#xff0c;传感器和控制器产生大量周…...

    2024/5/4 13:16:06
  19. --max-old-space-size=8192报错

    vue项目运行时&#xff0c;如果经常运行慢&#xff0c;崩溃停止服务&#xff0c;报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中&#xff0c;通过JavaScript使用内存时只能使用部分内存&#xff08;64位系统&…...

    2024/5/3 14:57:24
  20. 基于深度学习的恶意软件检测

    恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞&#xff0c;例如可以被劫持的合法软件&#xff08;例如浏览器或 Web 应用程序插件&#xff09;中的错误。 恶意软件渗透可能会造成灾难性的后果&#xff0c;包括数据被盗、勒索或网…...

    2024/5/4 14:46:05
  21. JS原型对象prototype

    让我简单的为大家介绍一下原型对象prototype吧&#xff01; 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定&#xff0c;每一个构造函数都有一个 prototype 属性&#xff0c;指向另一个对象&#xff0c;所以我们也称为原型对象…...

    2024/5/4 2:00:16
  22. C++中只能有一个实例的单例类

    C中只能有一个实例的单例类 前面讨论的 President 类很不错&#xff0c;但存在一个缺陷&#xff1a;无法禁止通过实例化多个对象来创建多名总统&#xff1a; President One, Two, Three; 由于复制构造函数是私有的&#xff0c;其中每个对象都是不可复制的&#xff0c;但您的目…...

    2024/5/3 22:03:11
  23. python django 小程序图书借阅源码

    开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…...

    2024/5/4 9:07:39
  24. 电子学会C/C++编程等级考试2022年03月(一级)真题解析

    C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...

    2024/5/4 14:46:02
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57