RabbitMQ提供了6种消息模型,但是第6种其实是RPC,并不是MQ,因此不予学习。那么也就剩下5种。
1.基本消息模型:生产者–>队列–>一个消费者
2.work消息模型:生产者–>队列–>多个消费者共同消费
3.订阅模型-Fanout:广播,将消息交给所有绑定到交换机的队列,每个消费者都可以收到同一条消息
4.订阅模型-Direct:定向,把消息交给符合指定 rotingKey 的队列(路由模式)
5.订阅模型-Topic:通配符,把消息交给符合routing pattern(主题模式) 的队列
(3、4、5这三种都属于订阅模型,只不过进行路由的方式不同。)
1、简单队列(模式)
单个发送者(生产者) 将消息发送到队列(每个队列都有一个唯一名字) 单个接受者(消费者)获取消息
添加maven依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.2.0</version>
</dependency>
生产者代码:
package cn.et;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* 简单模式(生产者)
*/
public class SimpleMode {
private static String QUEUE_NAME = "SIMPLE_MODE";
public static void main(String[] args) throws Exception {
Map map =new HashMap();
map.put("setTo", "1728973819@qq.com");
map.put("setSubject", "测试邮件");
map.put("setText", "5201314");
//连接远程rabbit-server服务器
ConnectionFactory factory = new ConnectionFactory();
//MQ的服务ip
factory.setHost("192.168.0.175");
//端口
factory.setPort(5672);
//创建一个连接
Connection connection = factory.newConnection();
//创建一个管道
Channel channel = connection.createChannel();
//定义创建一个队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//发送消息(注意发送和接受段相同字符集否则出现乱码)
channel.basicPublish("", QUEUE_NAME, null, ser(map));
System.out.println("生产成功!!");
channel.close();
connection.close();
}
/**
* 将对象序列化为字节数组
*/
private static byte[] ser(Object obj) throws IOException{
ByteArrayOutputStream baos =new ByteArrayOutputStream();
ObjectOutputStream oos =new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}
}
运行。。。。。。。。。
这个时候界面上就会出现一个队列,显示未消费
消费者代码:
package cn.et;
import com.rabbitmq.client.*;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Map;
/**
* 简单模式(消费者)
*/
public class SimpleMode {
private static String QUEUE_NAME = "SIMPLE_MODE";
public static void main(String[] args)throws Exception {
//连接远程rabbit-server服务器
ConnectionFactory factory = new ConnectionFactory();
//MQ的服务ip
factory.setHost("192.168.0.175");
//端口
factory.setPort(5672);
//创建一个连接
Connection connection = factory.newConnection();
//创建一个管道
Channel channel = connection.createChannel();
//创建一个队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定义回调抓取消息
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body){
try {
Map map = (Map) dser(body);
System.out.println(map.get("setTo").toString());
System.out.println(map.get("setSubject").toString());
System.out.println(map.get("setText").toString());
System.out.println("接收成功");
} catch (Exception e) {
e.printStackTrace();
}
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
/**
* 反序列化
*/
private static Object dser(byte[] src) throws Exception {
// 从字节数组读取数据
ByteArrayInputStream bis = new ByteArrayInputStream(src);
// 把字节数组反序列化成对象
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
运行效果,刚刚生产的数据以及全部获取到了,然后可以拿到这些数据做具体的业务操作了。
这个时候图形界面的消息也已经被消费了
2、工作队列模式(http://www.rabbitmq.com/tutorials/tutorial-two-java.html)
工作队列一般用于任务分配 发布者发布任务到队列 多个消息接受者 接受消息 谁接受到某个消息 其他接受者就只能消费其他消息 队列中的(跟第一种代码类似,只是消费者启动两个服务而已)
一个消息只能被一个接受者消费(类似12306抢票一样 比如某个车次 就相当于队列 该车次 出来一些座位票 一张票只能被一个人抢到 最终 所有的座位票
都被不同的人抢到 注意: 一个人可以抢多张票)
模拟 两个任务接受者 一个发布者发布10个消息 2个任务接受者抢这10个消息 一般来说都是rr轮询制 基本是平均分配给每个接受者的
生产者代码:
package cn.et;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 工作队列模式(生产者)
*/
public class WorkQueueMode {
private static String QUEUE_NAME = "WORK_QUEUE_MODE";
public static void main(String[] args) throws Exception {
//连接远程rabbit-server服务器
ConnectionFactory factory = new ConnectionFactory();
//MQ的服务ip
factory.setHost("192.168.0.175");
//端口
factory.setPort(5672);
//创建一个连接
Connection connection = factory.newConnection();
//创建一个管道
Channel channel = connection.createChannel();
//定义创建一个队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//发送消息 (注意发送和接受段相同字符集否则出现乱码)
for (int i = 0; i < 10; i++) {
channel.basicPublish("", QUEUE_NAME, null, ("这是:"+i).getBytes("UTF-8"));
}
System.out.println("生产成功!!");
channel.close();
connection.close();
}
}
消费者代码:
package cn.et;
import com.rabbitmq.client.*;
/**
* 消息队列模式(消费者)
*/
public class WorkQueueMode {
private static String QUEUE_NAME = "WORK_QUEUE_MODE";
public static void main(String[] args)throws Exception {
//连接远程rabbit-server服务器
ConnectionFactory factory = new ConnectionFactory();
//MQ的服务ip
factory.setHost("192.168.0.175");
//端口
factory.setPort(5672);
//创建一个连接
Connection connection = factory.newConnection();
//创建一个管道
Channel channel = connection.createChannel();
//创建一个队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定义回调抓取消息
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body){
try {
String message = new String(body, "UTF-8");
System.out.println(message);
} catch (Exception e) {
e.printStackTrace();
}
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
先同时启动两个任务接受者,这个时候两个消费者会一直处于等待状态,有生产他们就会平分来进行消费
运行 发布者 查看两个接受者的控制台 发现一个接收到5条消息
第一个接受者
第二个接受者
3、发布订阅模式(http://www.rabbitmq.com/tutorials/tutorial-three-java.html)
生产者发布主题消息 订阅者订阅该主题 订阅该主题的所有订阅者都可以接受消息 (类似于广播 广播有个频道(主题) 听广播的用户就是订阅者广播中推送的新闻和内容就是消息)
发布订阅模式 引入了交换器的概念 消息发布者发布消息到交换器 订阅者定义一个队列(每个订阅者定义一个)用于接受消息 交换器 起到了中转消息的作用 这里面还有个routingkey 如果定义了routingkey 交换器只会将消息发给自己的routingkey和订阅者队列绑定routringkey和相同或者相似的队列
比如交换器定义routingkey是 abc 订阅者将自己队列绑定到交换器时指定routingkey是abc 交换器发现routingkey相同所以消息被发送到这个队列,这里因为所有的订阅者需要获取消息 所以routingkey为空 订阅者产生的队列名称应该为随机字符串就可
交换器 有以下几种类型 direct, topic, headers and fanout
发布订阅模式 应该使用fanout(广播)
通过命令 rabbitmqctl list_exchanges 或者查看webgui 查看exchange
列表中有很多amq.*开头的交换器 第一个是默认 之前的两种简单模式和工作队列模式未定义交换器 使用的是第一个defalt
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"));
发布消息的第一个参数 就是交换器 为空 所以使用默认的交换器 也就是消息不经过交换器,直接发给给队列 第二个参数就是发送的队列名称
官网日志的例子 非常经典
比如 发布者应用打印了日志 同时有两个订阅者 其中一个订阅者保存日志 另外一个订阅者也输出日志 可以用于分布式统计日志
这里仅仅演示两个订阅者都是打印消息
生产者代码:
package cn.et;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
/**
* 发布订阅模式(生产者)
*/
public class PublishSubscribeMode {
private static String EXCHANGE_NAME = "PUBLISH_SUBSCRIBE_MODE";
public static void main(String[] args) throws Exception {
//连接远程rabbit-server服务器
ConnectionFactory factory = new ConnectionFactory();
//MQ的服务ip
factory.setHost("192.168.0.175");
//端口
factory.setPort(5672);
//创建一个连接
Connection connection = factory.newConnection();
//创建一个管道
Channel channel = connection.createChannel();
//定义一个交换器,设置类型为fanout
channel.exchangeDeclare(EXCHANGE_NAME, "fanout",true);
for(int i=0;i<=10;i++){
String message = "发送第"+i+"消息";
channel.basicPublish(EXCHANGE_NAME, "", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"));
}
System.out.println("生产成功!!");
channel.close();
connection.close();
}
}
消费者代码:
package cn.et;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 发布订阅模式(消费者)
*/
public class PublishSubscribeMode {
private static String EXCHANGE_NAME = "PUBLISH_SUBSCRIBE_MODE";
public static void main(String[] args)throws Exception {
//连接远程rabbit-server服务器
ConnectionFactory factory = new ConnectionFactory();
//MQ的服务ip
factory.setHost("192.168.0.175");
//端口
factory.setPort(5672);
//创建一个连接
Connection connection = factory.newConnection();
//创建一个管道
final Channel channel = connection.createChannel();
//定义一个交换器设置类型为fanout
channel.exchangeDeclare(EXCHANGE_NAME, "fanout",true);
//产生一个随机的队列 该队列用于从交换器获取消息
String queueName = channel.queueDeclare().getQueue();
//将队列和某个交换机绑定 就可以正式获取消息了 key和交换器的一样都设置成空
channel.queueBind(queueName, EXCHANGE_NAME, "");
//定义回调抓取消息
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(message);
//参数2 true表示确认该队列所有消息 false只确认当前消息 每个消息都有一个消息标记
channel.basicAck(envelope.getDeliveryTag(), true);
}
};
//参数2 等于false 表示手动确认
channel.basicConsume(queueName, false, consumer);
}
}
发布订阅模式的消息是 发布者发布到交换机后 交换机会将所有消息转发给在线的订阅者队列 发送完毕后消息就被删除 再次启动的的订阅者队列无法获取上一次发送的消息
先启动两个消费者、然后在启动生产者、发现两个消费者都获取到了消息
4、路由模式(http://www.rabbitmq.com/tutorials/tutorial-three-java.html)
前面第3种发布订阅模式 每个订阅者都会收到交换器发出的消息 因为发布者发布消息的routingkey是空 订阅者接受消息队列的routingkey也是空
发布者发布的消息 所有的订阅者都能接收到 路由模式表示 发布者发布的消息的routingkey和订阅者接受消息队列的routingkey都不为空 相同的则可以
接受消息 比如发送日志的例子
该种模式 交换器的类型 必须是direct 而不是之前的faout 比如 发布者P 发布的多个消息 使用的多个routingkey 比如error info
比如一条错误消息 routingkey定义为 error 提醒消息是info
如果某个订阅者的临时队列使用的routingkey是error 将接受到error消息
是info 将接受到提醒消息 其他消息如果没有队列绑定routingkey 将被丢弃
模拟上图的例子 比如有四类消息
C1订阅者程序绑定到交换机X routingkey是error 出现error写入到磁盘
C2订阅者程序绑定到交换机X routingkey绑定三个 info error warning 接受到消息只是打印
生产者代码:
package cn.et;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
/**
* 路由模式(生产者)
*/
public class RoutingMode {
private static String EXCHANGE_NAME = "ROUTING_MODE";
public static void main(String[] args) throws Exception {
//连接远程rabbit-server服务器
ConnectionFactory factory = new ConnectionFactory();
//MQ的服务ip
factory.setHost("192.168.0.175");
//端口
factory.setPort(5672);
//创建一个连接
Connection connection = factory.newConnection();
//创建一个管道
Channel channel = connection.createChannel();
//定义一个交换器设置类型为direct
channel.exchangeDeclare(EXCHANGE_NAME, "direct",true);
//第二个参数就是routingKey 不填 默认会转发给所有的订阅者队列
channel.basicPublish(EXCHANGE_NAME, "error", MessageProperties.PERSISTENT_TEXT_PLAIN, "系统崩溃内存不足".getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME, "warning", MessageProperties.PERSISTENT_TEXT_PLAIN, "张三涉嫌违法,警告一次".getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME, "success", MessageProperties.PERSISTENT_TEXT_PLAIN, "你已成功登陆".getBytes("UTF-8"));
System.out.println("生产成功!!");
channel.close();
connection.close();
}
}
消费者代码:
package cn.et;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 路由模式(消费者)
*/
public class RoutingMode {
private static String EXCHANGE_NAME = "ROUTING_MODE";
public static void main(String[] args)throws Exception {
//连接远程rabbit-server服务器
ConnectionFactory factory = new ConnectionFactory();
//MQ的服务ip
factory.setHost("192.168.0.175");
//端口
factory.setPort(5672);
//创建一个连接
Connection connection = factory.newConnection();
//创建一个管道
final Channel channel = connection.createChannel();
//定义一个交换器
channel.exchangeDeclare(EXCHANGE_NAME, "direct",true);
//产生一个随机的队列 该队列用于从交换器获取消息
String queueName = channel.queueDeclare().getQueue();
// routingKey设置为指定的key
channel.queueBind(queueName, EXCHANGE_NAME, "success");
//定义回调抓取消息
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(message);
//参数2 true表示确认该队列所有消息 false只确认当前消息 每个消息都有一个消息标记
channel.basicAck(envelope.getDeliveryTag(), true);
}
};
//参数2 false表示手动确认
channel.basicConsume(queueName, false, consumer);
}
}
启动两个消费者
启动第一个消费者 传递参数error
启动第二个消费者 传递参数 wawarning
运行开发者代码测试发送消息
消费者一程序控制台打印:
消费者二程序控制台打印:
5、Topics路由模式(http://www.rabbitmq.com/tutorials/tutorial-three-java.html)
该种模式和路由模式类似 只是消息的routingKey 是通过.隔开的多个字符组成 订阅者的消息队列绑定的routingKey可以使用通配符通配所有满足
条件的交换机消息 匹配上则接受消息 这种类型的消息 使用的交换器类型是 topic
比如 生产者生产消息(模拟routingkey消息格式是 a.info.)
channel.basicPublish(EXCHANGE_NAME, "a.info", MessageProperties.PERSISTENT_TEXT_PLAIN, "a.info".getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME, "a.info.xxx", MessageProperties.PERSISTENT_TEXT_PLAIN, "a.info.xxx".getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME, "a.info.bbb", MessageProperties.PERSISTENT_TEXT_PLAIN, "a.info.bbb".getBytes("UTF-8"));
消费者接受消息可以设置routingKey为表达式模糊匹配 *匹配一个单词 #匹配多个
生产者代码:
package cn.et;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
/**
* 配置路由模式(生产者)
*/
public class MatchRouteMode {
private static String EXCHANGE_NAME = "MATCH_ROUTE_MODE";
public static void main(String[] args) throws Exception {
//连接远程rabbit-server服务器
ConnectionFactory factory = new ConnectionFactory();
//MQ的服务ip
factory.setHost("192.168.0.175");
//端口
factory.setPort(5672);
//创建一个连接
Connection connection = factory.newConnection();
//创建一个管道
Channel channel = connection.createChannel();
//定义一个交换器设置类型为direct
channel.exchangeDeclare(EXCHANGE_NAME, "topic",true);
//第二个参数就是routingKey不填 默认会转发给所有的订阅者队列,*代表两个.中的一段 #代表多段
channel.basicPublish(EXCHANGE_NAME, "a.info", MessageProperties.PERSISTENT_TEXT_PLAIN, "a.info".getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME, "a.info.xxx", MessageProperties.PERSISTENT_TEXT_PLAIN, "a.info.xxx".getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME, "a.info.bbb", MessageProperties.PERSISTENT_TEXT_PLAIN, "a.info.bbb".getBytes("UTF-8"));
System.out.println("生产成功!!");
channel.close();
connection.close();
}
}
消费者代码:
package cn.et;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 配置路由模式(消费者)
*/
public class MatchRouteMode {
private static String EXCHANGE_NAME = "MATCH_ROUTE_MODE";
public static void main(String[] args)throws Exception {
//连接远程rabbit-server服务器
ConnectionFactory factory = new ConnectionFactory();
//MQ的服务ip
factory.setHost("192.168.0.175");
//端口
factory.setPort(5672);
//创建一个连接
Connection connection = factory.newConnection();
//创建一个管道
final Channel channel = connection.createChannel();
//定义一个交换器
channel.exchangeDeclare(EXCHANGE_NAME, "topic",true);
//产生一个随机的队列 该队列用于从交换器获取消息
String queueName = channel.queueDeclare().getQueue();
//消费者定义的key 可以使用通配符 通配交换器的消息 *代表两个.中的一段 #代表多段
channel.queueBind(queueName, EXCHANGE_NAME, "a.#");
//定义回调抓取消息
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(message);
//参数2 true表示确认该队列所有消息 false只确认当前消息 每个消息都有一个消息标记
channel.basicAck(envelope.getDeliveryTag(), true);
}
};
//参数2 表示手动确认
channel.basicConsume(queueName, false, consumer);
}
}
两个消费者用不同的符号匹配,比如 a.* 只能获取一条数据
两个消费者用不同的符号匹配,比如 a.# 可以获取所有数据
6、RPC模式
该模式是rpc 远程方法调用 这里不介绍 http协议调用更经典