消息中间件
概述
MQ全程message Queue,即消息队列,消息队列是应用程序和应用程序之间的通信方法。
RabbitMQ的使用
- 简单队列,直接提供者提供,消费者接受
- 在工作队列中,每一个消费者默认平均分配提供者提供的消息。
- 存在一个问题:两个消费者的处理能力不同
- 工作队列的"能者多劳模式",由于性能不一样,通常需要配置成性能好的,快速消费,而不是等待性能慢的来消费.
- 发布/订阅队列模式,每个消费者监听自己的队列。
- 中间需要交换机来实现功能(BuiltinExchangeType.FANOUT)
- 生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息
-
Routing路由模式,在发布订阅模式中每一个消费者都接受相同消息,但是在实际开发中,我们常常需要对消息进行不同的处理。例如:实际开发中,对日志信息的记录,对于error级别日志,我们可能既要保存日志,又要将它打印到控制台,而info,warning可能只需要打印在控制台,不需要保存到数据库中。
-
采用交换机–BuiltinExchangeType.DIRECT
路由模式特点:- 队列与交换机的绑定,不能是任意绑定了,而是要指定一个
RoutingKey
(路由key) - 消息的发送方在 向 Exchange发送消息时,也必须指定消息的
RoutingKey
。 - Exchange不再把消息交给每一个绑定的队列,而是根据消息的
Routing Key
进行判断,只有队列的Routingkey
与消息的Routing key
完全一致,才会接收到消息
Topic
类型与Direct
相比,都是可以根据RoutingKey
把消息路由到不同的队列。只不过Topic
类型Exchange
可以让队列在绑定Routing key
的时候使用通配符!
- 队列与交换机的绑定,不能是任意绑定了,而是要指定一个
Routingkey
一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:
#
:匹配一个或多个词
*
:匹配不多不少恰好1个词
举例:
item.#
:能够匹配item.insert.abc
或者 item.insert
item.*
:只能匹配item.insert
三套api
原生java使用RabbitMQ
这里使用最简单的工作模式来进行案例。
首先导入依赖,提供方和消费方的依赖一样
<dependencies>
<!--rabbitMQ的java客户端-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
<dependency><!--解决运行时代码爆红,可以不要-->
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
提供方代码
运行这段代码,会直接将信息传递到消息中间件中,由于没有消费者去取消息,此时消息会保存在消息中间件中,阻塞。
可以通过 http://localhost:15672/ 进入控制台进行查看。
package com.itheima.producer;
/*
*@AUTHOR:LIUCHAO;
*@DATE: 2020/10/12 20:51
*/
/*
* 原声api
* */
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 Producer_HelloWorld {
public static void main(String[] args) throws IOException, TimeoutException {
/*建立连接,通过工厂模式--所以先创建连接工厂*/
ConnectionFactory factory = new ConnectionFactory();
/*设置参数, 虚拟机,用户名,ip端口号*/
factory.setHost("192.168.164.131");//ip地址 默认值是localhost
factory.setPort(5672);//端口号 默认值是5672
factory.setVirtualHost("/itcast");//虚拟机 默认值是 /
factory.setUsername("heima");//用户名 默认值是guest
factory.setPassword("heima");//用户名 默认值是guest
/*获取连接,创建连接 connection*/
Connection connection = factory.newConnection();
/*创建channel*/
Channel channel = connection.createChannel();
/*如果不需要交换机,就直接创建队列Quene*/
/*queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
Map<String, Object> arguments)
参数:
1.queue : 队列名称
2.durable: 是否持久化,当mq重启之后,还在
3.exclusive: 是否独占,只能有一个消费者监听这队列
当connection关闭时,是否删除队列
一般设置为false
4.autoDelete: 是否自动删除,当没有consumer时,自动删除掉
5.arguments: 参数 ,直接设置成null
*/
//如果没有一个队列名字叫hello_world,则会创建该队列,如果有则不会创建
channel.queueDeclare("hello_world",true,false,false,null);
/*发送消息*/
/*
* basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
* exchange: 交换机名称,简单模式下交换机使用默认交换机 直接写 ""
* routingKey: 路由名称,和上面的队列名称保持一致
* props:配置信息
* body:发送的消息 字节数组
* */
String body="hello rabbitmq~~~~~~~~~~~~~";
channel.basicPublish("","hello_world",null,body.getBytes());
/*释放资源*/
channel.close();
connection.close();
}
}
此时工作台
消费方代码
基本上和提供方代码差不多,只是在消费方会有一个回调方法,用来接受到提供方的消息后,自动执行该方法。
当代码运行时,如果消息中间件中没有消息,那么程序一直运行,对该队列进行监听;如果消息中间件中有消息就取出,继续监听。
当获取到提供方的消息后,监控平台中的消息显示被消费掉
package com.itheima.consumer;
/*
*@AUTHOR:LIUCHAO;
*@DATE: 2020/10/12 21:30
*/
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_HelloWorld {
public static void main(String[] args) throws IOException, TimeoutException {
/*建立连接,通过工厂模式--所以先创建连接工厂*/
ConnectionFactory factory = new ConnectionFactory();
/*设置参数, 虚拟机,用户名,ip端口号*/
factory.setHost("192.168.164.131");//ip地址 默认值是localhost
factory.setPort(5672);//端口号 默认值是5672
factory.setVirtualHost("/itcast");//虚拟机 默认值是 /
factory.setUsername("heima");//用户名 默认值是guest
factory.setPassword("heima");//用户名 默认值是guest
/*获取连接,创建连接 connection*/
Connection connection = factory.newConnection();
/*创建channel*/
Channel channel = connection.createChannel();
/*如果不需要交换机,就直接创建队列Quene*/
/*queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
Map<String, Object> arguments)
参数:
1.queue : 队列名称
2.durable: 是否持久化,当mq重启之后,还在
3.exclusive: 是否独占,只能有一个消费者监听这队列
当connection关闭时,是否删除队列
一般设置为false
4.autoDelete: 是否自动删除,当没有consumer时,自动删除掉
5.arguments: 参数 ,直接设置成null
*/
//如果没有一个队列名字叫hello_world,则会创建该队列,如果有则不会创建--
//生产者创建队列后,消费者可以不用创建,写起也没啥错
channel.queueDeclare("hello_world",true,false,false,null);
/*接受消息*/
/*
* basicConsume(String queue, boolean autoAck, Consumer callback)
* queue:队列
* autoAck:是否自动确认
* callback:回调函数
* */
Consumer consumer=new DefaultConsumer(channel){
/*回调方法,当收到消息后,会自动执行该方法*/
@Override
/* 消息标识 获取一些信息,交换机,路由key 配置信息 正式数据 */
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag:"+consumerTag);//consumerTag:amq.ctag-mWgUgb4Zttu7ds1gjwSXTg
System.out.println("envelope.getExchange() = " + envelope.getExchange());//envelope.getExchange() =
System.out.println("envelope.getRoutingKey() = " + envelope.getRoutingKey());//envelope.getRoutingKey() = hello_world
System.out.println("properties = " + properties);//properties = #contentHeader<basic>(content-type=null, content-encoding=null, headers=null, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
System.out.println("body = " + new String(body));//body = hello rabbitmq~~~~~~~~~~~~~
}
};
/*basicConsume(String queue, boolean autoAck, Consumer callback)
* autoAck: 自动确认true,手动确认false
* 确认后,会删除消息中间件中的消息(如果消费者发生异常,没有正常消费,但是消息中间件中的消息又
* 已经删除,就是消息丢失)
* */
channel.basicConsume("hello_world",true,consumer);
//是否关闭资源--不关闭,消费者相当于一直处于监听状态
}
}