消息队列是应用程序之间通讯方法,无需即时返回且耗时的操作进行异步处理从而提高系统的吞吐量,可以实现程序之间的解耦合,常见的产品有 ActiveMQ、ZeroMQ、RabbitMQ、RocketMQ和Kafka。
安装rabbitmq前要先安装erlang:百度云下载erlang20.3和rabbitmq3.7.14 提取码:05z6
默认项目下一步就好,安装完erlang添加bin路径到path,例:%ERLANG_HOME%\bin
安装完rabbitmq后,打开cmd cd 安装路径/sbin(D:\DevInstall\rabbitmq3.7.14\rabbitmq_server-3.7.14\sbin)
启动rabbitmq输入:rabbitmq-plugins.bat enable rabbitmq_management,不出意外的话会出现下面的情况
D:\DevInstall\rabbitmq3.7.14\rabbitmq_server-3.7.14\sbin>rabbitmq-plugins.bat enable rabbitmq_management
Enabling plugins on node rabbit@LAPTOP-IAO5L5AN:
rabbitmq_management
The following plugins have been configured:
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
Applying plugin configuration to rabbit@LAPTOP-IAO5L5AN...
Plugin configuration unchanged.
这时访问rabbitmq管理页面这访问不到的(http://localhost:15672/#/),这时候输入:rabbitmq-server start 时可以访问
D:\DevInstall\rabbitmq3.7.14\rabbitmq_server-3.7.14\sbin>rabbitmq-server start
"WARNING: Using RABBITMQ_ADVANCED_CONFIG_FILE: C:\Users\GONGBIN\AppData\Roaming\RabbitMQ\advanced.config"
## ##
## ## RabbitMQ 3.7.14. Copyright (C) 2007-2019 Pivotal Software, Inc.
########## Licensed under the MPL. See https://www.rabbitmq.com/
###### ##
########## Logs: C:/Users/GONGBIN/AppData/Roaming/RabbitMQ/log/RABBIT~1.LOG
C:/Users/GONGBIN/AppData/Roaming/RabbitMQ/log/rabbit@LAPTOP-IAO5L5AN_upgrade.log
Starting broker...
completed with 3 plugins.
进入之后简单的配置添加一个用户,以及管理 Virtual Hosts 虚拟主机
到此创建了一个用户,点击 Virtual Hosts 创建虚拟主机
Users 默认是 guest 更改成刚创建的用户
点击 Set Permission 到此完成
com.rabbitmq
amqp-client
实现一个生产者和消费者都需要6步,其中公共的4个步骤为:1. 创建连接工厂设置服务参数、2. 创建连接、3. 创建频道、4. 声明队列,生产者的5、6步为:5. 发送消息、6.关闭资源,消息者为:5. 创建消费者、6.监听队列,消息者需要监听消息队列不需要关闭资源。
定义公共工具
public class ConnectionUtil {
public static Connection getConnection() throws IOException, TimeoutException {
// 1. 创建连接工厂设置服务参数
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setVirtualHost("itea");
connectionFactory.setPort(5672);
connectionFactory.setUsername("dxayga");
connectionFactory.setPassword("dxayga");
// 2. 创建连接
Connection connection = connectionFactory.newConnection();
return connection;
}
}
生产者代码
public class Producer {
final static String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接工厂设置服务参数
// 2. 创建连接
Connection connection = ConnectionUtil.getConnection();
// 3. 创建频道
Channel channel = connection.createChannel();
// 4. 声明队列
/**
* 参数1:队列名
* 参数2:是否持久化(会一直保存在服务器上)
* 参数3:是否独占本连接
* 参数4:是否在不使用时删除
* 参数5:其他参数
*/
channel.queueDeclare(QUEUE_NAME,true,false,true,null);
// 5. 发送消息
/**
* 参数1:交换机名,没有指定为为默认交换机
* 参数2:路由的key(简单模式下可以使用队列名)
* 参数3:消息的其他属性
* 参数4:消息内容
*/
channel.basicPublish("",QUEUE_NAME,null,"Hello World!".getBytes());
// 6.关闭资源
channel.close();
connection.close();
}
}
消费者代码
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接工厂设置服务参数
// 2. 创建连接
Connection connection = ConnectionUtil.getConnection();
// 3. 创建频道
Channel channel = connection.createChannel();
// 4. 声明队列
/**
* 参数1:队列名
* 参数2:是否持久化(会一直保存在服务器上)
* 参数3:是否独占本连接
* 参数4:是否在不使用时删除
* 参数5:其他参数
*/
channel.queueDeclare(Producer.QUEUE_NAME,true,false,true,null);
// 5. 创建消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由的key:"+envelope.getRoutingKey());
System.out.println("交换机为:"+envelope.getExchange());
System.out.println("消息id为:"+envelope.getDeliveryTag());
System.out.println("接收的消息为:"+new String(body,"utf-8"));
}
};
// 6.监听队列
/**
* 参数1:队列名
* 参数2:是否自动确认;true为收到消息自动从队列删除,false为手动确认,
* 参数3:消息者
*/
channel.basicConsume(Producer.QUEUE_NAME,true,defaultConsumer);
}
}
启动生产者和消费者,成功的话返回:
路由的key:simple_queue
交换机为:
消息id为:1
接收的消息为:Hello World!
而没有消费的的信息也可以在服务端看到
工作队列模式
点击查看这篇文章
消费者与消费者是竞争的关系,一条消息只能被一个消费者消费,不存在一条消息被两个消费者消费。
订阅模式与前面的两种模式比较∶多了一个角色Exchange交换机,接收生产者发送的消息并决定如何投递消息到其绑定的队列,消息的投递决定于交换机的类型。
交换机类型:广播( fanout )、定向( direct )、通配符( topic )
交换机只做消息转发,自身不存储数据。
一个消息可以被多个消费者接收,一个消费者对应一个队列,该队列只能被一个消费者监听。使用了订阅模式中交换机类型为∶广播。
生产者
public class Producer {
final static String FANOUT_EXCHANGE = "fanout_exchange";
final static String FANOUT_QUEUE_1 = "fanout_queue_1";
final static String FANOUT_QUEUE_2 = "fanout_queue_2";
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接
Connection connection = ConnectionUtil.getConnection();
// 2. 创建频道
Channel channel = connection.createChannel();
// 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
channel.exchangeDeclare(FANOUT_EXCHANGE,BuiltinExchangeType.FANOUT);
// 4. 声明队列
/**
* 参数1:队列名
* 参数2:是否持久化(会一直保存在服务器上)
* 参数3:是否独占本连接
* 参数4:是否在不使用时删除
* 参数5:其他参数
*/
channel.queueDeclare(FANOUT_QUEUE_1,true,false,true,null);
channel.queueDeclare(FANOUT_QUEUE_2,true,false,true,null);
// 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
channel.queueBind(FANOUT_QUEUE_1,FANOUT_EXCHANGE,"");
channel.queueBind(FANOUT_QUEUE_2,FANOUT_EXCHANGE,"");
// 6. 发送消息
/**
* 参数1:交换机名,没有指定为为默认交换机
* 参数2:路由的key(简单模式下可以使用队列名)
* 参数3:消息的其他属性
* 参数4:消息内容
*/
for (int i = 0; i < 10; i ++) {
String message = "Hello World!"+i;
channel.basicPublish(FANOUT_EXCHANGE,"",null,message.getBytes());
System.out.println("消息发送:"+message);
}
// 7.关闭资源
channel.close();
connection.close();
}
}
消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接
Connection connection = ConnectionUtil.getConnection();
// 2. 创建频道
Channel channel = connection.createChannel();
// 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
channel.exchangeDeclare(Producer.FANOUT_EXCHANGE,BuiltinExchangeType.FANOUT);
// 4. 声明队列
/**
* 参数1:队列名称
* 参数2:是否持久化(会一直保存在服务器上)
* 参数3:是否独占本连接
* 参数4:是否在不使用时删除
* 参数5:其他参数
*/
channel.queueDeclare(Producer.FANOUT_QUEUE_1,true,false,true,null);
// 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
channel.queueBind(Producer.FANOUT_QUEUE_1, Producer.FANOUT_EXCHANGE,"");
// 6. 创建消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由的key:"+envelope.getRoutingKey());
System.out.println("交换机为:"+envelope.getExchange());
System.out.println("消息id为:"+envelope.getDeliveryTag());
System.out.println("接收的消息为:"+new String(body,"utf-8"));
}
};
// 7.监听队列
/**
* 参数1:队列名称
* 参数2:是否自动确认;true为收到消息自动从队列删除,false为手动确认,
* 参数3:消费者
*/
channel.basicConsume(Producer.FANOUT_QUEUE_1,true,defaultConsumer);
}
}
在服务也可以看到两个队列已经绑定到交换机
路由模式(Routing)
Routing(路由模式)要求队列绑定到交换机的时候指定路由key,消费发送时候需要携带路由key,只有消费的路由key与队列路由key完全一致才能让该队列接收到消息。相比上面的代码更改了定向模式和加入了Routing Key
public static void main(String[] args) throws IOException, TimeoutException {
...
// 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
channel.exchangeDeclare(DIRECT_EXCHANGE,BuiltinExchangeType.DIRECT);
...
// 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
channel.queueBind(DIRECT_QUEUE_1,DIRECT_EXCHANGE,"insert");
channel.queueBind(DIRECT_QUEUE_2,DIRECT_EXCHANGE,"update");
// 6. 发送消息
/**
*/
String message1 = "Hello World!";
channel.basicPublish(DIRECT_EXCHANGE,"insert",null,message1.getBytes());
System.out.println("消息发送:"+message1);
String message2 = "Hello World!";
channel.basicPublish(DIRECT_EXCHANGE,"update",null,message2.getBytes());
System.out.println("消息发送:"+message2);
...
}
//消费者
public static void main(String[] args) throws IOException, TimeoutException {
...
// 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
channel.exchangeDeclare(Producer.DIRECT_EXCHANGE,BuiltinExchangeType.DIRECT);
...
// 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
channel.queueBind(Producer.DIRECT_QUEUE_1, Producer.DIRECT_EXCHANGE,"insert");
...
}
//消费者
public static void main(String[] args) throws IOException, TimeoutException {
...
// 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
channel.exchangeDeclare(Producer.DIRECT_EXCHANGE,BuiltinExchangeType.DIRECT);
...
// 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
channel.queueBind(Producer.DIRECT_QUEUE_1, Producer.DIRECT_EXCHANGE,"update");
...
}
Topics通配符模式∶可以根据路由key将消息传递到对应路由key的队列,队列绑定到交换机的路由key可以有多个,通配符模式中路由key可以使用 * 和 # 使用了通配符模式之后对于路由Key的配置更加灵活。
举例:定义了两个路由key:item.insert.abc 和 item.insert
当使用路由key为:item.* 可以匹配到一个(item.insert),当使用item.# 可以匹配到两个(item.insert、item.insert.abc)
...
// 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
channel.exchangeDeclare(DIRECT_EXCHANGE,BuiltinExchangeType.TOPIC);
而第3、4步可以省略
// 6. 发送消息
/**
* 参数1:交换机名,没有指定为为默认交换机
* 参数2:路由的key(简单模式下可以使用队列名)
* 参数3:消息的其他属性
* 参数4:消息内容
*/
String message1 = "Hello World!";
channel.basicPublish(DIRECT_EXCHANGE,"item.insert",null,message1.getBytes());
System.out.println("消息发送:"+message1);
String message2 = "Hello World!";
channel.basicPublish(DIRECT_EXCHANGE,"item.insert.abc",null,message2.getBytes());
System.out.println("消息发送:"+message2);
String message3 = "Hello World!";
channel.basicPublish(DIRECT_EXCHANGE,"item.delete",null,message3.getBytes());
System.out.println("消息发送:"+message3);
...
//消费者
public static void main(String[] args) throws IOException, TimeoutException {
...
// 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
channel.exchangeDeclare(Producer.DIRECT_EXCHANGE,BuiltinExchangeType.TOPIC);
...
// 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
channel.queueBind(Producer.DIRECT_QUEUE_1, Producer.DIRECT_EXCHANGE,"item.*");
channel.queueBind(Producer.DIRECT_QUEUE_1, Producer.DIRECT_EXCHANGE,"item.delete");
...
}
public static void main(String[] args) throws IOException, TimeoutException {
...
// 3. 声明交换机 参数1:交换机名称 参数2:交换机类型
channel.exchangeDeclare(Producer.DIRECT_EXCHANGE,BuiltinExchangeType.TOPIC);
...
// 5. 队列绑定到交换机 参数1:队列名称、参数2:交换机名称、参数3:路由key
channel.queueBind(Producer.DIRECT_QUEUE_2, Producer.DIRECT_EXCHANGE,"item.#");
...
}
总结
不直接Exchange交换机(默认交换机) :
1. simple简单模式:一个生产者生产一个消息到一个队列被一个消费者接收。
2. work工作队列模式:生产者发送消息到一个队列中,然后可以被多个消费者监听该队列,一个消息只能被一个消费者接收,消费者之间是竞争关系。
使用Exchange交换机;订阅模式(交换机:广播fanout,定向direct. 通配符topic ):
1.发布与订阅模式:使用了fanout广播类型的交换机,可以将一个消息发送到所有绑定了该交换机的队列。
2.路由模式:使佣了direct定向类型的交换机,消费会携带路由key,交换机根据消息的路由key与队列的路由key进行对比,一致的话那么该队列可以接收到消息。
3.通配符模式:使用了topic通配符类型的交换机,消费会携带路由key(*, #) , 交换机根据消息的路由key与队列的路由key进行对比,匹配的话那么该队列可以接收到消息。