[RabbitMQ] 延迟队列+事务+消息分发

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482
🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
在这里插入图片描述

目录

  • 1. 延迟队列
    • 1.1 概念
    • 1.2 TTL+死信队列实现
    • 1.3 延迟队列插件
      • 1.3.1 安装延迟队列
      • 1.3.2 基于插件延迟队列实现
    • 1.4 常见面试题
  • 2. 事务
    • 2.1 配置事务
    • 2.2 配置队列
    • 2.3 生产者
  • 3. 消息分发
    • 3.1 概念
    • 3.2 应用场景
      • 3.2.1 限流
      • 3.2.2 负载均衡

1. 延迟队列

1.1 概念

延迟队列就是在消息发送以后,并不想让消费者立刻拿到消息,而是等待特定的时间之后,消费者才可以拿到消息进行消费.
RabbitMQ本身并没有直接支持延迟队列的功能,但是可以通过TTL+死信队列的方式结合模拟出延迟队列的功能.
假设一个应用中需要每条消息都为10s延迟,生产者通过normal_exchange这个交换器将发送的消息存储在normal_queue这个队列,之后为这个队列或者队列中的消息的ttl设置为10s.但是消费者订阅的队列并不是normal_queue这个队列,而是dlx_queue这个队列,当消息从normal_queue这个队列中的消息经历10s过期之后存入dlx_queue这个队列中,消费者就恰好消费到了延迟10s之后的消息.
在这里插入图片描述

1.2 TTL+死信队列实现

代码实现:

  1. 先看TTL+死信队列实现延迟队列
    定义正常队列和死信队列,绑定正常队列和死信交换机.
@Bean
public Queue normalQueue(){
    return QueueBuilder.durable(Constant.NORMAL_QUEUE).
            deadLetterExchange(Constant.DLX_EXCHANGE).
            deadLetterRoutingKey("dlx").
            build();
}
@Bean
public DirectExchange normalExchange(){
    return ExchangeBuilder.directExchange(Constant.NORMAL_EXCHANGE).durable(true).build();
}
@Bean
public Binding normalBinding(
        @Qualifier("normalQueue") Queue queue,
        @Qualifier("normalExchange") DirectExchange exchange
){
    return BindingBuilder.bind(queue).to(exchange).with("normal");
}
@Bean
public Queue dlxQueue(){
    return QueueBuilder.durable(Constant.DLX_QUEUE).build();
}
@Bean
public DirectExchange dlxExchange(){
    return ExchangeBuilder.directExchange(Constant.DLX_EXCHANGE).durable(true).build();
}
@Bean
public Binding dlxBinding(
        @Qualifier("dlxQueue") Queue queue,
        @Qualifier("dlxExchange") DirectExchange exchange
){
    return BindingBuilder.bind(queue).to(exchange).with("dlx");
}

编写生产者:
发送一条10s过期的消息,再发送一条20s过期的消息.

@RequestMapping("/delay")
public String delay(){
    Message message1 = new Message("发送delay消息10s".getBytes(StandardCharsets.UTF_8));
    message1.getMessageProperties().setExpiration("10000");
    rabbitTemplate.convertAndSend(Constant.NORMAL_EXCHANGE,"normal",message1);
    Message message2 = new Message("发送delay消息20s".getBytes(StandardCharsets.UTF_8));
    message2.getMessageProperties().setExpiration("20000");
    rabbitTemplate.convertAndSend(Constant.NORMAL_EXCHANGE,"normal",message2);
    return "发送成功";
}

消费者:

@Component
public class DelayListener {
    @RabbitListener(queues = Constant.DLX_QUEUE)
    public void listener(Message message){
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println("接收到消息,deliveryTag:"+deliveryTag+",消息内容:"+msg);
    }
}

调用接口之后观察控制台接收消息的结果:等待10s和20s之后分别接收到消息
在这里插入图片描述
延迟队列希望达到的效果就是延迟一定的时间之后才收到消息,TTL刚好给消息设置延迟时间,成为死信,成为死信之后就会被投递到死信队列中,这样消费者就可以一直消费死信队列的消息就可以了.
但是这样的模式也会存在一定的问题
我们可以先发送20s的数据,再发送10s的数据:

@RequestMapping("/delay")
public String delay(){
    Message message2 = new Message("发送delay消息20s".getBytes(StandardCharsets.UTF_8));
    message2.getMessageProperties().setExpiration("20000");
    rabbitTemplate.convertAndSend(Constant.NORMAL_EXCHANGE,"normal",message2);
    Message message1 = new Message("发送delay消息10s".getBytes(StandardCharsets.UTF_8));
    message1.getMessageProperties().setExpiration("10000");
    rabbitTemplate.convertAndSend(Constant.NORMAL_EXCHANGE,"normal",message1);
    return "发送成功";
}

通过控制台观察死信队列消费情况:
在这里插入图片描述
我们发现10s过期的消息和20s过期的消息同时被消费者收到.10s过期的消息和20s过期的消息同时进入了死信队列.
这是由于在消息过期之后,消息不会被马上丢弃,消息只在消息被消费者消费的时候,即出队列的时候检测消息是否过期(扫描队头的消息是否过期),由于20s的消息在10s消息的前面,队列会优先扫描20s过期的消息,10s过期的消息还暂时不会被扫描到,当队列扫描到20s的消息过期的时候,10s的消息才会被扫描到,队列这才会认为10s的这条消息已经过期了,所以他和20s的消息便同时进入了死信队列中.
所以在考虑使用TTL+死信队列实现延迟任务队列的时候,需要确认业务上每个任务的延迟时间是⼀致的,如果遇到不同的任务类型需要不同的延迟的话,需要为每⼀种不同延迟时间的消息建立单独的消息队列.

1.3 延迟队列插件

RabbitMQ官方也提供了一个延迟的插件来实现延迟队列的功能.

1.3.1 安装延迟队列

  1. 下载并上传插件
    https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases
    在这里插入图片描述
    下载ez文件.下载到Windows环境之后通过Xshell上传到服务器.(这里需要注意的是,我们下载的插件版本需要和我们操作系统上安装的RabbitMQ的版本一致)
    我们在上传到服务器中的时候,我们需要把该文件上传到/usr/lib/rabbitmq/plugins目录中,RabbitMQ本身不会在此安装任何内容,如果没有这个路径,可以自己进行创建.
    在这里插入图片描述
  2. 启动插件:
    rabbitmq-plugins enable rabbitmq_delayed_message_exchange
    在这里插入图片描述
  3. 验证插件
    在RabbitMQ管理平台查看,新建交换机的时候是否有延迟消息的选项,如果有就说明延迟消息插件已经正常运行了.
    在这里插入图片描述

1.3.2 基于插件延迟队列实现

  1. 声明交换机和队列
    在交换机的声明之后加上delay()选项,这里需要注意的是,虽然我们叫的是延迟队列,但是我们是在交换机上声明延迟的.
public static final String DELAY_EXCHANGE = "delay_exchange";
public static final String DELAY_QUEUE = "delay_queue";
@Bean
public DirectExchange delayExchange(){
    return ExchangeBuilder.directExchange(Constant.DELAY_EXCHANGE).delayed().durable(true).build();
}
@Bean
public Queue delayQueue(){
    return QueueBuilder.durable(Constant.DELAY_QUEUE).build();
}
@Bean
public Binding delayBinding(@Qualifier("delayExchange") DirectExchange exchange,
                            @Qualifier("delayQueue") Queue queue){
    return BindingBuilder.bind(queue).to(exchange).with("delay");
}
  1. 生产者
    发送两条消息,并设置延迟时间,这里我们先设置20s的,再设置10s的,看看上面的问题有没有得到解决.我们前面使用ttl+死信队列的方式实现消息延迟的时候,我们设置消息设置的是过期时间(setExpiration),我们在这里设置的时候设置的是延迟时间(setDelayLong).
@RequestMapping("/delay")
public String delay(){
    Message message2 = new Message("发送delay消息20s".getBytes(StandardCharsets.UTF_8));
    message2.getMessageProperties().setDelayLong(20000L);
    rabbitTemplate.convertAndSend(Constant.DELAY_EXCHANGE,"delay",message2);
    Message message1 = new Message("发送delay消息10s".getBytes(StandardCharsets.UTF_8));
    message1.getMessageProperties().setDelayLong(10000L);
    rabbitTemplate.convertAndSend(Constant.DELAY_EXCHANGE,"delay",message1);
    return "发送成功";
}
  1. 消费者
@Component
public class DelayListener {
    @RabbitListener(queues = Constant.DLX_QUEUE)
    public void listener(Message message){
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println("接收到消息,deliveryTag:"+deliveryTag+",消息内容:"+msg);
    }
}
  1. 运行程序,观察控制台日志和RabbitMQ管理界面
    在这里插入图片描述
    我们看到delay_exchange的交换机类型是"x-delay-message".
    调用接口,发送消息,观察控制台日志:
    在这里插入图片描述
    我们发现我们上述的问题得到了解决,我们首先收到了延迟10s的消息,后收到了延迟20s的消息.

1.4 常见面试题

延迟队列作为RabbitMQ的高级特性,也是面试的一大重点

  1. 介绍一下RabbitMQ的延迟队列
    延迟队列就是在消息发送以后,并不想让消费者立刻拿到消息,而是等待特定的时间之后,消费者才可以拿到消息进行消费.
    但是RabbitMQ本身并没有直接实现延迟队列,有以下的两种方法来实现:
    • 通过ttl+死信队列的方式来实现
    • 但是通过这种方式实现存在一定的问题,如果延迟时间长的消息先到达队列,延迟时间短的后到达队列,延迟时间短的不会即时被消费者收到.
    • 可以通过官方提供的延迟插件实现延迟功能.
  2. 应用场景
    • 订单在10min之内未支付自动取消.
    • 用户在注册成功之后,3天之后发起调查问卷
    • 用户发起退款,24小时之内商家未处理,则默认同意退款.
  3. 二者对比
    • 基于死信实现的延迟队列
      优点就是灵活,不需要额外的插件来支持,缺点就是存在消息顺序问题,需要额外的逻辑来处理死信队列的消息,增加了系统的复杂性.
    • 基于插件实现的延迟队列
      优点就是通过插件可以直接创建延迟队列,简化延迟消息的实现,避免了DLX存在消息顺序问题.
      缺点就是需要依赖特定的插件,有运维的工作,其次RabbitMQ的版本必须和插件的版本对应.

2. 事务

RabbitMQ是基于AMQP协议实现的,该协议实现了事务的机制,因此RabbitMQ也支持事务的机制,Spring AMQP也提供了对事务的额相关操作.RabbitMQ事务允许开发者确保消息的发送和接收是原子性的,要么全部成功,要么全部失败.

2.1 配置事务

配置事务管理器的时候,分为两步,首先创建RabbitTemplate,使用setChannelTransacted(true)开启RabbitTemplate的信道事务.之后创建事务管理器RabbitTransactionManager.

@Configuration
public class TransactionConfig {
    @Bean
    public RabbitTransactionManager transactionManager(ConnectionFactory connectionFactory){
        return new RabbitTransactionManager(connectionFactory);
    }
    @Bean
    public RabbitTemplate transactionTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setChannelTransacted(true);
        return rabbitTemplate;
    }
}

2.2 配置队列

@Bean
public Queue tansactionQueue(){
    return QueueBuilder.durable("trans_queue").build();
}

2.3 生产者

在生产者中,我们需要在方法之上加上@Transactional才可以生效.我们在异常发生之前发送一条消息,在异常发生之后发送一条消息,查看数据是否会被回滚.

@RequestMapping("/trans")
@Transactional
public String trans(){
    transactionTemplate.convertAndSend("","trans_queue","trans1...");
    int i = 5/0;
    transactionTemplate.convertAndSend("","trans_queue","trans2...");
    return "发送成功";
}

测试:
在这里插入图片描述
在发送消息之后,报出了500的错误码.
在这里插入图片描述
我们看到trans_queue中没有接收到消息,说明第一条消息被回滚了.

3. 消息分发

3.1 概念

RabbitMQ队列拥有多个消费者的时候,队列会把收到的消息分派给不同的消费者,每条消息只会发送给订阅列表里的一个消费者.这种方式非常适合扩展,如果现在负载加重,那么只需要创建更多的消费者处理消息即可.
默认的情况下,消费者是轮训进行分发的,而不管消费者是否已经消费并已经确认了消息.这种方式是不太合理的,试想一下,如果某些消费者的消费速度较慢,而某些消费者的消费速度很快,这就会导致某些消费者的消息发生积压,某些消费者则很空闲,进而导致应用的整体吞吐量下降.所以我们就可以使用消息分发来解决问题.

3.2 应用场景

消息分发的常见应用场景如下:

  1. 限流
  2. 非公平分发

3.2.1 限流

RabbitMQ提供了限流的机制,可以控制消费端一次只能拉取N个请求.
通过设置prefetchCount参数,同时也必须设置消息应答方式为手动应答.
prefetchCount: 控制消费者从队列中预取消息的数量,以此来实现流控制和负载均衡.
代码示例:

  1. 首先我们需要在配置文件中加入prefetch参数
    listener:
      simple:
        acknowledge-mode: manual #需要设置为手动应答
        retry:
          initial-interval: 1000ms
          enabled: true
          max-attempts: 5
        prefetch: 5 # 表示一个队列最多可以有5条未确认的消息
  1. 配置队列和交换机
@Bean
public DirectExchange QOSExchange(){
    return ExchangeBuilder.directExchange(Constant.QOS_EXCHANGE).durable(true).build();
}
@Bean
public Queue QOSQueue(){
    return QueueBuilder.durable(Constant.QOS_QUEUE).build();
}
@Bean
public Binding QOSBinding(@Qualifier("QOSQueue") Queue queue,
                          @Qualifier("QOSExchange") DirectExchange exchange){
    return BindingBuilder.bind(queue).to(exchange).with("qos");
}
  1. 消费者监听队列
    首先我们先不对消息进行手动ack
@Component
public class QOSListener {
    @RabbitListener(queues = Constant.QOS_QUEUE)
    public void listener(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("消费者接收到消息,deliveryTag:"+deliveryTag+",消息内容:"+new String(message.getBody(),"UTF-8"));
//        channel.basicAck(deliveryTag,true);
    }
}
  1. 生产者发送消息,一次性发送20条消息
@RequestMapping("/qos")
public String qos(){
    for (int i = 0;i < 20 ;i++){
        rabbitTemplate.convertAndSend(Constant.QOS_EXCHANGE,"qos","message"+i);
    }
    return "发送成功";
}
  1. 测试
    调用接口,发送消息:
    在这里插入图片描述
    在这里插入图片描述
    我们看到,由于没有对消息进行手动应答,我们控制台只收到了5条消息.
    在这里插入图片描述
    由于5条消息还没有ack掉,所以剩下的15条消息就在队列中发生了堆积.
    之后我们对消息进行手动ack.观察控制台:
    在这里插入图片描述
    我们看到消息全部被应答了.

3.2.2 负载均衡

我们也可以使用此配置来实现负载均衡.
如图所示的情况下,一个消费者处理任务非常快,另一个非常慢,就会造成一个消费者很忙,一个消费者很闲.这是因为RabbitMQ只是在消息进入队列的时候分派消息,它不考虑消费者未确认消息的数量.
在这里插入图片描述
我们可以设置prefetch=1的方式来实现负载均衡.告诉RabbitMQ一次只给一个消费者发送消息,也就是说,在一个消费者对前一条消息进行确认之前,不会对该消费者发送新的消息,相反,它会将它分配给一个不处在繁忙阶段的消息队列.
代码示例:

  1. 配置prefetch参数为1,将消息应答机制设置为手动应答
    listener:
      simple:
        acknowledge-mode: manual #需要设置为手动应答
        retry:
          initial-interval: 1000ms
          enabled: true
          max-attempts: 5
        prefetch: 1 # 表示一个队列最多可以有1条未确认的消息
  1. 设置两个消费者,其中消费较慢的消费者使用Thread.sleep(100)来模拟消费慢.
@Component
public class QOSListener {
    @RabbitListener(queues = Constant.QOS_QUEUE)
    public void listener(Message message, Channel channel) throws IOException, InterruptedException {
        Thread.sleep(100);
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("消费者接收到消息,deliveryTag:"+deliveryTag+",消息内容:"+new String(message.getBody(),"UTF-8"));
        channel.basicAck(deliveryTag,true);
    }
    @RabbitListener(queues = Constant.QOS_QUEUE)
    public void listener2(Message message,Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("消费者2接收到消息,deliveryTag:"+deliveryTag+",消息内容:"+new String(message.getBody(),"UTF-8"));
        channel.basicAck(deliveryTag,true);
    }
}
  1. 测试
    调用接口,向两个消费者发送消息
    在这里插入图片描述
    我们可以很明显的看到,消费者2消费消息的速度比消费者1快很多.

deliveryTag有重复是因为两个消费者使用的是不同的Channel,每个Channel上的deliveryTag 是独立计数的.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[GKCTF 2021]签到

[GKCTF 2021]签到 wireshark跟踪http流&#xff0c;基本编解码&#xff0c;倒叙&#xff0c;栅栏密码 找到cat /f14g 把包里返回的字符串先hex解码&#xff0c;再base64解码&#xff0c;看到一个时间是倒叙&#xff0c;不含flag 继续往下面翻&#xff0c;可以看到cat%2Ff14g%7…

后端-mybatis的一对一查询

准备两张表菜单表和分类表&#xff0c;一个菜单对应一个分类&#xff0c;一个分类对应多个菜单&#xff0c;我们从菜单对分类来操作一对一的关系&#xff0c;首先在菜单表里面要有一个分类表的id字段。 使用mapper的映射文件.xml来写的话我们在resource目录下创建各自mapper的…

【科研】9如何高效阅读和理解学术论文

【科研】9如何高效阅读和理解学术论文 写在最前面一、为什么需要系统的阅读方法&#xff1f;二、阅读论文的11步方法三、实践示例四、常见问题解答五、结语 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 2024每日百字篆刻时光&#xff0c;感谢你的陪伴与支持 ~ …

【python】OpenCV—Tracking(10.5)—dlib

文章目录 1、功能描述2、代码实现3、效果展示4、完整代码5、涉及到的库函数dlib.correlation_tracker() 6、参考 1、功能描述 基于 dlib 库&#xff0c;实现指定类别的目标检测和单目标跟踪 2、代码实现 caffe 模型 https://github.com/MediosZ/MobileNet-SSD/tree/master/…

【Python数据分析】房价预测:使用线性回归模型预测波士顿房价

博客主页&#xff1a;小馒头学python 本文专栏: Python爬虫五十个小案例 专栏简介&#xff1a;分享五十个Python爬虫小案例 &#x1f4dd;引言 &#x1f4dd;房价预测的意义 房价预测对于房地产行业、投资者和政策制定者来说具有重要意义。通过对房价进行准确预测&#xf…

java——PV操作

PV操作通常指的是P操作和V操作&#xff0c;它们是信号量&#xff08;Semaphore&#xff09;机制中的两个基本操作&#xff0c;用于解决进程或线程间的同步和互斥问题。P操作&#xff08;也称为wait操作或down操作&#xff09;用于将信号量的值减1&#xff0c;如果结果为负数&am…

5. langgraph实现高级RAG (Adaptive RAG)

1. 数据准备 from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.document_loaders import WebBaseLoader from langchain_community.vectorstores import Chromaurls ["https://lilianweng.github.io/posts/2023-06-23-age…

redis基础spark操作redis

Redis内存淘汰策略 将Redis用作缓存时&#xff0c;如果内存空间用满&#xff0c;就会自动驱逐老的数据。 为什么要使用内存淘汰策略呢&#xff1f; 当海量数据涌入redis&#xff0c;导致redis装不下了咋办&#xff0c;我们需要根据redis的内存淘汰策略&#xff0c;淘汰一些不那…

C++优选算法十七 多源BFS

1.单源最短路问题 一个起点一个终点。 定义&#xff1a;在给定加权图中&#xff0c;选择一个顶点作为源点&#xff0c;计算该源点到图中所有其他顶点的最短路径长度。 2.多源最短路问题 定义&#xff1a;多源最短路问题指的是在图中存在多个起点&#xff0c;需要求出从这些…

DDR3保姆级使用教程:ZYNQ 7010

内容:使用DDR3 IP核&#xff0c;向DDR3写入数据&#xff0c;然后再读出数据&#xff0c;通过串口打印。 设备&#xff1a;ZYNQ 7010 xc7z010clg-400-1。软件VIVADO 2018.3 &#xff08;1&#xff09;工程模块&#xff1a;一个写FIFO&#xff0c;一个读FIFO。一个ZYNQ IP核&am…

vue3使用monaco编辑器(VSCode网页版)

vue3使用monaco编辑器&#xff08;VSCode网页版&#xff09; 文章说明参考文章核心代码效果展示实践说明源码下载 文章说明 一直在找网页版的编辑器&#xff0c;网页版的VSCode功能很强大&#xff0c;这个monaco就是VSCode样式的编辑器&#xff0c;功能很强大&#xff0c;可以直…

Y20030019 基于java+jsp+mysql的微信小程序校园二手交易平台的设计与实现 源代码 文档

旅游度假区微信小程序 1.摘要2. 系统开发的目的和意义3.系统功能4.界面展示5.源码获取 1.摘要 随着移动互联网的发展&#xff0c;微信小程序已经成为人们生活中不可或缺的一部分。微信小程序的优点在于其快速、轻量、易用&#xff0c;用户无需下载即可使用&#xff0c;节省了用…

uniapp实现列表页面,实用美观

咨询列表页面 组件 <template><view><view class"news_item" click"navigator(item.id)" v-for"item in list" :key"item.id"><image :src"item.img_url"></image><view class"righ…

哈希表,哈希桶的实现

哈希概念 顺序结构以及平衡树中&#xff0c;元素关键码与其存储位置之间没有对应的关系&#xff0c;因此在查找一个元素 时&#xff0c;必须要经过关键码的多次比较。顺序查找时间复杂度为O(N)&#xff0c;平衡树中为树的高度&#xff0c;即 O(logN)&#xff0c;搜索的效率取决…

网络安全(三):网路安全协议

网络安全协议设计的要求是实现协议过程中的认证性、机密性与不可否认性。网络安全协议涉及网络层、传输层与应用层。 1、网络层安全与IPSec协议、IPSec VPN 1.1、IPSec安全体系结构 IP协议本质上是不安全的额&#xff0c;伪造一个IP分组、篡改IP分组的内容、窥探传输中的IP分…

Golang教程第8篇(语言条件语句)

Go 语言条件语句 条件语句需要开发者通过指定一个或多个条件&#xff0c;并通过测试条件是否为 true 来决定是否执行指定语句&#xff0c;并在条件为 false 的情况在执行另外的语句。 Go 语言 if 语句 Go 语言条件语句 Go 语言条件语句 if 语句由布尔表达式后紧跟一个或多个语…

基于MFC实现的银行模拟系统

基于MFC实现的银行模拟系统 1.软硬件运行环境 1.1 项目研究背景与意义 为了能给学生熟悉银行业务系统提供真实的操作环境, 使学生在掌握理论知识的同时熟悉银行业务的实际操作过程&#xff0c;改变其知识结构&#xff0c;培养商业银行真正需要的实用人才&#xff0c;增强学生…

Qt自定义 Widget 组件

自定义 Widget 子类 QmyBattery Qt 的 UI 设计器提供了很多 GUI 设计的界面组件&#xff0c;可以满足常见的界面设计需求。但是某些时候需要设计一些特殊的界面组件&#xff0c;而在 UI 设计器的组件面板里根本没有合适的组件&#xff0c;这时就需要设计自定义的界面组件。 所…

Flink双流Join

在离线 Hive 中&#xff0c;我们经常会使用 Join 进行多表关联。那么在实时中我们应该如何实现两条流的 Join 呢&#xff1f;Flink DataStream API 为我们提供了3个算子来实现双流 join&#xff0c;分别是&#xff1a; join coGroup intervalJoin 下面我们分别详细看一下这…

WEB攻防-通用漏洞XSS跨站绕过修复http_onlyCSP标签符号

修复&#xff1a; 1、过滤一些危险字符&#xff1b; 2、HTTP-only Cookie; 3、设置CSP&#xff08;Content Security Policy&#xff09;; 4、输入内容长度限制&#xff0c;转义等&#xff1b; XSS绕过-CTFSHOW-316到331 关卡绕过WP XSS修复-过滤函数&http_only&C…