尚品汇-MQ模块搭建测试、消息不丢失(重)(四十三)

news2024/9/21 4:25:24

目录:

(1)消息不丢失

(2)消息确认

(3)消息确认业务封装

 (4)封装发送端消息确认

(5)封装消息发送

(6)发送确认消息测试

(7)消息发送失败,设置重发机制

(1)消息不丢失

消息的不丢失,在MQ角度考虑,一般有三种途径:

1,生产者不丢数据

2,MQ服务器不丢数据

3,消费者不丢数据

保证消息不丢失有两种实现方式:

  1. 开启事务模式
  2. 消息确认模式

说明:开启事务会大幅降低消息发送及接收效率,使用的相对较少,因此我们生产环境一般都采取消息确认模式,以下我们只是讲解消息确认模式

(2)消息确认

消息持久化

如果希望RabbitMQ重启之后消息不丢失,那么需要对以下3种实体均配置持久化

Exchange

声明exchange时设置持久化(durable = true)并且不自动删除(autoDelete = false)

Queue

声明queue时设置持久化(durable = true)并且不自动删除(autoDelete = false)

message

发送消息时通过设置deliveryMode=2持久化消息

处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。那么如何持久化呢,其实也很容易,就下面两步:

1、将queue的持久化标识durable设置为true,则代表是一个持久的队列

2、发送消息的时候将deliveryMode=2

这样设置以后,rabbitMQ就算挂了,重启后也能恢复数据

发送确认

有时,业务处理成功,消息也发了,但是我们并不知道消息是否成功到达了rabbitmq,如果由于网络等原因导致业务成功而消息发送失败,那么发送方将出现不一致的问题,此时可以使用rabbitmq的发送确认功能,即要求rabbitmq显式告知我们消息是否已成功发送。

手动消费确认

有时,消息被正确投递到消费方,但是消费方处理失败,那么便会出现消费方的不一致问题。比如:订单已创建的消息发送到用户积分子系统中用于增加用户积分,但是积分消费方处理却都失败了,用户就会问:我购买了东西为什么积分并没有增加呢?

要解决这个问题,需要引入消费方确认,即只有消息被成功处理之后才告知rabbitmq以ack,否则告知rabbitmq以nack

(3)消息确认业务封装

搭建rabbit-util模块

由于消息队列是公共模块,我们把mq的相关业务封装到该模块,其他service微服务模块都可能使用,因此我们把他封装到一个单独的模块,需要使用mq的模块直接引用该模块即可

搭建方式如:common-util,导入常量类 MqConst

package com.atguigu.gmall.constant;

public class MqConst {
    /**
     * 消息补偿
     */
    public static final String MQ_KEY_PREFIX = "mq:list";
    public static final int RETRY_COUNT = 3;
    /**
     * 商品上下架
     */
    public static final String EXCHANGE_DIRECT_GOODS = "exchange.direct.goods";
    public static final String ROUTING_GOODS_UPPER = "goods.upper";
    public static final String ROUTING_GOODS_LOWER = "goods.lower";
    //队列
    public static final String QUEUE_GOODS_UPPER  = "queue.goods.upper";
    public static final String QUEUE_GOODS_LOWER  = "queue.goods.lower";
    /**
     * 取消订单,发送延迟队列
     */
    public static final String EXCHANGE_DIRECT_ORDER_CANCEL = "exchange.direct.order.cancel";//"exchange.direct.order.create" test_exchange;
    public static final String ROUTING_ORDER_CANCEL = "order.create";
    //延迟取消订单队列
    public static final String QUEUE_ORDER_CANCEL  = "queue.order.cancel";
    //取消订单 延迟时间 单位:秒 真实业务
    public static final int DELAY_TIME  = 24*60*60;
    //  测试取消订单
    // public static final int DELAY_TIME  = 3;
    /**
     * 订单支付
     */
    public static final String EXCHANGE_DIRECT_PAYMENT_PAY = "exchange.direct.payment.pay";
    public static final String ROUTING_PAYMENT_PAY = "payment.pay";
    //队列
    public static final String QUEUE_PAYMENT_PAY  = "queue.payment.pay";

    /**
     * 减库存
     */
    public static final String EXCHANGE_DIRECT_WARE_STOCK = "exchange.direct.ware.stock";
    public static final String ROUTING_WARE_STOCK = "ware.stock";
    //队列
    public static final String QUEUE_WARE_STOCK  = "queue.ware.stock";
    /**
     * 减库存成功,更新订单状态
     */
    public static final String EXCHANGE_DIRECT_WARE_ORDER = "exchange.direct.ware.order";
    public static final String ROUTING_WARE_ORDER = "ware.order";
    //队列
    public static final String QUEUE_WARE_ORDER  = "queue.ware.order";

    /**
     * 关闭交易
     */
    public static final String EXCHANGE_DIRECT_PAYMENT_CLOSE = "exchange.direct.payment.close";
    public static final String ROUTING_PAYMENT_CLOSE = "payment.close";
    //队列
    public static final String QUEUE_PAYMENT_CLOSE  = "queue.payment.close";
    /**
     * 定时任务
     */
    public static final String EXCHANGE_DIRECT_TASK = "exchange.direct.task";
    public static final String ROUTING_TASK_1 = "seckill.task.1";
    //队列
    public static final String QUEUE_TASK_1  = "queue.task.1";
    /**
     * 秒杀
     */
    public static final String EXCHANGE_DIRECT_SECKILL_USER = "exchange.direct.seckill.user";
    public static final String ROUTING_SECKILL_USER = "seckill.user";
    //队列
    public static final String QUEUE_SECKILL_USER  = "queue.seckill.user";

    /**
     * 定时任务
     */

    public static final String ROUTING_TASK_18 = "seckill.task.18";
    //队列
    public static final String QUEUE_TASK_18  = "queue.task.18";


}

pom.xml

 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>common</artifactId>
        <groupId>com.atguigu.gmall</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>rabbit-util</artifactId>

    <dependencies>
        <!--rabbitmq消息队列-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--rabbitmq 协议-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
    </dependencies>

</project>

service-mq引入rabbit-util模块依赖

<!--rabbitmq消息-->

  <dependency>

   <groupId>com.atguigu.gmall</groupId>

   <artifactId>rabbit-util</artifactId>

   <version>1.0</version>

  </dependency>

 (4)封装发送端消息确认

在rabbit-util 中添加类

package com.atguigu.gmall.common.config;

/**
 * @Description 消息发送确认
 * <p>
 * ConfirmCallback  只确认消息是否正确到达 Exchange 中
 * ReturnCallback   消息没有正确到达队列时触发回调,如果正确到达队列不执行
 * <p>
 * 1. 如果消息没有到exchange,则confirm回调,ack=false
 * 2. 如果消息到达exchange,则confirm回调,ack=true
 * 3. exchange到queue成功,则不回调return
 * 4. exchange到queue失败,则回调return
 * 
 */
@Component
@Slf4j
public class MQProducerAckConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 修饰一个非静态的void()方法,在服务器加载Servlet的时候运行,并且只会被服务器执行一次在构造函数之后执行,init()方法之前执行。
    @PostConstruct
    public void init() {
        rabbitTemplate.setConfirmCallback(this);            //指定 ConfirmCallback
        rabbitTemplate.setReturnCallback(this);             //指定 ReturnCallback
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            log.info("消息发送成功:" + JSON.toJSONString(correlationData));
        } else {
            log.info("消息发送失败:" + cause + " 数据:" + JSON.toJSONString(correlationData));
        }
    }

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        // 反序列化对象输出
        System.out.println("消息主体: " + new String(message.getBody()));
        System.out.println("应答码: " + replyCode);
        System.out.println("描述:" + replyText);
        System.out.println("消息使用的交换器 exchange : " + exchange);
        System.out.println("消息使用的路由键 routing : " + routingKey);
    }

 }

(5)封装消息发送

在rabbit-util 中添加类RabbitService

package com.atguigu.gmall.common.service;


@Service
public class RabbitService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     *  发送消息
     * @param exchange 交换机
     * @param routingKey 路由键
     * @param message 消息
     */
    public boolean sendMessage(String exchange, String routingKey, Object message) {
 
        rabbitTemplate.convertAndSend(exchange, routingKey, message);
        return true;
    }
   
}

(6)发送确认消息测试

在service-mq编写测试代码

消息发送端

package com.atguigu.gmall.mq.controller;


@RestController
@RequestMapping("/mq")
public class MqController {


   @Autowired
   private RabbitService rabbitService;


   /**
    * 消息发送
    */
   //http://localhost:8282/mq/sendConfirm
   @GetMapping("sendConfirm")
   public Result sendConfirm() {
     
      rabbitService.sendMessage("exchange.confirm", "routing.confirm", "来人了,开始接客吧!");
      return Result.ok();
   }
}

消息接收端

在service-mq 中编写

这里交换机队列绑定:有两种方式一种是配置,一种是注解

我们这里使用注解进行创建绑定

@SneakyThrows 是Lombk的异常 处理注解

package com.atguigu.gmall.mq.receiver;


@Component
public class ConfirmReceiver {

@SneakyThrows
@RabbitListener(bindings=@QueueBinding(
        value = @Queue(value = "queue.confirm",autoDelete = "false"),
        exchange = @Exchange(value = "exchange.confirm",autoDelete = "false"),
        key = {"routing.confirm"}))
public void process(Message message, Channel channel){
    System.out.println("RabbitListener:"+new String(message.getBody()));

        // 参数一:消息的唯一标识,参数二:是否批量确认 false 确认一个消息,true 批量确认
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    
}
}

测试:http://localhost:8282/mq/sendConfirm

 加入了确认机制后,成功后:会有提示

把交换机写错,发送到交换机会失败

 

 

把路由key写错,交换机到队列消息失败:


 

(7)消息发送失败,设置重发机制

实现思路:借助redis来实现重发机制

在rabbit-util 模块中添加依赖

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

<!-- spring2.X集成redis所需common-pool2-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>

自定义一个实体类来接收消息

@Data
public class GmallCorrelationData extends CorrelationData {

    //  消息主体
    private Object message;
    //  交换机
    private String exchange;
    //  路由键
    private String routingKey;
    //  重试次数
    private int retryCount = 0;
    //  消息类型  是否是延迟消息
    private boolean isDelay = false;
    //  延迟时间
    private int delayTime = 10;
}

 

修改发送方法

//  封装一个发送消息的方法
public Boolean sendMsg(String exchange,String routingKey, Object msg){
    //  将发送的消息 赋值到 自定义的实体类
    GmallCorrelationData gmallCorrelationData = new GmallCorrelationData();
    //  声明一个correlationId的变量
    String correlationId = UUID.randomUUID().toString().replaceAll("-","");
    gmallCorrelationData.setId(correlationId);
    gmallCorrelationData.setExchange(exchange);
    gmallCorrelationData.setRoutingKey(routingKey);
    gmallCorrelationData.setMessage(msg);

    //  发送消息的时候,将这个gmallCorrelationData 对象放入缓存。
    redisTemplate.opsForValue().set(correlationId, JSON.toJSONString(gmallCorrelationData),10, TimeUnit.MINUTES);
    //  调用发送消息方法
//this.rabbitTemplate.convertAndSend(exchange,routingKey,msg);
    this.rabbitTemplate.convertAndSend(exchange,routingKey,msg,gmallCorrelationData);
    //  默认返回true
    return true;
}

发送失败调用重发方法  MQProducerAckConfig 配置类中修改 

@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
    //  ack = true 说明消息正确发送到了交换机
    if (ack){
        System.out.println("哥们你来了.");
        log.info("消息发送到了交换机");
    }else {
        //  消息没有到交换机
        log.info("消息没发送到交换机");
        //  调用重试发送方法
        this.retrySendMsg(correlationData);
    }
}

@Override
public void returnedMessage(Message message, int code, String codeText, String exchange, String routingKey) {
    System.out.println("消息主体: " + new String(message.getBody()));
    System.out.println("应答码: " + code);
    System.out.println("描述:" + codeText);
    System.out.println("消息使用的交换器 exchange : " + exchange);
    System.out.println("消息使用的路由键 routing : " + routingKey);

    //  获取这个CorrelationData对象的Id  spring_returned_message_correlation
    String correlationDataId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");
    //  因为在发送消息的时候,已经将数据存储到缓存,通过 correlationDataId 来获取缓存的数据
    String strJson = (String) this.redisTemplate.opsForValue().get(correlationDataId);
    //  消息没有到队列的时候,则会调用重试发送方法
    GmallCorrelationData gmallCorrelationData = JSON.parseObject(strJson,GmallCorrelationData.class);
    //  调用方法  gmallCorrelationData 这对象中,至少的有,交换机,路由键,消息等内容.
    this.retrySendMsg(gmallCorrelationData);
}

/**
 * 重试发送方法
 * @param correlationData   父类对象  它下面还有个子类对象 GmallCorrelationData
 */
private void retrySendMsg(CorrelationData correlationData) {
    //  数据类型转换  统一转换为子类处理
    GmallCorrelationData gmallCorrelationData = (GmallCorrelationData) correlationData;
    //  获取到重试次数 初始值 0
    int retryCount = gmallCorrelationData.getRetryCount();
    //  判断
    if (retryCount>=3){
        //  不需要重试了
        log.error("重试次数已到,发送消息失败:"+JSON.toJSONString(gmallCorrelationData));
    } else {
        //  变量更新
        retryCount+=1;
        //  重新赋值重试次数 第一次重试 0->1 1->2 2->3
        gmallCorrelationData.setRetryCount(retryCount);
        System.out.println("重试次数:\t"+retryCount);

        //  更新缓存中的数据
        this.redisTemplate.opsForValue().set(gmallCorrelationData.getId(),JSON.toJSONString(gmallCorrelationData),10, TimeUnit.MINUTES);

        //  调用发送消息方法 表示发送普通消息  发送消息的时候,不能调用 new RabbitService().sendMsg() 这个方法
            this.rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange(),gmallCorrelationData.getRoutingKey(),gmallCorrelationData.getMessage(),gmallCorrelationData);
   
    }
}

把路由写错,交换机到队列消息失败:

  把交换机写错:发送到交换机失败

 

总结

为了防止消息不丢失 ,从以下三方面考虑:

1,生产者不丢数据

2,MQ服务器不丢数据

3,消费者不丢数据

   

生产者:

MQ服务器:

RabbitMQ重启之后消息不丢失

消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号

开启确认回调(成功到达交换机发送ack标识,没有达到重发)、失败回调(设置重发机制)

为了防止消费者处理消息丢失,我们开启消费者消息消费确认回调,消息被成功处理之后才告知rabbitmq以ack,否则告知rabbitmq以nack

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

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

相关文章

【C#】Visual Studio 2017开发C#,按F1键没有跳转到C#帮助文档,反而跳到了Qt的帮助文档

1. 原因 Visual Studio中安装了Qt的插件&#xff0c;所以将F1的跳转链接转到了Qt的帮助文档。 2. F1改回微软帮助文档方法 工具 - 选项 - Qt - General - Try Qt Documentation when F1 is pressed改为Flase

Web服务端通过SSE推送消息给浏览器客户端的实现方案(附详细代码和仓库地址)

目录 1、SSE&#xff08;Server-Sent Events&#xff09;简介2、SSE 的工作原理3、SSE 与客户端轮询的区别和优势比较区别优势 4、SSE简单实现(单机应用Demo)演示效果SSE-Demo仓库地址下面直接贴代码&#xff1a;前端实现&#xff1a;后端实现&#xff1a; 5、SSE简单实现(分布…

【pycharm】汉化及翻译插件

汉化插件 翻译插件 使用 选中右键翻译

一键解决LBP2900通信错误的问题(同样支持Win 11系统)

**目录** **前言****常见解决方式****方案一&#xff1a;端口排除****方案二&#xff1a;服务重启****方案三&#xff1a;注册表注入修复** 前言 佳能LBP2900向来是经典耐用款的打印机。想必各位可能遇到过&#xff0c;由于老旧会出现奇葩的问题&#xff0c;譬如 就算USB接口已…

【C++篇】~类和对象(上)

【C篇】 类和对象上 一类二实例化内存对齐原因&#xff08;用空间换时间&#xff0c;提高效率&#xff09; 一类 ‘类’class可以理解为C语言阶段的‘结构体’&#xff0c;它的用法与struct大差不差很多地方都相同&#xff0c;但是C毕竟是C&#xff0c;类的用法肯定比结构体的…

Linux Kernel 6.12版预计将支持在崩溃后显示二维码 后续可以解码排查错误

7 月份时红帽工程师基于 systemd 255 版的全屏显示错误消息功能为 Linux Kernel 开发崩溃后显示二维码选项&#xff0c;这与微软在 Windows 10/11 蓝屏死机后显示二维码有异曲同工之妙。 不过 Linux 与 Windows 在崩溃时显示的二维码内容则有本质区别&#xff0c;因为 Window…

单链表反转(C语言)

1 问题描述 力扣&#xff08;LeetCode&#xff09;--反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 例如&#xff1a; 输入&#xff1a;head [1,2,3,4,5]输出&#xff1a;[5,4,3,2,1] 输入&#xff1a;head [1,2]输出&#x…

11 Java 方法引用、异常处理

文章目录 前言一、Java接口之函数式编程 --- 接口知识补充1 Function<T,R>泛型接口2 BiFunction<T, U, R>泛型接口3 自定义泛型函数式编程接口 二、方法引用1 方法引用初体验&#xff08;以Array.sort()方法为例&#xff09;2 引用静态方法3 引用其他类成员方法 前…

【面试五】PID控制算法

一、 PID算法简介 PID&#xff08;Proportional-Integral-Derivative&#xff09;控制算法是一种经典的反馈控制方法&#xff0c;广泛应用于自动控制系统&#xff0c;例如温度控制、速度控制、位置控制等。 PID控制算法的核心包含三个部分&#xff1a;比例项&#xff08;P&…

@antv/g6 业务场景:流程图

1、流程图是流经一个系统的信息流、观点流或部件流的图形代表。在企业中&#xff0c;流程图主要用来说明某一过程。这种过程既可以是生产线上的工艺流程&#xff0c;也可以是完成一项任务必需的管理过程。业务场景流程图如下&#xff1a; 2、绘制流程图的 Tips 流程图一般是用…

计算机毕业设计选题推荐-果树生长信息管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

Redis三种集群模式:主从模式、哨兵模式和Cluster模式

1. 总结经验 redis主从&#xff1a;可实现高并发(读)&#xff0c;典型部署方案&#xff1a;一主二从 redis哨兵&#xff1a;可实现高可用&#xff0c;典型部署方案&#xff1a;一主二从三哨兵 redis集群&#xff1a;可同时支持高可用(读与写)、高并发&#xff0c;典型部署方…

探索Python数据持久化的秘密:ZODB库的神奇之旅

文章目录 探索Python数据持久化的秘密&#xff1a;ZODB库的神奇之旅背景ZODB是什么&#xff1f;如何安装ZODB&#xff1f;简单库函数使用方法场景应用常见Bug及解决方案总结 探索Python数据持久化的秘密&#xff1a;ZODB库的神奇之旅 背景 在Python的广阔世界中&#xff0c;数…

基于单片机的水箱水质监测系统设计

本设计基于STM32F103C8T6为核心控制器设计了水质监测系统&#xff0c;选用DS18B20温度传感器对水箱水体温度进行采集&#xff1b;E-201-C PH传感器获取水体PH值&#xff1b;选用TS-300B浊度传感器检测水体浊度&#xff1b;采用YW01液位传感器获取水位&#xff0c;当检测水位低于…

网络压缩之知识蒸馏(knowledge distillation)

因为直接训练一个小的网络&#xff0c;往往结果就是没有从大的网络剪枝好。知识蒸馏的概念是 一样的&#xff0c;因为直接训练一个小的网络&#xff0c;没有小的网络根据大的网络来学习结果要来得 好。 因而&#xff0c;先训练一个 大的网络&#xff0c;这个大的网络在知识蒸馏…

Flutter 初识:Chip控件

Flutter Chip控件小结 Chip属性解析示例 InputChip属性解析示例 ChoiceChip属性解析示例 FilterChip属性解析示例 ActionChip属性解析示例 在 Flutter 中&#xff0c;Chip 是一种用于显示简洁信息的组件。它通常用来展示标签、属性、短的文本片段等&#xff0c;并可以包含可选的…

C语言推箱子迷宫

目录 开头程序程序的流程图程序游玩的效果下一篇博客要说的东西 开头 大家好&#xff0c;我叫这是我58。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <Windows.h> typedef stru…

python内置模块datetime.date类详细介绍

Python的datetime模块是一个强大的日期和时间处理库&#xff0c;它提供了多个类来处理日期和时间。主要包括几个功能类datetime.date、datetime.time、datetime.datetime、datetime.timedelta,datetime.timezone等。 使用datetime模块 要使用 datetime模块&#xff0c;直接导…

大模型辅助软件开发,助力工程师的开发之路

大模型与软件工程师&#xff1a;改变开发范式的力量 “是人类工程师的能力&#xff0c;而不是大模型的能力&#xff0c;决定了大模型协作式开发的上限。” 这句话深刻地揭示了在人工智能&#xff0c;尤其是大型语言模型&#xff08;LLM&#xff09;飞速发展的今天&#xff0c…

《数字信号处理》学习02-序列的能量及周期性

目录 一&#xff0c;序列的能量 二&#xff0c;序列的周期性 一&#xff0c;序列的能量 序列能量在数字信号处理中的应用&#xff1a;能量归一化。在信号处理中&#xff0c;有时需要对信号进行归一化处理&#xff0c;使得信号的能量为特定的值&#xff0c;这在一些算法和系统…