RabbitMQ官方案例学习记录

官方文档:RabbitMQ教程 — RabbitMQ (rabbitmq.com)

一、安装RabbitMQ服务

直接使用docker在服务器上安装

docker run -it -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.12-management

安装完成后,访问15672端口,默认用户名和密码都是 guest,即可进入 

二、hello world——梦开始的地方

1. 介绍

        RabbitMQ 是一个消息代理:它接受并转发消息。 你可以把它想象成一个邮局:当你把你想要邮寄的邮件放在邮箱里时, 您可以确定,邮递员最终会将邮件递送给您的收件人。 在这个类比中,RabbitMQ是一个邮政信箱,一个邮局和一个信件载体。RabbitMQ和邮局的主要区别在于它不处理纸张, 相反,它接受、存储和转发数据的二进制 blob - 消息

2. 一些术语

生产者:消息的发送方

队列queue:本质上是一个大型的消息缓冲区

消费者:消息的使用方

Channel 频道:理解为操作消息队列的 client(比如 jdbcClient、redisClient),提供了和消息队列 server 建立通信的传输方法(为了复用连接,提高传输效率)。程序通过 channel 操作 rabbitmq(收发消息)

3. 编写代码

用 Java 编写两个程序;一个发送单个消息的生产者,一个接收的使用者并将消息打印出来。

(1)消息生产者:

编码过程:

先创建连接工厂,然后通过工厂创建连接,再通过连接创建channel。通过channel来绑定队列或者交换机,再用channel来生产或者消费消息。

channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());

对于这行代码,可以看到消息是根据QUEUE_NAME路由到对应的队列。

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
public class MQProducer {//设置队列名private final static String QUEUE_NAME = "hello-zy";public static void main(String[] argv) throws Exception {//创建连接工厂ConnectionFactory factory = new ConnectionFactory();//设置连接的服务器IP和端口号factory.setHost("123.249.112.12");factory.setPort(5672);//创建一个连接和通道,这里使用 try-with-resources语句,// 因为Connection和Channel都实现了java.lang.AutoCloseable,不需要在代码中再显式的关闭他们try(Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {/*创建队列 channel.queueDeclare()用于声明一个队列queue(队列名称):指定要声明的队列的名称。durable(持久化):指定队列是否是持久化的。当 RabbitMQ 重新启动时,持久化的队列将被保留下来。如果将该参数设置为 true,则队列将被持久化;如果设置为 false,则队列不会被持久化。注意,这里指的是队列本身的持久化,而不是队列中的消息。exclusive(排他性):指定队列是否是排他的。如果将该参数设置为 true,则该队列只能被当前连接的消费者使用,并且在连接关闭时会自动删除该队列。如果设置为 false,则队列可供多个消费者使用。autoDelete(自动删除):指定队列在不再被使用时是否自动删除。如果将该参数设置为 true,则当队列不再被消费者使用时,将自动删除该队列。如果设置为 false,则队列不会自动删除。arguments(参数):指定队列的其他属性和参数,以键值对的形式提供。*/channel.queueDeclare(QUEUE_NAME, false, false, false, null);//发送消息String msg = "hello,world! RabbitMQ!";//channel.basicPublish("", QUEUE_NAME, null, msg.getBytes())// 这行代码的作用是将 msg 消息发布到默认交换器(空字符串)并使用QUEUE_NAME作为路由键,// 消息的属性设置为默认值,消息的内容为 msg.getBytes(),即将 msg 转换为字节数组后发送。channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());System.out.println(" 生产者发送消息:'" + msg + "'");}}
}

 然后运行这个生产者代码,去网页端查看:

(2)消息消费者:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class MQConsumer {//声明队列 和消息发送方保持一致private final static String QUEUE_NAME = "hello-zy";public static void main(String[] args) throws IOException, TimeoutException {ConnectionFactory factory = new ConnectionFactory();//设置rabbitMQ服务端ipfactory.setHost("123.249.112.12");//这里不用try-with-resources 因为消费方需要一致保持监听,不要关闭Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);System.out.println(" 等待接收消息,退出请按 CTRL+C");//创建了一个消费者对象并实现了 handleDelivery() 方法作为回调方法。当消费者收到消息时,将自动执行该方法。DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println(" 消费了消息:'" + message + "'");};channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });}
}

去网页端查看消息是否被消费,可以看到多了一个消费者,并且消息已经没了。 

三、工作队列 WorkQueue

        工作队列(又名:任务队列)背后的主要思想是避免立即执行资源密集型任务,必须等待它要完成。相反,我们将任务安排在以后完成。我们将任务封装为消息并将其发送到队列。正在运行的工作进程 在后台将弹出任务并最终执行工作。当您运行许多工作线程时,任务将在它们之间共享。

WorkQueue的模型跟前面第一个案例Hello,World!的模型,最明显的区别其实就是,第一个案例他只有一个消费者。我们知道RabbitMQ他的消息是阅完即焚,即消费者一旦接收,这个消息直接就从Queue中被弹出了。
而现在这个案例,他有两个消费者(画两个只是方便,他当然也可以有3个、4个),他的消息应该是通过某种算法做负载均衡送到不同的消费者,让消费者进行处理,让消息不至于处理不过来,从而导致滞留在Queue中的消息被弹出。

思路如下:
1、我们先让Publish服务每秒发布50条消息到 simple.queue,来演示消息的频繁发送。
2、在Consumer服务中定义两个消费者,来监听我们的 simple.queue队列。
3、消费者1每秒处理40条消息,消费者2每秒处理30条消息。

1. 循环调度

(1)生产者:

生成50条消息:

public class Send {//设置队列名private final static String QUEUE_NAME = "hello-zy";public static void main(String[] argv) throws Exception {//创建连接ConnectionFactory factory = new ConnectionFactory();//设置连接的服务器IP和端口号factory.setHost("123.249.112.12");factory.setPort(5672);//创建一个通道,这里使用 try-with-resources语句,// 因为Connection和Channel都实现了java.lang.AutoCloseable,不需要在代码中再显式的关闭他们try(Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {channel.queueDeclare(QUEUE_NAME, false, false, false, null);//发送消息for(int i = 1; i <= 50; i++) {String msg = "hello, I am";msg = msg + i;channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());Thread.sleep(200);System.out.println(" 生产者发送消息:'" + msg + "'");}}}
}

(2)消费者1和消费者2

public class Worker_01 {//声明队列 和消息发送方保持一致private final static String QUEUE_NAME = "hello-zy";public static void main(String[] args) throws IOException, TimeoutException {ConnectionFactory factory = new ConnectionFactory();//设置rabbitMQ服务端ipfactory.setHost("123.249.112.12");//这里不用try-with-resources 因为消费方需要一致保持监听,不要关闭Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);System.out.println(" 等待接收消息,退出请按 CTRL+C");//创建了一个消费者对象并实现了 handleDelivery() 方法作为回调方法。当消费者收到消息时,将自动执行该方法。DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println(" Worker_01消费了消息:'" + message + "'");};channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });}
}public class Worker_02 {//声明队列 和消息发送方保持一致private final static String QUEUE_NAME = "hello-zy";public static void main(String[] args) throws IOException, TimeoutException {ConnectionFactory factory = new ConnectionFactory();//设置rabbitMQ服务端ipfactory.setHost("123.249.112.12");//这里不用try-with-resources 因为消费方需要一致保持监听,不要关闭Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);System.out.println(" 等待接收消息,退出请按 CTRL+C");//创建了一个消费者对象并实现了 handleDelivery() 方法作为回调方法。当消费者收到消息时,将自动执行该方法。DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println(" Worker_02消费了消息:'" + message + "'");};channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });}
}

启动两个消费者,然后开始生产消息,控制台打印如下:

 可以看到是交替消费的:

2. 消息确认

        在上面的代码中,一旦消息传递给消费者,就会立即被删除。如果在消费过程中,消费者宕机,消息没消费成功,但是因为已经投递出去了,消息从队列删掉。就会出现:消息未消费,且丢失的问题。对于这种情况,我们希望没消费成功的消息,转交给其他的消费者消费。

        为了确保消息永远不会丢失,RabbitMQ 支持消息确认。确认由消费者告诉 RabbitMQ 已收到特定消息,并处理,RabbitMQ可以自由删除它。

        如果消费者没有发送确认消息,rabbitMQ可以知道消息没有消费成功,并将重新排队。

        在消费者返回确认消息时强制实施超时(默认为 30 分钟)。 这有助于检测从不确认交付的错误(卡住)消费者。 可以按照传递确认超时中所述增加此超时。

        默认情况下,手动消息确认处于打开状态。在上一个 示例,我们通过 autoAck=true 标志明确关闭了它们。是时候将此标志设置为 false ,来使消费者发送确认消息。

(1)消费者

生产者还是不变,生成10条消息到队列中:

创建消费者,启动手动确认:

public class Consumer {private final static String QUEUE_NAME = "hello-zy";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");factory.setPort(5672);Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);// 将autoAck参数设置为false,关闭自动消息确认/** new DefaultConsumer(channel) { ... }:这是一个匿名内部类,用于定义消息处理的逻辑。* 它继承自DefaultConsumer,并覆盖了 handleDelivery 方法,以自定义消息的处理方式。* */channel.basicConsume(QUEUE_NAME, false, new DefaultConsumer(channel) {/*handleDelivery 方法:这是 DefaultConsumer 类中的方法,用于处理从队列中接收的消息。consumerTag:标识消费者的标签。envelope:包含与消息相关的元数据,如交付标签、交付模式等。properties:包含消息的属性,如消息的头部信息。body:消息的内容,以字节数组形式提供。*/@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String message = new String(body, "UTF-8");System.out.println("消费者接收消息: '" + message + "'");// 在消息处理成功后,发送确认消息channel.basicAck(envelope.getDeliveryTag(), false);System.out.println("确认收到了消息:"+message);}});}
}

启动消费者:

(2)验证未成功消费情况

先生产6条消息到队列:

简单修改一下消费者代码,在中间添加判断逻辑,当碰到消息5的时候,退出消费者:

public class Consumer1 {private final static String QUEUE_NAME = "hello-zy";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);try {// 将autoAck参数设置为false,关闭自动消息确认channel.basicConsume(QUEUE_NAME, false, new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)throws IOException {String message = new String(body, "UTF-8");System.out.println("消费者接收消息: '" + message + "'");// 模拟某个条件,例如消息处理成功if (!message.contains("5")) {// 在消息处理成功后,发送确认消息channel.basicAck(envelope.getDeliveryTag(), false);System.out.println("确认收到了消息:" + message);}// 模拟消费者退出if (message.contains("5")) {throw new RuntimeException("Consumer exiting...");}}});} catch (RuntimeException e) {// 当消费者退出时,捕获异常System.out.println("Consumer exited.");}}
}

 

可以看到,当消费者消费到第五条消息后,因为抛出了异常,所以后面的消息都未消费成功,所以会把第五条消息和第六条消息再放回消息队列,查看消息队列,可以看到还有两条消息在队列中:

如果把参数改成true,也就是自动确认:这就会导致未消费的消息丢失

 

3. 消息持久性

我们已经保证了消息未消费的情况下不会丢失,但是如果RabbitMQ服务器宕机,消息还是会丢失。这就涉及到一个消息持久化的问题。

需要做两件事来确保 消息不会丢失:我们需要将队列和消息都标记为durable。 (重新定义一个队列)

此时,我们确信durable_queue队列不会丢失 即使 RabbitMQ 重新启动。现在我们需要将我们的消息标记为持久 - 通过设置消息属性(实现基本属性) 到值PERSISTENT_TEXT_PLAIN。 

 

如果不设置未持久性,重启docker的rabbitmq容器,队列消息就丢了,设置durable=true后, 即使重启docker容器,队列和消息都不会丢失。

四、发布/订阅模式

        在前面,都是一条消息由一个消费者消费。如果一条消息需要被多个消费者消费,那么就需要引入发布/订阅模式。

1. 交换机

关于交换机的概念():

一个生产者给 多个 队列发消息,1 个生产者对多个队列。
交换机的作用:提供消息转发功能,类似于网络路由器
要解决的问题:怎么把消息转发到不同的队列上,好让消费者从不同的队列消费。

交换机有多种类别:fanout、direct, topic, headers

fanout(扇出)

扇出、广播
特点:消息会被转发到所有绑定到该交换机的队列
场景:很适用于发布订阅的场景。比如写日志,可以多个系统间共享

Direct 直接

绑定:可以让交换机和队列进行关联,可以指定让交互机把什么样的消息发送给哪个队列(类似于计算机网络中,两个路由器,或者网络设备相互连接,也可以理解为网线)
routingKey:路由键,控制消息要转发给哪个队列的(IP 地址) 

特点:消息会根据路由键转发到指定的队列
场景:特定的消息只交给特定的系统(程序)来处理
绑定关系:完全匹配字符串

比如发日志的场景,希望用独立的程序来处理不同级别的日志,比如 C1 系统处理 error 日志,C2 系统处理其他级别的日志

 

 topic 交换机

特点:消息会根据一个 模糊的 路由键转发到指定的队列
场景:特定的一类消息可以交给特定的一类系统(程序)来处理
绑定关系:可以模糊匹配多个绑定
●*:匹配一个单词,比如 *.orange,那么 a.orange、b.orange 都能匹配
●#:匹配 0 个或多个单词,比如 a.#,那么 a.a、a.b、a.a.a 都能匹配

注意,这里的匹配和 MySQL 的like 的 % 不一样,只能按照单词来匹配,每个 '.' 分隔单词,如果是 '#.',其实可以忽略,匹配 0 个词也 ok

Headers 交换机 

类似主题和直接交换机,可以根据 headers 中的内容来指定发送到哪个队列。使用消息头headers来路由消息。

 2. 使用fanout交换机来实现发布/订阅

(1)生产者

public class LogProducer {private final static String EXCHANGE_NAME = "logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");factory.setPort(5672);try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {//声明交换机类型为:FANOUTchannel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);for (int i = 1; i <= 5; i++) {String message = "Log message " + i;//消息发到交换机中,而不是像之前点对点那样直接发到消息队列中channel.basicPublish(EXCHANGE_NAME, "", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());System.out.println("生产者发送日志: " + message);}}}
}

查看交换机列表,可以看到新增了一个名为logs的交换机,类型为 fanout:

 (2)消费者

创建两个消费者,都绑定一个交换机,名字为log

public class LogConsumer {private final static String EXCHANGE_NAME = "logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);// 创建一个临时队列String queueName = channel.queueDeclare().getQueue();// 将队列绑定到交换器channel.queueBind(queueName, EXCHANGE_NAME, "");System.out.println("等待接收日志消息...");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println("消费者1接收消息: '" + message + "'");};channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });}
}
public class LogConsumer2 {private final static String EXCHANGE_NAME = "logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);// 创建一个临时队列String queueName = channel.queueDeclare().getQueue();// 将队列绑定到交换器channel.queueBind(queueName, EXCHANGE_NAME, "");System.out.println("等待接收日志消息...");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println("消费者2接收消息: '" + message + "'");};channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});}
}

 启动两个消费者,然后启动生产者发送消息,可以看到两个消费者都消费了消息:

五、路由

在上面的例子中,我们实现了一个简单的日志记录系统。能够向多个消费者广播消息。

下面要增加一个新的功能:

        例如,我们将只能将关键错误消息定向到 日志文件(以节省磁盘空间),同时仍然能够打印所有控制台上的日志消息。

1. 绑定队列和交换机

之前是这样绑定的:第三个参数就是路由键

channel.queueBind(queueName, EXCHANGE_NAME, "");

绑定是交换和队列之间的关系。

        绑定可以采用额外的路由键参数。为了避免 与basic_publish参数混淆,我们将它称为绑定键。绑定键的含义取决于交换类型。

channel.queueBind(queueName, EXCHANGE_NAME, "black");

2. 使用direct交换机绑定

(1)生产者

模拟三条不同的消息,指定消息1的路由键为orange,消息2的路由键为black,消息3的路由键为green。

public class LogProducer {private final static String EXCHANGE_NAME = "direct_logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");factory.setPort(5672);try (Connection connection = factory.newConnection();Channel channel = connection.createChannel()) {//声明交换机类型为 directchannel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);String message1 = "Message with routing key orange";String message2 = "Message with routing key black";String message3 = "Message with routing key green";// 发布消息到交换器,并指定不同的路由键channel.basicPublish(EXCHANGE_NAME, "orange", null, message1.getBytes());channel.basicPublish(EXCHANGE_NAME, "black", null, message2.getBytes());channel.basicPublish(EXCHANGE_NAME, "green", null, message3.getBytes());System.out.println("生产者发送消息完成.");}}
}

(2)消费者

创建两个消费者

消费者1和消费者2都绑定对应的交换机,其中消费者1对应路由键orange,消费者2对应路由键black和orange。

public class LogConsumer1 {private final static String EXCHANGE_NAME = "direct_logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");factory.setPort(5672);Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);// 创建一个临时队列String queueName = channel.queueDeclare().getQueue();// 绑定队列到交换器,指定路由键为 "orange"channel.queueBind(queueName, EXCHANGE_NAME, "orange");System.out.println("等待接收 orange 消息...");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println("消费者1接收消息: '" + message + "'");};channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});}
}
public class LogConsumer2 {private final static String EXCHANGE_NAME = "direct_logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");factory.setPort(5672);Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);// 创建一个临时队列String queueName = channel.queueDeclare().getQueue();// 绑定队列到交换器,指定路由键为 "black" 和 "green"channel.queueBind(queueName, EXCHANGE_NAME, "black");channel.queueBind(queueName, EXCHANGE_NAME, "green");System.out.println("等待接收 black 和 green 消息...");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println("消费者2接收消息: '" + message + "'");};channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});}
}

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

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

相关文章

Python高频面试题——如何在字符串中删除指定字符,掌握strip()、replace()和re.sub ()正确使用方法!

关于python删除字符串是面试python测试开发工程师的一个经典问题。问题很简单&#xff0c;但是一下子就能测试出来被面试者是否能够熟练的进行python相关编码工作&#xff01; 对于有些临时抱佛脚的同学来讲&#xff0c;一看删除&#xff0c;很自然就说用remove 、del相关方法…

如何在电脑上设置新的蓝牙耳机

本文介绍如何将蓝牙耳机连接到Windows或Mac电脑。 如何在Windows上设置新的蓝牙耳机 蓝牙耳机的设置过程因平台而异&#xff0c;但以下是Windows 11的步骤&#xff1a; 1、选择“开始”&#xff0c;然后在搜索框中输入蓝牙&#xff0c;以显示蓝牙和其他设备。 2、选择添加设…

RabbitMQ中的核心概念和交换机类型

目录 一、RabbitMQ相关概念二、Exchange类型三、RabbitMQ概念模型总结 一、RabbitMQ相关概念 Producer&#xff1a;生产者&#xff0c;就是投递消息的一方。生产者创建消息&#xff0c;然后发布到RabbitMQ中。消息一般可以包含两个部分&#xff1a;消息体和附加消息。 消息体…

Unity之ShaderGraph如何实现UV抖动

前言 今天我们通过噪波图来实现一个UV抖动的效果。 如下图所示&#xff1a; 关键节点 Time&#xff1a;提供对着色器中各种时间参数的访问 UV&#xff1a;提供对网格顶点或片段的UV坐标的访问。可以使用通道下拉参数选择输出值的坐标通道。 SimpleNoise&#xff1a;根据…

Ubuntu20.4 设置代理

主要是涉及2个代理 涉及apt 可以在、/etc/apt/apt.conf 中进行修改 在系统全局可以在/etc/profile中进行修改

2023Jenkins连接k8s

首先配置k8s config文件 1.方式获取k8s密钥 cat .kube/config 2.导出方式或者密钥 kubectl config view --raw > k8s-config-admin pipeline {agent {kubernetes {yaml apiVersion: v1kind: Podmetadata:labels:some-label: devopsspec:containers:- name: dockerimage: d…

Python —— UI自动化之Page Object模式

1、Page Object模式简介 1、二层模型 Page Object Model&#xff08;页面对象模型&#xff09;, 或者也可称之为POM。在UI自动化测试广泛使用的一种分层设计 模式。核心是通过页面层封装所有的页面元素及操作&#xff0c;测试用例层通过调用页面层操作组装业务逻辑。 1、实战 …

弹出框,使用树结构查询数据

效果如下: 描述:希望点击某个按钮,弹出一个窗口,然后通过下拉框,点击下拉框里面的组织信息,然后查询对应组织的成员对象列表,并展示到表格中 HTML代码(最主要的就是树的那个): <el-dialog :visible.sync="TesteePage.showDialog" width="70%&quo…

codeforces (C++ Morning)

题目&#xff1a; 翻译&#xff1a; 思路&#xff1a; 1、要将四位数显示&#xff0c;每次操作可以选择移动光标&#xff08;移动到相邻的位置&#xff09;或者显示数字&#xff0c;计算最少需要多少次操作。 2、用flag表示当前光标位置&#xff0c;sum为记录操作次数&#…

vue 插槽 作用域插槽

vue 插槽 作用域插槽 **创建 工程&#xff1a; H:\java_work\java_springboot\vue_study ctrl按住不放 右键 悬着 powershell H:\java_work\java_springboot\js_study\Vue2_3入门到实战-配套资料\01-随堂代码素材\day05\准备代码\10-插槽-作用域插槽 vue --version vue crea…

一款简单漂亮的WPF UI - AduSkin

前言 经常会有同学会问&#xff0c;有没有好看简单的WPF UI库推荐的。今天就给大家推荐一款简单漂亮的WPF UI&#xff0c;融合多个开源框架组件&#xff1a;AduSkin。 WPF是什么&#xff1f; WPF 是一个强大的桌面应用程序框架&#xff0c;用于构建具有丰富用户界面的 Windo…

【JAVA-Day49】Java LinkedList集合详解

Java LinkedList集合详解 摘要引言Java LinkedList集合详解一、什么是LinkedList集合1.1 链表数据结构1.2 双向链表1.3 动态大小1.4 插入和删除元素1.5 适用场景 二、LinkedList集合的使用2.1 创建 LinkedList 集合、添加元素、遍历元素2.2 在指定位置插入元素2.3 获取指定位置…

聊聊分布式架构10——Zookeeper入门详解

目录 01ZooKeeper的ZAB协议 ZAB协议概念 ZAB协议基本模式 消息广播 崩溃恢复 选举出新的Leader服务器 数据同步 02Zookeeper的核心 ZooKeeper 的核心特点 ZooKeeper 的核心组件 选举算法概述 服务器启动时的Leader选举 服务器运行期间的Leader选举 03ZooKeeper的…

【vue+nestjs】qq第三方授权登录【超详细】

项目场景&#xff1a; 前端使用vue3ts 后端使用nestjs 1.申请appId,appKey 1.进入qq互联官网。创建应用 特别注意 1.在填写网站回调域时,需要你线上真实能访问的。不然审核不通过。我的回调地址是前端路由地址 2.如果你想本地调试&#xff0c;回调到你的线上地址。你可以在本…

面试算法31:最近最少使用缓存

题目 请设计实现一个最近最少使用&#xff08;Least Recently Used&#xff0c;LRU&#xff09;缓存&#xff0c;要求如下两个操作的时间复杂度都是O&#xff08;1&#xff09;。 get&#xff08;key&#xff09;&#xff1a;如果缓存中存在键key&#xff0c;则返回它对应的值…

基于Django与深度学习的股票预测系统 计算机竞赛

文章目录 0 前言1 课题背景2 实现效果3 Django框架4 数据整理5 模型准备和训练6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于Django与深度学习的股票预测系统 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff…

Redis基本命令和常用数据类型

文章目录 前言一、Redis简介二、基本操作1.赋值2.取值3.切换数据库4.查看数据库所有键&#xff08;key&#xff09;5.查看键值类型6.移动键值到其他数据库7.设置键值生存时间&#xff08;两种&#xff09;8.查看键值生存时间9.查看当前数据库大小10.判断键是否存在11.清空当前数…

基于ResNet34的花朵分类

一.数据集准备 新建一个项目文件夹ResNet&#xff0c;并在里面建立data_set文件夹用来保存数据集&#xff0c;在data_set文件夹下创建新文件夹"flower_data"&#xff0c;点击链接下载花分类数据集https://storage.googleapis.com/download.tensorflow.org/example_i…

英语什么时候加s和es

名词变复数一般情况下加s&#xff0c;以s,x,ch,sh结尾加es。一个名词如果表示一个或一样东西&#xff0c;它取单数形式&#xff0c;如果表示两个或更多的这类东西&#xff0c;则需要用名词复数形式。 1 以s,x,sh,ch结尾的词&#xff0c;加es。 2 以辅音字母&#xff08;除a/e/…

钢铁异常分类 few-shot 问题 小陈读paper 钢铁2

很清爽的 abstract 给出链接 前面的背景意义 其实 是通用的 这里替大家 整理一吓吓 1 缺陷分类在钢铁表面缺陷检测中 有 意义。 2 大多数缺陷分类模型都是基于完全监督的学习&#xff0c; 这需要大量带有图像标签的训练数据。 在工业场景中收集有缺陷的图像是非常困难…