RabbitMQ初步到精通-第五章-RabbitMQ之消息防丢失

news2025/7/9 0:29:29

目录

第五章-RabbitMQ之消息防丢失

1.消息是如何丢的

​编辑

2.如何控制消息丢失

2.1 生产者发送消息到Broker过程

2.2 Broker内部过程

        2.2.1 Exchange发送至queue过程-Return机制

        2.2.2 queue存储过程

2.3 消费者消费过程-消费端确认

3.最佳实践


第五章-RabbitMQ之消息防丢失

1.消息是如何丢的

经过前面的学习,我们终于来到一个跟我们实际开发应用场景贴合度非常高的话题了。-消息丢失。

这也是我们之前提到的引入MQ后系统的缺点,可用性降低,复杂度提升,那我们这次就着手看实际应用中如何规避这些问题,首先来解决消息丢失的问题。

消息是如何丢的呢?在搞清楚这个问题之前,我们需要再复习下,消息从生产者发出到消费者整个的过程,如下图:

 第一步:生产者发送到Exchange,这个过程可能由于网络抖动等原因,消息未能发送到Exchange,或者发送到了Exchange,而生产者没有得到发送成功的反馈

第二步:Exchange发送消息路由至Queue,这一步可能会因为mq内部问题,宕机等原因,导致Exchange并未将消息推送至Queue,而此时,生产者认为已将消息成功送达了。

第三步:Queue存储问题,我们知道Exchange是不存储消息的,但Queue是存储的,这里面就涉及到这个消息是否要持久化,还是放内存,mq出问题时会导致消息丢失

第四步:消费者消费的过程,从Queue到消费者的传输过程中,或消费的时候可能因为各种原因出现异常,未能按预期的程序逻辑将消息执行完,也作为消息丢失的一种。

2.如何控制消息丢失

那我们怎么解决呢?我们还是按照上面介绍的4个步骤分别去控制消息防丢,最终实现整体消息的一个完整消费过程。

2.1 生产者发送消息到Broker过程

2.1.1 事务

我们很容易想到,开启事务不就行了么,成功发送到Queue后事务提交,中间出现任何异常,事务回滚,一定万无一失,确保消息能完整的送到Queue中去。

的确rabbitmq提供了事务机制,我们看下核心代码:-基于java amqp agent

/*
*开启事务
* */
 channel.txSelect();

/**提交事务
* */
channel.txCommit();

/**回滚事务
* */
channel.txRollback();

但实际应用中呢,我们一般不采取这种方式,系统中引入mq的一个原因是想让系统处理更快,加入rabbitmq的事务后,性能急剧下降,mq失去了它原有的轻盈,快速,变成了一只老年兔子,很稳但太慢。

2.1.2 Confirm机制

RabbitMQ提供了Confirm机制。比事务效率高。分为:普通Confirm方式、批量Confirm方式、异步Confirm方式。使用Confirm机制后,mq会明确的告诉你,生产者一定是成功把消息发送到了Exchange,【注意:不是到queue】,

若返回失败,我们可以根据实际业务情况进行处理,是记录到日志后后续重试,还是立马重试,重试几次后做记录等等

上代码:

2.1.2.1 普通Confirm方式

针对普通的一条消息的发送成功后的确认


import com.longfor.apidemos.rabbit.common.RabbitCommonConfig;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;

import java.io.IOException;

/**
 * @author rabbit
 * @version 1.0.0
 * @Description 1.普通 confirm 模式
 * @createTime 2022/07/27 19:34:00
 */
public class NormalConfirmProducer {
    //生产者
    public static void main(String[] args) throws Exception {
        //1、获取connection
        Connection connection = RabbitCommonConfig.getConnection();
        //2、创建channel
        Channel channel = connection.createChannel();
        sendMsg(channel);
        //4、关闭管道和连接
        channel.close();
        connection.close();
    }

    private static void sendMsg(Channel channel) throws IOException, InterruptedException {
        //开启确认机制
        channel.confirmSelect();
        //3、发送消息到exchange
        String msg = "hello confirm";

        channel.basicPublish("", "no-lost", MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());

        if (channel.waitForConfirms()) {
            System.out.println("生产者发布消息至Exchange成功!");
        } else {
            System.out.println("生产者发布消息至Exchange失败!请重试");
        }
    }

}

2.1.2.2 批量Confirm方式

针对一批消息的确认,虽然性能较高了,但控制就不是很精准了,使用时自己权衡。与单笔确认区别:就是控制确认的时机发生了变化,单笔确认就是一条一确认。


import com.longfor.apidemos.rabbit.common.RabbitCommonConfig;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;

import java.io.IOException;

/**
 * @author rabbit
 * @version 1.0.0
 * @Description 2.批量 confirm 模式
 * @createTime 2022/07/27 19:34:00
 */
public class BatchConfirmProducer {
    //生产者
    public static void main(String[] args) throws Exception {
        //1、获取connection
        Connection connection = RabbitCommonConfig.getConnection();
        //2、创建channel
        Channel channel = connection.createChannel();
        sendMsg(channel);
        //4、关闭管道和连接
        channel.close();
        connection.close();
    }

    private static void sendMsg(Channel channel) throws IOException, InterruptedException {
        //开启确认机制
        channel.confirmSelect();
        //3、发送消息到exchange
        String msg = "hello confirm";
        for (int i = 0; i < 10; i++) {
            channel.basicPublish("", "no-lost", MessageProperties.PERSISTENT_TEXT_PLAIN, (msg + i).getBytes());
        }

        if (channel.waitForConfirms()) {
            System.out.println("生产者发布消息至Exchange成功!");
        } else {
            System.out.println("生产者发布消息至Exchange失败!请重试");
        }
    }

}

2.1.2.3 异步Confirm方式

有的小伙伴会说,这还是慢啊,我发条消息还得等结果,发送性能难以保证,好,提供异步的方式,先往里面发,注册一个监听,靠异步返回的形式来确认消息的确发送成功了,若收到消息会有明确的成功失败,一直收不到监听返回,也可认为发送失败了


import com.longfor.apidemos.rabbit.common.RabbitCommonConfig;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;

import java.io.IOException;

/**
 * @author rabbit
 * @version 1.0.0
 * @Description 3.异步confirm 模式
 * @createTime 2022/07/27 19:34:00
 */
public class AsynConfirmProducer {
    //生产者
    public static void main(String[] args) throws Exception {
        //1、获取connection
        Connection connection = RabbitCommonConfig.getConnection();
        //2、创建channel
        Channel channel = connection.createChannel();
        sendMsg(channel);
        //4、关闭管道和连接
        channel.close();
        connection.close();
    }

    private static void sendMsg(Channel channel) throws IOException, InterruptedException {
        //开启确认机制
        channel.confirmSelect();
        //3、发送消息到exchange
        String msg = "hello confirm";
        for (int i = 0; i < 10; i++) {
            channel.basicPublish("", "no-lost", MessageProperties.PERSISTENT_TEXT_PLAIN, (msg + i).getBytes());
        }

        //3.3、开启异步回调
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("生产者发布消息至Exchange成功,标示为:" + deliveryTag + ",是否为批量操作:" + multiple);
            }
            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("生产者发布消息至Exchange失败,标示为:" + deliveryTag + ",是否为批量操作:" + multiple);
            }
        });
        System.in.read();
    }

}

2.2 Broker内部过程

        2.2.1 Exchange发送至queue过程-Return机制

         好,保证了消息成功稳定的抵达了Exchange,那从Exchange路由到queue如何保证呢,请出 Return机制-采用Return机制来保证消息是否由exchange发送到了queue中。


import com.longfor.apidemos.rabbit.common.RabbitCommonConfig;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @author rabbit
 * @version 1.0.0
 * @Description 3.异步confirm 模式
 * @createTime 2022/07/27 19:34:00
 */
public class AsynConfirmReturnProducer {
    //生产者
    public static void main(String[] args) throws Exception {
        //1、获取connection
        Connection connection = RabbitCommonConfig.getConnection();
        //2、创建channel
        Channel channel = connection.createChannel();
        sendMsg(channel);
        //4、关闭管道和连接
        channel.close();
        connection.close();
    }

    private static void sendMsg(Channel channel) throws IOException, InterruptedException {
        //开启确认机制
        channel.confirmSelect();
        //3、发送消息到exchange
        String msg = "hello confirm";
        for (int i = 0; i < 5; i++) {
            //3.2、发送消息,第三个设置为true才会有Return机制,默认为false
            //使用void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)
            channel.basicPublish("", "no-lost", true, MessageProperties.PERSISTENT_TEXT_PLAIN, (msg + i).getBytes());
        }

        //3.3、开启异步回调
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("生产者发布消息至Exchange成功,标示为:" + deliveryTag + ",是否为批量操作:" + multiple);
            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("生产者发布消息至Exchange失败,标示为:" + deliveryTag + ",是否为批量操作:" + multiple);
            }
        });

        //3.1.2、开启Return机制
        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //当送达失败是才会回调
                System.out.println(new String(body, "utf-8") + ",消息没有送达到queue中");
            }
        });

        System.in.read();
    }

}

        2.2.2 queue存储过程

        好的,太不容易了,消息终于抵达了queue中,我们要保证消息不丢,需要将消息持久化,放内存中是不保险的, 这里有会有两个问题,

第一 发送的消息是有属性的,需要设置 持久化标识。

第二 创建的队列也是由属性的,也需要设置持久化, 

这样才保证了消息的确持久化到了磁盘中。

//deliveryMode  =2 持久化消息

channel.basicPublish("", "persist-show", MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());

 /** Content-type "text/plain", deliveryMode 2 (persistent), priority zero */
    public static final BasicProperties PERSISTENT_TEXT_PLAIN =
        new BasicProperties("text/plain",
                            null,
                            null,
                            2,
                            0, null, null, null,
                            null, null, null, null,
                            null, null);

//声明队列durable=true
channel.queueDeclare("persist-show", true, false, false, null);

Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
                                 Map<String, Object> arguments) throws IOException;

有的小伙伴会问,这样就会存储到磁盘么,如何证明?

看面板:

 看磁盘存储

 文件内容:

完整生产者代码:


/**
 * @author rabbit
 * @version 1.0.0
 * @Description 持久化
 * C:\Users\xxx\AppData\Roaming\RabbitMQ
 * @createTime 2022/07/27 19:34:00
 */
public class PersistProducer {
    //生产者
    public static void main(String[] args) throws Exception {
        //1、获取connection
        Connection connection = RabbitCommonConfig.getConnection();
        //2、创建channel
        Channel channel = connection.createChannel();

        channel.queueDeclare("persist-show", true, false, false, null);

        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            try {
                sendMsg(channel);
            } catch (Exception ex) {
                ex.printStackTrace();
                System.out.println("生产者发布消息失败!请重试");
            }
            Thread.sleep(1000);
        }

        //4、关闭管道和连接
        channel.close();
        connection.close();
    }

    private static void sendMsg(Channel channel) throws IOException, InterruptedException {
        //3、发送消息到exchange
        String msg = "i am a persist message";
        channel.basicPublish("", "persist-show", MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
        System.out.println("生产者发布消息成功");
    }

}

2.3 消费者消费过程-消费端确认

太不容易了,整了半天终于到消费了,前面所有的努力都是为成功的消费呀,消费这里确保消费成功咯。要回到前面所讲过的 消费端的ACK了。【rabbitmq模式中-WORK模式提及】

也就是开启消费端的手工确认机制-每次成功消费消息后,明确告知mq,消费成功了,你可以删除掉这条消息了。

若果没告知mq,那这条消息就一直会处于unacked状态,等消费者重启会再次消费。

所以消费完成后一定要明确告知 Broker,是 Ack ,还是NAck 还是Reject 了。

2.3.1 消费确认异常再次放回队列执行,【可设定次数-N次之后就不要重试了-直接Ack了】


import com.longfor.apidemos.rabbit.common.RabbitCommonConfig;
import com.rabbitmq.client.*;
import lombok.SneakyThrows;

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

/**
 * @author rabbit
 * @version 1.0.0
 * @Description 消息确认
 * @createTime 2022/07/27 19:36:00
 */
public class ConfirmConsumer1 {

    private static final Map<String, Integer> errorMap = new HashMap<>();

    //消费者
    public static void main(String[] args) throws Exception {
        //1、获取连对象、
        Connection connection = RabbitCommonConfig.getConnection();
        //2、创建channel
        Channel channel = connection.createChannel();

        //3、创建队列
        channel.queueDeclare("no-lost", true, false, false, null);

        //4.开启监听Queue
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @SneakyThrows
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收到消息:" + new String(body, "UTF-8"));
                try {
                    //具体业务
                    int i = 1 / 0;
                    //确认
                    channel.basicAck(envelope.getDeliveryTag(), false);
                } catch (Exception e) {
                    if (errorMap.get(new String(body, "UTF-8")) != null) {
                        System.out.println("消息已重复处理失败,拒绝再次接收...");
                        channel.basicReject(envelope.getDeliveryTag(), false);
                    } else {
                        System.out.println("消息即将再次返回队列处理...");
                        channel.basicNack(envelope.getDeliveryTag(), false, true);
                        errorMap.put(new String(body, "UTF-8"), 1);
                    }
                }
            }
        };
        channel.basicConsume("no-lost", false, consumer);
        System.out.println("消费者开始监听队列");

        //5、键盘录入,让程序不结束!
        System.in.read();

        //6、释放资源
        channel.close();
        connection.close();
    }

}

2.3.2 消费确认异常后直接ACK,会再finally块,记录此次异常记录,后续和发送端结合再次发起重试


import com.longfor.apidemos.rabbit.common.RabbitCommonConfig;
import com.rabbitmq.client.*;
import lombok.SneakyThrows;

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

/**
 * @author rabbit
 * @version 1.0.0
 * @Description 消息确认
 * @createTime 2022/07/27 19:36:00
 */
public class ConfirmConsumer2 {


    //消费者
    public static void main(String[] args) throws Exception {
        //1、获取连对象、
        Connection connection = RabbitCommonConfig.getConnection();
        //2、创建channel
        Channel channel = connection.createChannel();

        //3、创建队列
        channel.queueDeclare("no-lost", true, false, false, null);

        //4.开启监听Queue
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @SneakyThrows
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收到消息:" + new String(body, "UTF-8"));
                try {
                    //具体业务
                    int i = 1 / 0;
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //确认
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };
        channel.basicConsume("no-lost", false, consumer);
        System.out.println("消费者开始监听队列");

        //5、键盘录入,让程序不结束!
        System.in.read();

        //6、释放资源
        channel.close();
        connection.close();
    }

}

2.3.3 消费异常后使用死信队列-再死信队列里单独处理

死信队列后续单独介绍

原理也很简单就是将未消费的消息,再次转发到了一个Exchange,Exchange再路由到对应的Queue中去而已。

3.最佳实践

至此,兄弟们,若想保证消息不丢失,我们需要做这么多事情,各种环节,保障各种异常情况。是不是头都大了。代码复杂度高,性能也没法保证。

真正的实际应用中有各个环节都保证消息防丢的使用么?有的同学可以留言讨论下。 

本人在实际应用中,包括看大部分的同学的代码,都不会做这么多的机制去保证消息防丢,最多是做一个消费端的ACK。

甚至很多同学把MQ这里面的内容当做一个黑盒,或根本就不了解MQ里面的原理,只需要知道发送和接收,就能用起来,但发生了问题也必将麻烦。

推荐方式:预警+补偿=最终一致性

这里面我们引入一个实际的案例: 

场景描述:发红包,领取红包的人若超过一定限额,将红包退回。【不用关注是否合理】

 那这里面我实际没关注,mq过程中任何保证消息丢失的措施,完全把mq黑盒化。

最核心的内容是 设定了一个超退状态,消费完成后把这个状态置为已完成即可,

若此状态在一定时间后【例如5分钟】还没发生变化,那我们会JOB 扫描到再次发起重试,做好幂等即可。

仍补偿多次补偿不成功的,可以记录 超退失败,通过预警机制预警处理,人为干预即可。

当然场景不同,运用方式不同,还是需要结合自己实际的业务,选择合适的方式,来保证消息的准确、稳定、及时的投递。

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

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

相关文章

养老服务系统设计与实现-计算机毕业设计源码+LW文档

基于SSM的养老服务系统设计与实现 摘 要 本养老服务系统就是建立在充分利用现在完善科技技术这个理念基础之上&#xff0c;并使用IT技术进行对养老服务的管理&#xff0c;从而保证系统得到充分利用&#xff0c;可以实现养老服务的在线管理&#xff0c;这样保证了资源共享效率的…

牛客刷题记录(常见笔试题)

目录 一、Map的应用篇 乒 乓球筐 简单的错误记录 二、动态规划篇 计算字符串的编辑距离 年终奖 最长不含重复字符的子字符串 合唱团 三、数组篇 顺时针打印矩阵 一、Map的应用篇 乒 乓球筐 题目地址&#xff1a;乒乓球筐 小白代码 import java.util.*;// 注意类名必…

一次就能释放大量Mac内存空间的方法,你用过哪种?

清理Mac内存空间对Mac的运行速度有着非常大的好处&#xff0c;所以合理释放Mac内存空间是广大用户常做的一件事。那么小编整理了一些能够一次性大量释放Mac内存空间的方法&#xff0c;大家常用的是哪一种呢&#xff1f;欢迎一起交流哦~以下&#xff1a; 一、清理MAC缓存&#x…

微信小程序|从零动手实现俄罗斯方块

&#x1f4cc;个人主页&#xff1a;个人主页 ​&#x1f9c0; 推荐专栏&#xff1a;小程序开发成神之路 --【这是一个为想要入门和进阶小程序开发专门开启的精品专栏&#xff01;从个人到商业的全套开发教程&#xff0c;实打实的干货分享&#xff0c;确定不来看看&#xff1f; …

[Linux]----进程间通信之管道通信

文章目录前言一、进程间通信目的二、进程间通信发展三、进程间通信分类四、管道1. 匿名管道2. 管道内核代码3. 站在文件描述符角度-深度理解管道4. 站在内核角度-管道本质5. 管道的特征总结五、命名管道1. 创建命名管道总结前言 首先我基于通信背景来带大家了解进程间通讯&…

HTTP协议详细总结

目录 1.HTTP协议是什么? 2.什么叫做应用层协议 3.HTTP协议的工作流程 4.HTTP报文格式 请求报文: 响应报文: 5.URL 6.方法的认识 1.GET 2.POST 3.GET和POST的区别 4.其他方法 7.报头的认识 用户登陆过程: 8.状态码的认识 9.HTTPS 9.1HTTPS是什么? 9.2HTTPS的…

现代c++中实现精确延时方法总结

程序中实现延时有很多种办法&#xff0c;但是有些不建议用。比如还在用sleep()或者空转计数的方式延时&#xff1f;要么移植性不好&#xff0c;要么不够精确且效率太低。这里总结下现代c中推荐的一种实现精确延时的方法。 之前的一些用法 粗暴空转 long wait 0; while(wait…

十二、Mysql的索引

Mysql的索引十二、Mysql的索引一、什么是索引二、常见索引的种类(算法)三、B树 基于不同的查找算法分类介绍1、B树结构2、B-树四、索引的功能性分类1、辅助索引(S)及构建B树结构2、聚集索引(C)及构建B树结构3、聚集索引和辅助索引构成区别4、关于索引树的高度受什么影响五、索引…

Vue快速入门一:官网、下载、定义变量

Vue官网&#xff1a;Vue.js - 渐进式 JavaScript 框架 | Vue.js Vue2中文文档&#xff1a;Vue.js介绍 — Vue.jsVue.js Vue3中文文档&#xff1a;快速上手 | Vue.js Vue下载&#xff1a; Vue2下载&#xff1a; 引入Vue2版本&#xff1a;打开上面的中文文档&#xff0c;找到这…

Flink窗口及其分类-详细说明

文章目录&#x1f48e;Flink窗口的概念⚽窗口的分类&#x1faa9;窗口 API 概览⚾窗口分配器&#xff08;Window Assigners&#xff09;&#x1f603;&#x1f603;&#x1f603;&#x1f603;&#x1f603; 更多资源链接&#xff0c;欢迎访问作者gitee仓库&#xff1a;https:/…

Hive:BUG记录,错误使用动态分区导致的插入失败

1.场景 在Hive中&#xff0c;插入数据时可以指定动态分区&#xff0c;如果通过partition(day_partition)指定动态分区&#xff0c;而实际的select语句是直接把这个属性值写死了&#xff08;如‘2022-10-13’&#xff09;&#xff0c;就可以不需要指定hive变量set hive.exec.dy…

【牛客】四选一多路器

描述 制作一个四选一的多路选择器&#xff0c;要求输出定义上为线网类型 状态转换&#xff1a; d0 11 d1 10 d2 01 d3 00 信号示意图&#xff1a; 波形示意图&#xff1a; 输入描述&#xff1a; 输入信号 d1,d2,d3,d4 sel 类型 wire 输出描述&#xff1a; 输出信…

【信号处理】扩展卡尔曼滤波EKF(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

学习 MongoDB5 这一篇就够了

文章目录一、相关概念1.1、业务场景1.2、简介1.3、体系结构1.4、数据模型二、安装三、基本常用命令3.1、数据库操作3.2、集合操作3.3、文档基本CRUD1、插入2、查询3、更新4、删除3.4、分页查询3.5、更多查询3.6、小结四、索引4.1、概述4.2、类型4.3、管理操作4.4、索引的使用4.…

四十五、壁面函数理论及y+的确定

0. 前言 什么叫做壁面函数&#xff0c;为什么引入壁面函数的概念?? 因为流体无论流动&#xff0c;还是传热、传质都存在边界层。而之所以有壁面函数这个东西&#xff0c;根源就在于边界层理论。 1. 边界层理论 大家都知道什么是边界层理论&#xff0c;我们想要理解壁面函数…

美国这几年的人口死亡数据

2015年&#xff1a;总死亡271.20万&#xff0c;平均死亡年龄78.8 2016年&#xff1a;总死亡274.40万&#xff0c;平均死亡年龄78.6 2017年&#xff1a;总死亡281.35万&#xff0c;平均死亡年龄78.6 2018年&#xff1a;总死亡283.90万&#xff0c;平均死亡年龄78.7 2019年&#…

Java中的方法是什么?(Java系列2)

目录 前言&#xff1a; 1.什么是方法 2.方法的定义 3.方法调用的执行过程 4.实参和形参的关系 5.方法重载 6.方法签名 7.递归 8.关于“调用栈” 结束语&#xff1a; 前言&#xff1a; 在上一次博客中小编主要和大家分享了Java中的一些基础知识&#xff0c;与小编之前…

Python多任务编程

1.进程与多任务 1. 1 多任务的介绍 1.使用多任务能充分利用CPU资源&#xff0c;提高程序的执行效率&#xff0c;让程序具备处理多任务的能力。 2.多任务执行方式有两种&#xff1a; 并发&#xff1a;在一段时间内交替执行多个任务。 并行&#xff1a;在一段时间内真正的同…

第三周 青海之行——练练构图,培养你的摄影眼

目录3.1 油菜花海&#xff0c;怎么拍更好看&#xff1f;3.2 构图的元素&#xff1a;线条、形状、图案(一)3.3 构图的元素&#xff1a;光影、留白、框景(二)3.4 摄影构图 补充内容构图作业3.1 油菜花海&#xff0c;怎么拍更好看&#xff1f; 祁连山下的百里油菜花海 门源 雪山下…

冲冲冲!!!python计算机二级每日一套_8

文章目录一、选择题二、基本操作三、简单应用四、综合应用声明&#xff1a;例题均来源于网络&#xff0c;仅供学习笔记&#xff0c;若涉侵权请联系删除。所属练题来源于《小黑课堂》一、选择题 1、树的度为3&#xff0c;共有31个结点&#xff0c;但没有度为1和2的结点。则该树…