RabbitMQ消息应答与发布

news2024/11/25 8:23:15

消息应答

RabbitMQ一旦向消费者发送了一个消息,便立即将该消息,标记为删除.

消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个很长的任务并仅仅执行了一半就突然挂掉了,在这种情况下,我们将丢失正在处理的消息,后续给消费者发送的消息也就无法接收到了.

为了确保消息不丢失,我们引入了消息应答机制.

消息应答就是:消费者在接收到生产者的消息并且处理该消息之后,告诉RabbitMQ已经处理完成了,rabbitMQ可以进行删除了.

一个生产者,两个消费者.

/**
消费者1
*/
public class Consumer1 {
 public static final String QueueName = "TaskQueueName";
 public static void main(String[] args) throws IOException, TimeoutException {
     ConnectionFactory factory = new ConnectionFactory();
     factory.setHost("127.0.0.1");
     factory.setUsername("DGZ");
     factory.setPassword("Dgz@#151");
     Connection connection = factory.newConnection();
     Channel channel = connection.createChannel();
     //声明接收消息
     System.out.println("消费者1等待接收消息时间较短");
     DeliverCallback deliverCallback = (consumerTag,message) -> {
         Util.sleep(1);  //等待1s
         System.out.println("正在处理该消息" + new String(message.getBody()));

            /**
             * 手动应答
             * 1.消息的标记Tag
             * 2.是否批量应答 false表示不批量应答信道中的消息
             */
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };
        //取消消息时的回调
        CancelCallback cancelCallback = consumerTag ->{
            System.out.println("消息消费被中断");
        };
        /**
         * 消费者消费消息
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答true:代表自动应答      false:代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         */
        boolean autoAck = false;
        channel.basicConsume(QueueName,autoAck,deliverCallback,cancelCallback);
    }
}

/**
消费者2
*/
public class Consumer2 {
 public static final String QueueName = "TaskQueueName";
 public static void main(String[] args) throws IOException, TimeoutException {
     ConnectionFactory factory = new ConnectionFactory();
     factory.setHost("127.0.0.1");
     factory.setUsername("DGZ");
     factory.setPassword("Dgz@#151");
     Connection connection = factory.newConnection();
     Channel channel = connection.createChannel();
     System.out.println("消费者2等待接收消息时间较长");
     //声明接收消息
     DeliverCallback deliverCallback = (consumerTag,message) -> {
         Util.sleep(10); //等待10s
         System.out.println("正在处理该消息" + new String(message.getBody()));

            /**
             * 手动应答
             * 1.消息的标记Tag
             * 2.是否批量应答 false表示不批量应答信道中的消息
             */
            //当该消息还没有被处理的时候,如果此时这个应用挂掉,
            // 由于这个手动应答的机制,就不会删除该消息,而是将给消息交给其他应用去处理
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };
        //取消消息时的回调
        CancelCallback cancelCallback = consumerTag ->{
            System.out.println("消息消费被中断");
        };
        /**
         * 消费者消费消息
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答true:代表自动应答     false:代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         */
        boolean autoAck = false;
        channel.basicConsume(QueueName,autoAck,deliverCallback,cancelCallback);
    }
}

消费者1 1s处理一个消息,消费者2 10s处理一个消息

/**
生产者
*/
public class Producer {
 //队列名称
 public static final String QueueName = "TaskQueueName";
 public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
     ConnectionFactory factory = new ConnectionFactory();   //创建连接工厂
     factory.setHost("127.0.0.1");  //主机
     factory.setUsername("DGZ"); //用户名
     factory.setPassword("Dgz@#151"); // 密码
     Connection connection = factory.newConnection();  //通过连接工厂创建一个连接
     Channel channel = connection.createChannel();  //获取信道
     /**
      * 生产一个对列
      * 1.对列名称
      * 2.对列里面的消息是否持久化,默认情况下,消息存储在内存中
      * 3.该队列是否只供一个消费者进行消费,是否进行消息共享,true可以多个消费者消费 false:只能一个消费者消费
      * 4.是否自动删除,最后一个消费者端开链接以后,该队列是否自动删除,true表示自动删除
      * 5.其他参数
      */
     channel.queueDeclare(QueueName,false,false,false,null);
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入消息:");
        while(scanner.hasNext()) {
            String message = scanner.next();
            channel.basicPublish("",QueueName,null,message.getBytes());
            System.out.println("生产者发出消息: " + message);
        }
        /**
         * 发送一个消息
         * 1.发送到哪个交换机
         * 2.路由的key值是哪个本次是队列的名称
         * 3.其他参数信息
         * 4.发送消息的消息体
         */
        System.out.println("消息发送完毕");
    }
}

自动应答和手动应答:

自动应答就是MQ只要把消息发出去,不会管消息是否收到,都会立刻把这个消息进行删除.

手动应答就是MQ把消息发送出去之后,消费者在接收到生产者的消息并且处理该消息之后,告诉RabbitMQ已经处理完成了,rabbitMQ可以进行删除了.

RabbitMQ持久化

当RabbitMQ服务突然挂掉之后,消息生产者发送过来的消息如何保证不丢失.

默认情况下当RabbitMQ服务挂掉之后,它会忽略队列和消息,这时刚刚发送的消息和队列都会丢失.

确保消息不丢失我们需要干两件事,就是将队列和消息标记为持久化.

队列持久化

之前创建的队列都是非持久化的,如果RabbitMQ重启的话,队列就是丢失,如果需要实现持久化队列,那么就需要在声明队列的时候把durable参数设置为true.表示代表开启持久化.

public class Producer {
    public static final String QueueName = "CJ_QUEUE";
    public static void main(String[] args) throws IOException, TimeoutException {
        /**
         * RabbitMQ工具类,用来创建连接等信息
         */
        Channel channel = RabbitMQUtil.RabbitMQ_getChannel();
        boolean durable = true;
        //第二个参数为队列持久化的参数,设置为true,表示队列开启持久化,false表示不开启持久化
        channel.queueDeclare(QueueName,durable,false,false,null);
    }
}

此时web管理端就会看见:

 表示队列持久化

 消息持久化

消息持久化是指消息生产者发布消息的时候,开启消息持久化,

public class Producer {
    public static final String QueueName = "CJ_QUEUE";

    public static void main(String[] args) throws IOException, TimeoutException {
        /**
         * RabbitMQ工具类,用来创建连接等信息
         */
        Channel channel = RabbitMQUtil.RabbitMQ_getChannel();
        boolean durable = true;
        //第二个参数为队列持久化的参数,设置为true,表示队列开启持久化,false表示不开启持久化
        channel.queueDeclare(QueueName,durable,false,false,null);
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要发送的消息");
        while (scanner.hasNext()) {
            String message = scanner.next();
            /**
             * 将发布消息的basicPublish方法的第三个参数设置为MessageProperties.PERSISTENT_TEXT_PLAIN
             * 表示开启消息持久化
             */
            channel.basicPublish
                    ("",QueueName, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息" + message);
        }
    }
}

不公平分发

在最开始的时候RabbitMQ采用的是轮询分发,但是在某种场景下这种策略并不是很好,比如说两个消费者在处理消息,此时消费者1处理消息比较慢,而消费者2处理消息比较快,如果这个时候还是采用轮询分发的方式,那么处理慢的消费者就会一直在处理消息,而处理快的消费者就会有很大时间处于空闲状态.

为了解决这个问题,引入了不公平分发

不公平分发: 如果一个工作队列(消费者)还没有处理完一个消息或者没有应答签收一个消息,则RabbitMQ不会分配新的消息给该队列.

如果所有的消费者都没有完成手上的消息,生产者还在不停地生产消息,队列还在不停地添加新任务,这是不会给消费者分发消息,就有可能导致队列被撑爆.

这时就只能添加新的工作队列或者改变存储策略了.

设置不公平分发:

public class Consumer1 {
    public static final String QueueName = "CJ_QUEUE";
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtil.RabbitMQ_getChannel();
        System.out.println("消费消息时间较长-----------");
        DeliverCallback deliverCallback = ((consumerTag,message)->{
            RabbitMQUtil.sleep(10);
            System.out.println("消费消息:" + new String(message.getBody()));
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        });
        CancelCallback cancelCallback = consumerTag ->{
            System.out.println("消息消费被中断");
        };
        //设置不公平分发
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        //手动应答
        channel.basicConsume(QueueName,false,deliverCallback,cancelCallback);
    }
}

public class Consumer2 {
    public static final String QueueName = "CJ_QUEUE";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtil.RabbitMQ_getChannel();
        System.out.println("消费消息时间较短-----------");
        DeliverCallback deliverCallback = ((consumerTag,message)->{
            RabbitMQUtil.sleep(1);
            System.out.println("消费消息:" + new String(message.getBody()));
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        });
        CancelCallback cancelCallback = consumerTag ->{
            System.out.println("消息消费被中断");
        };
        //设置不公平分发
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);

        //采取手动应答
        channel.basicConsume(QueueName,false,deliverCallback,cancelCallback);
    }
}

预期值分发

带权的消息分发

默认的消息发送是异步的,所以在任何时候,channel中不止一个来自消费者收到确认的消息,因此这里就存在一个未确认的消息缓冲区.

因此我们希望限制这里的缓冲区的大小,避免缓冲区中无休止的未确认消息.

这时我们就可以通过basicqos()方法来设置(预取计数来完成).

basicqos方法里面设置通道上允许的未确认消息的最大数量,一旦数据达到配置的数量,RabbitMQ将停止在通道上传递更多的消息.除非有未确认的消息被确认.

例如:假设此时通道上未确认的消息有 4,6,7,9,5,10,并且通道上设置预取计数值为6,这时RabbitMQ将不会在该通道上传递消息.除非这里的消息被确认一个,RabbitMQ将感知到这一变化,并且在发送一条信息.

消息应答和Qos预取值对用户的吞吐量有着重大影响.

不公平分发和预取值分发都用到 basic.qos 方法,如果取值为 1,代表不公平分发,取值不为1,代表预取值分发 

public class Consumer2 {
    public static final String QueueName = "CJ_QUEUE";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtil.RabbitMQ_getChannel();
        System.out.println("消费消息时间较短-----------");
        DeliverCallback deliverCallback = ((consumerTag,message)->{
            RabbitMQUtil.sleep(1);
            System.out.println("消费消息:" + new String(message.getBody()));
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        });
        CancelCallback cancelCallback = consumerTag ->{
            System.out.println("消息消费被中断");
        };
        //设置不公平分发
        /*int prefetchCount = 1;
        channel.basicQos(prefetchCount);*/

        //设置预期值分发 值为4
        int prefetchCount = 4;
        channel.basicQos(prefetchCount);

        //采取手动应答
        channel.basicConsume(QueueName,false,deliverCallback,cancelCallback);
    }
}

发布确认

生产者发送消息到RabbitMQ后,需要RabbitMQ返回一个ack,表示RabbitMQ已经收到生产者发送的消息.这样生产者就知道自己发送的信息成功了.

发布确认逻辑

如果消息和队列是可持久化的,那么确认消息会在消息写入磁盘之后发出.

broker 回传给生产者的确认消息中 delivery-tag 域包含了确认消息的序列号,此外 broker 也可以设置 basic.ack 的 multiple 域,表示到这个序列号之前的所有消息都已经得到了处理。

confirm 模式最大的好处在于是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack 消息, 生产者应用程序同样可以在回调方法中处理该 nack 消息。

发布确认默认是没有开启的,如果要开启需要调用方法confirmSelect(),

channel.confirmSelect();

单个发布确认

这是一种简单的确认发布,他是一种同步确认发布的方式,也就是发布一个消息之后,只有它被确认,后续的消息才能被发布出去.

waitforconfirms()这个方法只有在消息被确认的时候才返回.

如果在指定的时间范围内没有返回,则抛出异常.

这种确认的方式就是发布速度特别慢,因为如果没有确认发布的消息,那么其他消息就只能阻塞等待.

这种方式最多每秒不超过百条的发送量.


/**
 * Created with IntelliJ IDEA.
 *
 * @Author: DongGuoZhen
 * @Date: 2024/01/05/10:28
 * @Description: 单个确认发布
 */
//确认发布指的是成功发送到了队列,并不是消费者消费了消息。
public class Producer1 {
    public static final int MESSAGE_MAX = 1000;
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        pushMessage();
    }

    private static void pushMessage() throws IOException, TimeoutException, InterruptedException {
        Channel channel = RabbitMQUtil.RabbitMQ_getChannel();
        String QueueName = UUID.randomUUID().toString();
        channel.queueDeclare(QueueName,false,true,false,null);
        //开启发布确认
        channel.confirmSelect();

        //开始时间
        long start = System.currentTimeMillis();
        //依次发送1000个消息
        for (int i = 0; i < 1000; i++) {
            String message = i+"";
            channel.basicPublish("",QueueName,null,message.getBytes());
            //发送单个消息立马进行发布确认
            boolean flag = channel.waitForConfirms();
            if(flag) {  //如果成功 true
                System.out.println("消息: "+ message + " 成功发送到队列:" + QueueName);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("发布: " + MESSAGE_MAX + "条消息共耗时:"+ (end-start)+ "ms");
    }
}

 

批量发布确认

单个确认发布的速度非常慢,与单个确认等待相比,如果发送一批然后在一起确认,这样就大大提高了消息的发送速度和吞吐量.

当然这种方式的缺点就是,当有一个消息出现问题时,我们无法知道是那个消息没有发布出去.

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: DongGuoZhen
 * @Date: 2024/01/05/10:28
 * @Description: 批量确认发布
 */
//确认发布指的是成功发送到了队列,并不是消费者消费了消息。
public class Producer2 {
    public static final int MESSAGE_MAX = 1000;
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        pushMessage();
    }

    private static void pushMessage() throws IOException, TimeoutException, InterruptedException {
        Channel channel = RabbitMQUtil.RabbitMQ_getChannel();
        String QueueName = UUID.randomUUID().toString();
        channel.queueDeclare(QueueName,false,true,false,null);
        //开启发布确认
        channel.confirmSelect();
        int bachSize = 100;  //批量发布确认的条数
        //开始时间
        long start = System.currentTimeMillis();
        //依次发送1000个消息
        for (int i = 0; i < 1000; i++) {
            String message = i+"";
            channel.basicPublish("",QueueName,null,message.getBytes());
            if((i+1) % bachSize == 0) {  //当发送的消息达到100条,进行确认发布一次
                channel.waitForConfirms();   //发布确认
                System.out.println("第"+ i+"条消息被确认");
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("发布: " + MESSAGE_MAX + "条消息共耗时:"+ (end-start)+ "ms");
    }
}

 

异步发布确认

异步确认虽然编程上逻辑比较复杂,但是性价比最高,可靠性和效率都很好,利用了回调函数来达到消息的可靠性传递.


/**
 * Created with IntelliJ IDEA.
 *
 * @Author: DongGuoZhen
 * @Date: 2024/01/05/11:02
 * @Description: 异步确认发布
 */
public class Producer3 {
    public static final int MESSAGE_MAX = 1000;

    public static void main(String[] args) throws IOException, TimeoutException {
        pushMessage();
    }

    private static void pushMessage() throws IOException, TimeoutException {
        Channel channel = RabbitMQUtil.RabbitMQ_getChannel();
        String QueueName = UUID.randomUUID().toString();
        channel.queueDeclare(QueueName,false,true,false,null);
        channel.confirmSelect();
        long start = System.currentTimeMillis();
        /**
         * deliveryTag 消息的标记
         * multiple 是否为批量确认
         */
        ConfirmCallback ackCallback = (deliveryTag,multiple) ->{
            System.out.println("确认的消息:" + deliveryTag);
        };
        ConfirmCallback nackCallback = (deliveryTag,multiple) ->{
            System.out.println("未确认的消息:" + deliveryTag);
        };
        //消息监听器  监听那些消息成功了,那些消息失败了
        channel.addConfirmListener(ackCallback,nackCallback);
        //批量发送消息
        for (int i = 0; i < 1000; i++) {
            String message = i+"消息";
            channel.basicPublish("", QueueName,null,message.getBytes());
        }
        long end = System.currentTimeMillis();
        System.out.println("异步确认发布: " + MESSAGE_MAX + "条消息共耗时:"+ (end-start)+ "ms");
    }
}
  • 单独发布消息

同步等待确认,简单,但吞吐量非常有限。

  • 批量发布消息

批量同步等待确认,简单,合理的吞吐量,一旦出现问题但很难推断出是哪条消息出现了问题。

  • 异步处理

最佳性能和资源使用,在出现错误的情况下可以很好地控制,但是实现起来稍微难些

注意:应答和发布的区别:

应答功能属于消费者,消费者消费完消息后告诉RabbitMQ已经消费成功

发布功能属于生产者,生产者生产消息到RabbitMQ,RabbitMQ需要告诉生产者已经收到消息

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

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

相关文章

C语言之反汇编查看函数栈帧的创建与销毁

文章目录 一、 什么是函数栈帧&#xff1f;二、 理解函数栈帧能解决什么问题呢&#xff1f;三、 函数栈帧的创建和销毁解析3.1、什么是栈&#xff1f;3.2、认识相关寄存器和汇编指令3.2.1 相关寄存器3.2.2 相关汇编命令 3.3、 解析函数栈帧的创建和销毁3.3.1 预备知识3.3.2 代码…

Ansible详解(架构,模块)及部署示例

目录 Ansible概述 Ansible作用 Ansible特点 Ansible架构 工作流程 ansible 环境安装部署 环境准备 安装Ansible服务 Ansible 命令行模块 模块详解 ansible-doc command模块 shell模块 cron 模块 user模块 group模块 copy 模块 file 模块 hostname 模块 pin…

【论文+视频控制】23.08DragNUWA1.5:通过集成文本、图像和轨迹来进行视频生成中的细粒度控制 (24.01.08开源最新模型)

论文链接&#xff1a;DragNUWA: Fine-grained Control in Video Generation by Integrating Text, Image, and Trajectory 代码&#xff1a;https://github.com/ProjectNUWA/DragNUWA 一、简介 中国科学技术大学微软亚洲研究院 在 NUWA多模态模型、 Stable Video Diffusion …

mockjs(3)

mockjs&#xff08;1&#xff09; mockjs&#xff08;2&#xff09; 这篇主要是Mock.random工具类&#xff0c;前段要用的话主要是在模版中的占位符。mockjs&#xff08;1&#xff09;里面的3.2 6 Mock.random Mock.Random 是一个工具类&#xff0c;用于生成各种随机数据。 …

即插即用篇 | YOLOv8 引入 SENetv2 | 多套版本配合使用

卷积神经网络(CNNs)通过提取空间特征并在基于视觉的任务中实现了最先进的准确性,彻底改变了图像分类。所提出的压缩激励网络模块收集输入的通道表示。多层感知机(MLP)从数据中学习全局表示,在大多数用于学习图像提取特征的图像分类模型中起到关键作用。在本文中,我们引入…

论文阅读2---多线激光lidar内参标定原理

前言&#xff1a;该论文介绍多线激光lidar的标定内参的原理&#xff0c;有兴趣的&#xff0c;可研读原论文。 1、标定参数 rotCorrection&#xff1a;旋转修正角&#xff0c;每束激光的方位角偏移&#xff08;与当前旋转角度的偏移&#xff0c;正值表示激光束逆时针旋转&…

实用的SQLite数据库可视化管理工具推荐

前言 俗话说得好“工欲善其事&#xff0c;必先利其器”&#xff0c;合理的选择和使用可视化的管理工具可以降低技术入门和使用门槛。今天推荐7款实用的SQLite数据库可视化管理工具(GUI)&#xff0c;帮助大家更好的管理SQLite数据库。 什么是SQLite&#xff1f; SQLite是一个…

【新闻感想】谈一下PandoraNext的覆灭(潘多拉Next-国内可访问的免费开放GPT共享站将于2024年1月30日关闭)

文章目录 悲报&#xff1a;TIME TO SAY GOODBYE&#xff01;PandoraNext&#xff01;PandoraNext作者言&#xff1a;你们赢了&#xff0c;但我却没有输我如何了解到PandoraNext的合照留念于是开始逆向&#xff01; 悲报&#xff1a;TIME TO SAY GOODBYE&#xff01;PandoraNext…

外包干了2个多月,技术退步明显。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入广州某软件公司&#xff0c;干了接近3年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

Java/Python/Go不同开发语言在进程、线程和协程的设计差异

Java/Python/Go不同开发语言在进程、线程和协程的设计差异 1. 进程、线程和协程上的差异1.1 进程、线程、协程的定义1.2 进程、线程、协程的差异1.3 进程、线程、协程的内存成本1.4 进程、线程、协程的切换成本 2. 线程、协程之间的通信和协作方式2.1 python如何实现线程通信&a…

换上龙年表盘,开启一整年的好运

农历新年即将到来&#xff0c;华为表盘市场陆续推出一系列龙年主题的表盘。其中&#xff0c;三款表盘的设计格外引人注目&#xff1a;云白腾龙机械、非凡腾龙多色、玄武腾龙机械。 这三款表盘不仅在艺术审美上展现了设计师的独特创意与深厚功底&#xff0c;更是在细微之处巧妙融…

Tensorflow2.0笔记 - 范式norm,reduce_min/max/mean,argmax/min, equal,unique

练习norm,reduce_min/max,argmax/min, equal,unique等相关操作。 范数主要有三种&#xff1a; import tensorflow as tf import numpy as nptf.__version__#范数参考&#xff1a;https://blog.csdn.net/HiWangWenBing/article/details/119707541 tensor tf.convert_to_tensor(…

python系列-函数(上)

&#x1f308;个人主页: 会编程的果子君 ​&#x1f4ab;个人格言:“成为自己未来的主人~” 目录 函数 函数是什么 语法格式 函数参数 函数返回值 函数 函数是什么 编程中的函数和数学中的函数有一定的相似之处 编程中的函数&#xff0c;是一段可以被重复利用的代码片段…

Oracle Linux 9.3 安装图解

风险告知 本人及本篇博文不为任何人及任何行为的任何风险承担责任&#xff0c;图解仅供参考&#xff0c;请悉知&#xff01;本次安装图解是在一个全新的演示环境下进行的&#xff0c;演示环境中没有任何有价值的数据&#xff0c;但这并不代表摆在你面前的环境也是如此。生产环境…

Unity工程没有创建.sln文件,导致打开C#文件无法打开解决方案

最近又开始折腾些Unity的小项目&#xff0c;重新遇到一些常见的小问题 点击报错文件 却没有打开文件 于是查看了下打开Window->Package Manager 选择Unity Registry 搜索Visual Studio Editor&#xff0c;发现并没有安装 同理&#xff0c;也可以安装VSCode的插件 问题解决了…

【服务器】安装Docker环境

目录 &#x1f33a;【前言】 &#x1f33c;1. 打开Xshell软件 &#x1f33b;2. 安装Docker环境 ①&#xff1a;下载docker.sh脚本 ②&#xff1a;列出下载的内容 ③&#xff1a;执行一下get-docker.sh文件&#xff0c;安装docker ④&#xff1a;运行docker服务 ⑤&…

Python IO流

第一章、IO流 一、概述 1、IO流概念 2、IO流的分类 在Python中&#xff0c;I/O&#xff08;输入/输出&#xff09;流是处理数据输入和输出的机制。它们用于从文件、网络连接、内存等源读取数据&#xff0c;或将数据写入到这些目标中。I/O流以字节流和字符流的形式存在。 Pyth…

C++版QT:电子时钟

digiclock.h #ifndef DIGICLOCK_H #define DIGICLOCK_H ​ #include <QLCDNumber> ​ class DigiClock : public QLCDNumber {Q_OBJECT public:DigiClock(QWidget* parent 0);void mousePressEvent(QMouseEvent*);void mouseMoveEvent(QMouseEvent*); public slots:voi…

Git--创建仓库(1)

git init Git 使用 git init 命令来初始化一个 Git 仓库&#xff0c;Git 的很多命令都需要在 Git 的仓库中运行&#xff0c;所以 git init 是使用 Git 的第一个命令。 在执行完成 git init 命令后&#xff0c;Git 仓库会生成一个 .git 目录&#xff0c;该目录包含了资源的所有…

使用trace工具分析Mysql如何选择索引

背景说明 工作中,可能会遇到执行一个SQL,明明有索引,但是采用explain分析后发现执行结果并未走索引。甚至还有部分SQL语句相同就只是查询条件不一样也会出现有的走索引,有的不走索引情况。比如: 我的示例环境有个employees表,并有个idx_name_age_position的联合索引…