一、什么是RabbitMQ
rabbitmq是基于amqp协议实现一套高效的数据传输组件,MQ(消息队列)。
常见的MQ:ActiveMQ、Kafka、RocketMQ、RabbitMQ
参考网址:
RabbitMQ的应用场景以及基本原理介绍
RabbitMQ的Java应用(1) – Rabbit Java Client使用
二、MQ的应用场景
1、消息异步通知(注册时邮箱认证、添加商品生成详情页和将商品添加到搜索库等)
2、消息顺序处理
3、消息延迟处理
4、请求削峰
三,RabbitMQ的基本结构:
几个概念说明:
Broker:它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输,
Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
rabbitmq交换机类型
Queue:消息的载体,每个消息都会被投到一个或多个队列。
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来.
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
vhost:虚拟主机,一个broker里可以有多个vhost,用作不同用户的权限分离。 虚拟机概念是RabbitMQ的核心,在用户未自定义虚拟机前已经内置有虚拟机,在使用RabbitMQ中,可以进行自定义配置虚拟机.一个虚拟机中可以含有多个队列信息虚拟机最大的好处在于可以根据不同的用户分配不同的操作空间
RabbitMQ虚拟主机配置
Producer:消息生产者,就是投递消息的程序.
Consumer:消息消费者,就是接受消息的程序.
Channel:消息通道,在客户端的每个连接里,可建立多个channel.
消息发布接收流程:
—–发送消息—–
1、生产者和Broker建立TCP连接。
2、生产者和Broker建立通道。
3、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。
4、Exchange将消息转发到指定的Queue(队列)
—-接收消息—–
1、消费者和Broker建立TCP连接
2、消费者和Broker建立通道
3、消费者监听指定的Queue(队列)
4、当有消息到达Queue时Broker默认将消息推送给消费者。
5、消费者接收到消息。
生产者操作流程如下:
1)创建连接
2)创建通道
3)声明队列
4)发送消息
消费者操作流程如下:
1)创建连接
2)创建通道
3)声明队列
4)监听队列
5)接收消息
6)ack回复
四. RabbitMq代码实现
引入pom文件:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
</dependencies>
编写连接工具类:
package com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 编写连接工具类
*/
public class ConnectionUtil {
/**
* 这里说一下配置虚拟主机和用户名密码
*
* 为什么会有虚拟主机:
*
* 当我们在创建用户时,会指定用户能访问一个虚拟机,并且该用户只能访问该虚拟机下的队列和交换机,如果没有指定,默认的是”/”;
* 一个rabbitmq服务器上可以运行多个vhost,以便于适用不同的业务需要,这样做既可以满足权限配置的要求,
* 也可以避免不同业务之间队列、交换机的命名冲突问题,因为不同vhost之间是隔离的。
* @return
* @throws IOException
* @throws TimeoutException
*/
public static Connection getConnection() throws IOException, TimeoutException {
//连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.8.144");
//连接5672端口 注意15672为工具界面端口 25672为集群端口
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("chaowei");
factory.setPassword("123456");
//获取连接
Connection connection = factory.newConnection();
return connection;
}
}
六种工作模式
1.1 simple简单模式
1)消息产生后将消息放入队列
2)消息的消费者(consumer) 监听(while) 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患 消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失)应用场景:聊天(中间有一个过度的服务器;p端,c端)
生产者:
package com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.simple;
import com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* <h4>springboot-rabbitmq</h4>
* <p>简单模式 生产者</p>
* @author : lgn
* @date : 2021-09-17 14:54
**/
public class Sender {
private final static String QUEUE_NAME = "simple_queue";
/**
* 简单模式:一个生产者,一个消费者
* @param args
* @throws IOException
* @throws TimeoutException
*/
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明队列
/**
* 队列名
* 是否持久化
* 是否排外 即只允许该channel访问该队列 一般等于true的话用于一个队列只能有一个消费者来消费的场景
* 是否自动删除 消费完删除
* 其他属性
*
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//消息内容
/**
* 交换机
* 队列名
* 其他属性 路由
* 消息body
*/
String message = "错的不是我,是这个世界~";
channel.basicPublish("", QUEUE_NAME,null,message.getBytes());
System.out.println("[x]Sent '"+message + "'");
//最后关闭通关和连接
channel.close();
connection.close();
}
}
消费者:
package com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.ConnectionUtil;
import com.rabbitmq.client.QueueingConsumer;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* <p>简单模式 消费者</p>
* @author lgn
* @version 1.0
* @date 2021/9/17 14:58
*/
public class Receiver {
private final static String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws IOException, InterruptedException, TimeoutException {
//获取连接
Connection connection = ConnectionUtil.getConnection();
//获取通道
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(QUEUE_NAME, true, consumer);
while(true){
//该方法会阻塞
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("[x] Received '"+message+"'");
}
}
}
1.2 work工作模式(资源的竞争)
1)消息产生者将消息放入队列消费者可以有多个,消费者1,消费者2,同时监听同一个队列,消息被消费?C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患,高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize,与同步锁的性能不一样。保证一条消息只能被一个消费者使用)
2)应用场景:红包;大项目中的资源调度(任务分配系统不需知道哪一个任务执行系统在空闲,直接将任务扔到消息队列中,空闲的系统自动争抢)
生产者:
package com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.work;
import com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* work模式 生产者
* @author lgn
* @version 1.0
* @date 2021/9/17 15:18
*/
public class Sender {
private final static String QUEUE_NAME = "queue_work";
/**
* work模式:一个生产者,多个消费者,每个消费者获取到的消息唯一。
* 消息产生者将消息放入队列消费者可以有多个,消费者1,消费者2,同时监听同一个队列,消息被消费?
* C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患,高并发情况下,默认会产生某一个消息被多个消费者共同使用,
* 可以设置一个开关(syncronize,与同步锁的性能不一样) 保证一条消息只能被一个消费者使用)
* 应用场景:红包;大项目中的资源调度(任务分配系统不需知道哪一个任务执行系统在空闲,直接将任务扔到消息队列中,
* 空闲的系统自动争抢)
* @param args
* @throws IOException
* @throws InterruptedException
* @throws TimeoutException
*/
public static void main(String[] args) throws IOException, InterruptedException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for(int i = 0; i < 100; i++){
String message = "周璐老师是个暴躁老哥" + i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("[x] Sent '"+message + "'");
Thread.sleep(i*10);
}
channel.close();
connection.close();
}
}
work 消费者1:
package com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.work;
import com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* work 消费者1
* @author lgn
* @version 1.0
* @date 2021/9/17 15:25
*/
public class Receiver1 {
private final static String QUEUE_NAME = "queue_work";
/**
* 结果:
*
* 1、一条消息只会被一个消费者接收;
*
* 2、rabbit采用轮询的方式将消息是平均发送给消费者的;
*
* 3、消费者在处理完某条消息后,才会收到下一条消息。
* @param args
* @throws IOException
* @throws InterruptedException
* @throws TimeoutException
*/
public static void main(String[] args) throws IOException, InterruptedException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false,false, false,null);
//同一时刻服务器只会发送一条消息给消费者
channel.basicQos(1);
QueueingConsumer consumer = new QueueingConsumer(channel);
//关于手工确认 待之后有时间研究下
channel.basicConsume(QUEUE_NAME, false, consumer);
while(true){
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("[消费者1] Received1 '"+message+"'");
Thread.sleep(10);
//返回确认状态
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
work 消费者2:
package com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.work;
import com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* work 消费者2
* @author lgn
* @version 1.0
* @date 2021/9/17 15:29
*/
public class Receiver2 {
private final static String QUEUE_NAME = "queue_work";
public static void main(String[] args) throws IOException, InterruptedException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false,false, false,null);
//同一时刻服务器只会发送一条消息给消费者
channel.basicQos(1);
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(QUEUE_NAME, false, consumer);
while(true){
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("[消费者2] Received2 '"+message+"'");
Thread.sleep(1000);
//返回确认状态
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
1.3 publish/subscribe发布订阅(共享资源)
1)X代表交换机rabbitMQ内部组件,erlang 消息产生者是代码完成,代码的执行效率不高,消息产生者将消息放入交换机,交换机发布订阅把消息发送到所有消息队列中,对应消息队列的消费者拿到消息进行消费
2)相关场景:邮件群发,群聊天,广播(广告)
订阅模式 生产者:
package com.nanlist.springboot.rabbitmq.common.ConnectionUtil.fanout;
import com.nanlist.springboot.rabbitmq.common.ConnectionUtil.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
* 订阅模式 生产者
* 订阅模式:一个生产者发送的消息会被多个消费者获取。
* 消息产生者将消息放入交换机,交换机发布订阅把消息发送到所有消息队列中,对应消息队列的消费者拿到消息进行消费
* 相关场景:邮件群发,群聊天,广播(广告)
* @author lgn
* @version 1.0
* @date 2021/9/17 16:42
*/
public class Send {
private final static String EXCHANGE_NAME = "test_exchange_fanout";
/**
* X代表交换机rabbitMQ内部组件,erlang 消息产生者是代码完成,代码的执行效率不高,消息产生者将消息放入交换机,
* 交换机发布订阅把消息发送到所有消息队列中,对应消息队列的消费者拿到消息进行消费
* 相关场景:邮件群发,群聊天,广播(广告)
* @param args
*/
public static void main(String[] args)
{
try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明交换机(分发:发布/订阅模式)
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//发送消息
for (int i = 0; i < 10; i++)
{
String message = "华晨宇是不是傻逼? " + i+"号嘉宾,觉得华晨宇十个傻逼!";
System.out.println("[send]:" + message);
//发送消息
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("utf-8"));
Thread.sleep(5 * i);
}
channel.close();
connection.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
订阅模式 消费者1:
package com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.fanout;
import com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 订阅模式 消费者1
* @author lgn
* @version 1.0
* @date 2021/9/17 16:44
*/
public class ReceiveEmail {
//交换机名称
private final static String EXCHANGE_NAME = "test_exchange_fanout";
//队列名称
private static final String QUEUE_NAME = "test_queue_email";
public static void main(String[] args) {
try {
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
final Channel channel = connection.createChannel();
//声明交换机(分发:发布/订阅模式)
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//将队列绑定到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
//保证一次只分发一个
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//定义消费者
DefaultConsumer 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("[email] Receive message:" + message);
try
{
//消费者休息2s处理业务
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
System.out.println("[1] done");
//手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//设置手动应答
boolean autoAck = false;
//监听队列
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
catch (IOException | TimeoutException e)
{
e.printStackTrace();
}
}
}
订阅模式 消费者2:
package com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.fanout;
import com.niezhiliang.springboot.rabbitmq.common.ConnectionUtil.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 订阅模式 消费者2
* @author lgn
* @version 1.0
* @date 2021/9/17 16:46
*/
public class ReceivePhone {
//交换机名称
private final static String EXCHANGE_NAME = "test_exchange_fanout";
//队列名称
private static final String QUEUE_NAME = "test_queue_phone";
public static void main(String[] args) {
try {
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
final Channel channel = connection.createChannel();
//声明交换机(分发:发布/订阅模式)
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//将队列绑定到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
//保证一次只分发一个
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//定义消费者
DefaultConsumer 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("[phone] Receive message:" + message);
try
{
//消费者休息2s处理业务
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
System.out.println("[2] done");
//手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//设置手动应答
boolean autoAck = false;
//监听队列
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
catch (IOException | TimeoutException e)
{
e.printStackTrace();
}
}
}
1.4 routing路由模式:
1)消息生产者将消息发送给交换机按照路由判断,路由是字符串(info) 当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息
2)根据业务功能定义路由字符串
3)从系统的代码逻辑中获取对应的功能字符串,将消息任务扔到对应的队列中业务场景:error 通知;EXCEPTION;错误通知的功能;传统意义的错误通知;客户通知;利用key路由,可以将程序中的错误封装成消息传入到消息队列中,开发者可以自定义消费者,实时接收错误
路由模式 生产者:
package com.siyi.routing;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
public class Producer {
//交换机名称
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 = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
/**
* 声明交换机
* 参数1:交换机名称
* 参数2:交换机类型,fanout,toppic,direct,headers
*/
channel.exchangeDeclare(DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(DIRECT_QUEUE_INSERT,true,false,false,null);
channel.queueDeclare(DIRECT_QUEUE_UPDATE,true,false,false,null);
//队列绑定交换机
channel.queueBind(DIRECT_QUEUE_INSERT,DIRECT_EXCHANGE,"insert");
channel.queueBind(DIRECT_QUEUE_UPDATE,DIRECT_EXCHANGE,"update");
//发送消息
String message = "新增了商品。路由模式:routing key 为 insert";
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish(DIRECT_EXCHANGE,"insert",null,message.getBytes());
System.out.println("已发送消息:"+message);
//发送消息
message = "修改了商品。路由模式:routing key 为 update";
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish(DIRECT_EXCHANGE,"update",null,message.getBytes());
System.out.println("已发送消息:"+message);
//关闭资源
channel.close();
connection.close();
}
}
路由模式 消费者1:
package com.siyi.routing;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer1 {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道(频道)
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
//声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.DIRECT_QUEUE_INSERT,true,false,false,null);
//队列绑定交换机
channel.queueBind(Producer.DIRECT_QUEUE_INSERT, Producer.DIRECT_EXCHAGE,"insert");
//创建消费这,并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel) {
/**
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,
* 消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者1-接收到的消息为:" + new String(body, "utf8"));
}
};
//监听消息
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(Producer.DIRECT_QUEUE_INSERT, true, consumer);
}
}
路由模式 消费者2:
package com.siyi.routing;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer2 {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道(频道)
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
//声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.DIRECT_QUEUE_UPDATE,true,false,false,null);
//队列绑定交换机
channel.queueBind(Producer.DIRECT_QUEUE_UPDATE, Producer.DIRECT_EXCHAGE,"update");
//创建消费这,并设置消息处理
DefaultConsumer consumer = new DefaultConsumer(channel) {
/**
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,
* 消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者2-接收到的消息为:" + new String(body, "utf8"));
}
};
//监听消息
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(Producer.DIRECT_QUEUE_UPDATE, true, consumer);
}
}
1.5 topic 主题模式(路由模式的一种)
1)星号井号代表通配符
2)星号代表多个单词,井号代表一个单词
3)路由功能添加模糊匹配
4)消息产生者产生消息,把消息交给交换机
5)交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费
topic模式 生产者:
public class TopicProducer {
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
HashMap<String,String> hashMap=new HashMap<>(16);
hashMap.put("a.orange.b","消费a.orange.b");
hashMap.put("a.b.rabbit","消费a.b.rabbit");
hashMap.put("lazy.orange.b.c","消费lazy.orange.b.c");
for (Map.Entry<String,String> item:hashMap.entrySet()) {
// 发送消息
channel.basicPublish(TopicCustomerOne.EXCHANGE_NAME, item.getKey(), null, item.getValue().getBytes("UTF-8"));
}
System.out.println("消息发送");
}
}
topic消费者1:
public class TopicCustomerOne {
public static final String EXCHANGE_NAME="topic_log";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 临时队列
String queue = channel.queueDeclare().getQueue();
// 绑定交换机
channel.queueBind(queue,EXCHANGE_NAME,"*.orange.*");
System.out.println("消息的接收.......");
DeliverCallback deliverCallback = (String consumerTag, Delivery message) -> {
System.out.println("消息消费: "+new String(message.getBody())+" 绑定的routingKey: "+message.getEnvelope().getRoutingKey());
// 手动应答
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
CancelCallback cancelCallback=(consumerTag)->{
System.out.println("消息消费被中断");
};
// 消费消息
channel.basicConsume(queue,false,deliverCallback,cancelCallback);
}
}
topic消费者2:
public class TopicCustomerTwo {
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare(TopicCustomerOne.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 临时队列
String queue = channel.queueDeclare().getQueue();
// 绑定交换机
channel.queueBind(queue,TopicCustomerOne.EXCHANGE_NAME,"*.*.rabbit");
// 绑定交换机
channel.queueBind(queue,TopicCustomerOne.EXCHANGE_NAME,"lazy.#");
System.out.println("消息的接收.......");
DeliverCallback deliverCallback = (String consumerTag, Delivery message) -> {
System.out.println("消息消费: "+new String(message.getBody())+" 绑定的routingKey: "+message.getEnvelope().getRoutingKey());
// 手动应答
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
CancelCallback cancelCallback=(consumerTag)->{
System.out.println("消息消费被中断");
};
// 消费消息
channel.basicConsume(queue,false,deliverCallback,cancelCallback);
}
}
rpc模式:
RPC即客户端远程调用服务端的方法 ,使用MQ可以实现RPC的异步调用,基于Direct交换机实现,流程如下:
1、客户端即是生产者就是消费者,向RPC请求队列发送RPC调用消息,同时监听RPC响应队列。
2、服务端监听RPC请求队列的消息,收到消息后执行服务端的方法,得到方法返回的结果
3、服务端将RPC方法 的结果发送到RPC响应队列
4、客户端(RPC调用方)监听RPC响应队列,接收到RPC调用结果。
小结
1、publish/subscribe与work queues有什么区别。
区别:
1)work queues不用定义交换机,而publish/subscribe需要定义交换机。
2)publish/subscribe的生产方是面向交换机发送消息,work queues的生产方是面向队列发送消息(底层使用默认交换机)。
3)publish/subscribe需要设置队列和交换机的绑定,work queues不需要设置,实质上work queues会将队列绑定到默认的交换机 。
相同点:
所以两者实现的发布/订阅的效果是一样的,多个消费端监听同一个队列不会重复消费消息。
2、实质工作用什么 publish/subscribe还是work queues。
建议使用 publish/subscribe,发布订阅模式比工作队列模式更强大,并且发布订阅模式可以指定自己专用的交换机。
3、Routing模式和Publish/subscibe有啥区别?
Routing模式要求队列在绑定交换机时要指定routingkey,消息会转发到符合routingkey的队列。