MQ全称Message Queue(消息队列),是在消息的传输过程中保存消息的容器,多用于分布式系统之间的通信。
(1)应用解耦
使用MQ使得应用间解耦,提升容错性和可维护性
(2)任务异步处理
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
(3)削峰填谷
当外部请求突然增加的时候,请求会到达MQ中间件,不会直接到达下游程序。下游的程序会根据自身的处理能力,从MQ中间件中获取数据处理,不会因为服务器(数据库)压力过大造成宕机。
(4)AMQP与JMS
AMQP,即 Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,遵循此协议,不收客户端和中间件产品和开发语言限制。
JMS 即 Java 消息服务(JavaMessage Service)应用程序接口,是一个 Java 平台中关于面向消息中间件的API。JMS 是 JavaEE 规范中的一种,类比JDBC
AMQP与JMS区别
JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
JMS规定了两种消息模式;而AMQP的消息模式更加丰富
RabbitMQ基础架构图
RabbitMQ 中的相关概念:
Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类
似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务
时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
Connection:publisher/consumer 和 broker 之间的 TCP 连接
Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP
Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,
如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含
了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。
Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销
Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发
消息到queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and
fanout (multicast)
Queue:消息最终被送到这里等待 consumer 取走
Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息
被保存到 exchange 中的查询表中,用于 message 的分发依据
RabbitMQ提供6中模式:简单模式(一个producer与一个Consumer)、work模式(一个Producer与两个及以上的Consumer)、发布与订阅模式、Routing路由模式、Topocs主题模式、RPC远程调用模式。
1、连接rabbitMQ服务器,创建连接
package com.zhb.utils;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConnectionUtil {
public static Connection getConnection() throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址(rabbitMQ虚拟机地址)
connectionFactory.setHost("192.168.56.114");
//连接端口:默认为 5672
connectionFactory.setPort(5672);
//rabbitMQ中虚拟主机名称
connectionFactory.setVirtualHost("/zhb");
//连接用户名:默认为guest
connectionFactory.setUsername("zhb");
//连接密码:默认为guest
connectionFactory.setPassword("zhb");
return connectionFactory.newConnection();
}
}
2、Producer
3、Consumer
工作模式与简单模式类似,都不需要声明交换机,只是多了一个或几个Consumer同时处理数据
发布订阅模式: 1、每个消费者监听自己的队列。 2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。(通俗讲:每一个与交换机绑定的队列都可获得消息)
1、Producer
2、Consumer
package com.zhb.rabbitmq;
import com.rabbitmq.client.*;
import com.zhb.utils.ConnectionUtil;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.FANOUT_NAME, BuiltinExchangeType.FANOUT);
//创建队列
channel.queueDeclare(Producer.FANOUT_QUEUE_1,true,false,false,null);
//队列绑定交换机
channel.queueBind(Producer.FANOUT_QUEUE_1,Producer.FANOUT_NAME,"");
Consumer consumer = new DefaultConsumer(channel){
/**
* No-op implementation of {@link Consumer#handleDelivery}.
*
* @param consumerTag 消息者标签,在channel.basicConsume时候可以指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重
* 传标志(收到消息失败后是否需要重新发送)
* @param properties
* @param body 接受到的消息
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
}
};
channel.basicConsume(Producer.FANOUT_QUEUE_1,true,consumer);
}
}
在Consumer中绑定队列和交换机路由也为空
路由模式有通配符模式类似,只是路由模式指定具体的路由名称,然而通配符模式由“*”指定匹配一个词、“#”指定匹配一个或者多个词。详情可以参照通配符模式代码。
Producer
package com.zhb.rabbitmq;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.zhb.utils.ConnectionUtil;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static final String TOPIC_EXCHANGE = "topic_exchange";
public static final String TOPIC_QUEUE_1 = "topic_queue_1";
public static final String TOPIC_QUEUE_2 = "topic_queue_2";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
String message = "topic模式:routing key = item.insert";
channel.basicPublish(TOPIC_EXCHANGE,"item.insert",null,message.getBytes());
System.out.println("消息已发送" + message);
message = "topic模式:routing key = item.update";
channel.basicPublish(TOPIC_EXCHANGE,"item.update",null,message.getBytes());
System.out.println("消息已发送" + message);
message = "topic模式:routing key = item.delete.123";
channel.basicPublish(TOPIC_EXCHANGE,"item.delete.123",null,message.getBytes());
System.out.println("消息已发送" + message);
channel.close();
connection.close();
}
}
Consumer
package com.zhb.rabbitmq;
import com.rabbitmq.client.*;
import com.zhb.utils.ConnectionUtil;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class TopicConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(Producer.TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
channel.queueDeclare(Producer.TOPIC_QUEUE_1,true,false,false,null);
channel.queueBind(Producer.TOPIC_QUEUE_1,Producer.TOPIC_EXCHANGE,"item.*");
Consumer consumer = new DefaultConsumer(channel){
/**
* No-op implementation of {@link Consumer#handleDelivery}.
*
* @param consumerTag
* @param envelope
* @param properties
* @param body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
}
};
//监听消息
channel.basicConsume(Producer.TOPIC_QUEUE_1,true,consumer);
}
}
package com.zhb.rabbitmq;
import com.rabbitmq.client.*;
import com.zhb.utils.ConnectionUtil;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class TopicConsumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(Producer.TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
channel.queueDeclare(Producer.TOPIC_QUEUE_2,true,false,false,null);
channel.queueBind(Producer.TOPIC_QUEUE_2,Producer.TOPIC_EXCHANGE,"item.#");
Consumer consumer = new DefaultConsumer(channel){
/**
* No-op implementation of {@link Consumer#handleDelivery}.
*
* @param consumerTag
* @param envelope
* @param properties
* @param body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
}
};
channel.basicConsume(Producer.TOPIC_QUEUE_2,true,consumer);
}
}
1、添加依赖
4.0.0
com.zhb
spring_rabbitmq
1.0-SNAPSHOT
8
8
org.springframework
spring-context
5.1.7.RELEASE
org.springframework.amqp
spring-rabbit
2.1.8.RELEASE
junit
junit
4.12
org.springframework
spring-test
5.1.7.RELEASE
spring整合rabbitmq是基于全配置文件。
需要连接rabbitmq的properties文件:
rabbitmq.host=192.168.56.114
rabbitmq.port=5672
rabbitmq.username=zhb
rabbitmq.password=zhb
rabbitmq.virtual-host=/zhb
2、在Producer的spring-rabbitmq.xml文件中:
(1)首先需要加载配置文件(连接rabbitmq的基本数据)。
(2)创建ConnectionFactory,相当于在代码中的生成连接的工具类。
(3)定义管理交换机和队列(相当于把声明交换机和声明队列交给了容器)
(4)声明队列:简单模式 => 不需要绑定交换机,使用rabbitmq默认的交换机
其余模式 => 需要绑定交换机(根据交换机的类型选择性绑定)
auto-declare="true" // 当rabbitmq中不存在该队列的时候,自动创建
(5)声明交换机:用于绑定队列
auto-declare="true" // 当rabbitmq中不存在该交换机的时候自动创建
(6)定义RabbitTemolate对象操作,Producer可以在代码中发送消息。
3、spring整合rabbitmq的Consumer
(1)依赖于Producer一样
(2)连接rabbitmq的配置文件也与Producer一样
4、在Consumer中的spring-rabbitmq.xml文件中
(1)首先加载连接rabbitmq的配置文件
(2)根据配置文件创建ConnectionFactory
(3)在Consumer工程中,需要对在Producer工程中产生的每一个交换机创建一个监听类,用于 接收Producer发送的数据
import org.springframework.amqp.core.MessageListener;
import java.io.UnsupportedEncodingException;
public class SpringFanoutQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
try {
String msg = new String(message.getBody(),"utf-8");
System.out.println("接受路由器名称:" + message.getMessageProperties().getReceivedExchange());
System.out.println("路由建为:" + message.getMessageProperties().getReceivedRoutingKey());
System.out.println("队列名称:" + message.getMessageProperties().getConsumerQueue());
System.out.println("接受到的消息:" + msg);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
(4)将监听类创建类权限交给spring容器
(5)在rabbitmq的监听容器内,配置每个交换机绑定的队列
1、springboot是基于注解的方式整合rabbitmq
2、Producer工程添加依赖
4.0.0
com.zhb
springboot-rabbitmq-producer
1.0-SNAPSHOT
8
8
org.springframework.boot
spring-boot-starter-parent
2.1.4.RELEASE
org.springframework.boot
spring-boot-starter-amqp
org.springframework.boot
spring-boot-starter-test
3、Producer工程创建application.yml文件:里面配置连接rabbitmq的基本数据
spring:
rabbitmq:
host: 192.168.56.114
virtual-host: /zhb
port: 5672
username: zhb
password: zhb
4、springboot是基于注解的方式整合rabbitmq,所以不需要使用xml配置文件(Producer工程)
(1)创建一个类:使用@Configration注解标注 => 该类为一个配置类
(2)在该类中对于每个方法使用@Bean注解,依次生成交换机、队列、绑定交换机与队列
package com.zhb.rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
public static final String TOPIC_EXCHANGE_NAME = "springboot_topic_exchange";
public static final String TOPIC_QUEUE_NAME = "springboot_topic_queue";
//声明交换机,交换机名称默认为方法名
@Bean
public Exchange topicExchange(){
return ExchangeBuilder.topicExchange(TOPIC_EXCHANGE_NAME).durable(true).build();
}
//声明队列
@Bean
public Queue topicQueue(){
return QueueBuilder.durable(TOPIC_QUEUE_NAME).build();
}
//绑定交换机与队列
@Bean
public Binding exchangeAndQueue(@Qualifier("topicQueue") Queue queue,
@Qualifier("topicExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("zhb.#").noargs();
}
}
(3)在声明交换机的时候,可以根据需要生成不同模式的交换机(如果需要声明多个交换机重新创建一个配置类即可,一个交换机可以绑定多个队列)
(4)使用RabbitTemplate发送消息。(RabbitTemplate类有springboot自动生成了,使用时只需一引入即可)。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootRabbitMQTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void springbootTest(){
rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE_NAME,"zhb.insert","路由键:zhb.insert");
rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE_NAME,"zhb.update.abc","路由键:zhb.update.abc");
rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE_NAME,"zhb.delete","路由键:zhb.delete");
}
}
5、Consumer工程依赖于Producer依赖一致
6、在Consumer工程中创建application.yml文件用于连接rabbitmq
7、在Consumer工程中只需要创建一个类来监听队列的消息即可,使用@RabbitListener注解
package com.zhb.rabbitmq.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class ConsumerListener {
/**
* 监听某个队列的消息
* @param message producer发送的消息
*/
@RabbitListener(queues = "springboot_topic_queue")
public void topicListener(String message){
System.out.println(message);
}
}
(1)在使用Rabbitmq的时候,作为消息的发送放希望杜绝任何消息的丢失或者投递失败的场景,Rabbitmq提供了两种方式用来控制消息的投递可靠性模式。
消息从producer到exchange失败会返回一个confirmCallback。
消息从echange到consumer投递失败会返回一个renturnCallback。
(2)在producer工程的配置文件中开启确认模式 publisher-confirms = ‘true’ => confirm 确认模式
(3)在producer工程配置文件中开启退回模式 publisher-returns="true" => return 退回模式
spring:
rabbitmq:
host: 192.168.56.114
virtual-host: /zhb
port: 5672
username: zhb
password: zhb
publisher-confirms: true
publisher-returns: true
/**
* 消息可靠性的投递 确认模式
* 从producer到exchange失败
*/
@Test
public void confirmTest(){
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* Confirmation callback.
*
* @param correlationData correlation data for the callback.
* @param ack true for ack, false for nack
* @param cause An optional cause, for nack, when available, otherwise null.
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("confirm方法被执行了。。。");
if(ack){
System.out.println("接受成功");
}else {
System.out.println("接受失败" + cause);
}
}
});
rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE_NAME,"zhb.insert","confirm确认消息可靠投递");
}
/**
* 消息投递可靠性测试 退回模式
* 从exchange到consumer
*/
@Test
public void returnTest(){
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
*
* @param message 消息对象
* @param replyCode 错误码
* @param replyText 错误信息
* @param exchange 交换机
* @param routingKey 路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
//此方法在发生错误的时候执行,可以处理
System.out.println("发送消息失败:从exchange到consumer");
}
});
rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE_NAME,"zhb.update","从exchange到consumer成功");
}
1、Consumer Ack
ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
有三种确认方式
其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从
RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,在业务处理成功后调用channel.basicAck(),手
动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。
2、springboot整合ACK,在Consumer的application.yml中配置
spring:
rabbitmq:
host: 192.168.56.114
port: 5672
virtual-host: /zhb
username: zhb
password: zhb
listener:
simple:
acknowledge-mode: manual
direct:
acknowledge-mode: manual
3、创建一个监听类实现ChannelAwareMessageListener,在重新方法的时候,在方法上面使用@RabbitListener注解,用于监听队列消息。
package com.zhb.rabbitmq.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class ACKListener implements ChannelAwareMessageListener {
/**
* Callback for processing a received Rabbit message.
* Implementors are supposed to process the given Message,
* typically sending reply messages through the given Session.
*
* @param message the received AMQP message (never null
)
* @param channel the underlying Rabbit Channel (never null
)
* @throws Exception Any.
*/
@Override
@RabbitListener(queues = "springboot_topic_queue")
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.println(new String(message.getBody()));
System.out.println("处理业务逻辑");
int i = 3/0;
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
//e.printStackTrace();
//第三个参数:代表获取到异常,消息重新回到队列,直到正常
channel.basicNack(deliveryTag,true,true);
}
}
}
1、代码实现过期队列的创建
@Bean
public Queue ttlQueue(){
HashMap ags = new HashMap<>();
ags.put("x-message-ttl",5000);
return new Queue("ttlQueue",true,false,false,ags);
}
2、将该队列与交换机绑定
//绑定交换机与队列
@Bean
public Binding exchangeAndTtlQueue(@Qualifier("ttlQueue") Queue queue,
@Qualifier("ttlExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("zhb.#").noargs();
}
完成
spring:
rabbitmq:
host: 192.168.56.114
port: 5672
virtual-host: /zhb
username: zhb
password: zhb
listener:
simple:
acknowledge-mode: manual
prefetch: 1 # 服务每秒从队列中获取一个消息
direct:
acknowledge-mode: manual
这样设置代表该工程下的所有队列都进行了限流