RabbitMQ死信队列详解

什么是死信队列

由于特定的**原因导致 Queue 中的某些消息无法被消费,**这类消费异常的数据将会保存在死信队列中防止消息丢失,例如用户在商城下单成功并点击支付后,在指定时间未支付时的订单自动失效
死信队列只不过是绑定在死信交换机上的队列。死信交换机只不过是用来接受死信的交换机,可以为任何类型【Direct、Fanout、Topic】。一般来说,会为每个业务队列分配一个独有的路由key,并对应的配置一个死信队列进行监听,也就是说,一般会为每个重要的业务队列配置一个死信队列。

来源

  1. 消息的存活时间到了,ttl过期
  2. 队列积压的消息达到最大长度(在队列中等待时间最久的消息会成为死信)
  3. 消息被拒(消费方返回nack进行否定应答)且不重新加入队列(requeue=false)

演示

模拟一条正常应该被C1消费者接收的消息,由于出现消费异常情况进入死信队列被C2消费者进行消费的案例

架构图

死信队列案例示意图
死信队列标记


TTL过期

  1. 在生产者方进行指定为当前发送消息的过期时间,缺点是消息即使过期也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的(** 如果当前队列有严重的消息积压情况,已过期的消息依旧会被积压在队列中,如果队列配置了消息积压上限,**将导致后续应当正常消费的消息全部进入死信队列 )
  2. 在队列指定为所有到达该队列的消息的过期时间,时间从消息入队列开始计算,只要超过了队列的超时时间配置,消息会自动清除

该案例为在生产者方进行指定
配置类:

package com.example.rabbitmqqoslimiting.demos;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class RabbitUtils {

    private static final ConnectionFactory connectionFactory;    //放到静态代码块中,在类加载时执行,只执行一次。达到工厂只创建一次,每次获取是新连接的效果

    static {
        //创建连接工厂
        connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("101.133.141.74");                   //设置MQ的主机地址
        connectionFactory.setPort(5672);                                //设置MQ服务端口
        connectionFactory.setVirtualHost("study");                      //设置Virtual Hosts(虚拟主机)
        connectionFactory.setUsername("guest");                         //设置MQ管理人的用户名(要在Web版先配置,保证该用户可以管理设置的虚拟主机)
        connectionFactory.setPassword("guest");                           //设置MQ管理人的密码
    }

    //定义提供连接对象的方法,封装
    public static Connection getConnection(){

        try {
            //创建连接对象并返回
            return connectionFactory.newConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }


    //关闭通道和关闭连接工具类的方法
    public static void closeConnectionAndChannel(Channel channel, Connection connection){
        try {
            if (channel!=null) {
                channel.close();
            }
            if (connection!=null){
                connection.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

生产者:

package com.dmbjz.one;

import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.nio.charset.StandardCharsets;

/* 死信队列TTL案例 生产者 */
public class Provider {

    private static final String EXCHANGE_NAME = "normal_exchange";              //正常交换机名称
    private static final String KEY = "zhangsan";        //普通队列 RoutingKey

    public static void main(String[] args) throws Exception {

        Connection connection = RabbitUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.DIRECT);  //声明交换机

        /*死信队列 设置TTL消息过期时间 单位毫秒*/
        AMQP.BasicProperties properties = new AMQP.BasicProperties()
                .builder()
                .expiration("20000")
                .build();

        /*模拟消息循环发送*/
        for(int i = 1; i < 11; i++) {
            String message = "INFO " + i;
            channel.basicPublish(EXCHANGE_NAME,KEY,properties,message.getBytes(StandardCharsets.UTF_8));  
        }


    }
}

消费者C1:

package com.dmbjz.one;

import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/* 死信队列TTL案例 消费者C1 */
public class ConsumerC1 {


    private static final String EXCHANGE_NAME = "normal_exchange";              //正常交换机名称
    private static final String DEAD_EXCHANGE_NAME = "dead_exchange";           //死信队列交换机名称

    private static final String KEY = "zhangsan";        //普通队列 RoutingKey
    private static final String DEAD_KEY = "lisi";       //死信队列 RoutingKey

    private static final String QUEUE_NAME = "normal-queue";       //普通队列名称
    private static final String DEAD_QUEUE_NAME = "dead-queue";    //死信队列名称


    public static void main(String[] args) throws IOException {

        Connection connection = RabbitUtils.getConnection();
        Channel channel = connection.createChannel();


        /*声明死信和普通交换机,正常交换机已被生产者声明,实际可以省略第一行代码*/
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);


        /*创建队列
        * 通过额外参数实现什么情况下转发到死信队列 ?,key都是固定的
        *   1、TTL过期时间设置(一般由生产者指定)
        *   2、死信交换机的名称
        *   3、死信交换机的RoutingKey
        * */
        Map<String,Object> arguments = new HashMap<>(8);
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE_NAME);     //死信交换机的名称
        arguments.put("x-dead-letter-routing-key",DEAD_KEY);            //死信交换机的RoutingKey
        // arguments.put("x-dead-letter-ttl",10000);   指定消息的有效时间为20秒(一般为生产者指定)
        channel.queueDeclare(QUEUE_NAME,false,false,false,arguments);
        channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);


        /*绑定队列*/
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,KEY);
        channel.queueBind(DEAD_QUEUE_NAME,DEAD_EXCHANGE_NAME,DEAD_KEY);



        DeliverCallback successBack = (consumerTag, message) -> {
            System.out.println("C1用户接收到的信息为:"+new String(message.getBody()));
        };

        CancelCallback cnaelBack = a->{
            System.out.println("C1用户进行取消消费操作!");
        };

        channel.basicConsume(QUEUE_NAME,true,successBack,cnaelBack);


    }
}

消费者C2:

package com.dmbjz.one;

import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

/* 死信队列TTL案例 消费者C2 */
public class ConsumerC2 {

    private static final String DEAD_EXCHANGE_NAME = "dead_exchange";           //死信队列交换机名称
    private static final String DEAD_KEY = "lisi";       //死信队列 RoutingKey
    private static final String DEAD_QUEUE_NAME = "dead-queue";    //死信队列名称


    public static void main(String[] args) throws IOException {

        Connection connection = RabbitUtils.getConnection();
        Channel channel = connection.createChannel();


        /*声明队列和普通交换机并进行绑定,由于消费者C1已经声明过了,这里实际可以省略这三行代码*/
        channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);
        channel.queueBind(DEAD_QUEUE_NAME,DEAD_EXCHANGE_NAME,DEAD_KEY);


        DeliverCallback successBack = (consumerTag, message) -> {
            System.out.println("C2用户接收到的信息为:"+new String(message.getBody()));
        };

        CancelCallback cnaelBack = a->{
            System.out.println("C2用户进行取消消费操作!");
        };

        channel.basicConsume(DEAD_QUEUE_NAME,true,successBack,cnaelBack);


    }


}

效果演示:
  1. 执行消费者C1,创建出所有交换机和队列绑定后停止运行。
  2. 执行生产者,等待10秒钟后查看控制台,消息全部通过交换机进入死信队列
  3. 运行消费者C2,死信队列消息被成功消费

20秒后消息全部进入死信队列
运行消费者C2,死信队列内的消息被全部取出


队列消息积压达到最大长度

在绑定死信队列的消费者端添加队列的消息积压长度限制即可,核心代码为TTL案例的消费者C1基础上再添加Map参数

//指定队列能够积压消息的大小,超出该范围的消息将进入死信队列
arguments.put("x-max-length",6);            

生产者:

package com.dmbjz.maxlength;

import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.nio.charset.StandardCharsets;

/* 死信队列 队列达到最大长度案例 生产者 */
public class Provider {

    private static final String EXCHANGE_NAME = "normal_exchange";              //正常交换机名称
    private static final String KEY = "zhangsan";        //普通队列 RoutingKey

    public static void main(String[] args) throws Exception {

        Connection connection = RabbitUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.DIRECT);  //声明交换机

        /*循环消息发送*/
        for(int i = 1; i < 11; i++) {
            String message = "INFO " + i;
            channel.basicPublish(EXCHANGE_NAME,KEY,null,message.getBytes(StandardCharsets.UTF_8));  //发送超级VIP消息
        }

    }

}

消费者C1:
消费者C2的代码与TTL案例中保持一致

package com.dmbjz.maxlength;

import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/* 死信队列 队列达到最大长度案例 消费者C1 */
public class ConsumerC1 {


    private static final String EXCHANGE_NAME = "normal_exchange";              //正常交换机名称
    private static final String DEAD_EXCHANGE_NAME = "dead_exchange";           //死信队列交换机名称

    private static final String KEY = "zhangsan";        //普通队列 RoutingKey
    private static final String DEAD_KEY = "lisi";       //死信队列 RoutingKey

    private static final String QUEUE_NAME = "normal-queue";       //普通队列名称
    private static final String DEAD_QUEUE_NAME = "dead-queue";    //死信队列名称


    public static void main(String[] args) throws IOException {

        Connection connection = RabbitUtils.getConnection();
        Channel channel = connection.createChannel();


        /*声明死信和普通交换机,正常交换机已被生产者声明,实际可以省略第一行代码*/
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);


        /*创建队列
        * 通过额外参数实现什么情况下转发到死信队列 ?,key都是固定的
        *   1、TTL过期时间设置(一般由生产者指定)
        *   2、死信交换机的名称
        *   3、死信交换机的RoutingKey
        * */
        Map<String,Object> arguments = new HashMap<>(8);
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE_NAME);     //死信交换机的名称
        arguments.put("x-dead-letter-routing-key",DEAD_KEY);            //死信交换机的RoutingKey
        arguments.put("x-max-length",6);            //指定正常队列的长度,超出该范围的消息将进入死信队列

        channel.queueDeclare(QUEUE_NAME,false,false,false,arguments);
        channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);


        /*绑定队列*/
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,KEY);
        channel.queueBind(DEAD_QUEUE_NAME,DEAD_EXCHANGE_NAME,DEAD_KEY);

        
        DeliverCallback successBack = (consumerTag, message) -> {
            System.out.println("C1用户接收到的信息为:"+new String(message.getBody()));
        };

        CancelCallback cnaelBack = a->{
            System.out.println("C1用户进行取消消费操作!");
        };

        channel.basicConsume(QUEUE_NAME,true,successBack,cnaelBack);
    }
}

效果演示:
  1. 执行消费者C1,创建出所有交换机和队列绑定后停止运行。
  2. 执行生产者,由于通道积压的六条消息从未被消费。剩余消息进入死信队列
  3. 运行消费者C2,死信队列消息被成功消费

消息积压六条,剩下四条消息进入死信队列,LIM为限制长度标记
启动消费者C2,死信队列消息被消费


消息拒绝应答

在绑定死信队列的消费者端添加需要拒绝应答的消息判断即可,核心代码为消息拒绝应答

/* requeue 设置为 false 代表拒绝重新入队 该队列如果配置了死信交换机将发送到死信队列中,未配置则进行丢弃操作*/
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);

生产者:

package com.dmbjz.noack;

import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.nio.charset.StandardCharsets;

/* 死信队列 队列达到最大长度案例 生产者 */
public class Provider {

    private static final String EXCHANGE_NAME = "normal_exchange";              //正常交换机名称
    private static final String KEY = "zhangsan";        //普通队列 RoutingKey

    public static void main(String[] args) throws Exception {

        Connection connection = RabbitUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.DIRECT);  //声明交换机

        /*循环消息发送*/
        for(int i = 1; i < 11; i++) {
            String message = "INFO " + i;
            channel.basicPublish(EXCHANGE_NAME,KEY,null,message.getBytes(StandardCharsets.UTF_8));  //发送超级VIP消息
        }
        
    }
}

消费者C1:
消费者C2的代码和TTL案例保持一致

package com.dmbjz.noack;

import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/* 死信队列 队列达到最大长度案例 消费者C1 */
public class ConsumerC1 {


    private static final String EXCHANGE_NAME = "normal_exchange";              //正常交换机名称
    private static final String DEAD_EXCHANGE_NAME = "dead_exchange";           //死信队列交换机名称

    private static final String KEY = "zhangsan";        //普通队列 RoutingKey
    private static final String DEAD_KEY = "lisi";       //死信队列 RoutingKey

    private static final String QUEUE_NAME = "normal-queue";       //普通队列名称
    private static final String DEAD_QUEUE_NAME = "dead-queue";    //死信队列名称


    public static void main(String[] args) throws IOException {

        Connection connection = RabbitUtils.getConnection();
        Channel channel = connection.createChannel();


        /*声明死信和普通交换机,正常交换机已被生产者声明,实际可以省略第一行代码*/
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);


        /*创建队列
        * 通过额外参数实现什么情况下转发到死信队列 ?,key都是固定的
        *   1、TTL过期时间设置(一般由生产者指定)
        *   2、死信交换机的名称
        *   3、死信交换机的RoutingKey
        * */
        Map<String,Object> arguments = new HashMap<>(8);
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE_NAME);     //死信交换机的名称
        arguments.put("x-dead-letter-routing-key",DEAD_KEY);            //死信交换机的RoutingKey

        channel.queueDeclare(QUEUE_NAME,false,false,false,arguments);
        channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);


        /*绑定队列*/
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,KEY);
        channel.queueBind(DEAD_QUEUE_NAME,DEAD_EXCHANGE_NAME,DEAD_KEY);


        DeliverCallback successBack = (consumerTag, message) -> {

            String info = new String(message.getBody(),"UTF-8");
            if(info.equals("INFO 5")){
                System.out.println("C1用户拒绝的信息为:"+new String(message.getBody()));
                /* requeue 设置为 false 代表拒绝重新入队 该队列如果配置了死信交换机将发送到死信队列中,未配置则进行丢弃操作*/
                channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
            }else{
                System.out.println("C1用户接收到的信息为:"+new String(message.getBody()));
                channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            }

        };

        CancelCallback cnaelBack = a->{
            System.out.println("C1用户进行取消消费操作!");
        };

        channel.basicConsume(QUEUE_NAME,false,successBack,cnaelBack);
    }
}

效果演示:
  1. 执行消费者C1
  2. 执行生产者,等待10秒钟后查看控制台,消息5被拒绝接收进入死信队列
  3. 运行消费者C2,死信队列消息被成功消费

消息5被消费者C1拒绝接收
死信队列中的消息5被消费者C2消费

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

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

相关文章

为什么谷歌每年向苹果支付180亿美元“保护费”

在大众印象里&#xff0c;谷歌和苹果似乎处处“水火不容”。 两大科技巨擘在各类产品和服务上竞争&#xff0c;比如操作系统、浏览器、地图、数字助理、手机&#xff0c;数不胜数。但是在这种敌对关系背后&#xff0c;却有着对他们双方都有利的秘密伙伴关系。 你或许不知道&am…

Qt 数据库QSqlDatabase使用记录

记录一些在QT中使用QSqlDatabase操作数据库时&#xff0c;需要注意的地方 创建数据库 bool CDBOperatorAbstract::_openDBConn(CDatabaseConfig config) {QWriteLocker locker(&m_locker);QSqlDatabase db;if(QSqlDatabase::contains(m_connectionName)){db QSqlDatabas…

CloudFlare 优选ip 和 优选域名的获取方法

1.CloudFlare优选IP网站:【链接直达】 2.CloudFlare 优选IP工具&#xff1a;【开源软件】 3.CloudFlare 优选域名&#xff1a;【GitHub开源工具】 4.CF优选域名推荐&#xff1a; time.cloudflare.com shopify.com time.is icook.hk icook.tw ip.sb japan.com malaysia.com rus…

深入学习《大学计算机》系列之第1章 1.2节——问题描述与抽象

一.欢迎来到我的酒馆 第1章 1.2节&#xff0c;问题描述与抽象。 目录 一.欢迎来到我的酒馆二.问题描述、抽象与建模1.什么是抽象2.为什么要抽象3.什么是建模4.建什么模 三.面向计算机的问题分析四.总结 二.问题描述、抽象与建模 什么是抽象&#xff1f;为什么要抽象&#xff1f…

Chrome限制第三方Cookie:未来无法再追踪你看过哪些敏感的“色色”内容了

当我们在浏览网络的时候&#xff0c;常听到「Cookie」这个词&#xff0c;但许多人可能不太清楚它到底是什么。最近&#xff0c;Google 宣布了一项重要更新&#xff0c;Google Chrome 将减少对第三方cookie 的支持&#xff0c;以提高用户隐私保护。 下面我会解释一下这个改变对…

打印机设置发票收据打印

由于各种打印机型号不一样&#xff0c;设置方式打印效果出入也很大&#xff0c;存在打印不全问题&#xff0c;发票右侧小数点后面的数字打印不出来、位置靠上下左右登问题&#xff0c;比较通用的设置方式如下&#xff1a; 首先找到控制面板&#xff0c;找到设备和打印机 进入到…

Aurora8B10B(二) 从手册和仿真学习Aurora8B10B

一. 简介 在上篇文章中&#xff0c;主要结合IP配置界面介绍了一下Aurora8B10B&#xff0c;这篇文章将结合文档来学习一下Aurora8B10B内部的一些细节 和 相关的时序吧。文档主要是参考的是这个 pg046-aurora-8b10b-en-us-11.1 二. Aurora8B10B内部细节 在手册上&#xff0c;对…

弧形导轨的精度等级

为符合工控自动化生产制造必须&#xff0c;弧形导轨在运输武器装备领域应时而生&#xff0c;并已在电子生产制造、手机上、半导体材料、动力锂电池等领域获得广泛运用。其中&#xff0c;弧形导轨的精度等级是评估其运动精度的重要指标&#xff0c;通常包括制造精度和运行精度两…

低代码核心能力详解:简化应用开发的新思路

低代码平台作为一种快速地应用开发解决方法&#xff0c;为中小企业实现数字化转型提供了机会。但是&#xff0c;对于一些刚开始触碰低代码平台的企业来说&#xff0c;了解其核心能力是很重要的。本文将详细分析低代码平台的核心能力&#xff0c;并在挑选低代码平台以前为中小企…

【星环云课堂大数据实验】InceptorSQL使用方法

文章目录 一、InceptorSQL概述二、实验环境三、实验准备四、实验目的五、实验步骤5.1 使用Waterdrop连接Inceptor5.2、WordCount5.3、外部表与内部表5.4、普通ORC表5.5、创建ORC事务表5.6、创建ORC分区表5.7、创建ORC分区分桶表 一、InceptorSQL概述 InceptorSQL是一个**分布式…

数据结构-迷宫问题

文章目录 1、题目描述2、题目分析3、代码实现 1、题目描述 题目链接&#xff1a;迷宫问题 、 注意不能斜着走&#xff01; 2、题目分析 &#xff08;1&#xff09;0为可以走&#xff0c;1不能走且只有唯一一条通路 &#xff08;2&#xff09;我们可以通过判断上下左右来确定…

开酸奶店为何失败,5年创业者和你分享赚钱经验

我是张峻荣&#xff0c;开鲜奶吧已经有 5 年时间了&#xff0c;在自媒体创业板块也是小有名气&#xff0c;经常在网络上分享一些酸奶店的创业知识。今天我要和大家分享的是开酸奶店失败的原因&#xff0c;以及如何赚钱的经验。 5 年前&#xff0c;是我第一次创业失败&#xff…

activiti并行网关执行时每个关联表的变化

activiti并行网关执行时每个关联表的变化 文章目录 &#x1f50a;流程图&#x1f4c6; 通过请假节点&#x1f4d5;通过一个并行节点&#x1f5a5;️再通过一个并行节点&#x1f516;再通过校长任务&#x1f58a;️最后总结 &#x1f50a;流程图 &#x1f4c6; 通过请假节点 &l…

口袋参谋:新品上架,如何获取更多免费流量?

​新品上架 如何获得更多的免费流量&#xff1f; 我相信 这是99.999%的商家&#xff0c;都关心的问题&#xff01; 今天我就来和大家好好说道说道。 01 流量的组成 新品本身是没有权重的&#xff0c;买家搜不到我们。 如果想要获得更多的免费流量&#xff0c;我们就要知道…

千梦网创:逮住一闪而过的机会疯狂摩擦

我这个人平时想的就多&#xff0c;睡觉也在想事情&#xff0c;有时候睡觉里想的事情往往都是很纯粹的、很绝妙的&#xff0c;但是经常性一醒过来就忘了&#xff0c;再去回忆怎么也想不起来了。 灵感只在特定的环境下产生&#xff0c;这类环境是不可再生和模拟的。 机会只因特…

17. 常用类

1.String类 1).什么是字符串? 字符串是由多个字符组成的一串数据(字符序列),字符串可以看成是字符数组. 2).String类的概述 String 类代表字符串。Java 程序中的所有字符串字面值&#xff08;如 “abc” &#xff09;都作为此类的实例实现。 字符串是常量&#xff1b;它们…

connect: Network is unreachable问题解决

第一步&#xff1a;查看ifcfg-ens33配置文件 cd /etc/sysconfig/network-scripts/ cat ifcfg-ens33 发现问题&#xff1a;GATEWAY写错成GATWAY 第二步&#xff1a;修改 vim ifcfg-ens33 第三步&#xff1a;检测是否成功 ping baidu.com 成功&#xff01;

【Unity动画】实现不同的肢体动作自由搭配播放Layer+Avatar Mask

这个教程教你学会使用Unity 动画层配合布偶遮罩&#xff08;AvaterMask&#xff09; 实现从2个动画身上只保留部分肢体动作&#xff0c;然后搭配播放 例如&#xff1a;一个正常跑的动画片段&#xff0c;我只保留腿部动作&#xff0c;形成一个层叫Run_leg 然后在从一个攻击动作…

Java-File类与IO流(2)

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

b-tree b+tree两种区别

Btree多了叶子节点&#xff0c;并可以看到多了个箭头&#xff0c;这样查询比如大于>2,Btree更容易。而b--tree则要返到第一层、第二层才可以最得所有>2的数据