【技术解析】消息中间件MQ:从原理到RabbitMQ实战(深入浅出)

文章目录

  • 【技术解析】消息中间件MQ:从原理到RabbitMQ实战(深入浅出)
    • 1.简介
      • 1.1 什么是消息中间件
      • 1.2 传统的http请求存在那些缺点
      • 1.3 Mq应用场景有那些
      • 1.4 为什么需要使用mq
      • 1.5 Mq与多线程之间区别
      • 1.6 Mq消息中间件名词
      • 1.7主流mq区别对比
      • 1.8 Mq设计基础知识
    • 2.RabbitMQ
      • 2.1 RabbitMQ环境的基本安装
      • 2.2 liunx安装rabbitmq
      • 2.3快速入门RabbitMQ简单队列
      • 2.4 RabbitMQ如何保证消息不丢失
      • 2.5 RabbitMQ五种消息模式
        • 2.5.1 RabbitMQ Fanout 发布订阅
        • 2.5.2 Direct路由模式
        • 2.5.3 Topic主题模式
      • 2.6 SpringBoot整合RabbitMQ
    • 3. RabbitMQ实战解决方案
      • 3.1 RabbitMQ死信队列
      • 3.2 RabbitMQ消息幂等问题
      • 3.3 SpringBoot开启消息确认机制

【技术解析】消息中间件MQ:从原理到RabbitMQ实战(深入浅出)

1.简介

1.1 什么是消息中间件

消息中间件基于队列模型实现异步/同步传输数据
作用:可以实现支撑高并发、异步解耦、流量削峰、降低耦合度。

1.2 传统的http请求存在那些缺点

  1. Http请求基于请求与响应的模型,在高并发的情况下,客户端发送大量的请求达到
    服务器端有可能会导致我们服务器端处理请求堆积。

  2. Tomcat服务器处理每个请求都有自己独立的线程,如果超过最大线程数会将该请求缓存到队列中,如果请求堆积过多的情况下,有可能会导致tomcat服务器崩溃的问题。所以一般都会在nginx入口实现限流,整合服务保护框架。
    在这里插入图片描述

  3. http请求处理业务逻辑如果比较耗时的情况下,容易造成客户端一直等待,阻塞等待过程中会导致客户端超时发生重试策略,有可能会引发幂等性问题。

注意事项:接口是为http协议的情况下,最好不要处理比较耗时的业务逻辑,耗时的业务逻辑应该单独交给多线程或者是mq处理。

1.3 Mq应用场景有那些

  1. 异步发送短信
  2. 异步发送新人优惠券
  3. 处理一些比较耗时的操作

1.4 为什么需要使用mq

可以实现支撑高并发、异步解耦、流量削峰、降低耦合度。

同步发送http请求
在这里插入图片描述
客户端发送请求到达服务器端,服务器端实现会员注册业务逻辑,

  1. insertMember() --插入会员数据 1s
  2. sendSms()----发送登陆短信提醒 3s
  3. sendCoupons()----发送新人优惠券 3s

总共响应需要6s时间,可能会导致客户端阻塞6s时间,对用户体验不是很好。

多线程处理业务逻辑
在这里插入图片描述

用户向数据库中插入一条数据之后,在单独开启一个线程异步发送短信和优惠操作。
客户端只需要等待1s时间
优点:适合于小项目 实现异步
缺点:有可能会消耗服务器cpu资源资源

MQ处理业务逻辑
在这里插入图片描述
先向数据库中插入一条会员数据,让后再向MQ中投递一个消息,MQ服务器端在将消息推送给消费者异步解耦处理发送短信和优惠券。

1.5 Mq与多线程之间区别

MQ可以实现异步/解耦/流量削峰问题;
多线程也可以实现异步,但是消耗到cpu资源,没有实现解耦。

1.6 Mq消息中间件名词

Producer 生产者:投递消息到MQ服务器端;
Consumer 消费者:从MQ服务器端获取消息处理业务逻辑;
Broker MQ服务器端
Topic 主题:分类业务逻辑发送短信主题、发送优惠券主题
Queue 存放消息模型 队列 先进先出 后进后出原则 数组/链表
Message 生产者投递消息报文:json

1.7主流mq区别对比

特性ActiveMQRabbitMQRocketMQKafka
开发语言javaerlangjavascala
单机吞吐量万级万级10万级10万级
时效性ms级us级ms级ms级以内
可用性高(主从架构)高(主从架构)非常高(分布式架构)非常高(分布式架构)
功能特性成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好基于erlang开发,所以并发能力很强,性能极其好,延时很低管理界面较丰富MQ功能比较完备,扩展性佳只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。

1.8 Mq设计基础知识

Maven依赖

<dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.0.23.Final</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.11</version></dependency>
</dependencies>

基于多线程队列简单实现mq

public class MayiktThreadMQ {private static LinkedBlockingDeque<JSONObject> msgs = new LinkedBlockingDeque<>();public static void main(String[] args) {//生产者线程Thread producerThread = new Thread(new Runnable() {@Overridepublic void run() {try {while (true){//每隔一秒时间存放一个消息到队列里面去Thread.sleep(1000);JSONObject data = new JSONObject();data.put("userId", "1234");msgs.offer(data);//存入队列}} catch (Exception e) {}}}, "生产者");producerThread.start();//消费者线程Thread consumerThread = new Thread(new Runnable() {@Overridepublic void run() {try {while (true) {JSONObject data = msgs.poll();//取出消息if (data != null) {System.out.println(Thread.currentThread().getName() + "," + data);}}} catch (Exception e) {}}}, "消费者");consumerThread.start();}
}

基于netty实现mq
消费者netty客户端与nettyServer端MQ服务器端保持长连接,MQ服务器端保存消费者连接。
生产者netty客户端发送请求给nettyServer端MQ服务器端,MQ服务器端在将该消息内容发送给消费者。
在这里插入图片描述
body:{"msg":{"userId":"123456","age":"23"},"type":"producer",”topic”:””}

生产者投递消息给MQ服务器端,MQ服务器端需要缓存该消息如果mq服务器端宕机之后,消息如何保证不丢失

  1. 持久化机制
    如果mq接收到生产者投递消息,如果消费者不在的情况下,该消息是否会丢失?
  • 不会丢失,消息确认机制 必须要消费者消费该消息成功之后,在通知给mq服务器端删除该消息。
  • Mq服务器端将该消息推送消费者:
    • 消费者已经和mq服务器保持长连接。
    • 消费者主动拉取消息:
    • 消费者第一次刚启动的时候

Mq如何实现抗高并发思想

Mq消费者根据自身能力情况 ,拉取mq服务器端消息消费。
默认的情况下是取出一条消息。

缺点:存在延迟的问题

需要考虑mq消费者提高速率的问题:如何消费者提高速率:消费者实现集群、消费者批量获取消息即可。

2.RabbitMQ

RabbitMQ基本介绍
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件),RabbitMQ服务器是用Erlang语言编写的。
RabitMQ官方网站
在这里插入图片描述
在这里插入图片描述

https://www.rabbitmq.com/getstarted.html

  1. 点对点(简单)的队列
  2. 工作(公平性)队列模式
  3. 发布订阅模式
  4. 路由模式Routing
  5. 通配符模式Topics
  6. RPC

2.1 RabbitMQ环境的基本安装

  1. 下载并安装erlang,下载地址:http://www.erlang.org/download
  2. 配置erlang环境变量信息
  • 新增环境变量ERLANG_HOME=erlang的安装地址
  • %ERLANG_HOME%\bin加入到path
  1. 下载并安装RabbitMQ,下载地址:http://www.rabbitmq.com/download.html
  • 注意: RabbitMQ 它依赖于Erlang,需要先安装Erlang
  • https://www.rabbitmq.com/install-windows.html

安装RabbitMQ环境步骤
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

配置Erlang环境变量:
在这里插入图片描述
在这里插入图片描述

安装rabbitmq-server-3.6.9
在这里插入图片描述
在这里插入图片描述

如何启动Rabbitmq
RabbitMQ Service - start

在这里插入图片描述

启动Rabbitmq常见问题
如果rabbitmq 启动成功无法访问 管理平台页面

进入到D:\java\rabbitmq-server-3.6.9\rabbitmq_server-3.6.9\sbin
执行
rabbitmq-plugins enable rabbitmq_management
rabbitmqctl start_app

Rabbitmq管理平台中心
RabbitMQ 管理平台地址 http://127.0.0.1:15672
默认账号:guest/guest 用户可以自己创建新的账号

Virtual Hosts:
像mysql有数据库的概念并且可以指定用户对库和表等操作的权限。那RabbitMQ呢?
RabbitMQ也有类似的权限管理。在RabbitMQ中可以虚拟消息服务器VirtualHost,每个VirtualHost相当月一个相对独立的RabbitMQ服务器,每个VirtualHost之间是相互隔离的。exchange、queue、message不能互通。

默认的端口15672:rabbitmq管理平台端口号
默认的端口5672: rabbitmq消息中间内部通讯的端口
默认的端口号25672 rabbitmq集群的端口号

RabbitMQ常见名词
/Virtual Hosts—分类
/队列 存放我们消息
Exchange 分派我们消息在那个队列存放起来 类似于nginx

15672—rabbitmq控制台管理平台 http协议
25672 rabbitmq 集群通信端口号
Amqp 5672 rabbitmq内部通信的一个端口号
在这里插入图片描述

2.2 liunx安装rabbitmq

# 安装启动rabbitmq容器
docker run -d --name myRabbitMQ -e RABBITMQ_DEFAULT_USER=root -e RABBITMQ_DEFAULT_PASS=0806 -p 15672:15672 -p 5672:5672 rabbitmq:3.8.14-management

-d :表示以后台(detached)模式运行容器
--name myRabbitMQ :指定容器的名称为"myRabbitMQ"
-e RABBITMQ_DEFAULT_USER=root -e RABBITMQ_DEFAULT_PASS=0806:设置RabbitMQ的默认用户名为"root",默认密码为"0806"
-p 15672:15672 -p 5672:5672 :将容器的15672端口映射到主机的15672端口(用于访问管理界面),将容器的5672端口映射到主机的5672端口(用于消息传输)
rabbitmq:3.8.14-management :指定使用的RabbitMQ镜像及版本号,这里使用了带管理插件的版本"rabbitmq:3.8.14-management"

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3快速入门RabbitMQ简单队列

首先需要再RabbitMQ平台创建Virtual Hosts 和队列。
/meiteVirtualHosts

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Maven依赖

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

RabbitMQConnection

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class RabbitMQConnection {/*** 获取链接*/public static Connection getConnection() throws IOException, TimeoutException {// 1.创建连接ConnectionFactory connectionFactory = new ConnectionFactory();// 2.设置连接地址connectionFactory.setHost("192.168.249.129");// 3.设置端口号connectionFactory.setPort(5672);// 4.设置账号和密码connectionFactory.setUsername("root");connectionFactory.setPassword("0806");// 5.设置VirtualHostconnectionFactory.setVirtualHost("/meiteVirtualHosts");return connectionFactory.newConnection();}
}

生产者

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class Producer {
/*** 生产者*/private static final String QUEUE_NAME = "mayikt-queue";//队列名称public static void main(String[] args) throws IOException, TimeoutException {//1.创建连接Connection connection = RabbitMQConnection.getConnection();//2.创建通道Channel channel = connection.createChannel();String msg = "测试555";channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());//3.关闭通道和连接channel.close();connection.close();}
}

运行前
在这里插入图片描述
运行后
在这里插入图片描述

mayikt-queue队列里面有一条消息没有被消费者消费(没被消费就会一直在队列中缓存)

import com.rabbitmq.client.*;import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeoutException;public class Consume {/*** 消费者*/private static final String QUEUE_NAME = "mayikt-queue";//队列名称public static void main(String[] args) throws IOException, TimeoutException {//1.创建连接Connection connection = RabbitMQConnection.getConnection();//2.设置通道Channel channel = connection.createChannel();DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws UnsupportedEncodingException {String msg = new String(body, "UTF-8");System.out.println("消费者获取消息:" + msg);}};//3.监听队列//autoAck ture:自动签收//autoAck false:手动签收channel.basicConsume(QUEUE_NAME, true, defaultConsumer);}
}

若报此错误:Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no queue 'mayikt-queu' in vhost '/meiteVirtualHosts', class-id=60, method-id=20)
证明没有名为此'mayikt-queu'的队列

2.4 RabbitMQ如何保证消息不丢失

Mq如何保证消息不丢失:

  1. 生产者角色
    确保生产者投递消息到MQ服务器端成功。
    Ack 消息确认机制
    同步或者异步的形式
    方式1:Confirms
    方式2:事务消息

  2. 消费者角色
    在rabbitmq情况下:
    必须要将消息消费成功之后,才会将该消息从mq服务器端中移除。
    在kafka中的情况下:
    不管是消费成功还是消费失败,该消息都不会立即从mq服务器端移除。

  3. Mq服务器端 在默认的情况下 都会对队列中的消息实现持久化持久化硬盘。

  • 使用消息确认机制+持久技术
    A.消费者确认收到消息机制
    channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
    注:第二个参数值为false代表关闭RabbitMQ的自动应答机制,改为手动应答。
    在处理完消息时,返回应答状态,true表示为自动应答模式。
    channel.basicAck(envelope.getDeliveryTag(), false);
    B.生产者确认投递消息成功 使用Confirm机制 或者事务消息
    在这里插入图片描述

Confirm机制 同步或者是异步的形式

  • RabbitMQ默认创建是持久化的
    在这里插入图片描述

(durable为持久化到硬盘 Transient为持久化到内存)
代码中设置 durable为true

参数名称详解:
durable是否持久化 durable为持久化、 Transient 不持久化
autoDelete 是否自动删除,当最后一个消费者断开连接之后队列是否自动被删除,可以通过RabbitMQ Management,查看某个队列的消费者数量,当consumers = 0时队列就会自动删除

  • 使用rabbitmq事务消息;
	channel.txSelect();channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
//	  int i = 1 / 0;channel.txCommit();

相关核心代码
生产者

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.zhaoli.RabbitMQConnection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class Producer {private static final String QUEUE_NAME = "mayikt-queue";public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {//1.创建一个新连接Connection connection = RabbitMQConnection.getConnection();//2.设置channelChannel channel = connection.createChannel();//3.发送消息String msg = "每特教育6666";
channel.confirmSelect();channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());boolean result = channel.waitForConfirms();if (result) {System.out.println("消息投递成功");} else {System.out.println("消息投递失败");}channel.close();connection.close();}
}

消费者

import com.rabbitmq.client.*;
import com.zhaoli.RabbitMQConnection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class Consumer {private static final String QUEUE_NAME = "mayikt-queue";public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {// 1.创建连接Connection connection = RabbitMQConnection.getConnection();// 2.设置通道Channel channel = connection.createChannel();DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String msg = new String(body, "UTF-8");System.out.println("消费者获取消息:" + msg);// 消费者完成 消费该消息channel.basicAck(envelope.getDeliveryTag(), false);}};// 3.监听队列channel.basicConsume(QUEUE_NAME, false, defaultConsumer);}
}

2.5 RabbitMQ五种消息模式

RabitMQ工作队列
默认的传统队列是为均摊消费,存在不公平性;如果每个消费者速度不一样的情况下,均摊消费是不公平的,应该是能者多劳。
在这里插入图片描述

采用工作队列
在通道中只需要设置basicQos为1即可,表示MQ服务器每次只会给消费者推送1条消息必须手动ack确认之后才会继续发送。
channel.basicQos(1);

RabbitMQ交换机类型

交换机类型描述
Direct直连交换机
Fanout扇型交换机
Topic主题交换机
Headers头交换机
/Virtual Hosts—区分不同的团队
----队列 存放消息
----交换机 路由消息存放在那个队列中 类似于nginx
—路由key 分发规则
2.5.1 RabbitMQ Fanout 发布订阅

生产者发送一条消息,经过交换机转发到多个不同的队列,多个不同的队列就多个不同的消费者。
在这里插入图片描述
在这里插入图片描述
原理:

  1. 需要创建两个队列 ,每个队列对应一个消费者;
  2. 队列需要绑定我们交换机
  3. 生产者投递消息到交换机中,交换机在将消息分配给两个队列中都存放起来;
  4. 消费者从队列中获取这个消息。

生产者代码

import com.zhaoli.RabbitMQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class ProducerFanout {//生产者/*** 定义交换机的名称*/private static final String EXCHANGE_NAME = "fanout_exchange";public static void main(String[] args) throws IOException, TimeoutException {//  创建ConnectionConnection connection = RabbitMQConnection.getConnection();// 创建ChannelChannel channel = connection.createChannel();// 通道关联交换机channel.exchangeDeclare(EXCHANGE_NAME, "fanout", true);String msg = "赵立6666";channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());channel.close();connection.close();}
}

添加交换机
在这里插入图片描述

消费者代码
邮件消费者

import com.rabbitmq.client.*;
import com.zhaoli.RabbitMQConnection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class MailConsumer {//邮件消费者/*** 定义邮件队列*/private static final String QUEUE_NAME = "fanout_email_queue";/*** 定义交换机的名称*/private static final String EXCHANGE_NAME = "fanout_exchange";public static void main(String[] args) throws IOException, TimeoutException {System.out.println("邮件消费者...");// 创建我们的连接Connection connection = RabbitMQConnection.getConnection();// 创建我们通道final Channel channel = connection.createChannel();// 关联队列消费者关联队列channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String msg = new String(body, "UTF-8");System.out.println("邮件消费者获取消息:" + msg);}};// 开始监听消息 自动签收channel.basicConsume(QUEUE_NAME, true, defaultConsumer);}
}

短信消费者

import com.rabbitmq.client.*;
import com.zhaoli.RabbitMQConnection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class SmsConsumer {//短信消费者/*** 定义短信队列*/private static final String QUEUE_NAME = "fanout_email_sms";/*** 定义交换机的名称*/private static final String EXCHANGE_NAME = "fanout_exchange";public static void main(String[] args) throws IOException, TimeoutException {System.out.println("短信消费者...");// 创建我们的连接Connection connection = RabbitMQConnection.getConnection();// 创建我们通道final Channel channel = connection.createChannel();// 关联队列消费者关联队列channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String msg = new String(body, "UTF-8");System.out.println("短信消费者获取消息:" + msg);}};// 开始监听消息 自动签收channel.basicConsume(QUEUE_NAME, true, defaultConsumer);}
}

添加队列
在这里插入图片描述

2.5.2 Direct路由模式

当交换机类型为direct类型时,根据队列绑定的路由建转发到具体的队列中存放消息
在这里插入图片描述

生产者

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.zhaoli.RabbitMQConnection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class ProducerDirect {/*** 定义交换机的名称*/private static final String EXCHANGE_NAME = "direct_exchange";public static void main(String[] args) throws IOException, TimeoutException {//  创建ConnectionConnection connection = RabbitMQConnection.getConnection();// 创建ChannelChannel channel = connection.createChannel();// 通道关联交换机channel.exchangeDeclare(EXCHANGE_NAME, "direct", true);String msg = "每特教育6666";channel.basicPublish(EXCHANGE_NAME, "email", null, msg.getBytes());channel.close();connection.close();}
}

添加交换机
在这里插入图片描述

消费者
邮件消费者

import com.rabbitmq.client.*;
import com.zhaoli.RabbitMQConnection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class MailConsumer {/*** 定义邮件队列*/private static final String QUEUE_NAME = "direct_email_queue";/*** 定义交换机的名称*/private static final String EXCHANGE_NAME = "direct_exchange";public static void main(String[] args) throws IOException, TimeoutException {System.out.println("邮件消费者...");// 创建我们的连接Connection connection = RabbitMQConnection.getConnection();// 创建我们通道final Channel channel = connection.createChannel();// 关联队列消费者关联队列channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "email");DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String msg = new String(body, "UTF-8");System.out.println("邮件消费者获取消息:" + msg);}};// 开始监听消息 自动签收channel.basicConsume(QUEUE_NAME, true, defaultConsumer);}
}

短信消费者

import com.rabbitmq.client.*;
import com.zhaoli.RabbitMQConnection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class SmsConsumer {/*** 定义短信队列*/private static final String QUEUE_NAME = "direct_sms_queue";/*** 定义交换机的名称*/private static final String EXCHANGE_NAME = "direct_exchange";public static void main(String[] args) throws IOException, TimeoutException {System.out.println("短信消费者...");// 创建我们的连接Connection connection = RabbitMQConnection.getConnection();// 创建我们通道final Channel channel = connection.createChannel();// 关联队列消费者关联队列channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "sms");DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String msg = new String(body, "UTF-8");System.out.println("短信消费者获取消息:" + msg);}};// 开始监听消息 自动签收channel.basicConsume(QUEUE_NAME, true, defaultConsumer);}
}

添加队列
在这里插入图片描述
在这里插入图片描述

2.5.3 Topic主题模式

当交换机类型为topic类型时,根据队列绑定的路由建模糊转发到具体的队列中存放。
#号表示支持匹配多个词;
*号表示只能匹配一个词
在这里插入图片描述

生产者

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.zhaoli.RabbitMQConnection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class ProducerTopic {/*** 定义交换机的名称*/private static final String EXCHANGE_NAME = "topic_exchange";public static void main(String[] args) throws IOException, TimeoutException {//  创建ConnectionConnection connection = RabbitMQConnection.getConnection();// 创建ChannelChannel channel = connection.createChannel();// 通道关联交换机channel.exchangeDeclare(EXCHANGE_NAME, "topic", true);String msg = "每特教育6666";channel.basicPublish(EXCHANGE_NAME, "mayikt.sms", null, msg.getBytes());channel.close();connection.close();}
}

添加交换机
在这里插入图片描述

消费者
邮件消费者

import com.rabbitmq.client.*;
import com.zhaoli.RabbitMQConnection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class MailConsumer {/*** 定义邮件队列*/private static final String QUEUE_NAME = "topic_email_queue";/*** 定义交换机的名称*/private static final String EXCHANGE_NAME = "topic_exchange";public static void main(String[] args) throws IOException, TimeoutException {System.out.println("邮件消费者...");// 创建我们的连接Connection connection = RabbitMQConnection.getConnection();// 创建我们通道final Channel channel = connection.createChannel();// 关联队列消费者关联队列channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "mayikt.*");DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String msg = new String(body, "UTF-8");System.out.println("邮件消费者获取消息:" + msg);}};// 开始监听消息 自动签收channel.basicConsume(QUEUE_NAME, true, defaultConsumer);}
}

短信消费者

import com.rabbitmq.client.*;
import com.zhaoli.RabbitMQConnection;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class SmsConsumer {/*** 定义短信队列*/private static final String QUEUE_NAME = "topic_sms_queue";/*** 定义交换机的名称*/private static final String EXCHANGE_NAME = "topic_exchange";public static void main(String[] args) throws IOException, TimeoutException {System.out.println("短信消费者...");// 创建我们的连接Connection connection = RabbitMQConnection.getConnection();// 创建我们通道final Channel channel = connection.createChannel();// 关联队列消费者关联队列channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "meite.*");DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String msg = new String(body, "UTF-8");System.out.println("短信消费者获取消息:" + msg);}};// 开始监听消息 自动签收channel.basicConsume(QUEUE_NAME, true, defaultConsumer);}
}

添加队列
在这里插入图片描述在这里插入图片描述

2.6 SpringBoot整合RabbitMQ

Maven依赖

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.0.RELEASE</version>
</parent>
<dependencies><!-- springboot-web组件 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 添加springboot对amqp的支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><!--fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.49</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>
</dependencies>

配置类

package com.zhaoli.config;import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;@Component
public class RabbitMQConfig {/*** 定义交换机*/private String EXCHANGE_SPRINGBOOT_NAME = "/mayikt_ex";/*** 短信队列*/private String FANOUT_SMS_QUEUE = "fanout_sms_queue";/*** 邮件队列*/private String FANOUT_EMAIL_QUEUE = "fanout_email_queue";//1.注入队列和交换机到spring容器中//2.关联交换机 xml中用的是<bean id="" class="";>  注解用@bean/*** 邮件队列、短信队列、交换机 注入到spring 容器中*/@Beanpublic Queue smsQueue() {return new Queue(FANOUT_SMS_QUEUE);}@Beanpublic Queue emailQueue() {return new Queue(FANOUT_EMAIL_QUEUE);}@Beanpublic FanoutExchange fanoutExchange() {return new FanoutExchange(EXCHANGE_SPRINGBOOT_NAME);}/*** 关联交换机* 根据参数名称在 ioc 容器中获取 Queue 对象*/@Beanpublic Binding bindingSmsFanoutExchange(Queue smsQueue, FanoutExchange fanoutExchange) {//将 FANOUT_SMS_QUEUE 队列关联 EXCHANGE_SPRINGBOOT_NAME 交换机return BindingBuilder.bind(smsQueue).to(fanoutExchange);}@Beanpublic Binding bindingEmailFanoutExchange(Queue emailQueue, FanoutExchange fanoutExchange) {//将 FANOUT_EMAIL_QUEUE 队列关联 EXCHANGE_SPRINGBOOT_NAME 交换机return BindingBuilder.bind(emailQueue).to(fanoutExchange);}
}

在这里插入图片描述

配置文件

Resources/application.yml
spring:rabbitmq:####连接地址host: 192.168.249.129####端口号port: 5672####账号username: guest####密码password: guest### 地址virtual-host: /meiteVirtualHosts

配置账号访问权限
在这里插入图片描述

Entity

package com.zhaoli.entity;import lombok.Data;import java.io.Serializable;@Data
public class MsgEntity implements Serializable {private String msgId;private String userId;private String phone;private String email;public MsgEntity(String msgId, String userId, String phone, String email) {this.msgId = msgId;this.userId = userId;this.phone = phone;this.email = email;}
}

启动类

package com.zhaoli;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class AppProducer {public static void main(String[] args) {SpringApplication.run(AppProducer.class);}
}

生产者

package com.zhaoli.service;import com.zhaoli.entity.MsgEntity;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.UUID;@RestController
public class ProducerService {/*** 从 springboot ioc 容器中拿到整合好的 rabbitmq 中的 AmqpTemplate 模板*/@Autowiredprivate AmqpTemplate amqpTemplate;@RequestMapping("/sendMsg")public void sendMsg() {MsgEntity msgEntity = new MsgEntity(UUID.randomUUID().toString(), "1234", "1764654557", "5204567891@qq.com");/*** 参数1 交换机名称* 参数2 路由 key* 参数3 发送内容*/amqpTemplate.convertAndSend("/mayikt_ex", "", msgEntity);}
}

在这里插入图片描述

消费者

package com.zhaoli;import com.zhaoli.entity.MsgEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Slf4j
@Component
@RabbitListener(queues = "fanout_email_queue")
public class FanoutEmailConsumer {@RabbitHandlerpublic void process(MsgEntity msgEntity) {log.info("email:msgEntity:" + msgEntity);}
}
package com.zhaoli;import com.zhaoli.entity.MsgEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Slf4j
@Component
@RabbitListener(queues = "fanout_sms_queue")
public class FanoutSmsConsumer {@RabbitHandlerpublic void process(MsgEntity msgEntity) {log.info("sms:msgEntity:" + msgEntity);}
}

在配置文件 Resources/application.yml 中设置一个新的端口号,避免同时启动端口号冲突

server:port: 8082

整个项目结构

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

生产者如何获取消费结果

  1. 根据业务来定
    消费者消费成功结果:
    能够在数据库中插入一条数据

  2. Rocketmq 自带全局消息id,能够根据该全局消息获取消费结果
    原理: 生产者投递消息到mq服务器,mq服务器端在这时候返回一个全局的消息id,
    当我们消费者消费该消息成功之后,消费者会给我们mq服务器端发送通知标记该消息
    消费成功。
    生产者获取到该消息全局id,每隔2s时间调用mq服务器端接口查询该消息是否
    有被消费成功。

  3. 异步返回一个全局id,前端使用ajax定时主动查询;

  4. 在rocketmq中,自带根据消息id查询是否消费成功

3. RabbitMQ实战解决方案

3.1 RabbitMQ死信队列

死信队列产生的背景
RabbitMQ死信队列俗称,备胎队列;消息中间件因为某种原因拒收该消息后,可以转移到死信队列中存放,死信队列也可以有交换机和路由key等。

产生死信队列的原因

  1. 消息投递到MQ中存放 消息已经过期 消费者没有及时的获取到我们消息,消息如果存放到mq服务器中过期之后,会转移到备胎死信队列存放。
  2. 队列达到最大的长度 (队列容器已经满了)
  3. 消费者消费多次消息失败,就会转移存放到死信队列中
    在这里插入图片描述

代码整合 参考 mayikt-springboot-rabbitmq|#中order-dead-letter-queue项目

死信队列的架构原理
死信队列和普通队列区别不是很大,普通与死信队列都有自己独立的交换机和路由key、队列和消费者。
区别:

  1. 生产者投递消息先投递到我们普通交换机中,普通交换机在将该消息投到普通队列中缓存起来,普通队列对应有自己独立普通消费者。
  2. 如果生产者投递消息到普通队列中,普通队列发现该消息一直没有被消费者消费的情况下,在这时候会将该消息转移到死信(备胎)交换机中,死信(备胎)交换机对应有自己独立的 死信(备胎)队列 对应独立死信(备胎)消费者。

死信队列应用场景
30分钟订单超时设计
A.Redis过期key :
在设计 Redis 的过期 key 时,可以使用 Redis 的过期时间特性来自动删除超时的订单。可以通过以下步骤实现:

  1. 创建订单时,生成一个唯一的订单 ID。
  2. 将订单 ID 作为 Redis 键(key)。例如,可以使用 “order:{订单ID}” 这样的键命名格式。
  3. 在订单创建完成后,设置 Redis 键的过期时间(TTL)为 30 分钟。可以使用 Redis 命令 EXPIRESETEX 设置过期时间。
  4. 当需要检查订单是否超时时,可以使用 Redis 的 TTL 命令来查看键的剩余生存时间。如果返回值小于 0,说明订单已超时。
  5. 如果订单超时,则根据业务需求执行相应的处理操作,例如取消订单、发送通知等。
    通过以上设计,Redis 会自动管理订单的过期与删除,避免了手动操作,并且可以提高订单过期检查的效率。

B.死信延迟队列实现:
采用死信队列,创建一个普通队列没有对应的消费者消费消息,在30分钟过后就会将该消息转移到死信备胎消费者实现消费。
备胎死信消费者会根据该订单号码查询是否已经支付过,如果没有支付的情况下则会开始回滚库存操作。

3.2 RabbitMQ消息幂等问题

RabbitMQ消息自动重试机制

  1. 当我们消费者处理执行我们业务代码的时候,如果抛出异常的情况下在这时候mq会自动触发重试机制,默认的情况下rabbitmq是无限次数的重试需要人为指定重试次数限制问题
  2. 在什么情况下消费者需要实现重试策略?
    A.消费者获取消息后,调用第三方接口,但是调用第三方接口失败呢?是否需要重试?
    该情况下需要实现重试策略,网络延迟只是暂时调用不通,重试多次有可能会调用通。

B.消费者获取消息后,因为代码问题抛出数据异常,是否需要重试?
该情况下是不需要实现重试策略,就算重试多次,最终还是失败的,可以将日志存放起来,后期通过定时任务或者人工补偿形式。
如果是重试多次还是失败消息,需要重新发布消费者版本实现消费。
可以使用死信队列。

Mq在重试的过程中,有可能会引发消费者重复消费的问题。
Mq消费者需要解决 幂等性问题
幂等性 保证数据唯一

方式1:
生产者在投递消息的时候,生成一个全局唯一id,放在我们消息中。
消费者获取到我们该消息,可以根据该全局唯一id实现去重复。
全局唯一id 根据业务来定的 订单号码作为全局的id
实际上还是需要再db层面解决数据防重复。
业务逻辑是在做insert操作 使用唯一主键约束
业务逻辑是在做update操作 使用乐观锁

  1. 当消费者业务逻辑代码中,抛出异常自动实现重试 (默认是无数次重试)
  2. 应该对RabbitMQ重试次数实现限制,比如最多重试5次,每次间隔3s;重试多次还是失败的情况下,存放到死信队列或者存放到数据库表中记录后期人工补偿

如何合理选择消息重试

  1. 消费者获取消息后,调用第三方接口,但是调用第三方接口失败呢?是否需要重试 ?
  2. 消费者获取消息后,应该代码问题抛出数据异常,是否需要重试?

总结:如果消费者处理消息时,因为代码原因抛出异常是需要从新发布版本才能解决的,那么就不需要重试,重试也解决不了该问题的。存放到死信队列或者是数据库表记录、后期人工实现补偿。

Rabbitmq如何开启重试策略

spring:rabbitmq:####连接地址host: 127.0.0.1####端口号port: 5672####账号username: guest####密码password: guest### 地址virtual-host: /meite_rabbitmqlistener:simple:retry:####开启消费者(程序出现异常的情况下会)进行重试enabled: true####最大重试次数max-attempts: 5####重试间隔次数initial-interval: 3000

消费者重试过程中,如何避免幂等性问题
重试的过程中,为了避免业务逻辑重复执行,建议提前全局id提前查询,如果存在的情况下,就无需再继续做该流程。
重试的次数最好有一定间隔次数,在数据库底层层面保证数据唯一性,比如加上唯一id。

3.3 SpringBoot开启消息确认机制

配置文件新增

spring:rabbitmq:####连接地址host: 127.0.0.1####端口号port: 5672####账号username: guest####密码password: guest### 地址virtual-host: /meiteVirtualHostslistener:simple:retry:####开启消费者(程序出现异常的情况下会)进行重试enabled: true####最大重试次数max-attempts: 5####重试间隔次数initial-interval: 3000acknowledge-mode: manualdatasource:url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8username: rootpassword: rootdriver-class-name: com.mysql.jdbc.Driver

消费者ack代码

@Slf4j
@Component
@RabbitListener(queues = "fanout_order_queue")
public class FanoutOrderConsumer {@Autowiredprivate OrderManager orderManager;@Autowiredprivate OrderMapper orderMapper;@RabbitHandlerpublic void process(OrderEntity orderEntity, Message message, Channel channel) throws IOException {
//        try {log.info(">>orderEntity:{}<<", orderEntity.toString());String orderId = orderEntity.getOrderId();if (StringUtils.isEmpty(orderId)) {log.error(">>orderId is null<<");return;}OrderEntity dbOrderEntity = orderMapper.getOrder(orderId);if (dbOrderEntity != null) {log.info(">>该订单已经被消费过,无需重复消费!<<");// 无需继续重试channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);return;}int result = orderManager.addOrder(orderEntity);log.info(">>插入数据库中数据成功<<");if (result >= 0) {// 开启消息确认机制channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}
//        int i = 1 / 0;
//        } catch (Exception e) {
//            // 将失败的消息记录下来,后期采用人工补偿的形式
//        }}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/53340.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端框架Vue、React、Angular、Svelte对比

在对比 React、Vue.js、Angular 和 Svelte 时&#xff0c;除了在高层次的特性上有显著差异&#xff0c;它们在核心设计理念和底层实现机制上也有明显的不同。为了清晰地理解这些框架&#xff0c;我们可以从以下几个方面来分析它们的核心不同点和底层不同点。 1. 框架类型和设计…

Golang | Leetcode Golang题解之第415题字符串相加

题目&#xff1a; 题解&#xff1a; func addStrings(num1 string, num2 string) string {add : 0ans : ""for i, j : len(num1) - 1, len(num2) - 1; i > 0 || j > 0 || add ! 0; i, j i - 1, j - 1 {var x, y intif i > 0 {x int(num1[i] - 0)}if j &g…

ChatGPT 4o 使用指南 (9月更新)

首先基础知识还是要介绍得~ 一、模型知识&#xff1a; GPT-4o&#xff1a;最新的版本模型&#xff0c;支持视觉等多模态&#xff0c;OpenAI 文档中已经更新了 GPT-4o 的介绍&#xff1a;128k 上下文&#xff0c;训练截止 2023 年 10 月&#xff08;作为对比&#xff0c;GPT-4…

深度学习自编码器 - 去噪自编码器篇

序言 在深度学习的广阔天地中&#xff0c;自编码器作为一种强大的无监督学习工具&#xff0c;通过重构输入数据的方式&#xff0c;不仅实现了数据的有效压缩&#xff0c;还探索了数据的内在表示。而去噪自编码器&#xff08; Denoising Autoencoder, DAE \text{Denoising Auto…

软件设计师——操作系统

&#x1f4d4;个人主页&#x1f4da;&#xff1a;秋邱-CSDN博客☀️专属专栏✨&#xff1a;软考——软件设计师&#x1f3c5;往期回顾&#x1f3c6;&#xff1a;C: 类和对象&#xff08;上&#xff09;&#x1f31f;其他专栏&#x1f31f;&#xff1a;C语言_秋邱 一、操作系统…

VGG16模型实现新冠肺炎图片多分类

1. 项目简介 本项目的目标是通过深度学习模型VGG16&#xff0c;实现对新冠肺炎图像的多分类任务&#xff0c;以帮助医疗人员对患者的影像进行快速、准确的诊断。新冠肺炎自爆发以来&#xff0c;利用医学影像如X光和CT扫描进行疾病诊断已成为重要手段之一。随着数据量的增加&am…

华为---以太网静态路由配置使用下一跳通信正常,而使用出接口无法通信

目录 1. 实验环境 2. 结果测试 3. 分析验证 3.1 以太网静态路由配置使用下一跳跨网段通信抓包分析 3.2 以太网静态路由配置使用出接口跨网段通信抓包分析 3.3 以太网静态路由配置使用出接口无法跨网段通信问题解决办法 1. 实验环境 以太网静态路由配置使用下一跳跨网段通…

网络丢包定位记录(二)

网卡驱动丢包 查看&#xff1a;ifconfig eth1/eth0 等接口 1.RX errors: 表示总的收包的错误数量&#xff0c;还包括too-long-frames错误&#xff0c;Ring Buffer 溢出错误&#xff0c;crc 校验错误&#xff0c;帧同步错误&#xff0c;fifo overruns 以及 missed pkg 等等。 …

Maven的详细解读和配置

目录 一、Maven 1.1 引言 1.2 介绍 1.3 下载安装 1.3.1 解压 1.3.2 配置环境变量 1.3.3 测试 1.4 仓库[了解] 1.5 Maven配置 1.5.1 修改仓库位置 1.5.2 设置镜像 二、IDEA - MAVEN 2.1 idea关联maven 2.2 为新项目设置 2.2 创建java项目[重点] 2.3 java项目结构…

Go-知识-定时器

Go-知识-定时器 1. 介绍2. Timer使用场景2.1 设定超时时间2.2 延迟执行某个方法 3. Timer 对外接口3.1 创建定时器3.2 停止定时器3.3 重置定时器3.4 After3.5 AfterFunc 4. Timer 的实现原理4.1 Timer数据结构4.1.1 Timer4.1.2 runtimeTimer 4.2 Timer 实现原理4.2.1 创建Timer…

特征工程与交叉验证在机器学习中的应用

数据入口&#xff1a;学生考试表现影响因素数据集 - Heywhale.com 本数据集提供了关于影响学生考试成绩的多种因素的全面概述。数据集包含了有关学习习惯、出勤率、家长参与、资源获取等方面的信息。 数据说明 字段名说明Hours_Studied每周学习的小时数Attendance出勤率&…

(笔记自用)位运算总结+LeetCode例题:颠倒二进制位+位1的个数

一.位运算总结: 在解题之前理解一下为什么需要位运算&#xff1f;它的本质是什么&#xff1f; 力扣上不少位运算相关的题&#xff0c;并且很多题也会用到位运算的技巧。这又是为什么&#xff1f; 位运算的由来 在计算机里面&#xff0c;任何数据最终都是用数字来表示的&…

[Linux]:信号(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;Linux学习 贝蒂的主页&#xff1a;Betty’s blog 1. 信号的阻塞 1.1 基本概念 信号被操作系统发送给进程之后&#xff0c;进程…

【Linux学习】基本指令其一

命令行界面 命令行终端是一个用户界面&#xff0c;允许用户通过输入文本命令与计算机系统进行交互。 比如Windows下&#xff0c; 键入winR&#xff0c;然后输入cmd&#xff0c;就可以输入文本指令与操作系统交互了。 Windows有另一个命令行界面Powershell,它的功能比cmd更强大…

电商ISV 电商SaaS 是什么

Independent Software Vendors的英文缩写&#xff0c;意为“独立软件开发商” 软件即服务(SaaS) 指一种基于云技术的软件交付模式 订阅收费 这些公司叫做ISV软件供应商&#xff0c;通过SaaS服务交付收费 为什么会有电商ISV 从商家角度划分&#xff1a;有独立品牌商家、大商…

微信支付的委托代扣功能服务如何申请开通?

扣款服务&#xff08;原委托代扣服务&#xff0c;以下均用委托代扣&#xff09;是微信支付旗下的重要产品 1、委托代扣是指商户取得用户的扣款授权后&#xff0c;向微信支付发起从用户账户扣款至商户账户的扣款指令&#xff0c;微信支付无需验证用户的支付密码&#xff0c;即可…

记录一下,Vcenter清理/storage/archive空间

一、根因 vpostgres&#xff1a;这个目录可能包含与 vCenter Server 使用的 PostgreSQL 数据库相关的归档文件过多&#xff0c;导致空间被占用。 二、处理过程 1、SSH登陆到Vcenter. 2、df -Th **图中可以看到 /storage/archive 使用占比很高。 /storage/archive 目录通常用…

fiddler抓包06_抓取https请求(chrome)

课程大纲 首次安装Fiddler&#xff0c;抓https请求&#xff0c;除打开抓包功能&#xff08;F12&#xff09;还需要&#xff1a; ① Fiddler开启https抓包 ② Fiddler导出证书&#xff1b; ③ 浏览器导入证书。 否则&#xff0c;无法访问https网站&#xff08;如下图&#xff0…

Qt优秀开源项目之二十三:QSimpleUpdater

QSimpleUpdater是开源的自动升级模块&#xff0c;用于检测、下载和安装更新。 github地址&#xff1a;https://github.com/alex-spataru/QSimpleUpdater QSimpleUpdater目前Star不多&#xff08;911个&#xff09;&#xff0c;但已在很多开源项目看到其身影&#xff0c;比如Not…

web网站的任意文件上传下载漏洞解析

免责申明 本文仅是用于学习检测自己搭建的任意文件上传下载漏洞相关原理,请勿用在非法途径上,若将其用于非法目的,所造成的一切后果由您自行承担,产生的一切风险和后果与笔者无关;本文开始前请认真详细学习《‌中华人民共和国网络安全法》‌及其所在国家地区相关法规内容【…