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 测试结果
    • 三、总结

在这里插入图片描述

RabbitMQ 是流行的开源消息队列中间件,使用 erlang 语言开发,由于其社区活跃度高,维护更新较快,深得很多企业的喜爱。

一、简介

1.1 定义

死信队列(Dead Letter Queue,简称 DLX)是 RabbitMQ 中一种特殊的队列,用于处理无法正常被消费者消费的消息。当消息在原始队列中因为 达到最大重试次数过期、或者 满足特定条件 时,可以 将这些消息重新路由到一个预定义的死信队列中 进行进一步处理或记录。

1.2 何时进入死信队列?

当发生以下情况,业务队列中的消息会进入死信队列:

  1. 消息被否定确认:使用 channel.basicNackchannel.basicReject,并且此时 requeue 属性被设置为 false
  2. 消息过期:消息在队列的存活时间超过设置的 TTL 时间。
  3. 消息溢出:队列中的消息数量已经超过最大队列长度。

当发生以上三种情况后,该消息将成为 死信。死信消息会被 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 死信消息的生命周期

死信消息的生命周期如下:

  1. 业务消息被 投入业务队列
  2. 消费者 消费业务队列的消息,由于处理过程中 发生异常,于是 进行了 NackReject 操作
  3. NackReject 的消息由 RabbitMQ 投递到死信交换机中
  4. 死信交换机将消息 投入相应的死信队列
  5. 死信队列的消费者 消费死信消息

二、代码实现

2.1 死信队列的配置步骤

死信队列的配置可以分为以下三步:

  1. 配置业务队列,绑定到业务交换机上;
  2. 为业务队列 配置死信交换机、路由键;
  3. 为死信交换机 配置死信队列

注意:

并不是直接声明一个公共的死信队列,然后所有死信消息就会自己进入死信队列中了。而是为每个需要使用死信的业务队列配置一个死信交换机,这里同一个项目的死信交换机可以共用一个,然后每个业务队列分配一个单独的路由键。

有了死信交换机和路由键后,接下来就像配置业务队列一样,配置死信队列,并绑定在死信交换机上。看到这里,大家应该可以明白:

  • 死信队列 并不是什么特殊的队列,只不过是绑定在死信交换机上的队列
  • 死信交换机 也不是什么特殊的交换机,只不过是用来接收死信队列的交换机

所以死信交换机可以为任何类型【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";

    // 声明业务交换机(广播)
    @Bean
    public FanoutExchange businessExchange() {
        return new FanoutExchange(BUSINESS_EXCHANGE_NAME);
    }

    // 声明死信交换机(直连)
    @Bean
    public DirectExchange deadLetterExchange() {
        return new DirectExchange(DEAD_LETTER_EXCHANGE);
    }

    // 声明业务队列A
    @Bean
    public 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
    @Bean
    public 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
    @Bean
    public Queue deadLetterQueueA() {
        return new Queue(DEAD_LETTER_QUEUEA_NAME);
    }

    // 声明死信队列B
    @Bean
    public Queue deadLetterQueueB() {
        return new Queue(DEAD_LETTER_QUEUEB_NAME);
    }

    // 声明业务队列A绑定关系
    @Bean
    public Binding businessBindingA(Queue businessQueueA, FanoutExchange businessExchange) {
        return BindingBuilder.bind(businessQueueA).to(businessExchange);
    }
    
    // 声明业务队列B绑定关系
    @Bean
    public Binding businessBindingB(Queue businessQueueB, FanoutExchange businessExchange) {
        return BindingBuilder.bind(businessQueueB).to(businessExchange);
    }
    
    // 声明死信队列A绑定关系
    @Bean
    public Binding deadLetterBindingA(Queue deadLetterQueueA, DirectExchange deadLetterExchange) {
        return BindingBuilder.bind(deadLetterQueueA).to(deadLetterExchange).with(DEAD_LETTER_QUEUEA_ROUTING_KEY);
    }
    
    // 声明死信队列B绑定关系
    @Bean
    public Binding deadLetterBindingB(Queue deadLetterQueueB, DirectExchange deadLetterExchange) {
        return BindingBuilder.bind(deadLetterQueueB).to(deadLetterExchange).with(DEAD_LETTER_QUEUEB_ROUTING_KEY);
    }
}

2.3 配置文件

application.yml

server:
  port: 8081

spring:
  application:
    name: springboot-rabbitmq-dead-letter
  rabbitmq:
    # 此处不建议单独配置host和port,单独配置不支持连接RabbitMQ集群
    addresses: 127.0.0.1:5672
    username: guest
    password: guest
    # 虚拟host 可以不设置,使用server默认host
    virtual-host: /
    # 是否开启发送端消息抵达队列的确认
    publisher-returns: true
    # 发送方确认机制,默认为NONE,即不进行确认;SIMPLE:同步等待消息确认;CORRELATED:异步确认
    publisher-confirm-type: correlated
    # 消费者监听相关配置
    listener:
      simple:
        acknowledge-mode: manual # 确认模式,默认auto,自动确认;manual:手动确认
        default-requeue-rejected: false # 消费端抛出异常后消息是否返回队列,默认值为true
        prefetch: 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,这提供了接收/发送等方法。
     */
    @Autowired
    private 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 想怎样配合就可以怎样配合。比如:

  • 从死信队列拉取消息,然后发送邮件、短信、钉钉通知来通知开发人员关注。
  • 或者将消息重新投递到一个队列然后设置过期时间,来进行延时消费等等。

整理完毕,完结撒花~ 🌻





参考地址:

1.【RabbitMQ】一文带你搞定RabbitMQ死信队列,https://cloud.tencent.com/developer/article/1463065

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

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

相关文章

【EI会议征稿通知】第三届城市规划与区域经济国际学术会议(UPRE 2024)

第三届城市规划与区域经济国际学术会议&#xff08;UPRE 2024&#xff09; 2024 3rd International Conference on Urban Planning and Regional Economy 第三届城市规划与区域经济国际学术会议&#xff08;UPRE 2024&#xff09;于2024年4月19-21日在泰国曼谷举行。会议旨在…

Qt/QML编程学习之心得:Linux下读写文件File(24)

在Linux嵌入式系统中,经常会使用Qt来读写一个文件,判断一个文件是否存在,具体如何实现呢? 首先,要使用linux系统中相关的头文件: #include <unistd.h> #include <stdio.h> #include <stdlib.h> 其次,判断路径是否存在, if(!dir.exists()){mkdir(…

智能驾驶打响“进阶战”:高、中、低阶赛道如何突围?

伴随智能驾驶普及进入新周期&#xff0c;高、中、低阶市场呈现不同的突围方式。 根据高工智能汽车研究院监测数据&#xff0c;2023年1-9月&#xff0c;中国市场&#xff08;不含进出口&#xff09;乘用车新车销售标配搭载L2&#xff08;含L2&#xff09;的渗透率达到36.31%&am…

QT常用控件使用及布局

QT常用控件使用及布局 文章目录 QT常用控件使用及布局1、创建带Ui的工程2、ui界面介绍1、界面设计区2、对象监视区3、对象监属性编辑区4、信号与槽5、布局器6、控件1、Layouts1、布局管理器2、布局的dome 2、Spacers3、Buttons4、项目视图组(Item Views)5、项目控件组(Item Wid…

【sgPasswordInput】自定义组件:带前端校验密码强度的密码输入框,能够提供密码强度颜色提示和文字提示

特性&#xff1a; 有密码强度颜色提示密码强度进度条提示支持设置默认输入提示和密码长度 sgPasswordInput源码 <template><div :class"$options.name" style"width: 100%"><el-inputstyle"width: 100%"ref"psw"type&…

软件使用手册

一简介 软件分两部分&#xff0c;股票监测程序&#xff0c;监测列表配置数据页面以及手机端接收监控数据。股票监测程序需要在电脑上运行。下载地址为程序下载地址。监控股票配置页面地址为动态列表。 二使用介绍 2.1监控客户端 2.1.1程序安装及登录 下载安装exe程序程序下载地…

C#开源的一款友好的.NET SDK管理器

前言 今天推荐一款由C#开源的、友好的.NET SDK管理器&#xff1a;Dots。 工具介绍 Dots 是一款 .NET SDK 管理器&#xff0c;可让您轻松安装、卸载和切换 .NET SDK。它是一款跨平台工具&#xff0c;可在 Windows 和 macOS 上运行&#xff0c;即将支持 Linux。它由 C# 编写&a…

部署可道云网盘的一个漏洞解决

目录 1漏洞展示 2.防范措施 1漏洞展示 因为可道云网盘的上传文档有保存在 /data/Group/public/home/文档/ 中,当别有用心之人知道个人部署的域名与上次的文件后&#xff0c;可以进行访问拿到uid。例我在我部署的网盘上上次一个aa.php 文件&#xff0c;然后拿来演示 然后通过…

Axure RP Extension For Chrome 插件安装

1. 下载好 AXURE RP EXTENSION For Chrome 插件之后解压成文件夹 2. 打开浏览器&#xff0c;找到设置--更多工具--扩展程序--加载已加压的扩展程序&#xff0c;选择解压好的文件夹 3. 点击详细信息&#xff0c;打开访问网址权限

静态路由、代理ARP

目录 静态路由静态路由指明下一跳和指明端口的区别代理ARP 我们知道&#xff0c;跨网络通信需要路由 路由有三种类型&#xff1a; 1.直连路由。 自动产生的路由&#xff0c;当网络设备连接到同一网络时&#xff0c;他们可以自动学习到对方的存在。自动学习相邻网络设备的直连信…

C/C++ 联合体

目录 联合体概述 联合体的内存分配 联合体大小计算 联合体概述 联合与结构非常的相似&#xff0c;主要区别就在于联合这两个字。 联合的特征&#xff1a;联合体所包含的成员变量使用的是同一块空间。 联合体定义 //联合类型的声明 union Un {char c;int i; }; //联合变量…

PAT 乙级 1049 数列的片段和

分数 20 作者 CAO, Peng 单位 Google 给定一个正数数列&#xff0c;我们可以从中截取任意的连续的几个数&#xff0c;称为片段。例如&#xff0c;给定数列 { 0.1, 0.2, 0.3, 0.4 }&#xff0c;我们有 (0.1) (0.1, 0.2) (0.1, 0.2, 0.3) (0.1, 0.2, 0.3, 0.4) (0.2) (0.2, 0.3) …

VSD Viewer for Mac Visio绘图文件阅读器

Visio Viewer for Mac是一款专为Mac用户设计的Visio文件查看工具。它允许用户在Mac上查看和共享Visio文件&#xff0c;无需安装微软的Visio软件。这对于那些没有购买或没有访问Windows操作系统的Mac用户来说&#xff0c;是一个非常方便的选择。 该软件支持Visio的各种文件格式…

五行能量,影响着你的运势,莫小看!

五行的能量不能小看&#xff0c;时时刻刻在影响着我们。五行为聚之成形&#xff0c;散之成气&#xff1b;五行学说讲的是能量与气场&#xff0c;它指出世界有五种元素&#xff1a;金、水、木、火、土&#xff0c;这代表世界的五种不同的能量。五行能量存在于我们的身边&#xf…

分析抖音直播弹幕评论和礼物的websocket数据流信息,通过proto协议解析消息内容思路

现在定位到一个解析的大概位置&#xff1a; e.decode function(e, t) {e instanceof o || (e o.create(e));for (var n, i, s void 0 t ? e.len : e.pos t, u new r.webcast.im.MemberMessage(r.webcast. 通过请求找到发送请求的js代码位置&#xff0c;然后通过跟踪这…

Halcon机器视觉和运动控制软件通用框架,24年1月最新版新增UI设计器,插件式开发,开箱即用 仅供学习!

24年1月更新 下载点我 此版本已经添加ui设计器。具体功能如上所示&#xff0c;可以自定义变量&#xff0c;写c#脚本&#xff0c;自定义流程&#xff0c;包含了halcon脚本和封装的算子&#xff0c;可自定义ui&#xff0c;通过插件形式开发很方便拓展自己的功能。 ui设计器

Web前端-JavaScript(BOM)

文章目录 1.1 常用的键盘事件1.1.1 键盘事件1.1.2 键盘事件对象1.1.3 案例一 1.2 BOM1.2.1 什么是BOM1.2.2 BOM的构成1.2.3 window1.2.4 window对象常见事件窗口/页面加载事件**第1种****第2种** 调整窗口大小事件 1.2.5 定时器setTimeout() 炸弹定时器停止定时器**案例&#x…

数据采集卡技术参数一览!

数据采集卡主要技术参数有如下几个指标&#xff1a; &#xff08;1&#xff09;通道数&#xff1a;即板卡可以采集几路信号&#xff0c;分为单端和双端&#xff08;差分&#xff09;。常用的有单端32路/差分16路、单端16路/差分8路。 &#xff08;2&#xff09;数据采集卡采样…

简单易懂的PyTorch线性层解析:神经网络的构建基石

目录 torch.nn子模块Linear Layers详解 nn.Identity Identity 类描述 Identity 类的功能和作用 Identity 类的参数 形状 示例代码 nn.Linear Linear 类描述 Linear 类的功能和作用 Linear 类的参数 形状 变量 示例代码 nn.Bilinear Bilinear 类的功能和作用 B…

Mudbox 各版本安装指南

​Mudbox下载链接 https://pan.baidu.com/s/1cYcITQ7OeI5PadNdXur_bA?pwd0531 1.鼠标右击【Mudbox2024(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到 Mudbox2024(64bit)】。 2.打开解压后的文件夹&#xff0c;双击打开【Setup】…