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

RabbitMQ消息可靠性保证机制3--消费端ACK机制

消费端ACK机制

​ 在这之前已经完成了发送端的确认机制。可以保证数据成功的发送到RabbitMQ,以及持久化机制,然尔这依然无法完全保证整个过程的可靠性,因为如果消息被消费过程中业务处理失败了,但是消息却已经被标记为消费了,如果又没有任何重度机制,那结果基本等于丢消息。在消费端如何保证消息不丢呢?

在rabbitMQ的消费端会有ACK机制。即消费端消费消息后需要发送ACK确认报文给Broker端,告知自己是否已经消费完成,否则可能会一直重发消息直到消息过期(AUTO模式)。同时这个也是最终一致性、可恢复性的基础。一般有如下手段:
  1. 采用NONE模式,消费的过程中自行捕捉异常,引发异常后直接记录日志并落到异常处理表,再通过后台定时任务扫描异常恢复表做重度动作。如果业务不自行处理则有丢失数据的风险。
  2. 采用AUTO(自动ACK)模式,不主动捕获异常,当消费过程中出现异常时,会将消息放回Queue中,然后消息会被重新分配到其他消费节点(如果没有则还是选择当前节点)重新被消费,默认会一直重发消息并直到消费完成返回ACK或者一直到过期。
  3. 采用MANUAL(手动ACK)模式,消费者自行控制流程并手动调用channel相关的方法返回ACK。
7.6.1 手动ACK机制-Reject

maven导入

            <dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.9.0</version></dependency>

生产者

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;import java.nio.charset.StandardCharsets;public class Product {public static void main(String[] args) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setUri("amqp://root:123456@node1:5672/%2f");try (Connection connection = factory.newConnection();Channel channel = connection.createChannel(); ) {// 定义交换器队列channel.exchangeDeclare("ack.ex", BuiltinExchangeType.DIRECT, false, false, false, null);// 定义队列channel.queueDeclare("ack.qu", false, false, false, null);// 队列绑定channel.queueBind("ack.qu", "ack.ex", "ack.rk");for (int i = 0; i < 5; i++) {byte[] sendBytes = ("hello-" + i).getBytes(StandardCharsets.UTF_8);channel.basicPublish("ack.ex", "ack.rk", null, sendBytes);}} catch (Exception e) {e.printStackTrace();}}
}

消费者

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;public class Consumer {public static void main(String[] args) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setUri("amqp://root:123456@node1:5672/%2f");Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare("ack.qu", false, false, false, null);DefaultConsumer consumer =new DefaultConsumer(channel) {@Overridepublic void handleDelivery(// 消费者标签String consumerTag,// 消费者的封装Envelope envelope,// 消息属性AMQP.BasicProperties properties,// 消息体byte[] body)throws IOException {System.out.println("确认的消息内容:" + new String(body));// 找收消息// Nack与Reject的区别在于,nack可以对多条消息进行拒收,而reject只能拒收一条。// requeue为true表示不确认的消息会重新放回队列。channel.basicReject(envelope.getDeliveryTag(), true);}};channel.basicConsume("ack.qu",// 非自动确认false,// 消费者的标签"ack.consumer",// 回调函数consumer);}
}

发送测试

首先执行生产者向队列中发送数据。然后执行消费者,检查拒收的处理。

在消费者的控制台,将持续不断的输出消息信息:

确认的消息内容:hello-0
确认的消息内容:hello-1
确认的消息内容:hello-2
确认的消息内容:hello-3
确认的消息内容:hello-4
确认的消息内容:hello-0
确认的消息内容:hello-1
......
确认的消息内容:hello-0

按照发送的顺序将不断的被打印。

那此时消息是什么状态呢?查看下消息队列中的信息

[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0551         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]# 

可以看到当前的消息处于unack的状态。由于消息被不断的重新放回队列,而消费者又只有当前这一个,所以,在不断拒收中被放回。

那如果将消息拒绝改为不重新放回队列,会如何呢?来验证下。

首先修改消费者的代码:

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;public class Consumer {public static void main(String[] args) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setUri("amqp://root:123456@node1:5672/%2f");Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare("ack.qu", false, false, false, null);DefaultConsumer consumer =new DefaultConsumer(channel) {@Overridepublic void handleDelivery(// 消费者标签String consumerTag,// 消费者的封装Envelope envelope,// 消息属性AMQP.BasicProperties properties,// 消息体byte[] body)throws IOException {System.out.println("确认的消息内容:" + new String(body));// 找收消息// Nack与Reject的区别在于,nack可以对多条消息进行拒收,而reject只能拒收一条。// requeue为false表示不确认的消息不会重新放回队列。//channel.basicReject(envelope.getDeliveryTag(), true);channel.basicReject(envelope.getDeliveryTag(), false);}};channel.basicConsume("ack.qu",// 非自动确认false,// 消费者的标签"ack.consumer",// 回调函数consumer);}
}

再次执行消费者。

确认的消息内容:hello-0
确认的消息内容:hello-1
确认的消息内容:hello-2
确认的消息内容:hello-3
确认的消息内容:hello-4

而这一次消息没有再循环打印。只输出一遍,再检查下消息在队列中的状态:

[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0001         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]# 

通过观察发现,消息已经没有在队列中了,那就是消息已经被丢弃了。

7.6.2 手动ACK机制-ack

消费者修改为ACK确认处理

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;import java.io.IOException;public class Consumer {public static void main(String[] args) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setUri("amqp://root:123456@node1:5672/%2f");Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare("ack.qu", false, false, false, null);DefaultConsumer consumer =new DefaultConsumer(channel) {@Overridepublic void handleDelivery(// 消费者标签String consumerTag,// 消费者的封装Envelope envelope,// 消息属性AMQP.BasicProperties properties,// 消息体byte[] body)throws IOException {System.out.println("确认的消息内容:" + new String(body));// 消息确认,并且非批量确认,multiple为false,表示只确认了单条channel.basicAck(envelope.getDeliveryTag(), false);}};channel.basicConsume("ack.qu",// 非自动确认false,// 消费者的标签"ack.consumer",// 回调函数consumer);}
}

此时可以先运行消息者。等待消息推送。然后运行生产者将消息推送,此时便可以看到消费者的控制台输出:

确认的消息内容:hello-0
确认的消息内容:hello-1
确认的消息内容:hello-2
确认的消息内容:hello-3
确认的消息内容:hello-4

观察队列中的信息

[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0001         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]# 

在队列中,消息已经被成功的消费了。

7.6.3 手动ACK机制-nack
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;public class Consumer {public static void main(String[] args) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setUri("amqp://root:123456@node1:5672/%2f");Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare("ack.qu", false, false, false, null);DefaultConsumer consumer =new DefaultConsumer(channel) {@Overridepublic void handleDelivery(// 消费者标签String consumerTag,// 消费者的封装Envelope envelope,// 消息属性AMQP.BasicProperties properties,// 消息体byte[] body)throws IOException {System.out.println("确认的消息内容:" + new String(body));// 消息批量不确认,即批量丢弃,每5个做一次批量消费// 参数1,消息的标签// multiple为false 表示不确认当前是一个消息。true就是多个消息。// requeue为true表示不确认的消息会重新放回队列。// 每5条做一次批量确认,_deliveryTag从1开始if (envelope.getDeliveryTag() % 5 == 0) {System.out.println("批量确认执行");channel.basicNack(envelope.getDeliveryTag(), true, false);}}};channel.basicConsume("ack.qu",// 非自动确认false,// 消费者的标签"ack.consumer",// 回调函数consumer);}
}

执行消费者程序,然后再执行生产者。查看消费端的控制台:

确认的消息内容:hello-0
确认的消息内容:hello-1
确认的消息内容:hello-2
确认的消息内容:hello-3
确认的消息内容:hello-4
批量确认执行

由于此处采用的是不重新放回队列,所以,数据接收到之后被丢弃了。

[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0000         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘

队列中的数据也已经被处理掉了。

7.6.4 手动ACK机制-SpringBoot

首先是Maven导入

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId><version>2.2.8.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.2.8.RELEASE</version></dependency>

配制文件application.yml

spring:application:name: consumer-ackrabbitmq:host: node1port: 5672virtual-host: /username: rootpassword: 123456# 配制消费端ack信息。listener:simple:acknowledge-mode: manual# 重试超过最大次数后是否拒绝default-requeue-rejected: falseretry:# 开启消费者重度(false时关闭消费者重试,false不是不重试,而是一直收到消息直到ack确认或者一直到超时)enable: true# 最大重度次数max-attempts: 5# 重试间隔时间(单位毫秒)initial-interval: 1000

主启动类

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;import java.nio.charset.StandardCharsets;@SpringBootApplication
public class Main {@Autowired private RabbitTemplate rabbitTemplate;public static void main(String[] args) {SpringApplication.run(Main.class, args);}/*** 在启动后就开始向MQ中发送消息** @return*/@Beanpublic ApplicationRunner runner() {return args -> {Thread.sleep(5000);for (int i = 0; i < 10; i++) {MessageProperties props = new MessageProperties();props.setDeliveryTag(i);Message message = new Message(("消息:" + i).getBytes(StandardCharsets.UTF_8), props);rabbitTemplate.convertAndSend("ack.ex", "ack.rk", message);}};}
}

当主类启动后,会延迟5秒,向MQ中发送10条记录。

队列配制

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitConfig {@Beanpublic Queue queue() {return new Queue("ack.qu", false, false, false, null);}@Beanpublic Exchange exchange(){return new DirectExchange("ack.ex",false,false,null);}@Beanpublic Binding binding(){return BindingBuilder.bind(queue()).to(exchange()).with("ack.rk").noargs();}
}

使用推送模式来查确认消息

监听器,MQ队列推送消息至listener

import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.util.concurrent.ThreadLocalRandom;@Component
public class MessageListener {/*** NONE模式,则只要收到消息后就立即确认(消息出列,标识已消费),有丢数据风险** <p>AUTO模式,看情况确认,如果此时消费者抛出异常则消息会返回队列中** <p>WANUAL模式,需要显示的调用当前channel的basicAck方法** @param channel* @param deliveryTag* @param msg*/// @RabbitListener(queues = "ack.qu", ackMode = "AUTO")// @RabbitListener(queues = "ack.qu", ackMode = "NONE")@RabbitListener(queues = "ack.qu", ackMode = "MANUAL")public void handMessageTopic(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, @Payload String msg) {System.out.println("消息内容:" + msg);ThreadLocalRandom current = ThreadLocalRandom.current();try {if (current.nextInt(10) % 3 != 0) {// 手动nack,告诉broker消费者处理失败,最后一个参数表示是否需要将消息重新入列// channel.basicNack(deliveryTag, false, true);// 手动拒绝消息,第二个参数表示是否重新入列channel.basicReject(deliveryTag, true);} else {// 手动ACK,deliveryTag表示消息的唯一标志,multiple表示是否批量确认channel.basicAck(deliveryTag, false);System.out.println("已经确认的消息" + msg);}Thread.sleep(ThreadLocalRandom.current().nextInt(500, 3000));} catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {}}
}

消息有33%的概率被拒绝,这样又会被重新放回队列,等待下次推送。

启动测试

运行main方法

【确认】消息内容:消息:0
【拒绝】消息内容:消息:1
【拒绝】消息内容:消息:2
【拒绝】消息内容:消息:3
【确认】消息内容:消息:4
【确认】消息内容:消息:5
【拒绝】消息内容:消息:6
【拒绝】消息内容:消息:7
【拒绝】消息内容:消息:8
【拒绝】消息内容:消息:9
【确认】消息内容:消息:1
【拒绝】消息内容:消息:2
【拒绝】消息内容:消息:3
【拒绝】消息内容:消息:6
【确认】消息内容:消息:7
【确认】消息内容:消息:8
【拒绝】消息内容:消息:9
【拒绝】消息内容:消息:2
【拒绝】消息内容:消息:3
【拒绝】消息内容:消息:6
【确认】消息内容:消息:9
【确认】消息内容:消息:2
【拒绝】消息内容:消息:3
【拒绝】消息内容:消息:6
【确认】消息内容:消息:3
【拒绝】消息内容:消息:6
【确认】消息内容:消息:6

从观察到的结果也印证了,反复的被推送,接收的一个过程中,使用命令查看队列的一个消费的情况

[root@nullnull-os rabbitmq]#  rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0661         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]#  rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0111         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]#  rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0001         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘

使用拉确认消息

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.GetResponse;
import org.springframework.amqp.rabbit.core.ChannelCallback;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.nio.charset.StandardCharsets;
import java.util.concurrent.ThreadLocalRandom;@RestController
public class MsgController {@Autowired private RabbitTemplate rabbitTemplate;@RequestMapping("/msg")public String getMessage() {String message =rabbitTemplate.execute(new ChannelCallback<String>() {@Overridepublic String doInRabbit(Channel channel) throws Exception {GetResponse getResponse = channel.basicGet("ack.qu", false);if (null == getResponse) {return "你已经消费完所有的消息";}String message = new String(getResponse.getBody(), StandardCharsets.UTF_8);if (ThreadLocalRandom.current().nextInt(10) % 3 == 0) {// 执行消息确认操作channel.basicAck(getResponse.getEnvelope().getDeliveryTag(), false);return "已确认的消息:" + message;} else {// 拒收一条消息并重新放回队列channel.basicReject(getResponse.getEnvelope().getDeliveryTag(), true);return "拒绝的消息:" + message;}}});return message;}
}

在浏览器中访问,同样有66%的概率会被拒绝,仅33%会被确认。

注:如果与监听在同一个工程,需将监听器给注释。

启动main函数,在浏览器中访问。http://127.0.0.1:8080/msg

可以看到返回:

拒绝的消息:消息:0
已确认的消息:消息:1
拒绝的消息:消息:2
......
已确认的消息:消息:9
你已经消费完所有的消息

同样的观察队列的一个消费情况:

[root@nullnull-os rabbitmq]#  rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 8080         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]#  rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 3030         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]#  rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ack.qu        │ 0000         │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ persistent.qu │ 1010         │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┘
[root@nullnull-os rabbitmq]#

使用拉模式进行消息ACK确认也已经完成。

相关文章:

RabbitMQ消息可靠性保证机制3--消费端ACK机制

消费端ACK机制 ​ 在这之前已经完成了发送端的确认机制。可以保证数据成功的发送到RabbitMQ&#xff0c;以及持久化机制&#xff0c;然尔这依然无法完全保证整个过程的可靠性&#xff0c;因为如果消息被消费过程中业务处理失败了&#xff0c;但是消息却已经被标记为消费了&…...

Copilot在Pycharm的应用和示例

Copilot 是 Github 在 2021 年发布的 AI 代码助手工具&#xff0c;它可以根据你提供的上下文信息&#xff0c;自动生成代码建议&#xff0c;帮助提高代码编写效率和准确性。在 Pycharm 中使用 Copilot&#xff0c;可以进一步提升 Python 开发效率&#xff0c;本文将分享如何在 …...

搜维尔科技:【简报】第九届元宇宙数字人设计大赛,报名已经进入白热化阶段!

随着元宇宙时代的来临&#xff0c;数字人设计成为了创新前沿领域之一。为了提高大学生元宇宙虚拟人角色策划与美术设计的专业核心能力&#xff0c;我们特别举办了这场元宇宙数字人设计赛道&#xff0c;赛道主题为「AI人工智能科技」 &#xff0c;只要与「AI人工智能科技」相关的…...

性能检测自动化(含内存泄露检测)

一、平台侧实现方案 1、UI case重复执行N次:进入页面,sleep 5s,记录start_time,sleep 30s,记录end_time,性能采集工具全程采集性能数据 2、根据采集到的性能数据,按照N次卡点性能数据:去掉最大的10%、最小的10%,求取平均值,作为单次性能数据结果f(n)…...

iec104和iec61850

iec104和iec61850 IEC104 规约详细解读(一) 协议结构 IEC104 规约详细解读(二)交互流程以及协议解析 61850开发知识总结与分享【1】 Get the necesarry projects next to each other in the same directory; $ git clone https://github.com/robidev/iec61850_open_server.g…...

redis 面试问题 (更新中 ing)

目录 reids 是做什么的为什么那么快有哪些使用场景redis有哪些 数据结构redis 有哪些底层数据结构为什么设计 sds一个 字符串 存储多大容量 stream为什么设计 streamstream 消费者消息丢失stream 消息私信问题 持久化机制redis 持久化机制&#xff0c;优缺点&#xff0c;怎么用…...

力扣(leetcode)第389题找不同(Python)

389.找不同 题目链接&#xff1a;389.找不同 给定两个字符串 s 和 t &#xff0c;它们只包含小写字母。 字符串 t 由字符串 s 随机重排&#xff0c;然后在随机位置添加一个字母。 请找出在 t 中被添加的字母。 示例 1&#xff1a; 输入&#xff1a;s “abcd”, t “abcde…...

Linux_源码编译安装LAMP

1. 安装httpd服务 在配置 Apache 网站服务之前&#xff0c;需要正确安装好 httpd 服务器软件。httpd 服务器的安装可以选用 RPM 安装、源码编译安装这两种方式&#xff0c;前者相对比较简单、快速&#xff0c;但是在功能上存在一定的局限性。在实际的生产环境中&#xff0c;使…...

静态网页设计——清雅古筝网(HTML+CSS+JavaScript)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 感谢大佬的视频&#xff1a; https://www.bilibili.com/video/BV1T64y1K7Zn/?vd_source5f425e0074a7f92921f53ab87712357b 使用技术&#xff1a;HTMLCSSJS&#xff08;…...

实战Flink Java api消费kafka实时数据落盘HDFS

文章目录 1 需求分析2 实验过程2.1 启动服务程序2.2 启动kafka生产 3 Java API 开发3.1 依赖3.2 代码部分 4 实验验证STEP1STEP2STEP3 5 时间窗口 1 需求分析 在Java api中&#xff0c;使用flink本地模式&#xff0c;消费kafka主题&#xff0c;并直接将数据存入hdfs中。 flin…...

爬虫与反爬-localStorage指纹(某易某盾滑块指纹检测)(Hook案例)

概述&#xff1a;本文将用于了解爬虫中localStorage的检测原理以及讲述一个用于检测localStorage的反爬虫案例&#xff0c;最后对该参数进行Hook断点定位 目录&#xff1a; 一、LocalStorage 二、爬虫中localStorage的案例&#xff08;以某盾滑块为例&#xff09; 三、如何…...

聊一聊 webpack 和 vite 的开发服务代理的问题

webpack 和 vite webpackVite重新编辑的问题 changOrigin: true如何定义 /api ? webPack And Vite 都是两个比较好用的打包工具&#xff0c;尤其是 Vite, 几几年流行忘记了&#xff0c;特色就是服务启动极快&#xff0c;实现预加载&#xff0c;感觉 webPack 要比 Vite 要复杂一…...

【鸿蒙4.0】安装DevEcoStudio

1.下载安装包 HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者华为鸿蒙DevEco Studio是面向全场景的一站式集成开发环境,&#xff0c;在鸿蒙官网下载或升级操作系统开发工具DevEco Studio最新版本&#xff0c;SDK配置和下载&#xff0c;2.1支持Mac、Windows操作系统。…...

[概率论]四小时不挂猴博士

贝叶斯公式是什么 贝叶斯公式是概率论中的一个重要定理&#xff0c;用于计算在已知一些先验信息的情况下&#xff0c;更新对事件发生概率的估计。贝叶斯公式的表达式如下&#xff1a; P(A|B) P(B|A) * P(A) / P(B) 其中&#xff0c;P(A|B)表示在事件B发生的条件下事件A发生的概…...

算法通关村第二十关-黄金挑战图的常见算法

大家好我是苏麟 , 今天聊聊图的常见算法 . 图里的算法是很多的&#xff0c;这里我们介绍一些常见的图算法。这些算法一般都比较复杂&#xff0c;我们这里介绍这些算法的基本含义&#xff0c;适合面试的时候装*&#xff0c;如果手写&#xff0c;那就不用啦。 图分析算法&#xf…...

服务器内存不足怎么办?会有什么影响?

服务器内存&#xff0c;也被称为RAM&#xff08;Random Access Memory&#xff09;&#xff0c;是一种临时存储设备&#xff0c;用于临时存放正在运行的程序和数据。它是服务器上的超高速存储介质&#xff0c;可以快速读取和写入数据&#xff0c;提供给CPU进行实时计算和操作。…...

GPT实战系列-简单聊聊LangChain

GPT实战系列-简单聊聊LangChain LLM大模型相关文章&#xff1a; GPT实战系列-ChatGLM3本地部署CUDA111080Ti显卡24G实战方案 GPT实战系列-Baichuan2本地化部署实战方案 GPT实战系列-大话LLM大模型训练 GPT实战系列-探究GPT等大模型的文本生成 GPT实战系列-Baichuan2等大模…...

【读书笔记】《白帽子讲web安全》浏览器安全

目录 第二篇 客户端脚本安全 第2章 浏览器安全 2.1同源策略 2.2浏览器沙箱 2.3恶意网址拦截 2.4高速发展的浏览器安全 第二篇 客户端脚本安全 第2章 浏览器安全 近年来随着互联网的发展&#xff0c;人们发现浏览器才是互联网最大的入口&#xff0c;绝大多数用户使用互联…...

海外服务器2核2G/4G/8G和4核8G配置16M公网带宽优惠价格表

腾讯云海外服务器租用优惠价格表&#xff0c;2核2G10M带宽、2核4G12M、2核8G14M、4核8G16M配置可选&#xff0c;可以选择Linux操作系统或Linux系统&#xff0c;相比较Linux服务器价格要更优惠一些&#xff0c;腾讯云服务器网txyfwq.com分享腾讯云国外服务器租用配置报价&#x…...

Linux 编译安装 Nginx

目录 一、前言二、四种安装方式介绍三、本文安装方式&#xff1a;源码安装3.1、安装依赖库3.2、开始安装 Nginx3.3、Nginx 相关操作3.4、把 Nginx 注册成系统服务 四、结尾 一、前言 Nginx 是一款轻量级的 Web 服务器、[反向代理]服务器&#xff0c;由于它的内存占用少&#xf…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

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…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...