RabbitMQ: 消息队列,是在消息的传输过程中保存消息的容器。
它接受并转发消息。 你可以把它想象成一个邮局:当你把你想要邮寄的邮件放在邮箱里时, 您可以确定,邮递员最终会将邮件递送给您的收件人。 在这个类比中,RabbitMQ是一个邮政信箱,一个邮局和一个信件载体。
1、MQ主要用途
1、流量消峰
在双十一12点时,用户都疯狂的进行购物,进行下单。原本只能处理5000w的订单,但是在双十一要处理高于5000w的订单。这个时候,我们就可以使用MQ来做缓冲,我们把这一时刻的订单,分散到其他时间来进行处理,这样就可以使得每个用户都能下单成功。
2、应用解耦
以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。
订单系统需要调用 库存系统、物流系统、支付系统。但是这3个系统,如果任何一个出现故障,都会使订单系统无法完成。当我们在订单和另外3个系统中加入MQ来进行缓存,库存系统、物流系统、支付系统中任何一个出现错误,我们可以把要处理的信息先放在消息队列中,等系统恢复正常后,继续执行,不会影响程序的正常执行,提示了可用性。
3、异步处理
未使用MQ:订单系统要执行完另外3个系统后才能返回用户数据
使用MQ:用户不用等另外3个系统执行完,就可以返回给用户信息。
2、四大核心概念
1、生产者 : producer
产生数据发送消息的程序是生产者
2、交换机: Exchange
首先接收来自于生产者的消息,message到达的第一站,根据分发规则,将消息推送到队列中。交换机要将消息推送到特定的队列或者推送到多个队列,亦或者吧消息丢失。
3、队列: Queue
消息被推送到的地方,在这里等待消费者取走
4、消费者:
处理消息,从队列中拿到消息。
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
<version>5.8.0version>
dependency>
/**
* 这个类处理的是生产者的处理
*/
public class Producer {
// 队列名称
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception{
// 1、创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.92.134");
factory.setUsername("zww");
factory.setPassword("自己的密码");
// 2、创建连接
Connection connection = factory.newConnection();
// 3、获取信道
Channel channel = connection.createChannel();
//channel 实现了自动 close 接口 自动
/**
* queueDeclare(String queue, boolean durable, boolean exclusive,
* boolean autoDelete, Map arguments)
* 生成一个队列
* 1.队列名称
* 2.队列里面的消息是否持久化,默认消息存储在内存中;不持久化的话,mq重启消息就不存在了
* 3.独占:该队列是否只供一个消费者进行消费 是否进行共享 true 可以多个消费者消费
* 当Connection关闭时,是否删除队列
* 4.是否自动删除
* 当没有Connection时,最后一个消费者端开连接以后,该队列是否自动删除 true 自动删除
* 5.其他参数
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String message="hello world";
/**
* basicPublish(String exchange, String routingKey, AMQP.BasicProperties props, byte[] body)
* 发送一个消息
* 1.发送到那个交换机
* 2.路由的 key 是哪个
* 3.其他的参数信息
* 4.发送消息的消息体
*/
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("消息发送完毕");
// 释放资源
channel.close();
connection.close();
}
}
/**
* 消费者,用来接收消息的
*/
public class Consumer {
// 队列名称
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception{
// 1、创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.92.134");
factory.setUsername("zww");
factory.setPassword("njgzydzm9339");
// 2、创建连接
Connection connection = factory.newConnection();
// 3、获取信道
Channel channel = connection.createChannel();
// 4、推送的消息如何进行消费的接口回调
/**
* (String consumerTag, Delivery message)
*/
DeliverCallback deliverCallback=(consumerTag, delivery)->{
String message= new String(delivery.getBody());
System.out.println(message);
};
//取消消费的一个回调接口 如在消费的时候队列被删除掉了
CancelCallback cancelCallback=(consumerTag)->{
System.out.println("消息消费被中断");
};
/**
* basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback)
* 消费者消费消息
* 1.消费哪个队列,队列名称
* 2.消费成功之后是否要自动应答 true 代表自动应答 false 手动应答
* 3.消费者成功消费的回调
* 4、消费者未成功消费的回调处理
*/
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
}
}
Work Queues官网
应用场景: 轮询来进行处理消息,减小压力。对于任务过重或者任务较多情况使用工作队列可以提高任务处理的速度。
1、首先我们创建一个创建工程,返回Channel的一个工具类
/**
* 这个是连接工厂创建信道工具类
*/
public class RabbitMqUtils {
public static Channel getChannel() throws Exception{
//创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.92.134");
factory.setUsername("zww");
factory.setPassword("123456");
// 创建连接
Connection connection = factory.newConnection();
// 获取信道
Channel channel = connection.createChannel();
return channel;
}
}
2、消费者1
/**
* 这是一个工作线程的处理(相当于消费者这个是消费者01)
*/
public class Worker01 {
// 队列名称
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception{
// 1、初始化,获取信道
Channel channel = RabbitMqUtils.getChannel();
// 2、收消息的处理
DeliverCallback deliverCallback = (s, message) -> System.out.println("接收到的消息为"+new String(message.getBody()));
// 3、接收被取消的处理
CancelCallback cancelCallback = con -> System.out.println("消息被取消回调处理了"+con );
System.out.println("C1等待处理");
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback );
}
}
2、消费者2
/**
* 这个是消费者02的处理
*/
public class Worker02 {
// 队列名称
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception{
// 初始化
Channel channel = RabbitMqUtils.getChannel();
// 接收消息的处理
DeliverCallback deliverCallback = (s, message) -> System.out.println("接收到的消息为"+new String(message.getBody()));
// 接收被取消的处理
CancelCallback cancelCallback = con -> System.out.println("消息被取消回调处理了"+con );
System.out.println("C2等待处理的过程");
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback );
}
}
3、生产者:
/**
* 生产者的处理
*/
public class Task01 {
// 队列名称
private static final String QUEUE_NAME = "hello";
// 发送大量消息
public static void main(String[] args) throws Exception{
// 工具类,获取信道
Channel channel = RabbitMqUtils.getChannel();
/**
* 生成一个队列
* 1.队列名称
* 2.队列里面的消息是否持久化 默认消息存储在内存中
* 3.该队列是否只供一个消费者进行消费 是否进行共享 true 可以多个消费者消费
* 4.是否自动删除 最后一个消费者端开连接以后 该队列是否自动删除 true 自动删除
* 5.其他参数
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 发送10条消息
for (int i = 0; i < 10; i++) {
String message = "消息"+i;
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("发送消息完成:"+message);
}
}
}
4、运行结果
我们要先运行两个消费者,让两个消费者先处于等待的状态,然后在运行生产者,这样才可以看到运行的结果。
由此可见,C1和C2两个消费者是在轮询进行消费的处理。
1、工具类RabbitMqUtils(看上文)
2、生产者(设置交换机类型,绑定队列)
public class PubSub {
public static void main(String[] args) throws Exception{
// 1、获取到Channel
Channel channel = RabbitMqUtils.getChannel();
// 2、创建交换机
/**
1、String exchange, 交换机名称
2、BuiltinExchangeType type, 交换机类型:
DIRECT("direct"): 定向方式
FANOUT("fanout"): 广播方式
TOPIC("topic"): 通配符
HEADERS("headers"):参数匹配
3、boolean durable: 是否持久化
4、boolean autoDelete: 自动删除
5、boolean internal: 内部使用 一般是false
5、Map arguments:参数
*/
String exchangeName = "test_fanout";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);
// 3、创建队列
String queueName1 = "test_queue1";
String queueName2 = "test_queue2";
channel.queueDeclare(queueName1,true,false,false,null);
channel.queueDeclare(queueName2,true,false,false,null);
// 4、绑定队列和交换机
/**
* queueBind(String queue, String exchange, String routingKey, Map arguments)
* 1、队列名称
* 2、交换机名称
* 3、绑定规则,路由key。如果交换机类型是FANOUT,设置为空就行
* 4、
*/
channel.queueBind(queueName1,exchangeName,"");
channel.queueBind(queueName2,exchangeName,"");
// 5、发送消息
String body = "日志信息: update()";
System.out.println("发送了消息:"+body);
channel.basicPublish(exchangeName,"",null,body.getBytes());
// 6、释放资源
channel.close();
}
}
3、消费者(指定队列)
/**
* 消费者的处理过程,消费者需要绑定对应的队列名就可以了
*/
public class PubSubConsumer1 {
private static final String TASK_QUEUE_NAME_1 = "test_queue1";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
// 接收消息
DeliverCallback deliverCallback = (s, delivery) -> {
System.out.println("队列1打印日志:"+new String(delivery.getBody()));
// 设置不公平分发处理
int prefetchCount = 2;
channel.basicQos(prefetchCount); // 设置的值超过1,那么就是预取值的处理了
// 采用手动应答
/**
* 1.消息标记 tag
* 2.是否批量应答未应答消息
*/
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
boolean autoAck = false;
channel.basicConsume(TASK_QUEUE_NAME_1, autoAck, deliverCallback, s -> System.out.println(s+"消费者取消接口回调处理"));
}
}
/**
* 消费者的处理过程,消费者需要绑定对应的队列名就可以了
*/
public class PubSubConsumer2 {
private static final String TASK_QUEUE_NAME_2 = "test_queue2";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
// 接收消息
DeliverCallback deliverCallback = (s, delivery) -> {
System.out.println("队列2保存数据库::"+new String(delivery.getBody()));
// 设置不公平分发处理
int prefetchCount = 2;
channel.basicQos(prefetchCount); // 设置的值超过1,那么就是预取值的处理了
// 采用手动应答
/**
* 1.消息标记 tag
* 2.是否批量应答未应答消息
*/
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
boolean autoAck = false;
channel.basicConsume(TASK_QUEUE_NAME_2, autoAck, deliverCallback, s -> System.out.println(s+"消费者取消接口回调处理"));
}
}
4、运行结果
Routing路由模式
注意:
1、交换机类型要设置成: BuiltinExchangeType.DIRECT
2、生产者在产生消息的时候,发送消息的rotingkey和 交换机与队列的key一样,那么才能发到对应的交换机上
1、生产者
/**
* Routing模式中: 发送消息的rotingkey和 交换机与队列的key一样,那么才能发到对应的交换机上
*/
public class Producer_Routing {
public static void main(String[] args) throws Exception{
// 1、获取到Channel
Channel channel = RabbitMqUtils.getChannel();
// 2、创建交换机
/**
1、String exchange, 交换机名称
2、BuiltinExchangeType type, 交换机类型:
DIRECT("direct"): 定向方式
FANOUT("fanout"): 广播方式
TOPIC("topic"): 通配符
HEADERS("headers"):参数匹配
3、boolean durable: 是否持久化
4、boolean autoDelete: 自动删除
5、boolean internal: 内部使用 一般是false
5、Map arguments:参数
*/
String exchangeName = "test_direct";
// 使用DIRECT的类型
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true,false,false,null);
// 3、创建队列
String queueName1 = "test_direct_queue1";
String queueName2 = "test_direct_queue2";
channel.queueDeclare(queueName1,true,false,false,null);
channel.queueDeclare(queueName2,true,false,false,null);
// 4、绑定队列和交换机
/**
* queueBind(String queue, String exchange, String routingKey, Map arguments)
* 1、队列名称
* 2、交换机名称
* 3、绑定规则,路由key。如果交换机类型是FANOUT,设置为空就行
* 4、
*/
// 第一个queue绑定 error级别的
channel.queueBind(queueName1,exchangeName,"error");
channel.queueBind(queueName2,exchangeName,"error");
channel.queueBind(queueName2,exchangeName,"info");
channel.queueBind(queueName2,exchangeName,"warning");
// 5、发送消息
String body = "日志信息: error级别的()";
System.out.println("发送了消息:"+body);
// channel.basicPublish(exchangeName,"info",null,body.getBytes());
// error级别两个都能收到
channel.basicPublish(exchangeName,"error",null,body.getBytes());
// 6、释放资源
channel.close();
}
}
2、消费者
/**
* 消费者的处理过程,消费者需要绑定对应的队列名就可以了
*/
public class RoutingConsumer1 {
private static final String TASK_QUEUE_NAME_1 = "test_direct_queue1";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
// 接收消息
DeliverCallback deliverCallback = (s, delivery) -> {
System.out.println("队列1打印日志:"+new String(delivery.getBody()));
// 设置不公平分发处理
int prefetchCount = 2;
channel.basicQos(prefetchCount); // 设置的值超过1,那么就是预取值的处理了
// 采用手动应答
/**
* 1.消息标记 tag
* 2.是否批量应答未应答消息
*/
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
boolean autoAck = false;
channel.basicConsume(TASK_QUEUE_NAME_1, autoAck, deliverCallback, s -> System.out.println(s+"消费者取消接口回调处理"));
}
}
/**
* 消费者的处理过程,消费者需要绑定对应的队列名就可以了
*/
public class RoutingConsumer2 {
private static final String TASK_QUEUE_NAME_1 = "test_direct_queue2";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
// 接收消息
DeliverCallback deliverCallback = (s, delivery) -> {
System.out.println("队列2存入数据库:"+new String(delivery.getBody()));
// 设置不公平分发处理
int prefetchCount = 2;
channel.basicQos(prefetchCount); // 设置的值超过1,那么就是预取值的处理了
// 采用手动应答
/**
* 1.消息标记 tag
* 2.是否批量应答未应答消息
*/
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
boolean autoAck = false;
channel.basicConsume(TASK_QUEUE_NAME_1, autoAck, deliverCallback, s -> System.out.println(s+"消费者取消接口回调处理"));
}
}
3、运行结果
info级别:一个队列可以收到,一个队列不能收到
Topices通配符模式
通配符:
1、生产者
/**
* Topics:使用的类型 BuiltinExchangeType.TOPIC
*/
public class Producer_Topics {
public static void main(String[] args) throws Exception{
// 1、获取到Channel
Channel channel = RabbitMqUtils.getChannel();
// 2、创建交换机
/**
1、String exchange, 交换机名称
2、BuiltinExchangeType type, 交换机类型:
DIRECT("direct"): 定向方式
FANOUT("fanout"): 广播方式
TOPIC("topic"): 通配符
HEADERS("headers"):参数匹配
3、boolean durable: 是否持久化
4、boolean autoDelete: 自动删除
5、boolean internal: 内部使用 一般是false
5、Map arguments:参数
*/
String exchangeName = "test_topic";
// 使用DIRECT的类型
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);
// 3、创建队列
String queueName1 = "test_topic_queue1";
String queueName2 = "test_topic_queue2";
channel.queueDeclare(queueName1,true,false,false,null);
channel.queueDeclare(queueName2,true,false,false,null);
// 4、绑定队列和交换机
/**
* queueBind(String queue, String exchange, String routingKey, Map arguments)
* 1、队列名称
* 2、交换机名称
* 3、绑定规则,路由key。如果交换机类型是FANOUT,设置为空就行
* 4、
*/
// 第一个queue绑定 error级别的
channel.queueBind(queueName1,exchangeName,"#.error");
channel.queueBind(queueName1,exchangeName,"order.*");
channel.queueBind(queueName2,exchangeName,"*.*");
// 5、发送消息
String body = "日志信息: 发的是 order.星的";
System.out.println("发送了消息:"+body);
// error级别两个都能收到
channel.basicPublish(exchangeName,"order哈.星的",null,body.getBytes());
// 6、释放资源
channel.close();
}
}
2、消费者
/**
* 消费者的处理过程,消费者需要绑定对应的队列名就可以了
*/
public class TopicConsumer1 {
private static final String TASK_QUEUE_NAME_1 = "test_topic_queue1";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
// 接收消息
DeliverCallback deliverCallback = (s, delivery) -> {
System.out.println("队列1的,#.error 和order.*:"+new String(delivery.getBody()));
// 设置不公平分发处理
int prefetchCount = 2;
channel.basicQos(prefetchCount); // 设置的值超过1,那么就是预取值的处理了
// 采用手动应答
/**
* 1.消息标记 tag
* 2.是否批量应答未应答消息
*/
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
boolean autoAck = false;
channel.basicConsume(TASK_QUEUE_NAME_1, autoAck, deliverCallback, s -> System.out.println(s+"消费者取消接口回调处理"));
}
}
/**
* 消费者的处理过程,消费者需要绑定对应的队列名就可以了
*/
public class TopicConsumer2 {
private static final String TASK_QUEUE_NAME_1 = "test_topic_queue2";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
// 接收消息
DeliverCallback deliverCallback = (s, delivery) -> {
System.out.println("队列2 *.*的:"+new String(delivery.getBody()));
// 设置不公平分发处理
int prefetchCount = 2;
channel.basicQos(prefetchCount); // 设置的值超过1,那么就是预取值的处理了
// 采用手动应答
/**
* 1.消息标记 tag
* 2.是否批量应答未应答消息
*/
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
boolean autoAck = false;
channel.basicConsume(TASK_QUEUE_NAME_1, autoAck, deliverCallback, s -> System.out.println(s+"消费者取消接口回调处理"));
}
}
3、运行结果
发送的key为: “order哈.星的”,按照道理,*.*的是可以接收到的,另外一个消费者是不能接收到的。
**1、依赖添加
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
dependency>
dependencies>
2、添加配置文件
spring:
rabbitmq:
host: 192.168.92.134
port: 5672
username: mycat
password: 123456
virtual-host: /
3、编写生产者的处理(配置类)
@Configuration
public class RabbitMQConfig {
public static final String QUEUE_NAME = "boot_queue";
public static final String EXCHANGE_NAME="boot_topic_exchange";
// 1、交换机的配置
@Bean("bootExchange")
public Exchange exchange(){
//durable(true) 持久化,mq重启之后交换机还在
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
// 2、队列的配置
@Bean("bootQueue")
public Queue bootQueue(){
return QueueBuilder.durable(QUEUE_NAME).build();
}
// 3、绑定关系 Binging
/**
* 1、知道哪个队列
* 2、知道哪个交换机
* 3、设置对应的 routing key
*/
@Bean
public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue,@Qualifier("bootExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
}
}
启动类:
@SpringBootApplication
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class,args);
}
}
4、进行生产者的测试处理
@SpringBootTest
@RunWith(SpringRunner.class)
public class MQTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSend(){
/**
* 交换机的名称,key,消息
*/
rabbitTemplate.convertAndSend(EXCHANGE_NAME,"boot.hhh","传的消息啊!!!!!!");
}
}
5、进行消费者的处理(消费者监听类)
@Component
public class RabbitListener {
@org.springframework.amqp.rabbit.annotation.RabbitListener(queues = "boot_queue")
public void ListenerQueque(Message message){
System.out.println("返回的message:"+message);
System.out.println("产生的消息:"+message.getBody().toString());
}
}