互联网分布式应用之RabbitMQ

RabbitMQ

Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机,Java 仍是企业和开发人员的首选开发平台。
  

课程内容的介绍

1. RabbitMQ介绍安装
2. RabbitMQ交换器
3. RabbitMQ高级
  

一、RabbitMQ介绍安装

1. AMQP 简介
AMQP (Advanced Message Queuing Protocol ,高级消息队列协议)是 个线路层的协议规范,而不是 API 规范(例如 JMS )。由于AMQP 是一个线路层协议规范,因此它天然就是跨平台的,就像 SMTP HTTP 等协议 样,只要开发者按照规范的格式发送数据,任何平台都可以通过 AMQP进行消息交互。像目前流行的 StormMQ RabbitMQ 等都实现了 AMQP。
   
2. RabbitMQ简介
RabbitMQ 一个实现了 AMQP 的开源消息中间件,使用高性能的 Erlang 编写。 RabbitMQ有可靠性、支持多种协议、高可用、支持消息集群以及多语言客户端等特点,在分布式系统中存储转发消息,具有不错的性能表现。
  
为什么要使用 RabbitMQ?他解决了什么问题?
现在的市面上有很多MQ可以选择,比如ActiveMQ、ZeroMQ、Appche Qpid,那问题来了为什么要选择RabbitMQ?
除了Qpid,RabbitMQ是唯一一个实现了AMQP标准的消息服务器;
可靠性,RabbitMQ的持久化支持,保证了消息的稳定性;
高并发,RabbitMQ使用了Erlang开发语言,Erlang是为电话交换机开发的语言,天生自带高并发光环,和高可用特性;
集群部署简单,正是应为Erlang使得RabbitMQ集群部署变的超级简单;
社区活跃度高,根据网上资料来看,RabbitMQ也是首选;

  

  

  

    

    
3. 消息队列基础知识
3.1 Provider
消息生产者,就是投递消息的程序。
  
3.2 Consumer
消息消费者,就是接受消息的程序。
 
3.3 非消息队列

  
3.4 使用消息队列

  
3.5 什么是队列?
队列就像存放了商品的仓库或者商店,是生产商品的工厂和购买商品的用户之间的中转站。
   
3.6 队列里存储了什么?
在 rabbitMQ 中,信息流从你的应用程序出发,来到 Rabbitmq 的队列,所有信息可以只存储在一个队列中。队列可以存储很多信息,因为它基本上是一个无限制的缓冲区,前提是你的机器有足够的存储空间。
  
3.7 队列和应用程序的关系?
多个生产者可以将消息发送到同一个队列中,多个消息者也可以只从同一个队列接收数据。
  
4. RabbitMQ安装
因为在Linux上面直接安装RabbitMQ比较复杂,而且容易出错,所以我们通过Docker来快速的安装我们的RabbitMQ。
 
4.1 查找镜像
docker search rabbitmq:management
  

  
4.2 拉取镜像
docker pull macintoshplus/rabbitmq-management
   

  
4.3 查看镜像
docker images
  

  
4.4 创建容器
docker run -d --hostname bobo01 --name rabbitmq -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest -p 15672:15672 -p 5672:5672 c20
  

  
4.5 查看容器
docker ps -a
  
4.6 访问测试
http://192.168.100.120:15672/

  

  
这就表示RabbitMQ安装成功并且访问成功了!
  
5.入门案例
5.1 创建项目
创建一个SpringBoot项目,并引入相关的依赖即可。
    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency></dependencies>
  
5.2 配置文件
我们需要在application.properties中添加RabbitMQ的相关的配置信息。
spring.application.name=rabbitmq-demo01
spring.rabbitmq.host=192.168.100.120
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest# 自定义一个属性 设置 队列的名称
mq.queue.name=hello-queue
 
5.3 队列配置文件
package com.bobo.config;import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class QueueConfig {@Value("${mq.queue.name}")private String queueName;@Beanpublic Queue createQueue(){return new Queue(queueName);}
}
   
5.4 消费者
package com.bobo.consumer;import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;/*** 消费者*/
@Component
public class Receiver {/*** 接收消息,然后处理消息*/@RabbitListener(queues = {"${mq.queue.name}"})public void process(String msg){// 处理消息System.out.println("recevier: " + msg);}
}
  
5.5 生产者
package com.bobo.provider;import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** 消息的提供者**/
@Component
public class Sender {@Autowiredprivate AmqpTemplate template;@Value("${mq.queue.name}")private String queueName;/*** 发送消息的方法*/public void send(String msg){// 队列名称  消息内容template.convertAndSend(queueName,msg);}}
  
5.6 测试
首先启动服务,消费者处于监听状态。

   
启动后可以在RabbitMQ的服务中看到对应的队列和消费者信息。

    
通过单元测试来发送消息。
package com.bobo;import com.bobo.provider.Sender;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class RabbitmqDemo01ApplicationTests {@Autowiredprivate Sender sender;@Testvoid contextLoads() {sender.send("你好啊....");}}
  

  
6. RabbitMQ原理介绍
6.1 原理图

  

  
6.2 概念介绍
1.Message
消息。消息是不具名的,它由消息头消息体组成。消息体是不透明的,而消息头则由一系列可选属性组成,这些属性包括:routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出消息可能持久性存储)等。
  
2.Publisher
消息的生产者。也是一个向交换器发布消息的客户端应用程序。
  
3.Consumer
消息的消费者。表示一个从消息队列中取得消息的客户端应用程序。
  
4.Exchange
交换器。用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
三种常用的交换器类型
1. direct(发布与订阅 完全匹配)
2. fanout(广播)
3. topic(主题,规则匹配)
  
5.Binding
绑定。用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
  
6.Queue
消息队列。用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者链接到这个队列将其取走。
  
7.Routing-key
路由键。RabbitMQ 决定消息该投递到哪个队列的规则。队列通过路由键绑定到交换器。消息发送到 MQ 服务器时,消息将拥有一个路由键,即便是空的 ,RabbitMQ 也会将其和绑定使用的路由键进行匹配。如果相匹配,消息将会投递到该队列。如果不匹配,消息将会进入黑洞。
  
8.Connection
链接。指 rabbit 服务器和服务建立的 TCP 链接。
  
9.Channel
1.Channel 中文叫做信道,是 TCP 里面的虚拟链接。例如:电缆相当于 TCP,信道是一个独立光纤束,一条 TCP 连接上创建多条信道是没有问题的。
2.TCP 一旦打开,就会创建 AMQP 信道。
3.无论是发布消息、接收消息、订阅队列,这些动作都是通过信道完成的。
  
10.Virtual Host
虚拟主机。表示一批交换器,消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是AMQP 概念的基础,必须在链接时指定,RabbitMQ 默认的 vhost 是/
11.Borker
表示消息队列服务器实体。
  
交换器和队列的关系
交换器是通过路由键和队列绑定在一起的,如果消息拥有的路由键跟队列和交换器的路由键匹配,那么消息就会被路由到该绑定的队列中。也就是说,消息到队列的过程中,消息首先会经过交换器,接下来交换器在通过路由键匹配分发消息到具体的队列中。路由键可以理解为匹配的规则。
  
RabbitMQ 为什么需要信道?为什么不是 TCP 直接通信?
1. TCP 的创建和销毁开销特别大。创建需要 3 次握手,销毁需要 4 次分手。
2. 如果不用信道,那应用程序就会以 TCP 链接 Rabbit,高峰时每秒成千上万条链接会造成资源巨大的浪费,而且操作系统每秒处理 TCP 链接数也是有限制的,必定造成性能瓶颈。

3. 信道的原理是一条线程一条通道,多条线程多条通道同用一条 TCP 链接。一条 TCP链接可以容纳无限的信道,即使每秒成千上万的请求也不会成为性能的瓶颈。
  

二、RabbitMQ交换器

  
1.Direct案例
DirectExchange 路由策略是将消息队列绑定到 DirectExchange 上,当 一条消息到达DirectExchange 时会被转发到与该条消息 routing key 相同的 Queue 上,例如消息队列名为“hello-queue ”,则 routingkey 为“hello-queue ”的消息会被该消息队列接收。

  
1.1 创建消费者
创建项目,并添加依赖。
    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency></dependencies>
  
配置文件
spring.application.name=rabbitmq-demo02
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
spring.rabbitmq.port=5672
spring.rabbitmq.host=192.168.100.120# 设置交换器名称
mq.config.exchange=log.direct# info 队列名称
mq.config.queue.info=log.info
# info 路由键
mq.config.queue.info.routing.key=log.info.routing.key# error 队列名称
mq.config.queue.error=log.error
# error 路由键
mq.config.queue.error.routing.key=log.error.routing.key
  
消费者
package com.bobo.consumer;import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;/*** info 日志的消费者*    @QueueBinding value 绑定的队列名称**    autoDelete:是否是一个可删除的临时队列*    @Exchange:交换器名称和类型*    key:路由键*/
@Component
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "${mq.config.queue.error}",autoDelete = "false"),exchange = @Exchange(value = "${mq.config.exchange}",type = ExchangeTypes.DIRECT),key = "${mq.config.queue.error.routing.key}")
)
public class ErrorRecevier {@RabbitHandlerpublic void process(String msg) {System.out.println("error....recevier:" + msg);boolean flag = true;if(flag){System.out.println(1/0);}}
}
package com.bobo.consumer;import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;/*** info 日志的消费者*    @QueueBinding value 绑定的队列名称**    autoDelete:是否是一个可删除的临时队列*    @Exchange:交换器名称和类型*    key:路由键*/
@Component
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "${mq.config.queue.info}",autoDelete = "false"),exchange = @Exchange(value = "${mq.config.exchange}",type = ExchangeTypes.DIRECT),key = "${mq.config.queue.info.routing.key}")
)
public class InfoRecevier {@RabbitHandlerpublic void process(String msg){System.out.println("info....recevier:" + msg);}
}
  
1.2 创建生产者
创建一个SpringBoot项目,添加和上面一样的依赖。
配置文件有区别,不需要添加队列的配置信息。
spring.application.name=rabbitmq-demo03
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
spring.rabbitmq.port=5672
spring.rabbitmq.host=192.168.100.120# 设置交换器名称
mq.config.exchange=log.direct# info 路由键
mq.config.queue.info.routing.key=log.info.routing.key# error 路由键
mq.config.queue.error.routing.key=log.error.routing.key
  
添加生产者的类
package com.bobo.provider;import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class Sender {@Autowiredprivate AmqpTemplate template;@Value("${mq.config.exchange}")private String exchange;@Value("${mq.config.queue.error.routing.key}")private String routingKey;public void send(String msg){// 发送消息template.convertAndSend(exchange,routingKey,msg);}
}
package com.bobo;import com.bobo.provider.Sender;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class RabbitmqDemo03ApplicationTests {@Autowiredprivate Sender sender;@Testvoid contextLoads() throws Exception{//sender.send("Hello RabbitMQ .... " + 666);}}
  
测试效果

    
2.Topic案例
TopicExchange 是比较复杂也比较灵活的 种路由策略,在TopicExchange 中,Queue 通过routingkey 绑定到 TopicExchange 上,当消息到达 TopicExchange 后,TopicExchange 根据消息的routingkey 消息路由到一个或者多 Queue上,相比direct模式topic会更加的灵活些。

  
package com.bobo.consumer;import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;@Component
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "${mq.config.queue.error}",autoDelete = "true"),exchange = @Exchange(value = "${mq.config.exchange}",type = ExchangeTypes.TOPIC),key = "*.log.error"))
public class ErrorRecevier {@RabbitHandlerpublic void process(String msg){System.out.println("error ... recevier:" + msg);}
}
package com.bobo.provider;import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class OrderSender {@Autowiredprivate AmqpTemplate template;@Value("${mq.config.exchange}")private String exchange;/*** 发送消息* @param msg*/public void send(String msg){template.convertAndSend(exchange,"Order.log.debug","Order log debug"+msg);template.convertAndSend(exchange,"Order.log.info","Order log info"+msg);template.convertAndSend(exchange,"Order.log.error","Order log error"+msg);template.convertAndSend(exchange,"Order.log.warn","Order log warn"+msg);}
}
  
3.Fanout案例
FanoutExchange 的数据交换策略是把所有到达 FanoutExchang 的消息转发给所有与它绑定的Queue ,在这种策略中, routingkey 将不起任何作用。

  

  
package com.bobo.consumer;import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;@Component
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "${mq.config.queue.sms}",autoDelete = "true"),exchange = @Exchange(value = "${mq.config.exchange}",type = ExchangeTypes.FANOUT))
)
public class SmsRecevier {@RabbitHandlerpublic void process(String msg){System.out.println("Sms .... recevider:" + msg);}
}
package com.bobo.provider;import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class Sender {@Autowiredprivate AmqpTemplate template;@Value("${mq.config.exchange}")private String exchange;/*** 发送消息* @param msg*/public void send(String msg){template.convertAndSend(exchange,"",msg);}
}
  

三、RabbitMQ高级

1. 持久化
消息的可靠性是RabbitMQ的一大特色,RabbitMQ是如何保证消息的可靠性的呢?--> 消息的持久化。
  
创建消费者

  
注意,此时我们需要设置autoDelete=false。

  
创建服务提供者
package com.bobo.provider;import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class Sender {@Autowiredprivate AmqpTemplate template;@Value("${mq.config.exchange}")private String exchange;@Value("${mq.config.queue.error.routing.key}")private String routingKey;public void send(String msg){// 发送消息template.convertAndSend(exchange,routingKey,msg);}
}
   
单元测试
package com.bobo;import com.bobo.provider.Sender;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class RabbitmqDemo03ApplicationTests {@Autowiredprivate Sender sender;@Testvoid contextLoads() throws Exception{//for (int i = 0; i < 10000; i++) {Thread.sleep(2000);sender.send("Hello RabbitMQ .... " + i);}}
}
  
当消费者处理了一段时间的消息之后,断开连接,然后消费者再上线我们发现消费者又能够处理掉下线后提供者发送的消息,保证了消息的完整性。

 
autoDelete属性
@Queue:当所有的消费者客户端连接断开后,是否自定删除队列
true:删除,false:不删除
@Exchange:当所有的绑定队列都不再使用时,是否自动删除交换器
true:删除,false:不删除
  
2. ACK确认机制
2.1 什么是ACK
如果消息在处理过程中,消费者的服务器在处理消息时出现了异常,那么可能这条正在处理的消息就没有完成消息消费,数据就会丢失,为了确保数据不会丢失,RabbitMQ支持消息确认机制-ACK。
  
2.2 ACK消息确认机制
ACK(Acknowledge Character)是消费者从RabbitMQ收到消息并处理完成后,反馈给RabbitMQ的,RabbitMQ接收到反馈信息后才会将消息从队列中删除。
1. 如果一个消费者在处理消息出现了网络不稳定,福区群异常等现象,会将消息重新放入队列中。
2. 如果在集群的情况下,RabbitMQ会立即将这个消息推送给这个在线的其他的消费者,这种机制保障了消费者在服务端故障的时候不会丢失任何的数据和任务。
3. 消息永远不会从RabbitMQ中删除:只有当消费者正确发送ACK反馈后,RabbitMQ收到确认后,消息才会从RabbitMQ的服务中删除。
4. 消息的ACK机制默认就是打开的。
  
ACK的验证
在服务端我们给出一个错误。

  
然后我们再去掉错误,发现消息会被正常的消费。

  
ACK的注意事项
如果忘记掉ACK,那么后果会比较严重,当Consumer退出时,Message会一直重复分发,然后RabbitMQ会占用越来越多的内存,由于RabbitMQ会长时间的运行,因此这个 内存泄漏 是致命的,我们可以通过设置重试次数来防止这个问题,在Consumer的application.properties中设置如下参数。
spring.rabbitmq.listener.simple.retry.enabled=true
## 设置重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
## 重试的间隔时间
spring.rabbitmq.listener.simple.retry.initial-interval=5000

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

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

相关文章

人工智能在金融领域的应用存在的4大挑战

金融服务供应商应该有计划地应对AI面临的难题 金融行业投资人工智能热潮带来有关数据安全和透明度的新问题。由于数据管理实践随着新的 AI 解决方案的引入而不断发展&#xff0c;应对这些新问题以及金融服务领域 AI 面临的其他挑战尤为重要。各组织必须认识到可能面临以下挑战…

2024腾讯云轻量应用服务器详细介绍_轻量全解析

腾讯云轻量应用服务器开箱即用、运维简单的轻量级云服务器&#xff0c;CPU内存带宽配置高并且价格特别便宜&#xff0c;大带宽&#xff0c;但是限制月流量。轻量2核2G3M带宽62元一年、2核2G4M优惠价118元一年&#xff0c;540元三年、2核4G5M带宽218元一年&#xff0c;756元3年、…

指令周期流程图相关题目

已知CPU结构如下图所示&#xff0c;其中包括一个累加器AC、一个状态寄存器和其他几个寄存器。各部分之间的连线表示数据通路&#xff0c;箭头表示信息传递方向。试完成以下工作&#xff1a;①写出图中四个寄存器A、B、C、D的名称和作用&#xff1b;②简述完成指令ADD Y的数据通…

DASS最新论文整理@2023.12

CVPR 2023 论文来源&#xff1a;https://openaccess.thecvf.com/CVPR2023?dayall 1 Planning-oriented Autonomous Driving 面向规划的自动驾驶 (Best papper) 项目地址&#xff1a;https://opendrivelab.github.io/UniAD/ 现代自动驾驶系统的特点是按顺序执行模块化任务…

【Linux】常用的基本命令指令②

前言&#xff1a;前面我们学习了Linux的部分指令&#xff0c;今天我们将接着上次的部分继续将Linux剩余的基本指令. &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:Linux的学习 &#x1f448; &#x1f4af;代码仓库:卫卫周大胖的学习日记…

ROS学习笔记(8)进一步深入了解ROS第二步

0.前提 在上一讲中我提到过该系列是基于宾夕法尼亚大学工程学院的ROS公开课&#xff0c;系列文章将来源于公开课中的课后习题。该系列可以很好的帮助大家更加深入的了解ROS的一些概念。&#xff08;有效面对HR的提问。&#xff09; 1. (C)What is a nodehandle object? Can we…

jmeter参数化的三种方式

1.用户定义变量 使用变量&#xff1a; ${变量名} 这个变量是全局变量&#xff0c;也就是在下面子节点中都可以使用&#xff1b; 使用场景&#xff1a;两个账号分别有不同的权限&#xff0c;A经办&#xff0c;B审核。等。。。 2.CSV数据文件设置 3.函数

感觉软件测试很简单,但为何这么多劝退的?

上一个说软件测试简单的&#xff0c;已经被面试官问死了。。。 现在已经过了 ”不会但我会学“ 就能感动面试官的时代&#xff0c;随着供需关系的变化&#xff0c;不论是对于面试官还是面试者&#xff0c;面试的成本越来越高。为了筛选到更优秀的程序员&#xff0c;面试官们可谓…

Go 编程必备:bufio 库的全面指南与实战技巧

Go 编程必备&#xff1a;bufio 库的全面指南与实战技巧 引言bufio 库概览bufio.Readerbufio.Writerbufio.Scanner 深入 bufio.Readerbufio.Reader 的基本使用高级功能应用场景 探索 bufio.Writerbufio.Writer 的基本使用高级功能应用场景 运用 bufio.Scannerbufio.Scanner 的基…

期末成绩出炉,这才是发成绩的正确打开方式

期末考试结束后&#xff0c;学生们和老师们都在期待着成绩的公布。对于学生们来说&#xff0c;这是一次检验自己学习成果的机会&#xff1b;对于老师们来说&#xff0c;这是评价自己教学效果的机会。然而&#xff0c;在现实中&#xff0c;很多学校和老师在发成绩时却存在一些问…

C++ 实现对战AI五子棋

个人主页&#xff1a;日刷百题 系列专栏&#xff1a;〖C/C小游戏〗〖Linux〗〖数据结构〗 〖C语言〗 &#x1f30e;欢迎各位→点赞&#x1f44d;收藏⭐️留言&#x1f4dd; ​ ​ 前言&#xff1a; 为了能够快速上手一门语言&#xff0c;我们往往在学习了基本语法后&#x…

vue保姆级教程----深入了解 Vue3路由守卫

&#x1f4e2; 鸿蒙专栏&#xff1a;想学鸿蒙的&#xff0c;冲 &#x1f4e2; C语言专栏&#xff1a;想学C语言的&#xff0c;冲 &#x1f4e2; VUE专栏&#xff1a;想学VUE的&#xff0c;冲这里 &#x1f4e2; CSS专栏&#xff1a;想学CSS的&#xff0c;冲这里 &#x1f4…

非英专生雅思首考 8 分经验贴

非英专生雅思首考 8 分经验贴 备考材料1.网站2.微信公众号3.APP4.PDF资料5.纸质教辅书 四个单项听力阅读写作口语注意事项 又来了&#xff0c;好文当分享&#xff01;如侵删。 背景&#xff1a;某工科学校经管类专业学生&#xff0c;7月份刚毕业。四级643&#xff0c;六级604。…

Netty实战(待完善)

Netty组件 1. Bootstrap, ServerBootstrap Netty 中 Bootstrap 类是客户端程序的启动引导类&#xff0c;ServerBootstrap 是服务端启动引导类。 2. NioEventLoop, NioEventLoopGroup NioEventLoop 中维护了一个线程和任务队列&#xff0c;支持异步提交执行任务&#xff0c;…

Spring——Spring IOC(1)

Spring IOC 创建工程&#xff1a; 1.程序的耦合 耦合&#xff1a;耦合指的就是对象之间的依赖关系。对象之间的耦合越高&#xff0c;维护成本越高。 案例&#xff1a;没有引入IOC容器时系统的Web层、业务层、持久层存在耦合 /*** 持久层实现类*/ public class UserDaoImpl …

SpringBoot的测试

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

12.递归汉诺塔

使用递归实现汉诺塔 public class Main {public static void move(char pos1,char pos2) {System.out.print(pos1" > "pos2" ");}public static void han(int n,char pos1,char pos2,char pos3) {if(n 1) {move(pos1,pos3);return ;}han(n-1,pos1,pos…

阿里、字节等大厂系统测试方法的知识点总结,终于被我搞到手了

系统测试一般采取黑盒测试&#xff0c;系统测试的方法也比较多&#xff0c;其中常用的方法有&#xff1a;多任务测试、临界测试、中断测试、等价划分测试 多任务测试 多任务测试是指在非idle状态下&#xff0c;测试对象处于工作状态时&#xff0c;有新的事件发生&#xff0c;…

multipath 内核接口及框架介绍

文章目录 1 云主机使用网络存储 io 流程2 multipath 介绍 1 云主机使用网络存储 io 流程 对于一个云服务环境&#xff0c;大致会有网络节点&#xff0c;存储节点&#xff0c;计算节点&#xff0c;控制节点&#xff0c;其中虚拟云主机在计算节点工作&#xff0c;而虚拟云主机&a…

LCR 176. 判断是否为平衡二叉树

解题思路&#xff1a; class Solution {public boolean isBalanced(TreeNode root) {return recur(root) ! -1;}private int recur(TreeNode root) {if (root null) return 0;int left recur(root.left);if(left -1) return -1;int right recur(root.right);if(right -1) …