本篇博客开始接触到exchange交换器,后续的几个模式都与exchange有莫大的关系,本身RabbitMQ就是基于exchange进行消息分发,如发布订阅就是基于exchange实现。与传统遵循JMS规范稍有不同。
RabbitMQ发布/订阅模式三种使用姿势:
用 [TOC]
来生成目录:
4.0.0
edurabbitmq-client
rabbitmq-client
1.0-SNAPSHOT
com.rabbitmq
amqp-client
5.3.0
package com.edu.rabbitmq;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author : alex
* @version :1.0.0
* @Date : create by 2018/7/19 22:01
* @description :获取连接
* @note 注意事项
*/
public class ConnectionUtils {
//获取接连
public static Connection getConnection() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
//设置连接MQ的IP地址
factory.setHost("192.168.199.128");
//设置连接端口号
factory.setPort(5672);
//设置要接连MQ的库(域)
factory.setVirtualHost("/test_vh");
//连接帐号
factory.setUsername("root");
//连接密码
factory.setPassword("123456");
return factory.newConnection();
}
}
package com.edu.handler;
import com.edu.rabbitmq.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author : alex
* @version :1.0.0
* @Date : create by 2018/7/19 22:16
* @description :消息生产者
* @note 注意事项
*/
public class Send {
//队列名称
public static String QUEUE_NAME = "test_publish_and_subscribe_queue";
//定义交换器名称
public static String EXCHANGE_NAME = "test_ps";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//创建消息声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
/**
*
* 声明一个交换器
*
* 文档地址:https://rabbitmq.github.io/rabbitmq-java-client/api/current/com/rabbitmq/client/Channel.html#exchangeDeclare-java.lang.String-com.rabbitmq.client.BuiltinExchangeType-boolean-boolean-java.util.Map-
* 该方法定义了许多的重载,拿出完整参数的方法来讲
* AMQP.Exchange.DeclareOk exchangeDeclare(String exchange,
* BuiltinExchangeType type,
* boolean durable,
* boolean autoDelete,
* Map arguments)
* throws IOException
*
* @param exchange 交换器的名字
* @param type 交换器的类型:direct, topic, headers, fanout
* @param durable 是否持久化,true持久化,false不持久化
* @param autoDelete 服务器不再使用该队列时,是否自动删除,true删除,false不删除
* @param arguments 其他参数,其实是定义交换器的构造方法
*/
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
for (int i = 0; i < 10; i++) {
Thread.sleep(500);//模拟耗时操作,别发那么快
//自定义消息
String msg = "hello word" + i;
//发布消息,通过交换器名称进行发布消息,无需指定队列名称
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
System.out.println("-->send " + msg);
}
channel.close();//关闭通道
connection.close();//关闭连接
}
}
package com.edu.handler;
import com.edu.rabbitmq.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author : alex
* @version :1.0.0
* @Date : create by 2018/7/19 22:16
* @description :消息消费者
* @note 注意事项
*/
public class Customer {
//队列名称
public static String QUEUE_NAME = "test_publish_and_subscribe_queue";
//定义交换器名称
public static String EXCHANGE_NAME = "test_ps";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接
Connection connection = ConnectionUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
//获取交换器
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//创建消息声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
/**
* 交换器绑定队列
* 该方法定义了2个重载
* AMQP.Queue.BindOk queueBind(String queue,
* String exchange,
* String routingKey,
* Map arguments)
* throws IOException
*
* @param queue 队列名称
* @param exchange 交换器名称
* @param routingKey 用于绑定的路由密钥
* @param arguments 其他参数,其实是定义交换器的构造方法
*/
channel.queueBind(QUEUE_NAME, "test_ps", "");
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
//获取并转成String
String message = new String(body, "UTF-8");
System.out.println("-->1号消费者收到消息,msg:"+message);
}
};
//监听队列
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
package com.edu.handler;
import com.edu.rabbitmq.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author : alex
* @version :1.0.0
* @Date : create by 2018/7/19 22:16
* @description :消息消费者2
* @note 注意事项
*/
public class Customer2 {
//队列名称
public static String QUEUE_NAME = "test_publish_and_subscribe_queue2";
//定义交换器名称
public static String EXCHANGE_NAME = "test_ps";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接
Connection connection = ConnectionUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
//获取交换器
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//创建消息声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
/**
* 交换器绑定队列,请记住,优先启动消费者,让队列先绑定交换器,再由生产者发送消息
* 该方法定义了2个重载
* AMQP.Queue.BindOk queueBind(String queue,
* String exchange,
* String routingKey,
* Map arguments)
* throws IOException
*
* @param queue 队列名称
* @param exchange 交换器名称
* @param routingKey 用于绑定的路由密钥
* @param arguments 其他参数,其实是定义交换器的构造方法
*/
channel.queueBind(QUEUE_NAME, "test_ps", "");
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
//获取并转成String
String message = new String(body, "UTF-8");
System.out.println("-->2号消费者收到消息,msg:"+message);
}
};
//监听队列
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
4.0.0
edurabbitmq-ps-springxml
ps-springxml
1.0-SNAPSHOT
com.rabbitmq
amqp-client
4.0.0
org.springframework.amqp
spring-rabbit
1.7.3.RELEASE
org.springframework
spring-core
5.0.7.RELEASE
org.springframework
spring-beans
5.0.7.RELEASE
org.springframework
spring-context
5.0.7.RELEASE
package com.rabbit;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author: Alex
* @DateTime: 2018/8/22 19:40
* @Description: 描述
* @Version: 1.0.0
**/
public class Send {
//交换器名称,这里的exchange名称要和rabbitmq.xml里面配置对应
private static String EXCHANGE_NAME = "test_ps_spring_exchange";
public static void main(String[] args) throws InterruptedException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:context.xml");
//获取rabbit模版(等价于@Autowired)
RabbitTemplate bean = context.getBean(RabbitTemplate.class);
//循环发送30条消息
for (int i =0;i<30;i++) {
//对应exchange发送消息
bean.convertAndSend(EXCHANGE_NAME,"","hello word"+i);
Thread.sleep(500);//休眠0.5秒
}
Thread.sleep(10000);//休眠2秒后,关闭spring容器
context.close();
}
}
package customer;
/**
* @author : alex
* @version :1.0.0
* @Date : create by 2018/7/19 23:39
* @description :我的消费者1
* @note 注意事项
*/
public class Customer1{
public void listen(String foo){
System.out.println("消费者消费1,获取消息msg:"+foo);
}
}
package customer;
/**
* @author : alex
* @version :1.0.0
* @Date : create by 2018/7/19 23:39
* @description :我的消费者2
* @note 注意事项
*/
public class Customer2 {
public void listen(String foo){
System.out.println("消费者消费2,获取消息msg:"+foo);
}
}
fanout的exchange模式,为扇形广播模式,相当于绑定到该exchange的所有queue都会受到消息。
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-amqp
spring:
rabbitmq:
username: root
password: 123456
host: 192.168.199.128
port: 5672
virtual-host: /test_vh
#rabbitMQ配置文件
rabbitMQconfig:
queueName:
first: test_spring_boot_ps1
second: test_spring_boot_ps2
exchangeName:
fanoutName: test_spring_boot_exchange_name
package cn.edu.rabbitps.ps;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PsApplication {
public static void main(String[] args) {
SpringApplication.run(PsApplication.class, args);
}
}
package cn.edu.rabbitps.ps.utils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author: Alex
* @DateTime: 2018/8/23 11:19
* @Description: 静态参数类
* @Version: 1.0.0
**/
@Component
public class ParamUtil {
@Value("${rabbitMQconfig.queueName.first}")
public String queueNameFirst;
@Value("${rabbitMQconfig.queueName.second}")
public String queueNameSecond;
@Value("${rabbitMQconfig.exchangeName.fanoutName}")
public String fanoutName;
}
package cn.edu.rabbitps.ps.utils;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author: Alex
* @DateTime: 2018/8/23 10:52
* @Description: rabbitMQ配置声明
* @Version: 1.0.0
**/
@Configuration
public class RabbitMQDeclareUtil {
@Autowired
private ParamUtil paramUtil;
@Bean
Queue getQueue1(){
//定义第一个队列队列,Ctrl+鼠标左键,点击Queue可以看到定义
return new Queue(paramUtil.queueNameFirst);
}
@Bean
Queue getQueue2(){
//定义第二个队列
return new Queue(paramUtil.queueNameSecond);
}
@Bean
FanoutExchange getExchange() {
//定义一个FanoutExchange交换器
return new FanoutExchange(paramUtil.fanoutName);
}
/**
* 第一个队列与交换器绑定
* @param getQueue1 定义的第一个队列
* @param getExchange 定义的交换器
* @return
*/
@Bean
Binding binding1(Queue getQueue1, FanoutExchange getExchange) {
return BindingBuilder.bind(getQueue1).to(getExchange);
}
/**
* 第二个队列与交换器绑定
* @param getQueue2
* @param getExchange
* @return
*/
@Bean
Binding binding2(Queue getQueue2, FanoutExchange getExchange) {
return BindingBuilder.bind(getQueue2).to(getExchange);
}
}
package cn.edu.rabbitps.ps.controller;
import cn.edu.rabbitps.ps.utils.ParamUtil;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: Alex
* @DateTime: 2018/8/17 16:36
* @Description: web访问模拟发送
* @Version: 1.0.0
**/
@RestController
public class IndexController {
@Autowired
private AmqpTemplate amqpTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private ParamUtil paramUtil;
/**
* 使用AmqpTemplate
* @return
* @throws Exception
*/
@PostMapping("/amqpSend")
public String amqpSend() throws Exception{
String msg = "amqp";
for (int i=0;i<20;i++){
amqpTemplate.convertAndSend(paramUtil.fanoutName,"",msg);//根据指定的exchange发送数据
// System.out.println("序号:"+i+",发送时间:"+System.currentTimeMillis()+",发送消息:"+msg);
Thread.sleep(1000);//1秒
}
return msg;
}
/**
* 使用RabbitTemplate
* @return
* @throws Exception
*/
@PostMapping("/rabbitSend")
public String rabbitSend() throws Exception{
String msg = "rabbit";
for (int i=0;i<20;i++) {
rabbitTemplate.convertAndSend(paramUtil.fanoutName,"test_mmr", msg+i);//根据指定的exchange发送数据
// System.out.println("生产者,序号:"+i+",发送时间:"+System.currentTimeMillis()+",发送消息:"+msg);
Thread.sleep(500);//0.5秒
}
return msg;
}
}
package cn.edu.rabbitps.ps.customer;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author: Alex
* @DateTime: 2018/8/17 16:35
* @Description: 模拟消费者1
* @Version: 1.0.0
**/
@Component
//监听的队列
@RabbitListener(queues = "test_spring_boot_ps1")
public class CustomerMsg {
/**
* 进行接收处理
* @param string
*/
@RabbitHandler
public void onMessage(String string,Channel channel, Message message) throws IOException, InterruptedException {
Thread.sleep(1000);
System.out.println("消费者1,接收时间:"+System.currentTimeMillis()+",收到消息,消息: " + string);
// channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);//手动确认
//丢弃这条消息
//channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
}
}
package cn.edu.rabbitps.ps.customer;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author: Alex
* @DateTime: 2018/8/17 16:35
* @Description: 模拟消费者2
* @Version: 1.0.0
**/
@Component
//监听的队列
@RabbitListener(queues = "test_spring_boot_ps2")
public class CustomerMsg2 {
/**
* 进行接收处理
* @param string
*/
@RabbitHandler
public void onMessage(String string,Channel channel, Message message) throws IOException, InterruptedException {
Thread.sleep(1000);
System.out.println("消费者2,接收时间:"+System.currentTimeMillis()+",收到消息,消息: " + string);
// channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);//手动确认
//丢弃这条消息
//channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
}
}
spring boot使用@bean的方式注入配置RabbitMQ
首先是关于定义Queue:
@Bean
Queue getQueue1(){
//定义第一个队列队列,Ctrl+鼠标左键,点击Queue可以看到定义
return new Queue(paramUtil.queueNameFirst);
}
上图为spring-amqp中定义的Queue
由此图可以看出,我们之前是通过Queue的构造方法进行定义队列,参数与原生client的一致,如队列名称,是否持久化,是否自动删除等等。
接下来就是定义exchange
@Bean
FanoutExchange getExchange() {
//定义一个FanoutExchange交换器
return new FanoutExchange(paramUtil.fanoutName);
}
FanoutExchange父类继承与AbstractExchange
构造方法重载了父类三个构造方法
分别是:super(name)、super(name,durable,autoDelete)和super(name,durable,autoDelete,arguments)
exchange的Type固定给定为fanout
此处看得不够仔细,我们往父类跳,AbstractExchange实现于Exchange,那我们再跳到Exchange,跳了两次
看到这里,我们就明白了。定义Exchange实际上与原生的client客户端参数一致。
再往下就是队列绑定交换器,参数不多,实际效果与spring xml配置得差不多
/**
* 第一个队列与交换器绑定
* @param getQueue1 定义的第一个队列
* @param getExchange 定义的交换器
* @return
*/
@Bean
Binding binding1(Queue getQueue1, FanoutExchange getExchange) {
return BindingBuilder.bind(getQueue1).to(getExchange);
}
本篇博文讲述了三类使用RabbitMQ的方式,由原生client客户端,到spring xml,再到spring boot,实际上使用原理都基本相通,同时接触了一个新的东西叫exchange,并且了解了第一个exchange的type类型叫fanout,fanout意为扇形,在rabbitMQ中即表示以扇形的方式进行广播消息,所有绑定此exchange的队列,都会收到消息。routingKey默认是不生效的。