RabbitMQ
MQ全称为Message Queue,即消息队列。
RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message
Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开
发中应用非常广泛。
AMPQ:高级消息队列协议
AMQP是一套公开的消息队列协议,最早在2003年被提出,它旨在从协议层定义消息通信数据的标准格式,
为的就是解决MQ市场上协议不统一的问题。RabbitMQ就是遵循AMQP标准协议开发的MQ服务。
官方:http://www.amqp.org/
JMS:Java消息服务
JMS是java提供的一套消息服务API标准,其目的是为所有的java应用程序提供统一的消息通信的标准,类似java的
jdbc,只要遵循jms标准的应用程序之间都可以进行消息通信。
它和AMQP有什么不同:jms是java语言专属的消
息服务标准,它是在api层定义标准,并且只能用于java应用;而AMQP是在协议层定义的标准,是跨语言的 。
RabbitMQ官方地址:http://www.rabbitmq.com/
应用场景:
- 任务异步处理。
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。 - 应用程序解耦合。MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
RabbitMQ优点:
- 使得简单,功能强大。
- 基于AMQP协议。
- 社区活跃,文档完善。
- 高并发性能好,这主要得益于Erlang语言。
- Spring Boot默认已集成RabbitMQ
RabbitMQ 工作原理
- Broker :消息队列服务进程,此进程包括两个部分:Exchange和Queue。
- Exchange :消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
- Queue :消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。
- Producer :消息生产者,即生产方客户端,生产方客户端将消息发送到MQ。
- Consumer :消息消费者,即消费方客户端,接收MQ转发的消息。
发送消息:
- 生产者和Broker建立TCP连接。
- 生产者和Broker建立通道。
- 生产者通过通道消息发送给Broker,由Exchange将消息进行转发。
- Exchange将消息转发到指定的Queue(队列)
接收消息:
- 消费者和Broker建立TCP连接
- 消费者和Broker建立通道
- 消费者监听指定的Queue(队列)
- 当有消息到达Queue时Broker默认将消息推送给消费者。
- 消费者接收到消息
下载与安装
注意erlang与RabbitMQ的版本匹配
1、下载安装 erlang
-
下载:http://erlang.org/download/otp_win64_20.3.exe
-
安装:以管理员方式运行下载文件,安装。
-
配置环境变量
ERLANG_HOME=E:\erl9.3。(改为自己的安装目录)
在path中添加: %ERLANG_HOME%\bin; (与前面的;隔开)
2、下载安装 RabbitMQ
-
下载:https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.7.3
-
安装:以管理员方式运行下载文件,安装。
-
查看电脑服务里面 RabbitMQ的启动是否服务。
如果没有服务,则需要安装启动服务。
-
配置环境变量
RABBITMQ_SERVER=E:\RabbitMQ\rabbitmq_server-3.7.3。(改为自己的安装目录)
在path中添加: %RABBITMQ_SERVER%\sbin; (与前面的;隔开)
-
在安装目录的skin路径下,cmd:
依次执行:
- rabbitmq-service.bat stop 停止服务
- rabbitmq-service.bat install 安装服务
- rabbitmq-service.bat start 开启服务
-
-
安装管理插件
在安装目录的skin路径下,cmd下执行:rabbitmq-plugins.bat enable rabbitmq_management
-
浏览器访问:http://localhost:15672
账号和密码都为:guest
安装注意:
-
安装 erlang 和 RabbitMQ 以管理员身份运行。
-
当卸载重新安装时会出现RabbitMQ服务注册失败,
此时需要进入注册表清理erlang
搜索RabbitMQ、ErlSrv,将对应的项全部删除。
入门Demo
-
依赖坐标
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>4.0.3</version><!--此版本与spring boot 1.5.9版本匹配--> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency>
-
消息生产者
public class producer01 { //声明队列 private static final String QUEUE = "helloWorld"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = null; Channel channel = null; try { //通过连接工厂创建新的连接和mq建立连接 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); //设置虚拟机(一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq) factory.setVirtualHost("/"); //创建与RabbitMQ服务的TCP连接 connection = factory.newConnection(); //创建与Exchange的会话通道,生产者和mq服务都在这个通道中完成 channel = connection.createChannel(); /** * 【声明队列】,如果Rabbit中没有此队列将自动创建 * param1:String queue 队列名称 * param2:boolean durable 是否持久化,true:mq重启后队列还在 * param3:boolean exclusive 队列是否独占此连接(队列只允许在该连接中访问,如果连接关闭队列自动删除,设置为true可以用于临时队列的创建 ) * param4:boolean autoDelete 队列不再使用时是否自动删除此队列 * param5:Map<String,Object> arguments 队列参数。设置队列的扩展参数(存活时间、......) */ channel.queueDeclare(QUEUE, true, false, false, null); //发送消息 String message = "你好,这是RabbitMQ"; /** * 【消息发布】方法 * param1:Exchange的名称,如果没有指定,则使用Default Exchange。 默认的交换机:需将routingKey设置为队列名称 * param2:routingKey,消息的路由Key,用于Exchange(交换机)将消息转发到指定的消息队列 * param3:消息包含的属性 * param4:消息体 */ channel.basicPublish("",QUEUE,null,message.getBytes()); System.out.println("send message:"+message); } finally { try { if(channel != null){ channel.close(); } if(connection != null){ connection.close(); } } catch (Exception e) { e.printStackTrace(); } } } }
-
消息消费者
public class consumer01 { //声明队列 private static final String QUEUE = "helloWorld"; public static void main(String[] args) { Connection connection = null; Channel channel = null; try { //通过连接工厂创建新的连接和mq建立连接 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); //设置虚拟机(一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq) factory.setVirtualHost("/"); //创建与RabbitMQ服务的TCP连接 connection = factory.newConnection(); //创建与Exchange的会话通道,生产者和mq服务都在这个通道中完成 channel = connection.createChannel(); /** * 【声明队列】,如果Rabbit中没有此队列将自动创建 * param1:队列名称 * param2:是否持久化 * param3:队列是否独占此连接 * param4:队列不再使用时是否自动删除此队列 * param5:队列参数 */ channel.queueDeclare(QUEUE, true, false, false, null); //实现消费方法 Consumer consumer = new DefaultConsumer(channel){ /** * 消费者接收消息调用此方法 * @param consumerTag 消费者的标签,在channel.basicConsume()去指定 * @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送) * @param properties 额外属性 * @param body 消息体 */ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String exchange = envelope.getExchange();//交换机 String routingKey = envelope.getRoutingKey();//路由key long deliveryTag = envelope.getDeliveryTag();//消息在管道中传输的id。用于确认消息已接收 String message = new String(body, "utf-8"); System.out.println("receive message:"+message); } }; /** * 【监听队列】String queue, boolean autoAck,Consumer callback * 参数明细 * param1、队列名称 * param2、是否自动回复。 设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息, 设置为false则需要手动回复 * param3、消费消息的方法,消费者接收到消息后调用此方法 */ channel.basicConsume(QUEUE,true,consumer); } catch (Exception e) { e.printStackTrace(); } } }
RabbitMQ 工作模式
入门案例之工作模式:
一个生产者,一个队列,一个消费者
Work Queues工作队列模式:
一个生产者,一个队列,多个消费者。
特点:
- 一条消息只会被一个消费者接收;
- rabbit采用轮询的方式将消息是平均发送给消费者的;
- 消费者在处理完某条消息后,才会收到下一条消息
应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
Publish/Subscribe 发布/订阅模式:
一个生产者,一个交换机,多个队列和多个消费者(一个队列可以绑定多个消费者,工作队列模式)
指定交换机类型为:BuiltinExchangeType.FANOUT
-
每个消费者监听自己的队列。
-
生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息
案例:用户通知,当用户充值成功或转账完成系统通知用户,通知方式有短信、邮件多种方法 。生产者:
public class producer02_publish { //声明邮件队列和短信队列 private static final String QUEUE_INFORM_EMAIL = "queue_inform_email"; private static final String QUEUE_INFORM_SMS = "queue_inform_sms"; //声明交换机 private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = null; Channel channel = null; try { //通过连接工厂创建新的连接和mq建立连接 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); //设置虚拟机(一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq) factory.setVirtualHost("/"); //创建与RabbitMQ服务的TCP连接 connection = factory.newConnection(); //创建与Exchange的会话通道,生产者和mq服务都在这个通道中完成 channel = connection.createChannel(); //声明邮件队列和短信队列 channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null); channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null); /** * 【声明交换机】 * 参数明细 * 1、交换机名称 * 2、交换机类型, * fanout、用于发布/订阅模式 * topic、用于topic模式 * direct、用于direct模式 * headers、用于headers模式 */ channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT); //交换机和对列进行绑定 channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,""); channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_FANOUT_INFORM,""); //发送消息,指定交换机 String message = "send message to user"; for (int i = 0; i < 5; i++) { channel.basicPublish(EXCHANGE_FANOUT_INFORM,"",null,message.getBytes()); System.out.println("send message:"+message); } } finally { try { if(channel != null){ channel.close(); } if(connection != null){ connection.close(); } } catch (Exception e) { e.printStackTrace(); } } } }
消费者1(接收邮件):
public class consumer02_subscribe_email { //声明邮件队列 private static final String QUEUE_INFORM_EMAIL = "queue_inform_email"; //声明交换机 private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform"; public static void main(String[] args) { Connection connection = null; Channel channel = null; try { //通过连接工厂创建新的连接和mq建立连接 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); //设置虚拟机(一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq) factory.setVirtualHost("/"); //创建与RabbitMQ服务的TCP连接 connection = factory.newConnection(); //创建与Exchange的会话通道,生产者和mq服务都在这个通道中完成 channel = connection.createChannel(); //声明邮件队列 channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null); //声明交换机 channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT); //交换机和邮件队列进行绑定 channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,""); //消费方法 Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String exchange = envelope.getExchange();//交换机 String routingKey = envelope.getRoutingKey();//路由key long deliveryTag = envelope.getDeliveryTag();//消息在管道中传输的id String message = new String(body, "utf-8"); System.out.println("receive message:"+message); } }; //监听邮件队列 channel.basicConsume(QUEUE_INFORM_EMAIL,true,consumer); } catch (Exception e) { e.printStackTrace(); } } }
消费者2(接收短信):
public class consumer02_subscribe_sms { //声明短信队列 private static final String QUEUE_INFORM_SMS = "queue_inform_sms"; //声明交换机 private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform"; public static void main(String[] args) { Connection connection = null; Channel channel = null; try { //通过连接工厂创建新的连接和mq建立连接 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); //设置虚拟机(一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq) factory.setVirtualHost("/"); //创建与RabbitMQ服务的TCP连接 connection = factory.newConnection(); //创建与Exchange的会话通道,生产者和mq服务都在这个通道中完成 channel = connection.createChannel(); //声明短信队列 channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null); //声明交换机 channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT); //交换机和短信队列进行绑定 channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_FANOUT_INFORM,""); //消费方法 Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String exchange = envelope.getExchange();//交换机 String routingKey = envelope.getRoutingKey();//路由key long deliveryTag = envelope.getDeliveryTag();//消息在管道中传输的id String message = new String(body, "utf-8"); System.out.println("receive message:"+message); } }; //监听短信队列 channel.basicConsume(QUEUE_INFORM_SMS,true,consumer); } catch (Exception e) { e.printStackTrace(); } } }
publish/subscribe模式与work queues模式区别?
区别:
- work queues不用定义交换机,而publish/subscribe需要定义交换机。
- publish/subscribe的生产方是面向交换机发送消息,work queues的生产方是面向队列发送消息(底层使用默认交换机)。
- publish/subscribe需要设置队列和交换机的绑定,work queues不需要设置,实质上work queues会将队列绑
定到默认的交换机 。
相同点:
- 所以两者实现的发布/订阅的效果是一样的,多个消费端监听同一个队列不会重复消费消息。
实质工作用什么 publish/subscribe还是work queues?
建议使用 publish/subscribe,发布订阅模式比工作队列模式更强大,并且发布订阅模式可以指定自己专用的交换机。
Routing 路由模式:
一个生产者,一个交换机,多个队列和多个消费者(队列和消费者一一对应)。
交换机通过不同的routingkey来分配消息到不同的队列
指定交换机类型为:BuiltinExchangeType.DIRECT
特点:
-
一个交换机绑定多个队列,每个队列设置routingkey值,一个队列可以设置多个routingkey值
-
每个消费者监听自己的队列,并且设置routingkey。
-
生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列。
案例:用户通知。当routingkey为email时,队列中routingkey为email的消费者接收;当routingkey为sms时,队列中routingkey为sms的消费者接收;当routingkey为inform时,队列中routingkey为infrom的消费者接收;(inform 为队列的公共routingkey)生产者:
public class producer03_routing { //声明队列 private static final String QUEUE_INFORM_EMAIL = "queue_inform_email"; private static final String QUEUE_INFORM_SMS = "queue_inform_sms"; //声明交换机 private static final String EXCHANGE_ROUTING_INFORM = "exchange_routing_inform"; //声明routingkey private static final String ROUTINGKEY_EMAIL="inform_email"; private static final String ROUTINGKEY_SMS="inform_sms"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = null; Channel channel = null; try { //通过连接工厂创建新的连接和mq建立连接 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); //设置虚拟机(一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq) factory.setVirtualHost("/"); //创建与RabbitMQ服务的TCP连接 connection = factory.newConnection(); //创建与Exchange的会话通道,生产者和mq服务都在这个通道中完成 channel = connection.createChannel(); //声明邮件队列和短信队列 channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null); channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null); //【声明交换机】 channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT); //交换机和队列进行绑定,指定routingkey channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_EMAIL); channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,"inform"); channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_SMS); channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,"inform"); //发送email消息,指定交换机和routingkey for (int i = 0; i < 5; i++) { String message = "send email message to user"; channel.basicPublish(EXCHANGE_ROUTING_INFORM,ROUTINGKEY_EMAIL,null,message.getBytes()); System.out.println("send email message:"+message); } //发送sms消息,指定交换机和routingkey for (int i = 0; i < 5; i++) { String message = "send sms message to user"; channel.basicPublish(EXCHANGE_ROUTING_INFORM,ROUTINGKEY_SMS,null,message.getBytes()); System.out.println("send sms message:"+message); } //发送公共inform消息,指定交换机和routingkey for (int i = 0; i < 5; i++) { String message = "send inform message to user"; channel.basicPublish(EXCHANGE_ROUTING_INFORM,"inform",null,message.getBytes()); System.out.println("send inform message:"+message); } } finally { try { if(channel != null){ channel.close(); } if(connection != null){ connection.close(); } } catch (Exception e) { e.printStackTrace(); } } } }
消费者1(email 和 inform):
public class consumer03_routing_email { //声明邮件队列 private static final String QUEUE_INFORM_EMAIL = "queue_inform_email"; //声明交换机 private static final String EXCHANGE_ROUTING_INFORM = "exchange_routing_inform"; //声明routingkey private static final String ROUTINGKEY_EMAIL="inform_email"; public static void main(String[] args) { Connection connection = null; Channel channel = null; try { //通过连接工厂创建新的连接和mq建立连接 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); //设置虚拟机(一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq) factory.setVirtualHost("/"); //创建与RabbitMQ服务的TCP连接 connection = factory.newConnection(); //创建与Exchange的会话通道,生产者和mq服务都在这个通道中完成 channel = connection.createChannel(); //声明邮件队列 channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null); //声明交换机 channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT); //交换机和邮件队列进行绑定 channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_EMAIL); //消费方法 Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String exchange = envelope.getExchange();//交换机 String routingKey = envelope.getRoutingKey();//路由key long deliveryTag = envelope.getDeliveryTag();//消息在管道中传输的id String message = new String(body, "utf-8"); System.out.println("receive message:"+message); } }; //监听邮件队列 channel.basicConsume(QUEUE_INFORM_EMAIL,true,consumer); } catch (Exception e) { e.printStackTrace(); } } }
消费者2(sms 和 infrom):
public class consumer03_routing_sms { //声明短信队列 private static final String QUEUE_INFORM_SMS = "queue_inform_sms"; //声明交换机 private static final String EXCHANGE_ROUTING_INFORM = "exchange_routing_inform"; //声明routingkey private static final String ROUTINGKEY_SMS="inform_sms"; public static void main(String[] args) { Connection connection = null; Channel channel = null; try { //通过连接工厂创建新的连接和mq建立连接 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setUsername("guest"); factory.setPassword("guest"); //设置虚拟机(一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq) factory.setVirtualHost("/"); //创建与RabbitMQ服务的TCP连接 connection = factory.newConnection(); //创建与Exchange的会话通道,生产者和mq服务都在这个通道中完成 channel = connection.createChannel(); //声明短信队列 channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null); //声明交换机 channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT); //交换机和短信队列进行绑定 channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_SMS); //消费方法 Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String exchange = envelope.getExchange();//交换机 String routingKey = envelope.getRoutingKey();//路由key long deliveryTag = envelope.getDeliveryTag();//消息在管道中传输的id String message = new String(body, "utf-8"); System.out.println("receive message:"+message); } }; //监听短信队列 channel.basicConsume(QUEUE_INFORM_SMS,true,consumer); } catch (Exception e) { e.printStackTrace(); } } }
Routing模式和Publish/subscibe模式区别?
Routing模式要求队列在绑定交换机时要指定routingkey,消息会转发到符合routingkey的队列
Topics 通配符模式:
通配符:
- #:匹配一个或多个词,中间以.隔开。eg:inform.sms、inform.email.sms
- *:只能匹配一个词,中间以.隔开。eg:inform.sms、inform.email
特点:
- 一个交换机可以绑定多个队列,每个队列可以设置一个或多个带通配符的routingkey
- 每个消费者监听自己的队列,并且设置带统配符的routingkey。
- 生产者将消息发给broker,由交换机根据routingkey来匹配消息到指定的队列。
指定交换机类型为:BuiltinExchangeType.topic
案例:生产者匹配三个routingkey。inform.sms用于发送短信消息;inform.email用于发送邮件消息;inform.#.#用于发送短信和email消息
生产者:
public class producer04_topics {
//声明队列
private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
//声明交换机
private static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
//使用通配符声明routingkey
private static final String ROUTINGKEY_EMAIL="inform.#.email.#";
private static final String ROUTINGKEY_SMS="inform.#.sms.#";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = null;
Channel channel = null;
try {
//通过连接工厂创建新的连接和mq建立连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
//设置虚拟机(一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq)
factory.setVirtualHost("/");
//创建与RabbitMQ服务的TCP连接
connection = factory.newConnection();
//创建与Exchange的会话通道,生产者和mq服务都在这个通道中完成
channel = connection.createChannel();
//声明邮件队列和短信队列
channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
//【声明交换机】
channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
//交换机和队列进行绑定,指定routingkey
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_EMAIL);
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_SMS);
//发送email消息,指定交换机和email的routingkey
for (int i = 0; i < 5; i++) {
String message = "send email message to user";
channel.basicPublish(EXCHANGE_TOPICS_INFORM,"inform.email",null,message.getBytes());
System.out.println("send email message:"+message);
}
//发送sms消息,指定交换机和sms的routingkey
for (int i = 0; i < 5; i++) {
String message = "send sms message to user";
channel.basicPublish(EXCHANGE_TOPICS_INFORM,"inform.sms",null,message.getBytes());
System.out.println("send sms message:"+message);
}
//发送公共inform消息,指定交换机和routingkey
for (int i = 0; i < 5; i++) {
String message = "send sms and email message to user";
channel.basicPublish(EXCHANGE_TOPICS_INFORM,"inform.sms.email",null,message.getBytes());
System.out.println("send inform message:"+message);
}
} finally {
try {
if(channel != null){
channel.close();
}
if(connection != null){
connection.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
消费者1(接收sms):
public class consumer04_topics_sms {
//声明短信队列
private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
//声明交换机
private static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
//使用通配符声明routingkey
private static final String ROUTINGKEY_SMS="inform.#.sms.#";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
//通过连接工厂创建新的连接和mq建立连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
//设置虚拟机(一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq)
factory.setVirtualHost("/");
//创建与RabbitMQ服务的TCP连接
connection = factory.newConnection();
//创建与Exchange的会话通道,生产者和mq服务都在这个通道中完成
channel = connection.createChannel();
//声明短信队列
channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
//声明交换机
channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
//交换机和短信队列进行绑定
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_SMS);
//消费方法
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String exchange = envelope.getExchange();//交换机
String routingKey = envelope.getRoutingKey();//路由key
long deliveryTag = envelope.getDeliveryTag();//消息在管道中传输的id
String message = new String(body, "utf-8");
System.out.println("receive message:"+message);
}
};
//监听短信队列
channel.basicConsume(QUEUE_INFORM_SMS,true,consumer);
} catch (Exception e) {
e.printStackTrace();
}
}
}
消费者2(接收email):
public class consumer04_topics_email {
//声明邮件队列
private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
//声明交换机
private static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
//使用通配符声明routingkey
private static final String ROUTINGKEY_EMAIL="inform.#.email.#";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
//通过连接工厂创建新的连接和mq建立连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
//设置虚拟机(一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq)
factory.setVirtualHost("/");
//创建与RabbitMQ服务的TCP连接
connection = factory.newConnection();
//创建与Exchange的会话通道,生产者和mq服务都在这个通道中完成
channel = connection.createChannel();
//声明邮件队列
channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
//声明交换机
channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
//交换机和邮件队列进行绑定
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_EMAIL);
//消费方法
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String exchange = envelope.getExchange();//交换机
String routingKey = envelope.getRoutingKey();//路由key
long deliveryTag = envelope.getDeliveryTag();//消息在管道中传输的id
String message = new String(body, "utf-8");
System.out.println("receive message:"+message);
}
};
//监听邮件队列
channel.basicConsume(QUEUE_INFORM_EMAIL,true,consumer);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Topic模式更多加强大,它可以实现Routing、publish/subscirbe模式的功能。
Header模式:
header模式与routing不同的地方在于,header模式取消routingkey,使用header中的 key/value(键值对)匹配
队列。
队列与交换机绑定的代码与之前不同
指定交换机类型为:BuiltinExchangeType.HEADERS
生产者:
Map<String, Object> headers_email = new Hashtable<String, Object>();
headers_email.put("inform_type", "email");
Map<String, Object> headers_sms = new Hashtable<String, Object>();
headers_sms.put("inform_type", "sms");
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_HEADERS_INFORM,"",headers_email);
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_HEADERS_INFORM,"",headers_sms);
String message = "email inform to user"+i;
Map<String,Object> headers = new Hashtable<String, Object>();
headers.put("inform_type", "email");//匹配email通知消费者绑定的header
//headers.put("inform_type", "sms");//匹配sms通知消费者绑定的header
AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties.Builder();
properties.headers(headers);
//Email通知
channel.basicPublish(EXCHANGE_HEADERS_INFORM, "", properties.build(), message.getBytes());
消费者:
channel.exchangeDeclare(EXCHANGE_HEADERS_INFORM, BuiltinExchangeType.HEADERS);
Map<String, Object> headers_email = new Hashtable<String, Object>();
headers_email.put("inform_email", "email");
//交换机和队列绑定
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_HEADERS_INFORM,"",headers_email);
//指定消费队列
channel.basicConsume(QUEUE_INFORM_EMAIL, true, consumer);
RPC远程调用模式:
RPC即客户端远程调用服务端的方法 ,使用MQ可以实现RPC的异步调用,基于Direct交换机实现。
流程如下:
- 客户端即是生产者就是消费者,向RPC请求队列发送RPC调用消息,同时监听RPC响应队列。
- 服务端监听RPC请求队列的消息,收到消息后执行服务端的方法,得到方法返回的结果
- 服务端将RPC方法 的结果发送到RPC响应队列
- 客户端(RPC调用方)监听RPC响应队列,接收到RPC调用结果
RabbitMQ 工作模式总结
生产者所必需的参数:
- 入门案例模式、Work Queues模式:只需设置队列。routingkey为" “。路由使用默认的,设置为” "即可。
- Publish/Subscribe模式:只需设置队列、路由。routingkey为" "。路由类型为:BuiltinExchangeType.FANOUT
- Routing模式:只需设置队列、路由、routingkey。路由类型为:BuiltinExchangeType.DIRECT
- Topics模式:只需设置队列、路由、routingkey用通配符表示。路由类型为:BuiltinExchangeType.TOPIC
消费者的参数根据生产者的参数和模式决定
SpringBoot 整合 RabbitMQ
以Topics模式为例。给用户发送email消息、短信消息
环境
-
坐标依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>
-
添加配置文件:application.yml
server: port: 44001 spring: application: name: test-rabbitmq-producer rabbitmq: host: 127.0.0.1 port: 5672 username: guest password: guest virtualHost: /
生产者
-
编写RabbitMQ配置类
注意该类所在的包必须与SpringBoot的启动类同级(在同一个包下)
/** * 以【topics模式】为例 * 声明RabbitMQ的交换机、队列、绑定交换机和队列 */ @Configuration public class RabbitmqConfig { //声明队列 public static final String QUEUE_INFORM_EMAIL = "queue_inform_email"; public static final String QUEUE_INFORM_SMS = "queue_inform_sms"; //声明交换机 public static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform"; //routingkey public static final String ROUTINGKEY_EMAIL="inform.#.email.#"; public static final String ROUTINGKEY_SMS="inform.#.sms.#"; /** * 【配置交换机】 * ExchangeBuilder提供了fanout、direct、topic、header交换机类型的配置 * @return */ @Bean(EXCHANGE_TOPICS_INFORM) public Exchange EXCHANGE_TOPICS_INFORM(){ //durable(true)持久化,消息队列重启后交换机仍然存在 return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM).durable(true).build(); } /** * 【配置email消息队列】 * @return */ @Bean(QUEUE_INFORM_EMAIL) public Queue QUEUE_INFORM_EMAIL(){ return new Queue(QUEUE_INFORM_EMAIL); } /** * 【配置SMS消息队列】 * @return */ @Bean(QUEUE_INFORM_SMS) public Queue QUEUE_INFORM_SMS(){ return new Queue(QUEUE_INFORM_SMS); } /** * 【email队列和交换机绑定】 * @param queue email队列 * @param exchange 交换机 * @return */ @Bean public Binding BINDING_QUEUE_INFORM_EMAIL(@Qualifier(QUEUE_INFORM_EMAIL) Queue queue, @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange){ //with(ROUTINGKEY_EMAIL):指定routingkey //.noargs():设置参数为 null return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_EMAIL).noargs(); } /** * 【SMS队列和交换机绑定】 * @param queue sms队列 * @param exchange 交换机 * @return */ @Bean public Binding BINDING_QUEUE_INFORM_SMS(@Qualifier(QUEUE_INFORM_SMS) Queue queue, @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange){ //with(ROUTINGKEY_SMS):指定routingkey //.noargs():设置参数为 null return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_SMS).noargs(); } }
-
编写启动类
@SpringBootApplication public class RabbitmqApplication { public static void main(String[] args) { SpringApplication.run(RabbitmqApplication.class); } }
-
单元测试,发送消息
注意,需要先启动工程,再进行单元测试
@SpringBootTest @RunWith(SpringRunner.class) public class producer05_topics_springboot { //注入RabbitTemplate @Autowired private RabbitTemplate rabbitTemplate; /** * 【发送email消息】 */ @Test public void testSendEmail(){ String message = "send email to user"; /** * 发送消息 * param1:交换机 * param2:routingkey * param3:消息体 */ rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM,"inform.email",message); } /** * 【发送sms消息】 */ @Test public void testSendSms(){ String message = "send sms to user"; rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM,"inform.sms",message); } /** * 【发送sms和email消息】 */ @Test public void testSendSmsAndEmail(){ String message = "send sms and email to user"; rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM,"inform.sms.email",message); } }
消费者
-
编写监听类,获取消息
在方法上使用**@RabbitListener**监听消息队列。参数使用String、Message类、Channel类都可以获取到消息内容
/** * 消息监听类 */ @Component public class RabbitmqHandler { /** * 【监听email队列】 * @param message */ @RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_EMAIL}) public void handler_email(String message){ System.out.println("receive email:"+message); } /** * 【监听sms队列】 * @param message */ @RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_SMS}) public void handler_sms(Message message){ System.out.println("receive sms:"+message.getBody().toString()); } }
age = “send sms to user”;
rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM,“inform.sms”,message);
}
/**
* 【发送sms和email消息】
*/
@Test
public void testSendSmsAndEmail(){
String message = "send sms and email to user";
rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM,"inform.sms.email",message);
}
}
### 消费者
1. 编写监听类,获取消息
在方法上使用**@RabbitListener**监听消息队列。参数使用String、Message类、Channel类都可以获取到消息内容
```java
/**
* 消息监听类
*/
@Component
public class RabbitmqHandler {
/**
* 【监听email队列】
* @param message
*/
@RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_EMAIL})
public void handler_email(String message){
System.out.println("receive email:"+message);
}
/**
* 【监听sms队列】
* @param message
*/
@RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_SMS})
public void handler_sms(Message message){
System.out.println("receive sms:"+message.getBody().toString());
}
}