模式总结:
1、简单模式helloworld
一个生产者,一个消费者,不需要交换机(使用默认交换机)
2、工作队列模式Work Queue
一个生产者,多个消费者(竞争关系),不需要设置交换机(默认交换机)
3、发布订阅模式Publish/subscribe
需要设置类型为fanout的交换机,并且交换机和队列绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列
4、路由模式Routing
需要设置类型为direct的交换机,交换机和队列绑定,并且指定routingkey,当发送消息给交换机后,交换机会根据队列的routingkey将消息给相应队列
5、通配符模式Topic
需要设置topic交换机,交换机和队列绑定,并且绑定指定通配符形式的routingkey,当发送消息给交换机后,交换机会根据routingkey将消息发送到对应的队列
一、Work queues工作队列模式
P:一个生产者,c1和c2是两个消费者,中间红色的是queue队列
Work Queues与入门程序的简单模式相比,多了一个或者一些消费端,多个消费端共同消费个队列中的消息
应用场景:对于任务过重或者任务较多情况使用工作队列可以提高任务处理的速度。
代码:
生产者
WorkProducer
/**
* @Author: jinv
* @CreateTime: 2020-06-10 23:22
* @Description: work_queue生产者
*/
public class WorkProducer {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
//创建连接
Channel channel = connection.createChannel();
/**
* 参数一:队列名称
* 参数二:是否定义持久化队列
* 参数三:是否独占本次连接
* 参数四:会否在不使用的时候自动删除队列
* 参数五:队列其他参数
*/
channel.queueDeclare("workque",true,false,false,null);
//4.通过Channel发送数据
for (int i = 0;i<30;i++){
System.out.println("生产消息"+i);
String msg = "Test consumer"+i;
channel.basicPublish("","workque",null,msg.getBytes());
}
//关闭连接
channel.close();
connection.close();
}
}
消费者
WorkConsumer1
/**
* @Author: jinv
* @CreateTime: 2020-06-10 23:28
* @Description: work_queue消费者
*/
public class WorkConsumer1 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
//创建连接
Channel channel = connection.createChannel();
//创建队列,并设置消息处理
channel.queueDeclare("workque",true,false,false,null);
//监听消息
DefaultConsumer consumer = new DefaultConsumer(channel){
/**
*
* @param consumerTag:消息标签,在channel.basicConsume时候可以指定
* @param envelope:消息包内容,可以从中取消息id,消息routingkey,交换机,消息和重转标记(收到消息失败后是否需要重新发送)
* @param properties:消息属性
* @param body:消息
* @throws IOException
*/
@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());
try {
Thread.sleep(1000);//休眠1s
} catch (InterruptedException e) {
e.printStackTrace();
}
//收到的消息
System.out.println("接收到的消息为:"+new String(body,"UTF-8"));
System.out.println();
System.out.println("=====================================================");
System.out.println();
}
};
/**
* 监听消息
* 参数一:队列名称
* 参数二:是否自动确认。设置为true表示消息接收到自动向mq回复收到了,mq接收到回复后会删除消息,设置为false则需要手动确认
*/
channel.basicConsume("workque",true,consumer);
//不关闭链接,应该一直坚挺消息
}
}
WorkConsumer2
/**
* @Author: jinv
* @CreateTime: 2020-06-10 23:28
* @Description: work_queue消费者
*/
public class WorkConsumer2 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
//创建连接
Channel channel = connection.createChannel();
//创建队列,并设置消息处理
channel.queueDeclare("workque",true,false,false,null);
//监听消息
DefaultConsumer consumer = new DefaultConsumer(channel){
/**
*
* @param consumerTag:消息标签,在channel.basicConsume时候可以指定
* @param envelope:消息包内容,可以从中取消息id,消息routingkey,交换机,消息和重转标记(收到消息失败后是否需要重新发送)
* @param properties:消息属性
* @param body:消息
* @throws IOException
*/
@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());
try {
Thread.sleep(1000);//休眠1s
} catch (InterruptedException e) {
e.printStackTrace();
}
//收到的消息
System.out.println("接收到的消息为:"+new String(body,"UTF-8"));
System.out.println();
System.out.println("=====================================================");
System.out.println();
}
};
/**
* 监听消息
* 参数一:队列名称
* 参数二:是否自动确认。设置为true表示消息接收到自动向mq回复收到了,mq接收到回复后会删除消息,设置为false则需要手动确认
*/
channel.basicConsume("workque",true,consumer);
//不关闭链接,应该一直坚挺消息
}
}
结果
消费者 1接收到的是0246。。。15条消息
消费者2接收到的是1357.。。。15条消息
结论
两个消费者绑定同一条队列,会异步消费,效率更高
二、Publish/Subscribe发布与订阅模式
发布订阅模式的交换机选择fanout广播
p:生产者,发送消息给交换机的程序
x:交换机(Exchange),一方面、接收生产者的消息。另一方面、知道如何处理消息,例如递交给某个特别队列,递交给所有队列,或是将消息丢弃。到底如何操作,取决于Exchange交换机的模式,以下三种:
Fanout:广播,将消息交给所有绑定到交换机的队列
Direct:定向,把消息交给符合规定的routing key队列
Topic:通配符,把消息给符合routing key(路由模式)的队列
红色queue:队列,接收缓存消息
c消费者,消息的接收者,会一直等待消息到来
图解:生产者发送消息给交换机,交换机将消息递交给其所绑定的队列,队列缓存消息并被c1c2两消费者取出相同的消息。
代码
PSProducer
/**
* @Author: jinv
* @CreateTime: 2020-06-11 00:06
* @Description: 发布订阅模式生产者
*/
public class PSProducer {
//交换机名称
static final String FANOUT_EXCHANGE="fanout_exchange";
//队列名称
static final String FANOUT_QUEUE_1="fanout_queue_1";
//队列名称
static final String FANOUT_QUEUE_2="fanout_queue_2";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
//创建连接
Channel channel = connection.createChannel();
//声明交换机
/**
* 参数1:交换机名称
* 参数2:交换机类型fanout,topic,direct,headers
*/
channel.exchangeDeclare(FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT);
//声明队列
/**
* 参数一:队列名称
* 参数二:是否定义持久化队列
* 参数三:是否独占本次连接
* 参数四:会否在不使用的时候自动删除队列
* 参数五:队列其他参数
*/
channel.queueDeclare(FANOUT_QUEUE_1,true,false,false,null);
channel.queueDeclare(FANOUT_QUEUE_2,true,false,false,null);
//对垒绑定交换机
channel.queueBind(FANOUT_QUEUE_1,FANOUT_EXCHANGE,"");
channel.queueBind(FANOUT_QUEUE_2,FANOUT_EXCHANGE,"");
//4.通过Channel发送数据
for (int i = 0;i<10;i++){
System.out.println("生产发布订阅模式消息!"+i);
String msg = "发布订阅模式消息"+i;
//消息发送给交换机而不是队列
channel.basicPublish(FANOUT_EXCHANGE,"",null,msg.getBytes());
}
//关闭连接
channel.close();
connection.close();
}
}
PSConsumer1
/**
* @Author: jinv
* @CreateTime: 2020-06-11 00:20
* @Description:
*/
public class PSConsumer1 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(PSProducer.FANOUT_QUEUE_1,true,false,false,null);
//队列绑定交换机
channel.queueBind(PSProducer.FANOUT_QUEUE_1,PSProducer.FANOUT_EXCHANGE,"");
//创建消费者,并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel){
/**
*
* @param consumerTag:消息标签,在channel.basicConsume时候可以指定
* @param envelope:消息包内容,可以从中取消息id,消息routingkey,交换机,消息和重转标记(收到消息失败后是否需要重新发送)
* @param properties:消息属性
* @param body:消息
* @throws IOException
*/
@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"));
System.out.println();
System.out.println("=====================================================");
System.out.println();
}
};
/**
* 监听消息
* 参数一:队列名称
* 参数二:是否自动确认。设置为true表示消息接收到自动向mq回复收到了,mq接收到回复后会删除消息,设置为false则需要手动确认
*/
channel.basicConsume(PSProducer.FANOUT_QUEUE_1,true,consumer);
//不关闭链接,应该一直坚挺消息
}
}
PSConsumer2
/**
* @Author: jinv
* @CreateTime: 2020-06-11 00:20
* @Description:
*/
public class PSConsumer2 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(PSProducer.FANOUT_QUEUE_2,true,false,false,null);
//队列绑定交换机
channel.queueBind(PSProducer.FANOUT_QUEUE_2,PSProducer.FANOUT_EXCHANGE,"");
//创建消费者,并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel){
/**
*
* @param consumerTag:消息标签,在channel.basicConsume时候可以指定
* @param envelope:消息包内容,可以从中取消息id,消息routingkey,交换机,消息和重转标记(收到消息失败后是否需要重新发送)
* @param properties:消息属性
* @param body:消息
* @throws IOException
*/
@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"));
System.out.println();
System.out.println("=====================================================");
System.out.println();
}
};
/**
* 监听消息
* 参数一:队列名称
* 参数二:是否自动确认。设置为true表示消息接收到自动向mq回复收到了,mq接收到回复后会删除消息,设置为false则需要手动确认
*/
channel.basicConsume(PSProducer.FANOUT_QUEUE_2,true,consumer);
//不关闭链接,应该一直坚挺消息
}
}
由于消息发送给交换机,而且交换机并没有设置通配符之类的,所以两条队列收到的消息是一样的,也就是两个消费者消费的消息也是一样的
三、Routing路由模式
1.队列与交换机的绑定,不能是任意绑定了,而是制定一个RoutingKey(路由key)
2.生产者在向Exchange发送消息时,也不行指定消息的RoutingKey
3.Exchange不再把消息交给每一个绑定的队列,而是根据RoutingKey进行判断,只有队列的RoutingKey与消息的RoutingKey完全一致时,才会接收消息
图解:
p:生产者,向Exchange发送消息会指定一个RoutingKey
X:Exchange,接收生产者的消息,然后把消息递给与RoutingKey完全匹配的队列
C1:消费者1,其所在队列指定了RoutingKey为error的消息
C2:消费者2,其所在队列制定了RoutingKey为info,error,warning的消息
注意:
Direct模式可以使用RabbitMQ自带的Exchange: default Exchange,所以不需要将Exchange进行任何绑定(binding)操作,消息传递时,RoutingKey必须完全匹配才会被队列接收,否则该消息会被抛弃
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会消失!
代码
RProducer
/**
* @Author: jinv
* @CreateTime: 2020-06-11 20:59
* @Description:
*/
public class RProducer {
//交换机名称
static final String DIRECT_EXCHANGE="direct_exchange";
//队列名称
static final String DIRECT_QUEUE_INSERT="direct_queue_insert";
//队列名称
static final String DIRECT_QUEUE_UPDATE="direct_queue_update";
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtils.getConnection();
//创建channel
Channel channel = connection.createChannel();
//声明交换机
/**
* 参数1:交换机名称
* 参数2:交换机类型fanout,topic,direct,headers
*/
channel.exchangeDeclare(DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明队列
/**
* 参数一:队列名称
* 参数二:是否定义持久化队列
* 参数三:是否独占本次连接
* 参数四:会否在不使用的时候自动删除队列
* 参数五:队列其他参数
*/
channel.queueDeclare(DIRECT_QUEUE_INSERT,true,false,false,null);
channel.queueDeclare(DIRECT_QUEUE_UPDATE,true,false,false,null);
//对垒绑定交换机 S2是RoutingKey
channel.queueBind(DIRECT_QUEUE_INSERT,DIRECT_EXCHANGE,"insert");
channel.queueBind(DIRECT_QUEUE_UPDATE,DIRECT_EXCHANGE,"update");
//4.通过Channel发送数据
String msg = "新增商品,路由模式RoutingKey为insert";
/**
* 参数1:交换机名称
* 参数2:routingkey
* 参数3:消息其他属性
* 参数4:消息内容 byte[]
*/
channel.basicPublish(DIRECT_EXCHANGE,"insert",null,msg.getBytes());
System.out.println("已发送消息:"+msg);
//发送消息
msg = "修改了商品。路由模式:routingkey为update";
/**
* 参数1:交换机名称
* 参数2:routingkey
* 参数3:消息其他属性
* 参数4:消息内容 byte[]
*/
channel.basicPublish(DIRECT_EXCHANGE,"update",null,msg.getBytes());
System.out.println("已发送消息:"+msg);
//关闭连接
channel.close();
connection.close();
}
}
RCousume1
/**
* @Author: jinv
* @CreateTime: 2020-06-11 00:20
* @Description:
*/
public class PSConsumer1 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(PSProducer.FANOUT_QUEUE_1,true,false,false,null);
//队列绑定交换机
channel.queueBind(PSProducer.FANOUT_QUEUE_1,PSProducer.FANOUT_EXCHANGE,"");
//创建消费者,并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel){
/**
*
* @param consumerTag:消息标签,在channel.basicConsume时候可以指定
* @param envelope:消息包内容,可以从中取消息id,消息routingkey,交换机,消息和重转标记(收到消息失败后是否需要重新发送)
* @param properties:消息属性
* @param body:消息
* @throws IOException
*/
@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"));
System.out.println();
System.out.println("=====================================================");
System.out.println();
}
};
/**
* 监听消息
* 参数一:队列名称
* 参数二:是否自动确认。设置为true表示消息接收到自动向mq回复收到了,mq接收到回复后会删除消息,设置为false则需要手动确认
*/
channel.basicConsume(PSProducer.FANOUT_QUEUE_1,true,consumer);
//不关闭链接,应该一直坚挺消息
}
}
RConsume2
/**
* @Author: jinv
* @CreateTime: 2020-06-11 21:58
* @Description:
*/
public class RConsumer2 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明交换机 DIRECT
channel.exchangeDeclare(RProducer.DIRECT_EXCHANGE,BuiltinExchangeType.DIRECT);
//声明队列
channel.queueDeclare(RProducer.DIRECT_QUEUE_UPDATE,true,false,false,null);
//队列绑定交换机
channel.queueBind(RProducer.DIRECT_QUEUE_UPDATE,RProducer.DIRECT_EXCHANGE,"update");
//创建消费者,并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel){
/**
*
* @param consumerTag:消息标签,在channel.basicConsume时候可以指定
* @param envelope:消息包内容,可以从中取消息id,消息routingkey,交换机,消息和重转标记(收到消息失败后是否需要重新发送)
* @param properties:消息属性
* @param body:消息
* @throws IOException
*/
@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"));
System.out.println();
System.out.println("=====================================================");
System.out.println();
}
};
/**
* 监听消息
* 参数一:队列名称
* 参数二:是否自动确认。设置为true表示消息接收到自动向mq回复收到了,mq接收到回复后会删除消息,设置为false则需要手动确认
*/
channel.basicConsume(RProducer.DIRECT_QUEUE_UPDATE,true,consumer);
//不关闭链接,应该一直坚挺消息
}
}
消费者直接定向消费队列DIRECT_QUEUE_INSERT消息
消费者直接定向消费队列DIRECT_QUEUE_UPDATE消息
四、Topics通配符模式
Topic类型和Direct相比,都是可以根据RoutingKey把消息路由到不同的消息队列,只不过Topic类型Exchange可以让队列绑定RoutingKey的时候用通配符
RoutingKey一般由一个或者多个词组成,多个词之间以“.”分割,例如:item.insert、item.update、item.delete
#:匹配一个或多个词
*:匹配不多不少恰好1个词
举例:
item.#:能够匹配item.insert.abc或者item.insert
item.*:能够匹配item.insert
图解:
红色Queue:绑定的是usa.#,因此凡是以usa.开头的routingkey都会被匹配到
黄色Queue:绑定的是#.news,因此凡是以.news结尾的routingkey都匹配
代码
TopicProducer
//交换机名称
static final String TOPIC_EXCHANGE="topic_exchange";
//队列名称
static final String TOPIC_QUEUE_1="topic_queue_1";
//队列名称
static final String TOPIC_QUEUE_2="topic_queue_2";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
//创建连接
Channel channel = connection.createChannel();
//声明交换机
/**
* 参数1:交换机名称
* 参数2:交换机类型fanout,topic,direct,headers
*/
channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
//声明队列(创建队列,要是用已有队列,则不需要这一段)
/**
* 参数一:队列名称
* 参数二:是否定义持久化队列
* 参数三:是否独占本次连接
* 参数四:会否在不使用的时候自动删除队列
* 参数五:队列其他参数
*/
channel.queueDeclare(TOPIC_QUEUE_1,true,false,false,null);
channel.queueDeclare(TOPIC_QUEUE_2,true,false,false,null);
//队列绑定交换机(#代表可以多个字符,*代表只能一个字符)——如果已存在队列并且绑定好了,这段也不需要
channel.queueBind(TOPIC_QUEUE_1,TOPIC_EXCHANGE,"item.#");
channel.queueBind(TOPIC_QUEUE_2,TOPIC_EXCHANGE,"*.delete");
//4.发送消息
String msg = "新增商品,Topic模式:routingkey为item.insert";
channel.basicPublish(TOPIC_EXCHANGE,"item.insert",null,msg.getBytes());
System.out.println("已发送消息:"+msg);
//4.发送消息
msg = "修改商品,Topic模式:routingkey为item.update";
channel.basicPublish(TOPIC_EXCHANGE,"item.update",null,msg.getBytes());
System.out.println("已发送消息:"+msg);
//4.发送消息
msg = "删除商品,Topic模式:routingkey为item.delete";
channel.basicPublish(TOPIC_EXCHANGE,"item.delete",null,msg.getBytes());
System.out.println("已发送消息:"+msg);
//关闭连接
channel.close();
connection.close();
}
TopicConsumer1
/**
* @Author: jinv
* @CreateTime: 2020-06-11 00:20
* @Description:
*/
public class TOPICConsumer1 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//创建消费者,并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel){
/**
*
* @param consumerTag:消息标签,在channel.basicConsume时候可以指定
* @param envelope:消息包内容,可以从中取消息id,消息routingkey,交换机,消息和重转标记(收到消息失败后是否需要重新发送)
* @param properties:消息属性
* @param body:消息
* @throws IOException
*/
@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"));
System.out.println();
System.out.println("=====================================================");
System.out.println();
}
};
/**
* 监听消息
* 参数一:队列名称
* 参数二:是否自动确认。设置为true表示消息接收到自动向mq回复收到了,mq接收到回复后会删除消息,设置为false则需要手动确认
*/
channel.basicConsume(TopicProducer.TOPIC_QUEUE_1,true,consumer);
//不关闭链接,应该一直坚挺消息
}
}
TopicConsumer2
/**
* @Author: jinv
* @CreateTime: 2020-06-11 00:20
* @Description:
*/
public class TOPICConsumer2 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//创建消费者,并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel){
/**
*
* @param consumerTag:消息标签,在channel.basicConsume时候可以指定
* @param envelope:消息包内容,可以从中取消息id,消息routingkey,交换机,消息和重转标记(收到消息失败后是否需要重新发送)
* @param properties:消息属性
* @param body:消息
* @throws IOException
*/
@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"));
System.out.println();
System.out.println("=====================================================");
System.out.println();
}
};
/**
* 监听消息
* 参数一:队列名称
* 参数二:是否自动确认。设置为true表示消息接收到自动向mq回复收到了,mq接收到回复后会删除消息,设置为false则需要手动确认
*/
channel.basicConsume(TopicProducer.TOPIC_QUEUE_2,true,consumer);
//不关闭链接,应该一直坚挺消息
}
}
结果:
topic_queue_1:通配符为item.#,所以匹配了3条消息
topic_queue_2通配符为*.delete所以只有这一条