RabbitMQ(九)死信队列
目录
- 一、简介
- 1.1 定义
- 1.2 何时进入死信队列?
- 1.3 死信消息的变化
- 1.4 死信队列的应用场景
- 1.5 死信消息的生命周期
- 二、代码实现
- 2.1 死信队列的配置步骤
- 2.2 配置类
- 2.3 配置文件
- 2.4 生产者
- 2.5 业务消费者
- 2.6 死信消费者
- 2.7 测试结果
- 三、总结
- 四、补充
- 4.1 启动报错 inequivalent arg 'x-dead-letter-exchange'
RabbitMQ
是流行的开源消息队列中间件,使用 erlang 语言开发,由于其社区活跃度高,维护更新较快,深得很多企业的喜爱。
一、简介
1.1 定义
死信队列
(Dead Letter Queue,简称 DLX)是 RabbitMQ 中一种特殊的队列,用于处理无法正常被消费者消费的消息。当消息在原始队列中因为 达到最大重试次数、过期、或者 满足特定条件 时,可以 将这些消息重新路由到一个预定义的死信队列中 进行进一步处理或记录。
1.2 何时进入死信队列?
当发生以下情况,业务队列中的消息会进入死信队列:
消息被否定确认
:使用channel.basicNack
或channel.basicReject
,并且此时requeue
属性被设置为false
。消息过期
:消息在队列的存活时间超过设置的 TTL 时间。消息溢出
:队列中的消息数量已经超过最大队列长度。
当发生以上三种情况后,该消息将成为 死信
。死信消息会被 RabbitMQ 进行特殊处理:
- 如果配置了死信队列,那么该消息将会被丢进死信队列中;
- 如果没有配置,则该消息将会被丢弃。
1.3 死信消息的变化
那么 死信
被丢到死信队列后,会发生什么变化呢?
- 如果队列配置了
x-dead-letter-routing-key
的话,“死信” 的路由键会被替换成该参数对应的值。 - 如果没有配置,则保留该消息原有的路由键。
举个例子:
原有队列的路由键是 RoutingKey1
,有以下两种情况:
- 如果配置队列的
x-dead-letter-routing-key
参数值为RoutingKey2
,则该消息成为 “死信” 后,会将路由键更改为RoutingKey2
,从而进入死信交换机中的死信队列。 - 如果没有配置
x-dead-letter-routing-key
参数,则该消息成为 “死信” 后,路由键不会更改,也不会进入死信队列。
当配置了 x-dead-letter-routing-key
参数后,消息成为 “死信” 后,会在消息的 Header
中添加很多奇奇怪怪的字段,我们可以在死信队列的消费端通过以下方式进行打印:
log.info("死信消息properties: {}", message.getMessageProperties());
日志内容如下:
2024-01-07 21:16:19.745 INFO 11776 --- [ntContainer#3-1] c.d.receiver.DeadLetterMessageReceiver :消息properties: MessageProperties [headers={x-first-death-exchange=demo.simple.business.exchange, x-death=[{reason=rejected, count=1, exchange=demo.simple.business.exchange, time=Sun Jan 07 21:16:19 CST 2024, routing-keys=[], queue=demo.simple.business.queuea}], x-first-death-reason=rejected, x-first-death-queue=demo.simple.business.queuea}, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=demo.simple.deadletter.exchange, receivedRoutingKey=demo.simple.deadletter.queuea.routingkey, deliveryTag=1, consumerTag=amq.ctag-RPfmKjM8Lau9X7Fl0CtbEA, consumerQueue=demo.simple.deadletter.queuea]
格式化后:
Header 中看起来有很多信息,实际上并不多,只是值比较长而已。下面就简单说明一下 Header 中的值:
字段名 | 含义 |
---|---|
x-first-death-exchange | 第一次成为死信时的交换机名称。 |
x-first-death-reason | 第一次成为死信的原因:rejected :消息在进入队列时被队列拒绝。expired :消息过期。maxlen :队列内消息数量超过队列最大容量。 |
x-first-death-queue | 第一次成为死信时的队列名称。 |
x-death | 历史被投入死信交换机的信息列表,同一个消息每进入一次死信交换机,这个数组的信息就会被更新。 |
1.4 死信队列的应用场景
通过上面的信息,我们已经知道如何使用死信队列了,那么死信队列一般在什么场景下使用呢?
死信队列 一般用在较为重要的业务队列中,确保未被正确消费的消息不被丢弃,一般发生消费异常可能原因主要是消息信息本身存在错误导致处理异常,处理过程中参数校验异常,或者因网络波动导致的查询异常等等。当发生异常时,当然 不能每次通过日志来获取原消息,然后让运维帮忙重新投递消息 (没错,以前很多人这么干的 = =)。通过配置死信队列,可以让未正确处理的消息暂存到另一个队列中,待后续排查清楚问题后,编写相应的处理代码来处理死信消息,这样比手工恢复数据要好得多。
1.5 死信消息的生命周期
死信消息的生命周期如下:
- 业务消息被 投入业务队列;
- 消费者 消费业务队列的消息,由于处理过程中 发生异常,于是 进行了
Nack
或Reject
操作; - 被
Nack
或Reject
的消息由 RabbitMQ 投递到死信交换机中; - 死信交换机将消息 投入相应的死信队列;
- 死信队列的消费者 消费死信消息。
二、代码实现
2.1 死信队列的配置步骤
死信队列的配置可以分为以下三步:
- 配置业务队列,绑定到业务交换机上;
- 为业务队列 配置死信交换机、路由键;
- 为死信交换机 配置死信队列。
注意:
并不是直接声明一个公共的死信队列,然后所有死信消息就会自己进入死信队列中了。而是为每个需要使用死信的业务队列配置一个死信交换机,这里同一个项目的死信交换机可以共用一个,然后每个业务队列分配一个单独的路由键。
有了死信交换机和路由键后,接下来就像配置业务队列一样,配置死信队列,并绑定在死信交换机上。看到这里,大家应该可以明白:
- 死信队列 并不是什么特殊的队列,只不过是绑定在死信交换机上的队列。
- 死信交换机 也不是什么特殊的交换机,只不过是用来接收死信队列的交换机。
所以死信交换机可以为任何类型【Direct、Fanout、Topic】。一般来说,因为开发过程中会为每个业务队列分配一个独有的路由 key,并对应的配置一个死信队列进行监听。
有了前面的这些描述后,我们接下来实战操作一下。
2.2 配置类
配置类中声明了两个交换机:
业务交换机(广播)
,绑定了两个业务队列:- 业务队列A;
- 业务队列B。
死信交换机(直连)
,绑定了两个死信队列,并配置了相应的路由键:- 死信队列A;
- 死信队列B。
RabbitMQConfig.java
package com.demo.config;import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.HashMap;
import java.util.Map;/*** <p> @Title RabbitMQOrderConfig* <p> @Description RabbitMQ配置** @author ACGkaka* @date 2023/12/22 14:05*/
@Configuration
public class RabbitMQConfig {/** 业务队列 */public static final String BUSINESS_EXCHANGE_NAME = "demo.simple.business.exchange";public static final String BUSINESS_QUEUEA_NAME = "demo.simple.business.queuea";public static final String BUSINESS_QUEUEB_NAME = "demo.simple.business.queueb";/** 死信队列 */public static final String DEAD_LETTER_EXCHANGE = "demo.simple.deadletter.exchange";public static final String DEAD_LETTER_QUEUEA_ROUTING_KEY = "demo.simple.deadletter.queuea.routingkey";public static final String DEAD_LETTER_QUEUEB_ROUTING_KEY = "demo.simple.deadletter.queueb.routingkey";public static final String DEAD_LETTER_QUEUEA_NAME = "demo.simple.deadletter.queuea";public static final String DEAD_LETTER_QUEUEB_NAME = "demo.simple.deadletter.queueb";// 声明业务交换机(广播)@Beanpublic FanoutExchange businessExchange() {return new FanoutExchange(BUSINESS_EXCHANGE_NAME);}// 声明死信交换机(直连)@Beanpublic DirectExchange deadLetterExchange() {return new DirectExchange(DEAD_LETTER_EXCHANGE);}// 声明业务队列A@Beanpublic Queue businessQueueA() {Map<String, Object> args = new HashMap<>(2);// 声明当前队列绑定的死信交换机args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);// 声明当前队列绑定的死信路由键args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEA_ROUTING_KEY);return QueueBuilder.durable(BUSINESS_QUEUEA_NAME).withArguments(args).build();}// 声明业务队列B@Beanpublic Queue businessQueueB() {Map<String, Object> args = new HashMap<>(2);// 声明当前队列绑定的死信交换机args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);// 声明当前队列绑定的死信路由键args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEB_ROUTING_KEY);return QueueBuilder.durable(BUSINESS_QUEUEB_NAME).withArguments(args).build();}// 声明死信队列A@Beanpublic Queue deadLetterQueueA() {return new Queue(DEAD_LETTER_QUEUEA_NAME);}// 声明死信队列B@Beanpublic Queue deadLetterQueueB() {return new Queue(DEAD_LETTER_QUEUEB_NAME);}// 声明业务队列A绑定关系@Beanpublic Binding businessBindingA(Queue businessQueueA, FanoutExchange businessExchange) {return BindingBuilder.bind(businessQueueA).to(businessExchange);}// 声明业务队列B绑定关系@Beanpublic Binding businessBindingB(Queue businessQueueB, FanoutExchange businessExchange) {return BindingBuilder.bind(businessQueueB).to(businessExchange);}// 声明死信队列A绑定关系@Beanpublic Binding deadLetterBindingA(Queue deadLetterQueueA, DirectExchange deadLetterExchange) {return BindingBuilder.bind(deadLetterQueueA).to(deadLetterExchange).with(DEAD_LETTER_QUEUEA_ROUTING_KEY);}// 声明死信队列B绑定关系@Beanpublic Binding deadLetterBindingB(Queue deadLetterQueueB, DirectExchange deadLetterExchange) {return BindingBuilder.bind(deadLetterQueueB).to(deadLetterExchange).with(DEAD_LETTER_QUEUEB_ROUTING_KEY);}
}
2.3 配置文件
application.yml
server:port: 8081spring:application:name: springboot-rabbitmq-dead-letterrabbitmq:# 此处不建议单独配置host和port,单独配置不支持连接RabbitMQ集群addresses: 127.0.0.1:5672username: guestpassword: guest# 虚拟host 可以不设置,使用server默认hostvirtual-host: /# 是否开启发送端消息抵达队列的确认publisher-returns: true# 发送方确认机制,默认为NONE,即不进行确认;SIMPLE:同步等待消息确认;CORRELATED:异步确认publisher-confirm-type: correlated# 消费者监听相关配置listener:simple:acknowledge-mode: manual # 确认模式,默认auto,自动确认;manual:手动确认default-requeue-rejected: false # 消费端抛出异常后消息是否返回队列,默认值为trueprefetch: 1 # 限制每次发送一条数据concurrency: 1 # 同一个队列启动几个消费者max-concurrency: 1 # 启动消费者最大数量# 重试机制retry:# 开启消费者(程序出现异常)重试机制,默认开启并一直重试enabled: true# 最大重试次数max-attempts: 3# 重试间隔时间(毫秒)initial-interval: 3000
2.4 生产者
为了方便测试,写一个简单的消息生产者,通过controller层来生产消息。
SendMessageController.java
import com.demo.config.RabbitMQConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** <p> @Title SendMessageController* <p> @Description 推送消息接口** @author ACGkaka* @date 2023/1/12 15:23*/
@Slf4j
@RestController
public class SendMessageController {/*** 使用 RabbitTemplate,这提供了接收/发送等方法。*/@Autowiredprivate RabbitTemplate rabbitTemplate;@GetMapping("/sendMessage")public String sendMessage(String message) {rabbitTemplate.convertAndSend(RabbitMQConfig.BUSINESS_EXCHANGE_NAME, "", message);return "OK";}
}
2.5 业务消费者
接下来是业务队列的消费端代码
BusinessMessageReceiver.java
import com.demo.config.RabbitMQConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import java.io.IOException;/*** <p> @Title BusinessMessageReceiver* <p> @Description RabbitMQ业务队列消费端** @author ACGkaka* @date 2024/1/7 17:43*/
@Slf4j
@Component
public class BusinessMessageReceiver {@RabbitListener(queues = RabbitMQConfig.BUSINESS_QUEUEA_NAME)public void receiveA(String body, Message message, Channel channel) throws IOException {log.info("业务队列A收到消息: {}", body);try {if (body.contains("deadletter")) {throw new RuntimeException("dead letter exception");}channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);} catch (Exception e) {log.error("业务队列A消息消费发生异常,error msg: {}", e.getMessage(), e);channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);}}@RabbitListener(queues = RabbitMQConfig.BUSINESS_QUEUEB_NAME)public void receiveB(String body, Message message, Channel channel) throws IOException {log.info("业务队列B收到消息: {}", body);channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}
}
2.6 死信消费者
接下来是死信队列的消费端代码
DeadLetterMessageReceiver.java
import com.demo.config.RabbitMQConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import java.io.IOException;/*** <p> @Title DeadLetterMessageReceiver* <p> @Description RabbitMQ死信队列消费端** @author ACGkaka* @date 2024/1/7 18:14*/
@Slf4j
@Component
public class DeadLetterMessageReceiver {@RabbitListener(queues = RabbitMQConfig.DEAD_LETTER_QUEUEA_NAME)public void receiveA(String body, Message message, Channel channel) throws IOException {log.info("死信队列A收到消息: {}", body);channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}@RabbitListener(queues = RabbitMQConfig.DEAD_LETTER_QUEUEB_NAME)public void receiveB(String body, Message message, Channel channel) throws IOException {log.info("死信队列B收到消息: {}", body);channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}
}
2.7 测试结果
消费正常消息,请求结果:
请求地址:http://localhost:8081/sendMessage?message=Hello
从日志可以看到:两个业务队列成功消费。
消费错误消息,请求结果:
请求地址:http://localhost:8081/sendMessage?message=deadletter
从日志可以看到:业务队列A和B都收到了消息,但是 业务队列A消费发生异常,然后消息就被 转到了死信队列,死信队列消费端成功消费。
三、总结
死信队列其实并没有什么神秘的地方,不过是绑定在死信交换机上的普通队列,而死信交换机也只是一个普通的交换机,不过是用来专门处理死信的交换机。
死信消息
时 RabbitMQ 为我们做的一层保障,其实我们 也可以不使用死信队列,而是 在消息消费异常的时候,将消息主动投递到另一个交换机中,当你明白了这些之后,这些 Exchange 和 Queue 想怎样配合就可以怎样配合。比如:
- 从死信队列拉取消息,然后发送邮件、短信、钉钉通知来通知开发人员关注。
- 或者将消息重新投递到一个队列然后设置过期时间,来进行延时消费等等。
四、补充
4.1 启动报错 inequivalent arg ‘x-dead-letter-exchange’
如果之前已经存在队列,添加死信队列后可能会报错:
reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg ‘x-dead-letter-exchange’ for queue ‘MY_QUEUE’ in vhost ‘/’: received the value ‘MY_DEAD_LETTER_EXCHANGE’ of type ‘longstr’ but current is none
完整报错信息:
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange' for queue 'MY_QUEUE' in vhost '/': received the value 'MY_DEAD_LETTER_EXCHANGE' of type 'longstr' but current is none, class-id=50, method-id=10)at com.rabbitmq.client.impl.ChannelN.asyncShutdown(ChannelN.java:517)at com.rabbitmq.client.impl.ChannelN.processAsync(ChannelN.java:341)at com.rabbitmq.client.impl.AMQChannel.handleCompleteInboundCommand(AMQChannel.java:185)at com.rabbitmq.client.impl.AMQChannel.handleFrame(AMQChannel.java:117)at com.rabbitmq.client.impl.AMQConnection.readFrame(AMQConnection.java:742)at com.rabbitmq.client.impl.AMQConnection.access$300(AMQConnection.java:47)at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:669)
修复方案:
将之前已经存在的队列删除后重启应用即可。
整理完毕,完结撒花~ 🌻
参考地址:
1.【RabbitMQ】一文带你搞定RabbitMQ死信队列,https://cloud.tencent.com/developer/article/1463065
相关文章:

RabbitMQ(九)死信队列
目录 一、简介1.1 定义1.2 何时进入死信队列?1.3 死信消息的变化1.4 死信队列的应用场景1.5 死信消息的生命周期 二、代码实现2.1 死信队列的配置步骤2.2 配置类2.3 配置文件2.4 生产者2.5 业务消费者2.6 死信消费者2.7 测试结果 三、总结四、补充4.1 启动报错 ineq…...

KEI5许可证没到期,编译却出现Error: C9555E: Failed to check out a license.问题解决
一、编译出现如下报错 二、检查一下许可证 三、许可证在许可日期内,故应该不是许可证的问题 四、检查一下编译器,我用的是这个,这几个编译器的区别其实我不太明白,但我把问题解决是选的这个 五、找到编译器的路径,去复…...

南京观海微电子----时序图绘制工具
Wavedrom 是一款功能强大且简单易用的文本转图表工具,被广泛应用于生成时序图、波形图等交互式波形。其特点在于使用简单的文本语法,使得开发人员能够以可视化的方式表示数字信号和时间序列数据。Wavedrom 的优势在于其高度灵活性和可扩展性,…...

Gin CORS 跨域请求资源共享与中间件
Gin CORS 跨域请求资源共享与中间件 文章目录 Gin CORS 跨域请求资源共享与中间件一、同源策略1.1 什么是浏览器的同源策略?1.2 同源策略判依据1.3 跨域问题三种解决方案 二、CORS:跨域资源共享简介(后端技术)三 CORS基本流程1.CORS请求分类2.基本流程 四、CORS两种…...

TS:.d.ts 文件 和 declare 的作用
1 declare 做外部声明1.1 声明外部类型1.2 声明外部模块1.2.1 解决引入资源模块报错1.2.2 跳过对第三方库的类型检查 1.3 声明外部变量1.4 声明外部命名空间(作用域) 2 .d.ts 文件做外部声明3 declare global {} 在模块中做外部声明 先说一下我对 .d.ts文…...

JavaScript-jQuery2-笔记
1.获取元素文本、属性、内部结构、表单中的值 获取标签中所夹的文本内容:text() 获取标签的属性值:prop(属性名) 获取表单元素的内容:如 文本框中的内容 val() 获取元素的内部html结构:html() 2.筛选选择器 筛选选择器࿱…...

设计模式之多线程版本的if------Balking模式
系列文章目录 设计模式之避免共享的设计模式Immutability(不变性)模式 设计模式之并发特定场景下的设计模式 Two-phase Termination(两阶段终止)模式 设计模式之避免共享的设计模式Copy-on-Write模式 设计模式之避免共享的设计模…...

mybatis核心配置文件介绍
mybatis核心配置文件 1. properties配置介绍 properties标签:加载外部的资源配置文件 属性:resource 指定要引入的配置文件路径 在核心配置文件中,通过:${key}方式引入外部配置文件的数据 jdbc.peroperties 的文件内容…...

Linux完全卸载Anaconda3和MiniConda3
如何安装Anaconda3和MiniConda3请看这篇文章: 安装Anaconda3和MiniConda3_minianaconda3-CSDN博客文章浏览阅读474次。MiniConda3官方版是一款优秀的Python环境管理软件。MiniConda3最新版只包含conda及其依赖项如果您更愿意拥有conda以及超过720个开源软件包&…...

Apache Answer,最好的开源问答系统
Apache Answer是一款适合任何团队的问答平台软件。无论是社区论坛、帮助中心还是知识管理平台,你可以永远信赖 Answer。 目前该项目在github超过10K星,系统采用go语言开发,安装配置简单,界面清洁易用,且开源免费。项目…...

【C】内存分配
首先,回顾一下内存分配。所有程序都必须预留足够的内存来存储程序使用的数据。这些内存中有些是自动分配的: float x; int place[100]; 这些声明预留了足够的空间,还为内存提供了一个标识符,可以使用x或place识别数据。 1、mal…...

MySQL 从零开始:03 基本入门语句
文章目录 1、连接数据库1.1 命令提示符登陆1.2 MySQL 8.0 Command Line Client 登陆1.3 MySQL Workbench 登陆 2、基本语句2.1 查看所有库2.2 创建库2.3 删除库2.4 选择数据库2.5 查看表2.6 创建表2.7 删除表2.8 改表名2.9 清空表 在上一小节中介绍了 MySQL 数据库的安装&#…...

井盖异动传感器,守护脚下安全
随着城市化进程的加速,城市基础设施的安全问题日益受到关注。其中,井盖作为城市地下管道的重要入口,其安全问题不容忽视。然而,传统的井盖监控方式往往存在盲区,无法及时发现井盖的异常移动。为此,我们推出…...

复合机器人作为一种新型的智能制造装备高效、精准和灵活的生产方式
随着汽车制造业的快速发展,对于高效、精准和灵活的生产方式需求日益增强。复合机器人作为一种新型的智能制造装备,以其独特的优势在汽车制造中发挥着越来越重要的作用。因此,富唯智能顺应时代的发展趋势,研发出了ICR系列的复合机器…...

重置 Docker 中 Gitlab 的账号密码
1、首先进入Docker容器 docker exec -it gitlab bash 2、连接到 gitlab 的数据库 需要谨慎操作 gitlab-rails console -e production 等待加载完后会进入控制台 ------------------------------------------------------------------------------------------------------…...

任务类型划分
以下内容来自于ChatGPT内存密集型应用和IO密集型应用是两种不同类型的计算应用,它们在资源需求和性能特点上有所不同。 内存密集型应用(Memory-Intensive Applications): 特点: 这类应用主要依赖大量的内存资源来执行任…...

docker搭建部署mysql并挂载指定目录
Docker是一种轻量级、可移植的容器化平台,可以简化应用程序的部署和管理。在本文中,我们将探讨如何使用Docker来搭建和部署MySQL数据库,并将数据和配置文件挂载到外部目录,以实现数据持久化和方便的配置管理。 1: 安装Docker 首…...

即将推出的 OpenWrt One/AP-24.XY:OpenWrt 和 Banana Pi 合作路由器板
OpenWrt开发人员正在与Banana Pi合作开发OpenWrt One/AP-24.XY路由器板。OpenWrt 是一个轻量级嵌入式 Linux 操作系统,支持近 1,800 个路由器和其他设备。然而,这将是第一块由 OpenWrt 直接开发的路由器板。 该主板将基于 MediaTek MT7981B (Filogic 82…...

【uniapp-小程序-分享图5/4】
utils.js //裁剪分享的图片为5:4 const makeCanvas (imgUrl) > {console.log("imgUrl",imgUrl);return new Promise((resolve, reject) > {// 获取图片信息,小程序下获取网络图片信息需先配置download域名白名单才能生效uni.getImageInfo({src: imgUrl,succe…...

【响应式编程】前置知识和相关技术的总结
前置知识 这些概念都与响应式编程密切相关。🦌 1. 并发和多线程编程:响应式编程需要处理并发性,它允许多个操作独立地并行执行。这使得应用程序可以在不同的线程、进程或设备上处理多个事件。 2. 事件驱动编程:响应式编程是一种…...

K8S--安装MySQL8(单机)
原文网址:K8S--安装MySQL8(单机)-CSDN博客 简介 本文介绍K8S部署MySQL8(单机)的方法。 ----------------------------------------------------------------------------------------------- 分享Java真实高频面试题…...

CMake+QT+大漠插件的桌面应用开发
文章目录 CMakeQT大漠插件的桌面应用开发说明环境项目结构配置编译环境代码 CMakeQT大漠插件的桌面应用开发 说明 在CMake大漠插件的应用开发——处理dm.dll,免注册调用大漠插件中已经说明了如何免注册调用大漠插件,以及做了几个简单的功能调用&#x…...

OpenCV-24双边滤波
一、概念 双边滤波对于图像的边缘信息能够更好的保存。其原理为一个与空间距离相关的高斯函数与一个灰度距离相关的高斯函数相乘。 空间距离:指的是当前点与中心点的欧式距离。空间域的高斯函数及其数学形式为: 其中(xi,yi&…...

AI智能分析网关V4:太阳能+4G智慧水库远程可视化智能监管方案
一、背景需求分析 由于水库位置分散的原因,水库视频监控建设在立杆、布线等方面都存在一定的难度,且需要人力、物力的前期投入和后期维护。目前水库的监管存在一定的问题,管理人员工作强度大但管理质量并不高,人为巡检无法实时发…...

第8章-第8节-Java中的文件类File的简单介绍
1、我们已经写过File的相关代码: BufferedReader bf new BufferedReader(new FileReader(new File("aa.txt"))); 2、其实FIle这个类自身也是非常强大的,封装了很多操作文件/目录的方法,今天我们就需要去详细的学习这个类&#x…...

CTF-PWN-沙箱逃脱-【seccomp和prtcl-2】
文章目录 沙箱逃脱prtcl题HITCON CTF 2017 Quals Impeccable Artifact libc 2.24flag文件对应prctl函数检查源码思路exp 沙箱逃脱prtcl题 HITCON CTF 2017 Quals Impeccable Artifact libc 2.24 flag文件 此时的flag文件在本文件夹建一个即可 此时的我设置的flag为 对应prc…...

【Docker篇】使用Docker操作镜像
文章目录 🛸镜像🌺基本操作⭐docker --help⭐docker pull [ 参数 ]⭐docker images⭐docker save -- 导出⭐docker rmi -- 删除⭐docker load -- 导入 🛸镜像 镜像是指在计算机领域中,通过复制和创建一个与原始对象相似的副本的过…...

css宽度适应内容
废话不多说,看如下demo,我需要将下面这个盒子的宽度变成内容自适应 方法有很多,如下 父元素设置display:flex 实现子元素宽度适应内容 如下给父元素设置flex能实现宽度自适应内容 <!DOCTYPE html><html lang"en"><head><meta charset"U…...

粒子物理和原子核物理的理论在模拟和分析电路中的粒子束和辐射效应中的应用
粒子物理和原子核物理的理论可以应用于模拟和分析电路中的粒子束和辐射效应,特别是在粒子加速器和辐射探测器的设计和优化方面。通过这些理论的应用,可以提高加速器和探测器的性能,推动粒子物理和原子核物理的研究进展。粒子物理和原子核物理…...

Opentsdb官方优化文档 - 翻译
文档地址 : Tuning — OpenTSDB 2.4 documentation Tuning As with any database there are many tuning parameters for OpenTSDB that can be used to improve write and read performance. Some of these options are specific to certain backends, others are global. …...