一.RabbitMQ概述
1.MQ概念
2.MQ优势和缺点
- MQ优势
-MQ缺点
3.MQ使用场景
4.常见MQ产品
5.RabbitMQ概念
二:RabbitMq的java客户端操作
1.RabbitMq快速入门
-
常见两个项目,一个consumer,一个producer
-
导入依赖
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
</dependencies>
- 编写类
- producer类
package com.ayue.producer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ProducerHello {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory=new ConnectionFactory();
//2.设置连接参数
factory.setHost("192.168.242.129");//rabbitmq的ip 默认值localhost
factory.setPort(5672);//端口
factory.setVirtualHost("/itYue");//虚拟机名字
factory.setUsername("ayue");//用户名 默认guest
factory.setPassword("123");//密码 默认guest
//3.创建连接 connection
Connection connection = factory.newConnection();
//4.创建管道channel
Channel channel = connection.createChannel();
//5.创建队列,如果不存在"hello_world"这个队列,自动创建
channel.queueDeclare("hello_world",true,false,false,null);
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1.queue:队列名称
2.durable:是否持久化,mq重启,还在
3.exclusive:
*是否独占,只能有一个消费组监听这个队列
*当connection关闭时,是否删除队列
4.autoDelete:是否自动删除,当没有consumer时,自动删除
5.arguments:参数信息,配置怎么删除
*/
//6.发送信息
/*
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
参数:
1.exchange:交换机名称,简单模式下使用默认交换机--”“
2.routingKey:路由名称,队列名称和路由一样就绑定
3.props:配置信息
4.body:信息数据
*/
String body="hello rabbitmq!";
channel.basicPublish("","hello_world",null,body.getBytes());
//7.释放资源
channel.close();
connection.close();
}
}
- consumer类
package com.ayue.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConsumerHello {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory=new ConnectionFactory();
//2.设置连接参数
factory.setHost("192.168.242.129");//rabbitmq的ip 默认值localhost
factory.setPort(5672);//端口
factory.setVirtualHost("/itYue");//虚拟机名字
factory.setUsername("ayue");//用户名 默认guest
factory.setPassword("123");//密码 默认guest
//3.创建连接 connection
Connection connection = factory.newConnection();
//4.创建管道channel
Channel channel = connection.createChannel();
//5.接收消息
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1.queue:队列名字
2.autoAck:是否自动确认
3.callback:回调对象
*/
DefaultConsumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1.consumerTag:标时
2.envelope:获取一些信息,交换机,路由key....
3.properties:配置信息
4.body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag:"+consumerTag);
System.out.println("exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("body:"+new String(body));
}
};
channel.basicConsume("hello_world",true,consumer);
}
}
2.RabbitMq的工作模式
- WorkQueues(适用于任务过多或过重下可以提高任务处理速度–例如:短信服务部署多个,只需要一个节点发送即可)
- Pub/Sub模式
- RoutingKey模式(交换机模式必须为direct)
- Topic模式
3.Spring整合RabbitMq
3.1.提供者
- 导入依赖
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
- 编写rabbit配置
rabbitmq.host=192.168.242.129
rabbitmq.port=5672
rabbitmq.username=ayue
rabbitmq.password=123
rabbitmq.virtual-host=/itYue
- 编写交换机,队列信息
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
默认交换机类型为direct,名字为:"",路由键为队列的名称
-->
<!--
id: bean的名字
name:队列名字
auto-declare:自动创建
auto—delete:自动删除,最后一个消费者和队列断开连接后,自动删除队列
exclusive:是否独占
durable:是否持久化
-->
<rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~广播;所有队列都能收到消息~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>
<!--定义广播类型交换机;并绑定上述两个队列-->
<rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_fanout_queue_1"/>
<rabbit:binding queue="spring_fanout_queue_2"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>
<rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding pattern="heima.*" queue="spring_topic_queue_star"/>
<rabbit:binding pattern="heima.#" queue="spring_topic_queue_well"/>
<rabbit:binding pattern="itcast.#" queue="spring_topic_queue_well2"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
- 发送消息
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class producerTest {
//1.注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testHello(){
//发送信息 direct
rabbitTemplate.convertAndSend("spring_queue","Hello World Spring.....");
}
@Test
public void fanoutHello(){
//发送信息 fanout
rabbitTemplate.convertAndSend("spring_fanout_exchange","","Hello World Spring.....");
}
}
3.2.消费者
- 导入依赖
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
- 配置rabbitmq信息
rabbitmq.host=192.168.242.129
rabbitmq.port=5672
rabbitmq.username=ayue
rabbitmq.password=123
rabbitmq.virtual-host=/itYue
- 接收信息
package com.yue.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
public class SpringQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
System.out.println(new String(message.getBody()));
}
}
三:RabbitMq高级特性
1.消息可靠投递性
- 步骤
/**
* 确认模式:
* 步骤:
* 1.开启确认模式:connection-factory中开启publisher-confirms="true"
* 2.在rabbitTemplate定义ConfirmCallback回调函数
*/
@Test
public void testConfirm(){
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 相关配置信息
* @param b 是否发送成功
* @param s 发送失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
System.out.println("confirm被执行...");
if (b){
System.out.println("没有错误..");
}
}
});
rabbitTemplate.convertAndSend("spring_fanout_exchange","","Hello World Spring.....");
}
/**
* 回退模式:当发信息给Exchange后,Exchange路由失败时,会执行
* 步骤:
* 1.开启回退模式
* 2,设置ReturnCallBack
* 3.设置Exchange处理信息的模式
* 1.如果信息没有路由到Queue,则丢弃信息(默认)
* 2.如果信息没有路由到Queue,返回给信息发送方设置ReturnCallBack
*/
@Test
public void testReturn(){
//设置交换机处理信息失败模式(返回给生产者)
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
*
* @param message 信息对象
* @param i 错误码
* @param s 错误信息
* @param s1 交换机
* @param s2 路由键
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
}
});
rabbitTemplate.convertAndSend("spring_fanout_exchange","","Hello World Spring.....");
}
2.Consumer Ack机制
- 步骤
package com.yue.rabbitmq.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.logging.Handler;
/**
* Consumer ACK机制:
* 1.设置手动签收 acknowledge="manual"
* 2.实现接口 ChannelAwareMessageListener
* 3.如果消息成功处理,调用Channel的basicAck()签收
* 4.如果消息处理失败,调用Channel的basicNack()拒绝签收,broker重新发送给consumer
*/
@Component
public class AckListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
System.out.println(new String(message.getBody()));
//2.处理业务逻辑
System.out.println("处理业务逻辑");
int i=3/0;//模拟出错
//3.手动签收
channel.basicAck(deliveryTag,true);
} catch (IOException e) {
//4.拒绝签收
/*
第三个参数:requeue:重回队列。如果设置为true,消息重新回到queue,broker重新发送消息给消费端
*/
channel.basicNack(deliveryTag,true,true);
}
}
}
3.消费端限流
- 步骤
package com.yue.rabbitmq.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* Consumer 限流
* 1.确保ack机制为手动确认
* 2.listener-container中配置属性
* 1。perfetch=1,表示消费端每次从mq拉取一条消息来消费,直到手动确认消费完毕后,才会去继续拉取下一条消息
*/
@Component
public class QosListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//1.获取消息
System.out.println(new String(message.getBody()));
//2.处理业务逻辑
//3.签收
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
4.TTL
-
类型
-
配置文件
<rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
<!--设置queue参数-->
<rabbit:queue-arguments>
<!--设置queue过期时间,10s-->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>
</rabbit:queue-arguments>
</rabbit:queue>
- 业务代码
/**
*TTL:过期时间
* 1.队列统一过期(配置文件中设置即可)
*
* 2.消息单独过期
*
* 如果设置了消息和队列的过期,时间短的为准
* 队列过期后,会将队列所有消息移除
* 消息过期,只有消息在队列顶端,才会判断是否过期
*/
@Test
public void testTTL(){
//1发送信息
//rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.aaa","Hello Spring.....");
//2.消息单独过期
//消息后处理对象,设置消息参数信息
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//1.设置message的消息
message.getMessageProperties().setExpiration("5000");//消息是过期时间 ms
return message;
}
};
rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.aaa",
"Hello Spring.....",messagePostProcessor);
}
5.死信队列
- 概念
-
成为死信队列
-
队列如何绑定死信交换机
-
配置文件
<!--
死信队列
1.声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx)
2.声明死信队列(queue_dlx)和交换机(exchange_dlx)
3.正常队列绑定死信交换机
s设置两个参数
* x-dead-letter-exchange:死信交换机
* x-dead-letter-routing-key:发送给死信交换机的routingkey
-->
<!--
1.声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx)
-->
<rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
<!--3.正常队列绑定死信交换机-->
<rabbit:queue-arguments>
<!--3.1 x-dead-letter-exchange:死信交换机-->
<entry key="x-dead-letter-exchange" value="exchange_dlx"/>
<!--3.2 x-dead-letter-routing-key:发送给死信交换机的routingkey-->
<entry key="x-dead-letter-routing-key" value="dlx.aa"/>
<!--4.1 设置队列的过期时间-->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
<!--4.2 设置队列长度限制-->
<entry key="x-max-length" value="10" value-type="java.lang.Integer"/>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="test_exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--
2.声明死信队列(queue_dlx)和交换机(exchange_dlx)
-->
<rabbit:queue id="queue_dlx" name="queue_dlx"></rabbit:queue>
<rabbit:topic-exchange name="exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
- 业务代码
/**
* 发送测试死信消息:
* 1.过期时间
* 2.长度限制
* 3.消息拒收(在消费端实现,拒收时的requeue要设置为false)
*/
@Test
public void testDlx() {
//1.测试过期时间,死信消息
//rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.aaa", "消息来了!");
//2.测试长度限制后,消息死信
/* for (int i=1;i<=15;i++){
rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.aaa", "消息来了!"+i);
}*/
//3.测试消息拒收,死信消息
rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.aaa", "消息来了!");
}
6.延迟队列
- 概念
- 实现思路
- 配置文件
<!--
延迟队列:
1.声明正常的队列(order_queue)和交换机(order_exchange),并设置队列时间为10s
2.声明死信队列(order_queue_dlx)和交换机(order_exchange_dlx)
3.正常队列绑定死信交换机
s设置两个参数
* x-dead-letter-exchange:死信交换机
* x-dead-letter-routing-key:发送给死信交换机的routingkey
-->
<!--
1.声明正常的队列(order_queue)和交换机(order_exchange)
-->
<rabbit:queue name="order_queue" id="order_queue">
<!--3.正常队列绑定死信交换机-->
<rabbit:queue-arguments>
<!--3.1 x-dead-letter-exchange:死信交换机-->
<entry key="x-dead-letter-exchange" value="order_exchange_dlx"/>
<!--3.2 x-dead-letter-routing-key:发送给死信交换机的routingkey-->
<entry key="x-dead-letter-routing-key" value="dlx.order.aa"/>
<!--4.1 设置队列的过期时间-->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="order_exchange">
<rabbit:bindings>
<rabbit:binding pattern="order.#" queue="order_queue"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--
2.声明死信队列(order_queue_dlx)和交换机(order_exchange_dlx)
-->
<rabbit:queue id="order_queue_dlx" name="order_queue_dlx"></rabbit:queue>
<rabbit:topic-exchange name="order_exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="dlx.order.#" queue="order_queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
- 业务代码
/**
* 延迟队列
*/
@Test
public void testDelay(){
//1.发送订单消息,将来实在订单系统中下单成功发送
rabbitTemplate.convertAndSend("order_exchange","order.message","订单信息:id=1,price=200");
}
四.RabbitMq应用问题
1.消息可靠性保障–消息补偿机制
2.消息幂等性保障