中间件知识点-消息中间件(Rabbitmq)一

news2024/11/13 15:44:30

消息中间件介绍

MQ的作用(优点)主要有以下三个方面:
a.异步
b.解耦
c.削峰

MQ的作用(缺点)主要有以下三个方面:
a.系统可用性降低
b.系统复杂度提高
c.存在消息一致性问题需要解决
备注:
引入MQ后系统的复杂度会大大提高。
以前服务之间可以进行同步的服务调用,引入MQ后,会变为异步调用,数据的链路就会变得更复杂。并且还会带来其他一些问
题。比如:如何保证消费不会丢失?不会被重复调用?怎么保证消息的顺序性等问题。(消息一致性问题)
A系统处理完业务,通过MQ发送消息给B、C系统进行后续的业务处理。如果B系统处理成功,C系统处理失败怎么办?这就需要考虑如何保证消息数据处理的一致性。

几大MQ产品特点比较(简单介绍)
rabbitmq可靠性:即不会丢数据,每个节点都存着全量数据(即数据一致性,rabbitmq仲裁队列quorum基于raft协议保证数据一致性,保证多半节点确认后才将消息写到队列中)。

kafka高可用:即使用了分区副本,把数据分散,保证高可用,不会因为一台机器宕机就无法使用。
kafka吞吐量大:使用了分区后可以在多台机器都存储数据,能存储的数据比较多
kafka性能非常好:使用了直接内存,零拷贝。

rocketmq借鉴了kafka、rabbitmq。

rabbitmq适合企业内部使用。一般企业使用rabbitmq就够了,一般企业5个节点就已经很多了。

在这里插入图片描述上面说的RabbitMq吞吐量比较低、消息积累会影响性能这些,其实在RabbitMq设计了Stream队列之后,这些都已经解决了。所以说RabbitMq的缺点只是对其过去版本的总结,现有的版本已经提升了很多了。因为加入了Stream队列这些,所以Stream队列借助了Kafka、RocketMq的思想。

另外上面说的RabbitMq吞吐量比较低、消息积累会影响性能这些是因为Classic和Quorum队列在收到消息时,如果是持久化消息,则会将消息储存在内存中,同时也会写入磁盘,所以消息量大的时候会内存会不断变小,吞吐量和性能就会变差。而RocketMQ或者kafka是会存在本地的,需要的时候再读取到内存,没那么耗性能。
1.kafka、rocketmq吞吐量高,是因为会将数据分散存储。那他会把收到的消息数据存到内存?
不会,他们是记录到日志中的。
但是在操作系统层面,当应用程序写入一个文件时,文件内容并不会直接写入到硬件当中,而是会先写入到操作系统中的一个缓存PageCache中。但可以认为会把收到的消息数据存到内存。
2.Stream队列不会将消息存在内存?不会。
3.Stream类似Kafka、RocketMq,以append-only log方式将消息记录到日志文件,然后消费的时候也是通过offset方式进行消费。
4.Quorum、Classic队列没有offset,不能快速找到数据,才需要将全部数据存放到内存。

这3个主要中间件一直在更新,一直在发展,越来越像。

activemq现在很少使用了,主要是用在很老的项目。

rocketmq适合业务比较多(topic比较多)的场景。

rabbitmq里面的routingkey和rocketmq里面的tag的区别?
routingkey在用在从交换机转换到哪个队列的,用在服务端。而tag用于消息过滤、消息路由、业务分类。

mq一般使用推模式。

rocketmq和kafka在互联网公司用的比较多,rabbitmq在中小公司用的比较多。
kafka一般用在大数据场景。

Rabbitmq

Rabbitmq单机搭建
略。

Rabbitmq集群(多节点的集群有两种方式模式)
a.默认的普通集群模式
不支持高可用:master宕机需要手动重启。
消息也不可靠:消息只存在一个节点中。
集群的各个节点之间只会有相同的元数据(比如队列、交换机。)。另外消息不会进行冗余,只存在一个节点中。消费时,如果消费的不是存有数据的节点, RabbitMQ会临时在节点之间进行数据传输,将消息从存有数据的节点传输到消费的节点。这种集群模式的消息可靠性不是很高。并且,这种集群模式也不支持高可用,即当某一个节点服务挂了后,需要手动重启服务,才能保证这一部分消息能正常消费。
所以这种集群模式只适合一些对消息安全性不是很高的场景。而在使用这种模式时,消费者应该尽量的连接上每一个节点,减少消息在集群中的传输。
b.镜像模式(会主动同步数据,而不是被动)
高可用:会自动选举新master节点。
消息可靠:消息会存在各节点。
但吞吐量不高:消息存在各节点,这样会消耗大量带宽。
这种模式是在普通集群模式基础上的一种增强方案,这也就是RabbitMQ的官方HA高可用方案。需要在搭建了普通集群之后再补充搭建。其本质区别在于,这种模式会在镜像节点中间主动进行消息同步,而不是在客户端拉取消息时临时同步。
并且在集群内部有一个算法会选举产生master和slave,当一个master挂了后,也会自动选出一个来。从而给整个集群提供高可用能力。
这种模式的消息可靠性更高,因为每个节点上都存着全量的消息。而他的弊端也是明显的,集群内部的网络带宽会被这种同步通讯大量的消耗(每个节点都存在全量数据,需要进行大量数据同步,不像kafka将数据分散存储),进而降低整个集群的性能。这种模式下,队列数量最好不要过多。

Exchanges:消息发送到RabbitMQ中后,会首先进入一个交换机,然后由交换机负责将数据转发到不同的队列中。即交换机的作用是可以对发送到RabbitMq Server的消息进行转发,转发到不同的队列中。
RabbitMQ中有多种不同类型的交换机来支持不同的路由策略。从Web管理界面就能看到,在每个虚拟主机中,RabbitMQ都会默认创建几个不同类型的交换机来。
交换机多用来与生产者打交道。生产者发送的消息通过Exchange交换机分配到各个不同的Queue队列上,而对于消息消费者来说,通常只需要关注自己感兴趣的队列就可以了。
在这里插入图片描述
队列消息删除机制:包括手动确认、自动确认、定时删除(TTL)等方式。
可以通过给队列设置TTL属性,来定时删除消息。
// 声明一个队列
String queueName = “testQueue”;
Map<String, Object> arguments = new HashMap<>();
// 设置消息的TTL为30000毫秒(30秒)
arguments.put(“x-message-ttl”, 30000);
channel.queueDeclare(queueName, true, false, false, arguments);

stream队列中的消息被消费了,是不会被删除的,而是通过类似kafka offset去定位从哪里去消费(只有Stream队列类型才会有offset概念,Classic经典队列没有),因为还需要发到其它消费组去推送消息。当过了一段时间后会统一删除队列的消息。

rabbitmq和kafka一样,同一个消费组的只有一个消费者才会消费消费消息。不同消费者组会重复消费一个消息。

rabbitmq没有类似kafka topic的概念,只有queue、exchanges的概念,只是在exchanges有topic的概念。而kafka、rocketmq有topic、queue的概念。
exchanges topic的概念?(Topic Exchange 类型支持通配符匹配路由键,然后将消息发送到符合特定规则的Queue 中)
RabbitMQ 和 Kafka 在消息队列架构上有一些不同之处。在 RabbitMQ 中,消息传递主要围绕着 Exchange 和 Queue 来进行,而 Exchange 中的类型包括 Direct、Fanout、Topic 和 Headers。
a.Exchange:Exchange 接收来自生产者的消息,并根据特定的规则将消息路由到一个或多个与之绑定的 Queue 中。Exchange 的类型决定了消息的路由方式。
b.Queue:Queue 是消息的容器,消费者从中接收消息。消息在进入 Queue 之前通过 Exchange 进行路由。
在 RabbitMQ 中,并没有像 Kafka 中的 Topic 概念那样直接映射到 Exchange。相对应的是,在 RabbitMQ 中的 Exchange 类型中,Topic Exchange 类型支持通配符匹配路由键,类似于 Kafka 中的 Topic,但并不是一一对应的概念。
因此,在 RabbitMQ 中,你可以使用 Topic Exchange 来实现类似于 Kafka Topic 的功能,通过设置特定的 Routing Key 和 Binding Key,将消息发送到符合特定规则的 Queue 中,实现灵活的消息路由和订阅功能。

Direct Exchange:将消息路由到绑定键(Binding Key)与消息的路由键(Routing Key)完全匹配的队列中。
Fanout Exchange:将消息广播到所有绑定的队列中。
Topic Exchange:根据消息的路由键和绑定键的模式进行匹配,将消息路由到一个或多个符合匹配规则的队列中。
Headers Exchange:使用消息的头部信息(headers)而不是路由键来决定消息的路由方式。

rabbitmq Exchanges四种类型:Direct、Fanout、Topic 和 Headers的java代码使用案例:
1.Direct Exchange

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

public class DirectExchangeExample {
    private static final String EXCHANGE_NAME = "direct_exchange";
    private static final String QUEUE_NAME = "direct_queue";
    private static final String ROUTING_KEY = "direct_routing_key";

    public static void main(String[] args) throws Exception {
        // 创建连接和通道
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            // 声明一个 Direct Exchange
            channel.exchangeDeclare(EXCHANGE_NAME, "direct");

            // 声明一个队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);

            // 将队列绑定到 Exchange,并指定 Routing Key
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);

            // 发送消息到 Exchange,并指定 Routing Key
            String message = "Hello, Direct Exchange!";
            channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, message.getBytes("UTF-8"));
            System.out.println("Sent message: '" + message + "'");
        }
    }
}

解释:
在这个例子中,我们创建了一个 Direct Exchange 名称为 direct_exchange。
创建了一个队列 direct_queue,并将它绑定到 direct_exchange,绑定时使用了路由键 direct_routing_key。
发送了一条消息到 Exchange,并指定了与队列绑定时相同的路由键 direct_routing_key。

2 Fanout Exchange

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

public class FanoutExchangeExample {
    private static final String EXCHANGE_NAME = "fanout_exchange";
    private static final String QUEUE_NAME1 = "fanout_queue1";
    private static final String QUEUE_NAME2 = "fanout_queue2";

    public static void main(String[] args) throws Exception {
        // 创建连接和通道
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            // 声明一个 Fanout Exchange
            channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

            // 声明两个队列
            channel.queueDeclare(QUEUE_NAME1, false, false, false, null);
            channel.queueDeclare(QUEUE_NAME2, false, false, false, null);

            // 将队列1绑定到 Exchange
            channel.queueBind(QUEUE_NAME1, EXCHANGE_NAME, "");

            // 将队列2绑定到 Exchange
            channel.queueBind(QUEUE_NAME2, EXCHANGE_NAME, "");

            // 发送消息到 Exchange
            String message = "Hello, Fanout Exchange!";
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
            System.out.println("Sent message: '" + message + "'");
        }
    }
}

解释:
在这个例子中,我们创建了一个 Fanout Exchange 名称为 fanout_exchange。
创建了两个队列 fanout_queue1 和 fanout_queue2,并将它们都绑定到 fanout_exchange。
发送了一条消息到 Exchange,消息会被广播到所有与 Exchange 绑定的队列上。

3.Topic Exchange

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

public class TopicExchangeExample {
    private static final String EXCHANGE_NAME = "topic_exchange";
    private static final String QUEUE_NAME1 = "topic_queue1";
    private static final String QUEUE_NAME2 = "topic_queue2";
    private static final String ROUTING_KEY1 = "topic.key1";
    private static final String ROUTING_KEY2 = "topic.key2";

    public static void main(String[] args) throws Exception {
        // 创建连接和通道
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            // 声明一个 Topic Exchange
            channel.exchangeDeclare(EXCHANGE_NAME, "topic");

            // 声明两个队列
            channel.queueDeclare(QUEUE_NAME1, false, false, false, null);
            channel.queueDeclare(QUEUE_NAME2, false, false, false, null);

            // 将队列1绑定到 Exchange,使用通配符路由键 "*.key1"
            channel.queueBind(QUEUE_NAME1, EXCHANGE_NAME, "*.key1");

            // 将队列2绑定到 Exchange,使用通配符路由键 "topic.*"
            channel.queueBind(QUEUE_NAME2, EXCHANGE_NAME, "topic.*");

            // 发送消息到 Exchange,使用路由键 "topic.key1"
            String message1 = "Hello, Topic Exchange - Key1!";
            channel.basicPublish(EXCHANGE_NAME, "topic.key1", null, message1.getBytes("UTF-8"));
            System.out.println("Sent message 1: '" + message1 + "'");

            // 发送消息到 Exchange,使用路由键 "topic.key2"
            String message2 = "Hello, Topic Exchange - Key2!";
            channel.basicPublish(EXCHANGE_NAME, "topic.key2", null, message2.getBytes("UTF-8"));
            System.out.println("Sent message 2: '" + message2 + "'");
        }
    }
}

解释:
在这个例子中,我们创建了一个 Topic Exchange 名称为 topic_exchange。
创建了两个队列 topic_queue1 和 topic_queue2,并分别使用不同的通配符路由键将它们绑定到 topic_exchange。
发送了两条消息到 Exchange,分别使用了符合队列绑定模式的路由键。

4.Headers Exchange

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import java.util.HashMap;
import java.util.Map;

public class HeadersExchangeExample {
    private static final String EXCHANGE_NAME = "headers_exchange";
    private static final String QUEUE_NAME = "headers_queue";
    private static final Map<String, Object> HEADERS = new HashMap<>();

    public static void main(String[] args) throws Exception {
        // 创建连接和通道
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            // 声明一个 Headers Exchange
            channel.exchangeDeclare(EXCHANGE_NAME, "headers");

            // 声明一个队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);

            // 定义 headers 属性匹配规则
            HEADERS.put("header1", "value1");

            // 将队列绑定到 Exchange,指定 headers 匹配规则
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "", HEADERS);

            // 发送消息到 Exchange,并设置匹配的 headers 属性
            Map<String, Object> messageHeaders = new HashMap<>();
            messageHeaders.put("header1", "value1");
            String message = "Hello, Headers Exchange!";
            channel.basicPublish(EXCHANGE_NAME, "", new BasicProperties.Builder().headers(messageHeaders).build(), message.getBytes("UTF-8"));
            System.out.println("Sent message: '" + message + "'");
        }
    }
}

解释:
在这个例子中,我们创建了一个 Headers Exchange 名称为 headers_exchange。
创建了一个队列 headers_queue,并使用 headers 属性 header1=value1 将它绑定到 headers_exchange。
发送了一条消息到 Exchange,并设置了符合队列绑定 headers 属性的消息头。

rabbitmq实现顺序消费需要在单队列才能实现。

在一个worker(节点)中创建了VirtualHost、Queues后,集群中的其它节点也会有这个VirtualHosts、Queues。即集群中所有的节
点数据都是一样的,是会进行同步的。
消费者消费消息有推模式和拉模式

队列
Classic 经典队列(最为常用)
单机环境中(不是集群),拥有比较高的消息可靠性。因为可以持久化数据。
多节点下采用主从复制,可能会丢失数据。

Quorum 仲裁队列(针对Classic优化方式一)
(单机下通过持久化保证消息可靠性。集群下通过Raft协议保证分布式消息可靠性)
a.仲裁队列相比Classic经典队列,在分布式环境下对消息的可靠性保障更高。官方文档中表示,未来会使用Quorum仲裁队列代替传统Classic队列。
b.基于Raft一致性协议实现的一种新型的分布式消息队列,他实现了持久化,多备份的FIFO队列,主要就是针对RabbitMQ的镜像模式设计的。
c.仲裁队列的消息都是持久化的,同时队列的所有消息一直会保存在内存中。经典队列也一样,都会保存在内存中。
d.适合场景
Quorum队列更适合:需要队列长期存在,并且对容错、数据安全方面的要求比较高。对低延迟要求不高,且不需要临时消息的场景。例如 电商系统的订单,引入MQ后,处理速度可以慢一点,但是订单不能丢失。
e.不适合场景
响应的Quorum队列不适合使用的场景:
1、一些临时使用的队列:比如transient临时队列,exclusive独占队列,或者经常会修改和删除的队列。(即Quorum不支持临时队列和独占队列)
2、对消息低延迟要求高: 一致性算法会影响消息的延迟。
3、对数据安全性要求不高:Quorum队列需要消费者手动通知或者生产者手动确认。
4、队列消息积压严重 : 如果队列中的消息很大,或者积压的消息很多,就不要使用Quorum队列(因为Classic和Quorum队列在收到消息时,如果是持久化消息,则会将消息储存在内存中,同时也会写入磁盘,所以消息量大的时候会内存会不断变小,吞吐量和性能就会变差)。Quorum队列当前会将所有消息始终保存在内存中,直到达到内存使用极限。
所以,对于消息特别多且消息积压严重,我们一般不使用activemq/rabbitmq的经典队列和仲裁队列。

rabbimq中Quorum队列和Classic经典队列的区别总结:
1.可靠性方面:单节点时,经典队列和仲裁队列可靠性都很高。多节点集群下,经典队列仅采用主从复制,即一个主节点和多个从节点,主节点负责处理所有的读写操作,从节点则复制主节点的数据,数据可能会不一致。而仲裁队列采用了基于Raft一致性协议实现数据一致性,需要又集群中多半节点同意确认后,才会写入到队列中,可靠性更高。
单机下通过持久化保证消息可靠性。集群下通过Raft协议保证分布式消息可靠性
2.持久化:经典队列可以设置是否持久化,而仲裁队列都是持久化。同时队列的所有消息一直会保存在内存中。经典队列也一样,都会保存在内存中。所以对于消息特别多且消息积压严重,我们一般不使用activemq/rabbitmq的经典队列和仲裁队列。
3.仲裁队列大部分功能都是在经典队列基础上做减法,比如仲裁队列相比经典队列少了是否持久化、是否独占这些属性配置。
4.仲裁队列有毒消息机制,而经典队列没有。

Stream队列(针对Classic优化方式二,推荐)
Stream队列不会把所有的消息都放在内存中,需要的时候才从磁盘上去取(通过offset快速找到,而不像Quorum、Classic队列没有offset,不能快速找到数据,才需要将全部数据存放到内存)

RabbitMQ编程模型
(可以使用下面三种方式来使用RabbitMq)
从原生API、SpringBoot集成(使用最多)、SpringCloudStream集成。

RabbitMQ高级使用场景
1.Header路由
2.分组消费模式
3.死信队列
4.消费优先级与流量控制
5.远程数据分发插件
6.懒队列(懒加载) Lazy Queue
RabbitMQ从3.6.0版本开始,就引入了懒队列的概念。懒队列会尽可能早的将消息内容保存到硬盘当中,并且只有在用户请求到时,才临时从硬盘加载到RAM内存当中。
懒队列的设计目标是为了支持非常长的队列(数百万级别)。队列可能会因为一些原因变得非常长-也就是数据堆积。
懒队列适合消息量大且长期有堆积的队列,可以减少内存使用,加快消费速度。但是这是以大量消耗集群的网络及磁盘IO为代价的(即尽早将消息持久化,尽早多节点同步)。
7.消息分片存储插件
如何提高吞吐量?
上面的懒队列其实就是针对这个问题的一种解决方案。但是很显然,懒队列的方式属于治标不治本。真正要提升RabbitMQ单队列的吞吐量,还是要从数据也就是消息入手,只有将数据真正的分开存储才行。RabbitMQ提供的Sharding插件,就是一个可选的方案。他会真正将一个队列中的消息分散存储到不同的节点上,并提供多个节点的负载均衡策略实现对等的读与写功能。

RabbitMQ使用中的常见问题
一、如何使用RabbitMQ保证消息不丢失?
二、如何保证消息幂等
三、如何保证消息的顺序?
四、关于RabbitMQ的数据堆积问题
五、RabbitMQ的备份与恢复
六、RabbitMQ的性能监控
七、搭建HAProxy,实现高可用集群(在集群基础上增加负载均衡的能力,将客户端的请求能够尽量均匀的分配到集群中各个节点上)

总结:
基于MQ的事件驱动机制,给庞大的互联网应用带来了不一样的方向。MQ的异步、解耦、削峰三大功能特点在很多业务场景下都能带来极大的性能提升,在日常工作过程中,应该尝试总结这些设计的思想。虽然MQ的功能,说起来比较简单,但是随着MQ的应用逐渐深化,所需要解决的问题也更深入。对各种细化问题的挖掘程度,很大程度上决定了开发团队能不能真正Hold得住MQ产品。通常面向互联网的应用场景,更加注重MQ的吞吐量,需要将消息尽快的保存下来,再供后端慢慢消费。而针对企业内部的应用场景,更加注重MQ的数据安全性,在复杂多变的业务场景下,每一个消息都需要有更加严格的安全保障。而在当今互联网,Kafka(吞吐量大)是第一个场景(即面向互联网的应用场景)的不二代表,但是他会丢失消息的特性,让kafka的使用场景比较局限。RabbitMQ作为一个老牌产品,是第二个场景(即对企业内部的应用场景)最有力的代表。当然,随着互联网应用不段成熟,也不断有其他更全能的产品冒出来,比如阿里的RocketMQ(所以rocketmq是rabbitmq和kafka之后推出的)以及雅虎的Pulsar。但是不管未来MQ领域会是什么样子,RabbitMQ依然是目前企业级最为经典也最为重要的一个产品。他的功能最为全面,周边生态也非常成熟,并且RabbitMQ有庞大的Spring社区支持,本身也在吸收其他产品的各种优点,持续进化,所以未来RabbitMQ的重要性也会更加凸显。

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

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

相关文章

移动开发(三):使用.NET MAUI打包第一个安卓APK完整过程

目录 一、修改AndroidManifest.xml 配置APP基本信息权限 二、修改项目属性调整输出Android包格式为APK 三、项目发布 四、APP分发 五、总结 之前给大家介绍过使用使用.NET MAUI开发第一个安卓APP,今天给大家介绍如何打包成APK,然后安装到安卓手机正常运行。这里还是沿用…

如何下载ComfyUI开发版

看B站视频&#xff0c;见用绘世可以下载ComfyUI开发版&#xff0c;而我又不想在电脑里放太多东西&#xff0c;于是研究了一下&#xff0c;如何直接从GitHub网站下载。具体步骤看图示。 看压缩包内容&#xff0c;应该直接解压覆盖就可以了&#xff0c;暂未有时间测试。

【JS】ESMoudle机制与符号绑定

前言 JS 模块化有两种方式&#xff0c;分别为&#xff1a;CommonJS 和 ESModule。与 CommonJS 不同&#xff0c;ESModule 是静态模块系统&#xff0c;意味着在代码编译阶段&#xff08;而不是运行时&#xff09;&#xff0c;模块依赖关系就已经被确定。 ESModule 优势 更好地…

传知代码-多示例AI模型实现病理图像分类

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 概述 本文将基于多示例深度学习EPLA模型实现对乳腺癌数据集BreaKHis_v1的分类。EPLA模型是处理组织病理学图像的经典之作。EPLA模型是基于多示例学习来进行了&#xff0c;那么多示例学习模型对处理病理学图像具有…

VCNet论文阅读笔记

VCNet论文阅读笔记 0、基本信息 信息细节英文题目VCNet and Functional Targeted Regularization For Learning Causal Effects of Continuous Treatments翻译VCNet和功能目标正则化用于学习连续处理的因果效应单位芝加哥大学年份2021论文链接[2103.07861] VCNet和功能定向正…

java数据结构----树

二叉查找树 二叉查找树的API设计 put方法的实现思想: public class BinaryTree<Key extends Comparable<Key>, Value> {private Node root;private int N;public int size(){return N;}public void put(Key key, Value value){root put(root,key,value);}public …

k8s 中的 Ingress 简介

一、关于 Ingress Ingress 是 K8s 中的一个 API 对象&#xff0c;用于管理和配置外部对集群内服务的访问。它可定义 HTTP 和 HTTPS 路由规则&#xff0c;将请求从集群外部的负载均衡器引导到相应的服务。Ingress 的灵活性使得我们能够实现高级的应用程序路由、SSL 终端和负载均…

一种新的电子邮件攻击方式:AiTM

新的攻击组利用合作伙伴组织之间的信任关系来绕过多重身份验证。 一种新的攻击方式开始出现&#xff0c;它利用合作伙伴组织之间的信任关系绕过多重身份验证。在一个利用不同组织之间关系的攻击中&#xff0c;攻击者成功地对四家或更多组织进行了商业电子邮件欺诈(BEC)攻击&…

中泰免签,准备去泰国旅游了吗?《泰语翻译通》app支持文本翻译和语音识别翻译,解放双手对着说话就能翻译。

泰国是很多中国游客的热门选择&#xff0c;现在去泰国旅游更方便了&#xff0c;因为泰国对中国免签了。如果你打算去泰国&#xff0c;那么下载一个好用的泰语翻译软件是很有必要的。 简单好用的翻译工具 《泰语翻译通》App就是为泰国旅游设计的&#xff0c;它翻译准确&#x…

Golang | Leetcode Golang题解之第420题强密码检验器

题目&#xff1a; 题解&#xff1a; func strongPasswordChecker(password string) int {hasLower, hasUpper, hasDigit : 0, 0, 0for _, ch : range password {if unicode.IsLower(ch) {hasLower 1} else if unicode.IsUpper(ch) {hasUpper 1} else if unicode.IsDigit(ch)…

Python | Leetcode Python题解之第421题数组中两个数的最大异或值

题目&#xff1a; 题解&#xff1a; class Trie:def __init__(self):# 左子树指向表示 0 的子节点self.left None# 右子树指向表示 1 的子节点self.right Noneclass Solution:def findMaximumXOR(self, nums: List[int]) -> int:# 字典树的根节点root Trie()# 最高位的二…

大模型中常见 loss 函数

loss 函数 首先&#xff0c;Loss 是允许不降到 0 的&#xff0c;模型计算的 loss 最终结果可以接近 0。 可以成为 loss 函数的条件## 常用 loss 以下函数调用基于 Pytorch&#xff0c;头文件导入&#xff1a; import torch.nn as nn 均方差&#xff08;MSE&#xff09; nn.…

基于微信小程序的剧本杀游玩一体化平台

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于微信小程序JavaSpringBootVueMySQL的剧…

详细分析Java中的ObjectMapper基本知识(附Demo)

目录 1. 基本知识2. 基本操作2.1 转换Java对象为JSON2.2 转换JSON为Java对象 3. 拓展 1. 基本知识 ObjectMapper 是 Jackson 数据处理库中的核心类之一&#xff0c;主要用于将 Java 对象转换为 JSON 和将 JSON 转换为 Java 对象 Jackson 是当前最流行的 JSON 处理库之一&…

秒懂Linux之消息队列与信号量(了解)

目录 前言 消息队列原理 信号量理论 信号量原理 IPC资源 前言 消息队列与信息量目前已经不常用了&#xff0c;大家也可以参考共享内存去了解基本原理即可。 消息队列原理 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法 每个数据块都被认为是有一个类型&…

ArcGIS10.2/10.6安装包下载与安装(附详细安装步骤)

相信从事地理专业的小伙伴来说&#xff0c;应该对今天的标题不会陌生。Arcgis是一款很常用的地理信息系统软件&#xff0c;主要用于地理数据的采集、管理、分析和展示。目前比较常见的版本有ArcGIS 10.2和ArcGIS 10.6。 不可否认&#xff0c;Arcgis具有强大的地图制作、空间分…

Linux环境Docker安装Mongodb

Linux环境Docker安装Mongodb 环境要求拉取指定版本镜像创建映射目录&#xff08;相当于数据存放于容器外&#xff0c;容器被删除不会影响数据&#xff09;启动容器 进入mongo命令行为指定db创建新用户查看mongodb的容器id进入命令行查看所有db切换db为指定db创建新用户使用新账…

5、论文阅读:深水下的图像增强

深水下的图像增强 前言介绍贡献UWCNN介绍网络架构残差Residuals块 Blocks网络层密集串联网络深度减少边界伪影网络损失Loss后处理前言 水下场景中,与波长相关的光吸收和散射会降低图像的可见度,导致对比度低和色偏失真。为了解决这个问题,我们提出了一种基于卷积神经网络的…

【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯&#xff0c;你们的点赞收藏是我前进最大的动力&#xff01;&#xff01;7000字长文&#xff0c;希望本文内容能够帮助到你&#xff01; 目录 一&#xff1a;创建线程五种方式 方式一&#xff1a;继承Thread类&#xff0c;…

服务器非法关闭后MySQL服务启动失败

在写这篇文章前&#xff0c;我弄好了&#xff0c;写完之后把成功安装的几个MySQL都删除了&#xff0c;只留了最后测试成功的服务“mysql-test” ,然后点击运行&#xff0c;发现又出现上图的错误。心态炸了。 本以为定位到问题了&#xff0c;但是这个错误让我迷茫了。我只能临时…