java+集成消息rabitmq_SpringBoot集成RabbitMQ

官方说明:http://www.rabbitmq.com/getstarted.html

什么是MQ?

MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。MQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取队列中的消息。

RabbitMQ是MQ的一种。下面详细介绍一下RabbitMQ的基本概念。

1、队列、生产者、消费者

队列是RabbitMQ的内部对象,用于存储消息。生产者(下图中的P)生产消息并投递到队列中,消费者(下图中的C)可以从队列中获取消息并消费。

d818782d8a408999323734a35bcd60b7.png

多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理。

java+集成消息rabitmq_SpringBoot集成RabbitMQ_第1张图片

2、Publish/Subscribe,订阅发布模式,每个通道都会收到消息

java+集成消息rabitmq_SpringBoot集成RabbitMQ_第2张图片

3、Exchange、Binding

刚才我们看到生产者将消息投递到队列中,实际上这在RabbitMQ中这种事情永远都不会发生。实际的情况是,生产者将消息发送到Exchange(交换器,下图中的X),再通过Binding将Exchange与Queue关联起来。

a.Direct exchange,一个exchange和多个queue绑定,会根据绑定的不同routingKey,发送到不同的Queue中

java+集成消息rabitmq_SpringBoot集成RabbitMQ_第3张图片

b.Topic exchange,按模式匹配路由键。模式符号 "#" 表示一个或多个单词,"*" 仅匹配一个单词。

java+集成消息rabitmq_SpringBoot集成RabbitMQ_第4张图片

c.RPC

java+集成消息rabitmq_SpringBoot集成RabbitMQ_第5张图片

4、Exchange Type、Bingding key、routing key

在绑定(Binding)Exchange与Queue的同时,一般会指定一个binding key。在绑定多个Queue到同一个Exchange的时候,这些Binding允许使用相同的binding key。

生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定这个消息的路由规则,生产者就可以在发送消息给Exchange时,通过指定routing key来决定消息流向哪里。

RabbitMQ常用的Exchange Type有三种:fanout、direct、topic。

fanout:把所有发送到该Exchange的消息投递到所有与它绑定的队列中。

direct:把消息投递到那些binding key与routing key完全匹配的队列中。

topic:将消息路由到binding key与routing key模式匹配的队列中。

附上一张RabbitMQ的结构图:

java+集成消息rabitmq_SpringBoot集成RabbitMQ_第6张图片

最后来具体解析一下几个问题:

1、可以自动创建队列,也可以手动创建队列,如果自动创建队列,那么是谁负责创建队列呢?是生产者?还是消费者?

如果队列不存在,当然消费者不会收到任何的消息。但是如果队列不存在,那么生产者发送的消息就会丢失。所以,为了数据不丢失,消费者和生产者都可以创建队列。那么如果创建一个已经存在的队列呢?那么不会有任何的影响。需要注意的是没有任何的影响,也就是说第二次创建如果参数和第一次不一样,那么该操作虽然成功,但是队列属性并不会改变。

队列对于负载均衡的处理是完美的。对于多个消费者来说,RabbitMQ使用轮询的方式均衡的发送给不同的消费者。

2、RabbitMQ的消息确认机制

默认情况下,如果消息已经被某个消费者正确的接收到了,那么该消息就会被从队列中移除。当然也可以让同一个消息发送到很多的消费者。

如果一个队列没有消费者,那么,如果这个队列有数据到达,那么这个数据会被缓存,不会被丢弃。当有消费者时,这个数据会被立即发送到这个消费者,这个数据被消费者正确收到时,这个数据就被从队列中删除。

那么什么是正确收到呢?通过ack。每个消息都要被acknowledged(确认,ack)。我们可以显示的在程序中去ack,也可以自动的ack。如果有数据没有被ack,那么:

RabbitMQ Server会把这个信息发送到下一个消费者。

如果这个app有bug,忘记了ack,那么RabbitMQServer不会再发送数据给它,因为Server认为这个消费者处理能力有限。

而且ack的机制可以起到限流的作用(Benefitto throttling):在消费者处理完成数据后发送ack,甚至在额外的延时后发送ack,将有效的均衡消费者的负载。

使用springboot调用RabbitMQ例子

环境:

apache-tomcat-8.5.15

jdk1.8.0_172

IDEA

搭建好RabbitMQ服务器环境,这点就不在叙述了。

使用RabbitTemplate的convertAndSend发送自定义的类消息的时候要统一类的包路径,不然在序列化的时候要报错,

通过IDEA创建springboot的WEB项目,引入了freemarker和和rabbitmq

创建完后的pom.xml文件为:

4.0.0

com.xuan

springrabbitmq

0.0.1-SNAPSHOT

jar

springrabbitmq

Demo project for Spring Boot

org.springframework.boot

spring-boot-starter-parent

2.0.4.RELEASE

UTF-8

UTF-8

1.8

org.springframework.boot

spring-boot-starter-amqp

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-freemarker

org.springframework.boot

spring-boot-starter-test

test

org.springframework.boot

spring-boot-maven-plugin

配置运行环境

修改application.yml文件,配置freemarker和rabbitmq的相关参数:

# Tomcat

server:

tomcat:

uri-encoding: UTF-8max-threads: 1000min-spare-threads: 30port:8070servlet:

context-path: /rabbitmq

spring:

servlet:

multipart:

max-file-size: 100MB

max-request-size: 100MB

enabled:truefreemarker:

suffix: .html

rabbitmq:

host: localhost

port:5672username: admin

password:123456

virtual-host: /testmq

listener:

simple:

#acknowledge-mode: manual #设置确认模式手工确认

concurrency:3 #消费者最小数量max-concurrency: 10 # 消费者最大数量

如果配置acknowledge-mode为manual则需要在消费消费的地方调用channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);进行确认,不然消息会一直保存在通道中,指定concurrency最小的消费者数量和max-concurrency最大的消费者数量后,是多线程消费消息

这点的rabbitmq配置也可以通过RabbitConfig.java类来配置:

packagecom.xuan.springrabbitmq.config;importorg.springframework.amqp.core.AcknowledgeMode;importorg.springframework.amqp.core.AmqpAdmin;importorg.springframework.amqp.rabbit.annotation.EnableRabbit;importorg.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;importorg.springframework.amqp.rabbit.connection.CachingConnectionFactory;importorg.springframework.amqp.rabbit.connection.ConnectionFactory;importorg.springframework.amqp.rabbit.core.RabbitAdmin;importorg.springframework.amqp.rabbit.core.RabbitTemplate;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;//连接rabbitMQ的基本配置

@Configuration

@EnableRabbitpublic classRabbitConfig {

@BeanpublicConnectionFactory connectionFactory() {

CachingConnectionFactory connectionFactory= new CachingConnectionFactory("localhost");

connectionFactory.setUsername("admin");

connectionFactory.setPassword("123456");

connectionFactory.setPort(5672);

connectionFactory.setVirtualHost("/testmq");returnconnectionFactory;

}

@BeanpublicAmqpAdmin amqpAdmin() {return newRabbitAdmin(connectionFactory());

}

@BeanpublicRabbitTemplate rabbitTemplate() {return newRabbitTemplate(connectionFactory());

}//配置消费者监听的容器

@BeanpublicSimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {

SimpleRabbitListenerContainerFactory factory= newSimpleRabbitListenerContainerFactory();

factory.setConnectionFactory(connectionFactory());

factory.setConcurrentConsumers(3);

factory.setMaxConcurrentConsumers(10);

//factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);//设置确认模式手工确认

returnfactory;

}

}

配置路由和通道

配置最简单的生产者消费者模式ProducerConsumerConfig.java

packagecom.xuan.springrabbitmq.config;importorg.springframework.amqp.core.Queue;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;//生产者消费者模式的配置,包括一个队列和两个对应的消费者

@Configurationpublic classProducerConsumerConfig {

@BeanpublicQueue myQueue() {

Queue queue= new Queue("myqueue");returnqueue;

}

}

配置订阅发布模式PublishSubscribeConfig.java

packagecom.xuan.springrabbitmq.config;importorg.springframework.amqp.core.Binding;importorg.springframework.amqp.core.BindingBuilder;importorg.springframework.amqp.core.FanoutExchange;importorg.springframework.amqp.core.Queue;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;//发布订阅模式的配置,包括两个队列和对应的订阅者,发布者的交换机类型使用fanout(子网广播),两根网线binding用来绑定队列到交换机

@Configurationpublic classPublishSubscribeConfig {

@BeanpublicQueue myQueue1() {

Queue queue= new Queue("queue1");returnqueue;

}

@BeanpublicQueue myQueue2() {

Queue queue= new Queue("queue2");returnqueue;

}

@BeanpublicFanoutExchange fanoutExchange() {

FanoutExchange fanoutExchange= new FanoutExchange("fanout");returnfanoutExchange;

}

@BeanpublicBinding binding1() {

Binding binding=BindingBuilder.bind(myQueue1()).to(fanoutExchange());returnbinding;

}

@BeanpublicBinding binding2() {

Binding binding=BindingBuilder.bind(myQueue2()).to(fanoutExchange());returnbinding;

}

}

配置direct直连模式DirectExchangeConfig.java

packagecom.xuan.springrabbitmq.config;importorg.springframework.amqp.core.Binding;importorg.springframework.amqp.core.BindingBuilder;importorg.springframework.amqp.core.DirectExchange;importorg.springframework.amqp.core.Queue;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;//direct直连模式的交换机配置,包括一个direct交换机,两个队列,三根网线binding

@Configurationpublic classDirectExchangeConfig {

@BeanpublicDirectExchange directExchange() {

DirectExchange directExchange= new DirectExchange("direct");returndirectExchange;

}

@BeanpublicQueue directQueue1() {

Queue queue= new Queue("directqueue1");returnqueue;

}

@BeanpublicQueue directQueue2() {

Queue queue= new Queue("directqueue2");returnqueue;

}//3个binding将交换机和相应队列连起来

@BeanpublicBinding bindingorange() {

Binding binding= BindingBuilder.bind(directQueue1()).to(directExchange()).with("orange");returnbinding;

}

@BeanpublicBinding bindingblack() {

Binding binding= BindingBuilder.bind(directQueue2()).to(directExchange()).with("black");returnbinding;

}

@BeanpublicBinding bindinggreen() {

Binding binding= BindingBuilder.bind(directQueue2()).to(directExchange()).with("green");returnbinding;

}

}

配置topic交换机模型TopicExchangeConfig.java

packagecom.xuan.springrabbitmq.config;importorg.springframework.amqp.core.Binding;importorg.springframework.amqp.core.BindingBuilder;importorg.springframework.amqp.core.Queue;importorg.springframework.amqp.core.TopicExchange;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;//topic交换机模型,需要一个topic交换机,两个队列和三个binding

@Configurationpublic classTopicExchangeConfig {

@BeanpublicTopicExchange topicExchange(){

TopicExchange topicExchange=new TopicExchange("mytopic");returntopicExchange;

}

@BeanpublicQueue topicQueue1() {

Queue queue=new Queue("topicqueue1");returnqueue;

}

@BeanpublicQueue topicQueue2() {

Queue queue=new Queue("topicqueue2");returnqueue;

}//3个binding将交换机和相应队列连起来

@BeanpublicBinding bindingtopic1(){

Binding binding= BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("*.orange.*");//binding key

returnbinding;

}

@BeanpublicBinding bindingtopic2(){

Binding binding= BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("*.*.rabbit");returnbinding;

}

@BeanpublicBinding bindingtopic3(){

Binding binding= BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("lazy.#");//#表示0个或若干个关键字,*表示一个关键字

returnbinding;

}

}

定义发送的消息Mail.java

packagepo;importjava.io.Serializable;public class Mail implementsSerializable {private static final long serialVersionUID = -8140693840257585779L;privateString mailId;privateString country;privateDouble weight;publicMail() {

}public Mail(String mailId, String country, doubleweight) {this.mailId =mailId;this.country =country;this.weight =weight;

}publicString getMailId() {returnmailId;

}public voidsetMailId(String mailId) {this.mailId =mailId;

}publicString getCountry() {returncountry;

}public voidsetCountry(String country) {this.country =country;

}public doublegetWeight() {returnweight;

}public void setWeight(doubleweight) {this.weight =weight;

}

@OverridepublicString toString() {return "Mail [mailId=" + mailId + ", country=" + country + ", weight="

+ weight + "]";

}

}

继承的消息TopicMail.java

packagepo;public class TopicMail extendsMail {

String routingkey;publicString getRoutingkey() {returnroutingkey;

}public voidsetRoutingkey(String routingkey) {this.routingkey =routingkey;

}

@OverridepublicString toString() {return "TopicMail [routingkey=" + routingkey + "]";

}

}

定义发送接口的实现ProducerImpl.java

packagecom.xuan.springrabbitmq.service.impl;importcom.xuan.springrabbitmq.service.Producer;importorg.springframework.amqp.rabbit.core.RabbitTemplate;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;importpo.Mail;

@Transactional

@Service("producer")public class ProducerImpl implementsProducer {

@Autowired

RabbitTemplate rabbitTemplate;public voidsendMail(String queue, Mail mail) {

rabbitTemplate.setQueue(queue);

rabbitTemplate.convertAndSend(queue,mail);

}

}

订阅发布时的发送消息实现PublisherImpl.java

packagecom.xuan.springrabbitmq.service.impl;importpo.Mail;importcom.xuan.springrabbitmq.service.Publisher;importorg.springframework.amqp.rabbit.core.RabbitTemplate;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;

@Service("publisher")public class PublisherImpl implementsPublisher {

@Autowired

RabbitTemplate rabbitTemplate;public voidpublishMail(Mail mail) {

rabbitTemplate.convertAndSend("fanout", "", mail);

}public voidsenddirectMail(Mail mail, String routingkey) {

rabbitTemplate.convertAndSend("direct", routingkey, mail);

}public voidsendtopicMail(Mail mail, String routingkey) {

rabbitTemplate.convertAndSend("mytopic", routingkey, mail);

}

}

消费者的实现代码QueueListener1.java

packagecom.xuan.springrabbitmq.listener;importcom.rabbitmq.client.Channel;importorg.springframework.amqp.core.Message;importorg.springframework.amqp.rabbit.annotation.RabbitHandler;importorg.springframework.amqp.rabbit.annotation.RabbitListener;importorg.springframework.stereotype.Component;importpo.Mail;

@Component

@RabbitListener(queues= "myqueue")public classQueueListener1 {

@RabbitHandlerpublic void displayMail(Mail mail, Channel channel, Message message) throwsException {

System.out.println("队列监听器1号收到消息" +mail.toString());

//channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);//如果需要确认的要调用

}

}

或者QueueListener2.java

packagecom.xuan.springrabbitmq.listener;importorg.springframework.amqp.rabbit.annotation.RabbitListener;importorg.springframework.stereotype.Component;importpo.Mail;

@Componentpublic classQueueListener2 {

@RabbitListener(queues= "myqueue")public void displayMail(Mail mail) throwsException {

System.out.println("队列监听器2号收到消息"+mail.toString());

}

}

其它模式的消费者也是类似的,指定queues 的名称就可以了。

源码位置:https://gitee.com/xuantest/SpringBoot-RabbitMQ

你可能感兴趣的:(java+集成消息rabitmq_SpringBoot集成RabbitMQ)