RabbitMQ_00000

news2024/11/24 13:49:31

MQ的相关概念

RabbitMQ官网地址:https://www.rabbitmq.com

RabbitMQ API地址:https://rabbitmq.github.io/rabbitmq-java-client/api/current/

什么是MQ?

MQ(message queue)本质是个队列,FIFO先入先出,只不过队列中存放的内容是message 而已,还是一种跨进程的通信机制,用于上下游传递消息。在互联网架构中,MQ 是一种非常常见的上下游“逻辑解耦+物理解耦”的消息通信服务。使用了MQ之后,消息发送上游只需要依赖 MQ,不用依赖其他服务。

为什么要用MQ?

1.流量消峰。
举个例子,如果订单系统最多能处理一万次订单,这个处理能力应付正常时段的下单时绰绰有余,正常时段我们下单一秒后就能返回结果。但是在高峰期,如果有两万次下单操作系统是处理不了的,只能限制订单超过一万后不允许用户下单。使用消息队列做缓冲,我们可以取消这个限制,把一秒内下的订单分散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体验要好。

2.应用解耦
以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。当转变成基于消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户的下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,中单用户感受不到物流系统的故障,提升系统的可用性。

3.异步处理
有些服务间调用是异步的,例如A调用B,B需要花费很长时间执行,但是A需要知道B什么时候可以执行完,以前一般有两种方式,A过一段时间去调用B的查询api查询。或者A提供一个callback api,
B 执行完之后调用api通知A服务。这两种方式都不是很优雅,使用消息总线,可以很方便解决这个问题,A调用B服务后,只需要监听B处理完成的消息,当B处理完成后,会发送一条消息给MQ,MQ会将此消息转发给A服务。这样A服务既不用循环调用B的查询api,也不用提供callback api。同样B服务也不用做这些操作。A服务还能及时的得到异步处理成功的消息。

RabbitMQ

2007 年发布,是一个在 AMQP(高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。

生产者
产生数据发送消息的程序是生产者。

交换机
交换机是RabbitMQ非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息推送到队列中。交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是推送到多个队列,亦或者是把消息丢弃,这个得有交换机类型决定。

队列
队列是RabbitMQ内部使用的一种数据结构,尽管消息流经RabbitMQ和应用程序,但它们只能存储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区。许多生产者可以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据。这就是我们使用队列的方式

消费者
消费与接收具有相似的含义。消费者大多时候是一个等待接收消息的程序。请注意生产者,消费
者和消息中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又可以是消费者。

RabbitMQ的下载、安装

安装

1、在官网下载Linux版RabbitMQ安装文件。
erlang-21.3-1.el7.x86_64.rpm
rabbitmq-server-3.8.8-1.el7.noarch.rpm
2、将文件上传至Linux系统中。
上传到/usr/local/software目录下(如果没有software目录,则创建。)。
3、安装文件(按照以下顺序安装。)。
rpm -ivh erlang-21.3-1.el7.x86_64.rpm
yum install socat -y
rpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm
4、(1)添加开机启动RabbitMQ服务。
chkconfig rabbitmq-server on
(2)启动服务。
/sbin/service rabbitmq-server start
(3)查看服务状态。
/sbin/service rabbitmq-server status
(4)开启web管理插件
rabbitmq-plugins enable rabbitmq_management

停止服务:
/sbin/service rabbitmq-server stop
5、在浏览器地址栏中输入:http://ip:15672
例如:http://192.168.6.128:15672
输入默认:username:guest password:guest
点击登陆出现如下提示:
在这里插入图片描述
(1)添加一个新用户。
rabbitmqctl add_user 用户名 密码
例如:rabbitmqctl add_user admin 123
(2)设置用户角色。
rabbitmqctl set_user_tags admin administrator
(3)设置用户权限。

set_permissions [-p <vhostpath>] <user> <conf> <write> <read>
命令:rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"

6、查看当前用户和角色。
rabbitmqctl list_users
7、使用添加的用户和对应的密码再次登陆。
8、关闭应用的命令:
rabbitmqctl stop_app
清除的命令:
rabbitmqctl reset
重新启动命令:
rabbitmqctl start_app

案例

在idea开发工具中创建一个project,在该project中创建module。
引入依赖:

<?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">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.company</groupId>
  <artifactId>rabbitmq_00000</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>rabbitmq_00000</name>

  <dependencies>

    <!--RabbitMQ依赖客户端-->
    <dependency>
      <groupId>com.rabbitmq</groupId>
      <artifactId>amqp-client</artifactId>
      <version>5.20.0</version>
    </dependency>

    <!--操作文件流的一个依赖-->
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.15.1</version>
    </dependency>

  </dependencies>

  <build>

    <plugins>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>8</source>
          <target>8</target>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0</version>
        <configuration>
          <skipTests>true</skipTests>
        </configuration>
      </plugin>

    </plugins>

  </build>

</project>

工具类

package com.company.rabbitmq.utils;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class RabbitMQUtils {

    public static Channel getChannel() {

        ConnectionFactory connectionFactory = new ConnectionFactory();

        connectionFactory.setHost("192.168.6.128");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("123");

        Channel channel = null;

        try {

            Connection connection = connectionFactory.newConnection();
            channel = connection.createChannel();

        } catch (IOException | TimeoutException e) {
            e.printStackTrace();
        }

        return channel;

    }

}

生产者

package com.company.rabbitmq.one;

import com.company.rabbitmq.utils.RabbitMQUtils;
import com.rabbitmq.client.Channel;

import java.io.IOException;

public class Producer {

    private final static String QUEUE_NAME = "hello";

    public static void main(String[] args){

        Channel channel = RabbitMQUtils.getChannel();

        /*
        生成一个队列
        1.队列名称。
        2.队列里面的消息是否持久化。默认消息存储在内存中。
        3.该队列是否只供一个消费者进行消费。是否进行共享:true:可以多个消费者消费。
        4.是否自动删除。最后一个消费者断开连接以后,该队列是否自动删除。true:自动删除。
        5.其他参数。

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

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

            String message="hello world";
            /*
            发送一个消息。
            1.发送到哪个交换机。
            2.路由的key是哪个。
            3.其他的参数信息。
            4.发送消息的消息体。

            void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
            */
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());

            System.out.println("消息发送成功!");

        } catch (IOException ioException) {
            ioException.printStackTrace();
        }

    }

}

消费者

package com.company.rabbitmq.one;

import com.company.rabbitmq.utils.RabbitMQUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer {

    private final static String QUEUE_NAME = "hello";

    public static void main(String[] args){

        Channel channel = RabbitMQUtils.getChannel();

        System.out.println("等待接收消息......");

        //推送的消息如何进行消费的接口回调。
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {

            String message= new String(delivery.getBody());
            System.out.println(message);

        };

        //取消消费的一个回调接口。如在消费的时候队列被删除掉了。
        CancelCallback cancelCallback = consumerTag -> {

            System.out.println(" 消息消费被中断。");

        };

        /*
        消费者消费消息
        1.消费哪个队列。
        2.消费成功之后是否要自动应答。true:代表自动应答 false:代表手动应答。
        3.消费者未成功消费的回调。
        
        String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException;
        */
        try {

            channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);

        } catch (IOException ioException) {
            ioException.printStackTrace();
        }

    }

}

Work Queues

工作队列(又称任务队列)的主要思想是避免立即执行资源密集型任务,而不得不等待它完成。相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时,这些工作线程将一起处理这些任务。

轮训分发消息

生产者:消息发送者。

package com.company.rabbitmq.two;

import com.rabbitmq.client.Channel;

import java.io.IOException;
import java.util.Scanner;

import com.company.rabbitmq.utils.RabbitMQUtils;

public class Task01 {

    private static final String QUEUE_NAME = "hello";

    public static void main(String[] args) {

        Channel channel = RabbitMQUtils.getChannel();

        try {

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

            //从控制台当中接收信息。
            Scanner scanner = new Scanner(System.in);
            while(scanner.hasNext()){
                String message = scanner.next();
                //void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
                channel.basicPublish("",QUEUE_NAME,null,message.getBytes());

                System.out.println("发送消息完成:" + message);

            }

        } catch (IOException ioException) {
            ioException.printStackTrace();
        }

    }

}

消费者:消息接收者。

package com.company.rabbitmq.two;

import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;

import com.company.rabbitmq.utils.RabbitMQUtils;

public class Worker01 {

    //队列名称。
    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) {

        Channel channel = RabbitMQUtils.getChannel();

        //消息的接收
        DeliverCallback deliverCallback = (consumerTag, message) -> {

            System.out.println("接收到的消息:" + new String(message.getBody()));

        };

        //消息接收被取消时,执行下面的内容。
        CancelCallback cancelCallback = consumerTag -> {

            System.out.println(consumerTag + "消息者取消消费接口回调逻辑");

        };

        try {

            System.out.println("C1等待接收消息......");
            //String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException;
            channel.basicConsume(QUEUE_NAME,true, deliverCallback, cancelCallback);

        } catch (IOException ioException) {
            ioException.printStackTrace();
        }

    }

}

结果:
通过程序执行发现生产者总共发送4个消息,消费者1和消费者2分别分得两个消息,并且是按照有序的一个接收一次消息。
在这里插入图片描述

消息应答

消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成了部分突然它挂掉了,会发生什么情况。RabbitMQ 一旦向消费者传递了一条消息,便立即将该消息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息。以及后续发送给该消费者的消息,因为它无法接收到。为了保证消息在发送过程中不丢失,RabbitMQ引入了消息应答机制,消息应答就是: 消费者在接收到消息并且处理该消息之后,告诉RabbitMQ它已经处理了,RabbitMQ可以把该消息删除了。

自动应答

消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收到之前,消费者那边出现连接或者channel关闭,那么消息就丢失了,当然另一方面这种模式消费者那边可以传递过载的消息, 没有对传递的消息数量进行限制,当然这样有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终使得内存耗尽,最终这些消费者线程被操作系统杀死, 所以这种模式仅适用在消费者可以高效并以某种速率能够处理这些消息的情况下使用。

消息应答的方法

  • Channel.basicAck (用于肯定确认。)
    RabbitMQ已知道该消息并且成功的处理消息,可以将其丢弃了。
  • Channel.basicNack(用于否定确认。)
  • Channel.basicReject(用于否定确认。)
    与Channel.basicNack相比少一个参数,不处理该消息了直接拒绝,可以将其丢弃了。
    在这里插入图片描述
    multiple的true和false代表不同意思:
    true:代表批量应答channel上未应答的消息。

消息自动重新入队

如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或TCP连接丢失),导致消息未发送ACK确认,RabbitMQ将了解到消息未完全处理,并将对其重新排队。如果此时其他消费者可以处理,它将很快将其重新分发给另一个消费者。这样,即使某个消费者偶尔死亡,也可以确保不会丢失任何消息。

package com.company.rabbitmq.utils;

public class SleepUtils {

    public static void sleep(int second){

        try {
            Thread.sleep(1000*second);
        } catch (InterruptedException _ignored) {
            Thread.currentThread().interrupt();
        }

    }

}
package com.company.rabbitmq.three;

import com.rabbitmq.client.Channel;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

import com.company.rabbitmq.utils.RabbitMQUtils;

/*
消息在手动应答时是不丢失的,放回队列中重新消费。
*/
public class Task2 {

    //队列名称
    private static final String TASK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) {

        Channel channel = RabbitMQUtils.getChannel();

        //声明队列
        try {

            channel.queueDeclare(TASK_QUEUE_NAME,false,false,false,null);

            //从控制台中输入信息。
            Scanner scanner = new Scanner(System.in);
            while(scanner.hasNext()){
                String message = scanner.next();
                channel.basicPublish("", TASK_QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
                System.out.println("生产者发出消息:" + message);

            }

        } catch (IOException ioException) {
            ioException.printStackTrace();
        }

    }

}
package com.company.rabbitmq.three;

import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;

import com.company.rabbitmq.utils.RabbitMQUtils;
import com.company.rabbitmq.utils.SleepUtils;

/*
消息在手动应答时是不丢失的,放回队列中重新消费。
*/
public class Work03{

        //队列名称
        private static final String TASK_QUEUE_NAME = "ack_queue";

        public static void main(String[] args) {

            Channel channel = RabbitMQUtils.getChannel();
            System.out.println("C1等待接收消息处理时间较短。");

            DeliverCallback deliverCallback = (consumerTag, message) -> {

                //沉睡1秒
                SleepUtils.sleep(1);

                System.out.println("接收到的消息:" + new String(message.getBody(), "UTF-8"));

                //采用手动应答
                /*
                1、消息的标记:tag
                2、是否批量应答。false:不批量应答信道中的消息。true:批量应答。
                */
                //void basicAck(long deliveryTag, boolean multiple) throws IOException;
                channel.basicAck(message.getEnvelope().getDeliveryTag(), false);

            };

            //void handle(String consumerTag) throws IOException;
            /*CancelCallback cancelCallback = consumerTag -> {
                System.out.println(consumerTag + "消费者取消消费接口回调逻辑。");
            };*/

            try {

                //String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException;
                channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback,(consumerTag -> {
                    System.out.println(consumerTag + "消费者取消消费接口回调逻辑。");
                }));

            } catch (IOException ioException) {
                ioException.printStackTrace();
            }

        }

}
package com.company.rabbitmq.three;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import com.company.rabbitmq.utils.RabbitMQUtils;
import com.company.rabbitmq.utils.SleepUtils;

/*
消息在手动应答时是不丢失的,放回队列中重新消费。
*/
public class Work04 {

        //队列名称
        private static final String TASK_QUEUE_NAME = "ack_queue";

        public static void main(String[] args) {

            Channel channel = RabbitMQUtils.getChannel();
            System.out.println("C2等待接收消息处理时间较长。");

            DeliverCallback deliverCallback = (consumerTag, message) -> {

                //沉睡30秒。
                SleepUtils.sleep(30);

                System.out.println("接收到的消息:" + new String(message.getBody(), StandardCharsets.UTF_8));

                //采用手动应答
                /*
                1、消息的标记:tag。
                2、是否批量应答。false:不批量应答信道中的消息。true:批量应答。
                */
                channel.basicAck(message.getEnvelope().getDeliveryTag(), false);

            };

            try {

                channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback, (consumerTag ->

                    System.out.println(consumerTag + "消费者取消消费接口回调逻辑。")

                ));

            } catch (IOException ioException) {
                ioException.printStackTrace();
            }

        }

}

正常情况下消息发送方发送两个消息C1和C2,分别接收到消息并进行处理。
在这里插入图片描述
在发送者发送消息dd,发出消息之后把C2消费者停掉,按理说该C2来处理该消息,但是由于它处理时间较长,在还未处理完,也就是说 C2还没有执行ack代码的时候,C2被停掉了,此时会看到消息被C1接收到了,说明消息dd被重新入队,然后分配给能处理消息的C1处理了。
在这里插入图片描述

RabbitMQ持久化

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

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

相关文章

opencv#41 轮廓检测

轮廓概念介绍 通常我们使用二值化的图像进行轮廓检测&#xff0c;对轮廓以外到内进行数字命名&#xff0c;如下图&#xff0c;最外面的轮廓命名为0&#xff0c;向内部进行扩展&#xff0c;遇到黑色白色相交区域&#xff0c;就是一个新的轮廓&#xff0c;然后依次对轮廓进行编号…

ShardingSphere 5.x 系列【5】Spring Boot 3.1 集成Sharding Sphere-JDBC并实现读写分离

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Spring Boot 版本 3.1.0 本系列ShardingSphere 版本 5.4.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-sharding-sphere-demo 文章目录 1. 概述2. 使用限制3. 案例演示3.…

教师考编制一年有几次报名机会

作为一名教师&#xff0c;考编制是职业生涯中重要的一环。但你知道吗&#xff1f;教师考编制的报名机会并不是固定的&#xff0c;而是因地区和考试类型而异。那么&#xff0c;教师考编制一年到底有几次报名机会呢&#xff1f; 目前&#xff0c;教师考编制主要有两种类型&#…

瑞_数据结构与算法_B树

文章目录 1 什么是B树1.1 B树的背景1.2 B 的含义1.3 B-树的度和阶1.4 B-树的特性1.5 B-树演变过程示例 2 B-树的Java实现2.1 B树节点类Node &#x1f64a;前言&#xff1a;本文章为瑞_系列专栏之《数据结构与算法》的B树篇。由于博主是从B站黑马程序员的《数据结构与算法》学习…

vue学习91-105

vue的基本认知p91 创建一个空仓库p93 vue 路由 vuex版本 2 3 3 3 4 4 npm的vuex装包npm install vuex --save vuex里有仓库,仓库放vuex核心代码&#xff0c;所有组件都能访问到 const store new Vuex.Store()//访问stored this.$store如何提供$访问vuex的数据p94 核心概念-…

SQL Server之DML触发器

一、如何创建一个触发器呢 触发器的定义语言如下&#xff1a; CREATE [ OR ALTER ] TRIGGER trigger_nameon {table_name | view_name}{for | After | Instead of }[ insert, update,delete ]assql_statement从这个定义语言我们可以知道如下信息&#xff1a; trigger_name&…

Java 获取操作时区 ZonedDateTime

Java 获取操作时区 ZonedDateTime package com.zhong.timeaddress;import java.time.Clock; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Set;public class TimeAddress {public static void main(String[] args) {// 获取系统默认时区ZoneId…

OIS1 OIS1N OSSR OSSI用途

自记&#xff1a; 还是没有理解了 互补通道与刹车 互补通道与刹车功能是高级寄存器特有的功能。 在使用互补通道时&#xff0c;往往还需要考虑死区等特殊功能。而本文为了简单起见&#xff0c;不再考虑死区区间。通过配置TIMx_CCER寄存器&#xff0c;来使能互补通道&#x…

Spring IOC 之深入分析 Aware 接口

&#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是小徐&#x1f947;☁️博客首页&#xff1a;CSDN主页小徐的博客&#x1f304;每日一句&#xff1a;好学而不勤非真好学者 &#x1f4dc; 欢迎大家关注&#xff01; ❤️ &#xfeff;AbstractAutowireCapableBeanFacto…

Leetcode—33. 搜索旋转排序数组【中等】

2024每日刷题&#xff08;110&#xff09; Leetcode—33. 搜索旋转排序数组 实现代码 class Solution { public:int search(vector<int>& nums, int target) {int n nums.size();int l 0, r n - 1;while(l < r) {int m l (r - l) / 2;if(nums[m] target) …

1 月 30 日算法练习-数论

唯一分解定理 唯一分解定理指的是&#xff1a;对于任意一个>1的正整数&#xff0c;都可以以唯一的一种方式分解为若干质因数的乘积。 x p 1 k 1 ⋅ p 2 k 2 ⋅ … ⋅ p m k m x p_1^{k_1} \cdot p_2^{k_2} \cdot \ldots \cdot p_m^{k_m} xp1k1​​⋅p2k2​​⋅…⋅pmkm​…

推荐系统(Recommender Systems)

一、问题形式化 在接下来的内容中&#xff0c;我将开始讲解推荐系统的一些理论知识。我们从一个例子开始定义推荐系统&#xff0c;假使我们是一个电影供应商&#xff0c;我们有 5 部电影和 4 个用户&#xff0c;我们要求用户为电影打分 前三部电影是爱情片&#xff0c;后两部是…

整理:汉诺塔简析

大体上&#xff0c;要解决一个汉诺塔问题&#xff0c;就需要解决两个更简单的汉诺塔问题 以盘子数量 3 的汉诺塔问题为例 要将 3 个盘子从 A 移动到 C&#xff0c;就要&#xff1a; 将两个盘子从 A 移动到 B&#xff08;子问题 1&#xff09; 为了解决子问题 1&#xff0c;就…

图论练习4

内容&#xff1a;染色划分&#xff0c;带权并查集&#xff0c;扩展并查集 Arpa’s overnight party and Mehrdad’s silent entering 题目链接 题目大意 个点围成一圈&#xff0c;分为对&#xff0c;对内两点不同染色同时&#xff0c;相邻3个点之间必须有两个点不同染色问构…

高端酒店宴会包间桌位预定小程序h5开源版开发

高端酒店宴会包间桌位预定小程序h5开源版开发 餐厅预定桌位系统&#xff0c;支持多店切换预约&#xff0c;提供全部前后台无加密源代码和数据库 功能特性 为你介绍餐厅预订系统的功能特性 多端适配 采用uniapp,目前适配小程序和微信H5 多店铺 支持多店铺预定 付费和免费预定 支…

数据结构中的时间复杂度和空间复杂度基础

目录 数据结构 数据结构中的基本名词 数据 数据对象 数据元素 数据项 数据类型 数据对象、数据元素和数据项之间的关系 数据结构及分类 逻辑结构 物理结构 算法 算法的特点 算法设计上的要求 算法效率的衡量 时间复杂度 大O渐进表示法 最坏情况和平均情况 常…

【Simulink系列】——动态系统仿真 之 离散系统线性离散系统

一、离散系统定义 离散系统是指系统的输入与输出仅在离散的时间上取值&#xff0c;而且离散的时间具有相同的时间间隔。满足下列条件&#xff1a; ①系统&#xff08;的输入输出&#xff09;每隔固定时间间隔才更新一次。固定时间间隔称为采样时间。 ②系统的输出依赖当前的…

【Spring】Spring 启示录

一、OCP 开闭原则 核⼼&#xff1a;在扩展系统功能时不需要修改原先写好的代码&#xff0c;就是符合OCP原则的&#xff0c;反之修改了原先写好的代码&#xff0c;则违背了OCP原则的 若在扩展系统功能时修改原先稳定运⾏程序&#xff0c;原先的所有程序都需要进⾏重新测试&…

景区导览系统|智能导览|景区电子导览|智慧景区导览|AI智能导览

景区/园区导览系统是必不可少的服务内容&#xff0c;可提供提供指引导航&#xff0c;讲解景点、VR游览、预约购票等服务。随着元宇宙、VR、AR等数字科技的不断发展&#xff0c;导览系统的形式也从传统的纸质地图、指示牌等形式&#xff0c;发展为如今的VR/AR智慧导览。 作为国…

【OpenCV人脸检测】写了个智能锁屏小工具!人离开电脑自动锁屏

文章目录 1. 写在前面2. 设计思路3. 人脸检测4. 程序实现 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff1a;对JS逆向感兴趣的朋…