要使用RabbitMQ要在pom.xml中引入依赖,如下:
<!-- Rabbitmq --> <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.6.0</version> </dependency>
接下来介绍RabbitMQ的几种队列的使用:
1、简单队列,一个消息的生产者对应一个消费者
首先,创建获取连接Connection的工具类
public class ConnectionUtil {
//连接工厂
public static ConnectionFactory factory;
//连接
private static Connection connection;
private ConnectionUtil(){ }
public static Connection getInstance(){
if(connection == null){
factory = new ConnectionFactory();
//IP
factory.setHost("localhost");
try {
//通过连接工厂获取连接
connection = factory.newConnection();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
return connection;
}
}
第二步:创建生产者,并加入到Spring容器
@Component
public class Publisher {
//队列名称
private String queue = "hello_queue";
public void sender(){
Connection connection = null;
Channel channel = null;
try{
connection = ConnectionUtil.getInstance();
channel = connection.createChannel();
//声明一个队列,只有在队列不存在的情况下去创建队列
channel.queueDeclare(queue, false, false, false, null);
String message = "Hello World!";
//向队列发送消息
channel.basicPublish("", queue, null, message.getBytes());
System.out.println(" [Pbulisher] Sent '" + message + "'");
}catch(Exception e){
e.printStackTrace();
}
}
}
第三步:创建消费者,并加入到Spring容器
@Component
public class Receiver {
private String queue = "hello_queue";
public void receiver(){
Connection connection = null;
Channel channel = null;
try{
connection = ConnectionUtil.getInstance();
channel = connection.createChannel();
channel.queueDeclare(queue, false, false, false, null);
Consumer consumer = new DefaultConsumer(channel){
/**
* envelope主要存放生产者相关信息(比如交换机、路由key等)body是消息实体。
* @throws IOException
*/
@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("Customer Received '" + message + "'");
}
};
//监听队列,自动回复队列应答 -- RabbitMQ中的消息确认机制
final String s = channel.basicConsume(queue, true, consumer);
}catch(Exception e){
e.printStackTrace();
}
}
}
第四步:编写Controller测试,注意消费者启动后,会一直监听队列中的消息,不会关闭连接,所以/reciever请求只需要发送一次即可。(下边所有的测试与之相同)
@Controller
public class sendController {
@Autowired
private Publisher publisher;
@Autowired
private Receiver receiver;
@RequestMapping("/sender")
@ResponseBody
public void send(){
publisher.sender();
}
@RequestMapping("/reciever")
@ResponseBody
public void reciever(){
receiver.receiver();
}
}
2、Work queues(工作队列)一个生产者对应多个消费者(一个消息只能被一个消费者消费,两个消费者消费消息的数量为消息队列消息的总和)
第一步:创建新的队列和生产者
@Component
public class WQ_Publisher {
public String WQ_QUEUE = "workqueue";
public void publicsher(){
//获取连接
Connection connection = ConnectionUtil.getInstance();
try{
Channel channel = connection.createChannel();
//队列持久存在,即使RabbitMQ退出或崩溃
boolean durable = true;
//声明队列
channel.queueDeclare(WQ_QUEUE,durable,false,false,null);
for(int i=0;i<100;i++){
String message = "Hello WorkQueue "+i;
System.out.println("[WorkQueue 生产者:]" + message);
//MessageProperties.PERSISTENT_TEXT_PLAIN 将消息持久化,保证消息不会丢失
channel.basicPublish("",
WQ_QUEUE,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());
//阻塞1秒模拟发送大消息
Thread.sleep(1000);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
第二步:创建两个生产者
@Component
public class WQ_Receiver {
public String WQ_QUEUE = "workqueue";
public void receiver(){
Connection connection = ConnectionUtil.getInstance();
try{
Channel channel = connection.createChannel();
channel.queueDeclare(WQ_QUEUE,false,false,false,null);
//创建customer
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("消费者1:"+message);
}
};
channel.basicConsume(WQ_QUEUE,true,consumer);
}catch(Exception e){
e.printStackTrace();
}
}
}
@Component
public class WQ_Receiver1 {
public String WQ_QUEUE = "workqueue";
public void receiver(){
Connection connection = ConnectionUtil.getInstance();
try{
Channel channel = connection.createChannel();
channel.queueDeclare(WQ_QUEUE,false,false,false,null);
//创建customer
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("消费者2:"+message);
try {
Thread.sleep(1000);
}catch (Exception e) {
e.printStackTrace();
}
}
};
channel.basicConsume(WQ_QUEUE,true,consumer);
}catch(Exception e){
e.printStackTrace();
}
}
}
第三步:编写controller测试
@Controller
public class WQ_sendController {
@Autowired
private WQ_Publisher wq_publisher;
@Autowired
private WQ_Receiver wq_receiver;
@Autowired
private WQ_Receiver1 wq_receiver1;
//发送消息
@RequestMapping("/wqsender")
@ResponseBody
public void send(){
wq_publisher.publicsher();
}
//消费者1
@RequestMapping("/wqreceiver")
@ResponseBody
public void receiver(){
wq_receiver.receiver();
}
//消费者2
@RequestMapping("/wqreceiver1")
@ResponseBody
public void receiver1(){
wq_receiver1.receiver();
}
}
注意:此种消费者的分发方式为轮询分发,即RabbitMQ将按顺序将每条消息发送给下一个消费者。平均而言,每个消费者将获得相同数量的消息。
我们可以将RabbitMQ实现 公平分发,即消费快的消费者多消费几条消息,消费慢的消费者少消费几条消息。要实现公平分发,必须关闭消息自动应答,改为手动应答。则我们需要对上边的代码做一定的修改。
在队列的生产者中使用basicQos(1)确保每次只发送一条消息,代码如下:
/*
公平分发,消息队列对消费者发送消息之前,如果没有收到该消费者的消息确认,
则不会对该消费者发送消息。确保每次只发送一条消息
*/
channel.basicQos(1);
消费者中同样需要添加如下代码:
/*
公平分发,消息队列对消费者发送消息之前,如果没有收到该消费者的消息确认,
则不会对该消费者发送消息。确保每次只发送一条消息
*/
channel.basicQos(1);
同时消费者中还需要关闭消息的自动应答,改为手动应答。
//关闭消息自动应答
boolean autoAck = false;
channel.basicConsume(WQ_QUEUE,autoAck,consumer);
//手动应答
channel.basicAck(envelope.getDeliveryTag(),false);
修改后的完整代码如下:
生产者代码:
/**
* 消息的公平分发
*/
@Component
public class WQ_Publisher {
public String WQ_QUEUE = "work_queue";
public void publicsher(){
//获取连接
Connection connection = ConnectionUtil.getInstance();
try{
Channel channel = connection.createChannel();
//队列持久存在,即使RabbitMQ退出或崩溃
boolean durable = true;
/*
公平分发,消息队列对消费者发送消息之前,如果没有收到该消费者的消息确认,
则不会对该消费者发送消息。确保每次只发送一条消息
*/
channel.basicQos(1);
//声明队列
channel.queueDeclare(WQ_QUEUE,durable,false,false,null);
for(int i=0;i<100;i++){
String message = "Hello WorkQueue "+i;
System.out.println("[WorkQueue 生产者:]" + message);
//MessageProperties.PERSISTENT_TEXT_PLAIN 将消息持久化,保证消息不会丢失
channel.basicPublish("",
WQ_QUEUE,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());
//阻塞1秒模拟发送大消息
Thread.sleep(5);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
消费者代码:
@Component
public class WQ_Receiver {
public String WQ_QUEUE = "work_queue";
public void receiver(){
Connection connection = ConnectionUtil.getInstance();
try{
Channel channel = connection.createChannel();
//队列持久存在,即使RabbitMQ退出或崩溃
boolean durable = true;
//公平分发,消息队列对消费者发送消息之前,如果没有收到该消费者的消息确认,则不会对该消费者发送消息
channel.basicQos(1);
channel.queueDeclare(WQ_QUEUE,durable,false,false,null);
//创建customer
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("消费者1:"+message);
try {
Thread.sleep(2000);
}catch (Exception e) {
e.printStackTrace();
}finally{
//手动应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//关闭消息自动应答
boolean autoAck = false;
channel.basicConsume(WQ_QUEUE,autoAck,consumer);
}catch(Exception e){
e.printStackTrace();
}
}
}
@Component
public class WQ_Receiver1 {
public String WQ_QUEUE = "work_queue";
public void receiver(){
Connection connection = ConnectionUtil.getInstance();
try{
Channel channel = connection.createChannel();
//队列持久存在,即使RabbitMQ退出或崩溃
boolean durable = true;
//公平分发,消息队列对消费者发送消息之前,如果没有收到该消费者的消息确认,则不会对该消费者发送消息
channel.basicQos(1);
channel.queueDeclare(WQ_QUEUE,durable,false,false,null);
//创建customer
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("消费者2:"+message);
try {
Thread.sleep(1000);
}catch (Exception e) {
e.printStackTrace();
}finally{
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//关闭消息自动应答
boolean autoAck = false;
channel.basicConsume(WQ_QUEUE,autoAck,consumer);
}catch(Exception e){
e.printStackTrace();
}
}
}
Controller中的代码没有修改,为了例子的完整性,代码如下:
@Controller
public class WQ_sendController {
@Autowired
private WQ_Publisher wq_publisher;
@Autowired
private WQ_Receiver wq_receiver;
@Autowired
private WQ_Receiver1 wq_receiver1;
//发送消息
@RequestMapping("/wqsender")
@ResponseBody
public void send(){
wq_publisher.publicsher();
}
//消费者1
@RequestMapping("/wqreceiver")
@ResponseBody
public void receiver(){
wq_receiver.receiver();
}
//消费者2
@RequestMapping("/wqreceiver1")
@ResponseBody
public void receiver1(){
wq_receiver1.receiver();
}
}
3、Publish/Subscribe(订阅模式)
订阅模式解读:
1)、一个生产者,多个消费者
2)、每一个消费者有自己的消息队列
3)、生产者(P)没有把消息直接发送到消息队列,而是发送到了转发器(X)
4)、每一个队列都需要绑定到转发器上
5)、一条消息会被多个消费者消费
##讲订阅模式前先搞清楚什么是转发器?
Exchange(转发器),一方面是接受生产者发送的消息,另一方面是将接受到的消息推送到队列。
转发器有两种类型:
fanout : 不处理路由密钥
direct : 处理路由密钥
topic : 通配符匹配密钥
##本次讲解的订阅模式使用的是fanout类型的转发器,至于什么是路由密钥,将在下面讲的第四种模式时给大家讲解。
第一步,声明转发器(fanout类型),创建生产者发送消息
@Component
public class ExchangePublisher {
private static final String EXCHANGE_FANOUT = "exchange_fanout";
public void send(){
Connection connection = ConnectionUtil.getInstance();
try {
Channel channel = connection.createChannel();
//声明转发器
channel.exchangeDeclare(EXCHANGE_FANOUT,"fanout");
String message = "exchange word!";
channel.basicPublish(EXCHANGE_FANOUT,"",null,message.getBytes());
System.out.println("发送给转发器的消息:"+message);
}catch (Exception e){
e.printStackTrace();
}
}
}
注意:转发器并没有存储功能,如果你只是声明了一个转发器,没有消费者队列绑定该转发器。消息会消失,并不会存在转发器中。
第二步:声明两个消费者,绑定上边声明的转发器。
@Component
public class ExchangeReceiver {
private static final String QUQUE_NAME = "exchange_queue";
private static final String EXCHANGE_NAME = "exchange_fanout";
public void receiver(){
Connection connection = ConnectionUtil.getInstance();
try {
Channel channel = connection.createChannel();
channel.basicQos(1);
//声明队列
channel.queueDeclare(QUQUE_NAME,false,false,false,null);
//绑定队列到转发器
channel.queueBind(QUQUE_NAME,EXCHANGE_NAME,"");
//创建customer
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("消费者1:"+message);
try {
Thread.sleep(2000);
}catch (Exception e) {
e.printStackTrace();
}finally{
//手动应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//关闭消息自动应答
boolean autoAck = false;
channel.basicConsume(QUQUE_NAME,autoAck,consumer);
}catch (Exception e){
e.printStackTrace();
}
}
}
@Component
public class ExchangeReceiver2 {
private static final String QUQUE_NAME = "exchange_queue2";
private static final String EXCHANGE_NAME = "exchange_fanout";
public void receiver(){
Connection connection = ConnectionUtil.getInstance();
try {
Channel channel = connection.createChannel();
channel.basicQos(1);
//声明队列
channel.queueDeclare(QUQUE_NAME,false,false,false,null);
//绑定队列到转发器
channel.queueBind(QUQUE_NAME,EXCHANGE_NAME,"");
//创建customer
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("消费者2:"+message);
try {
Thread.sleep(2000);
}catch (Exception e) {
e.printStackTrace();
}finally{
//手动应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//关闭消息自动应答
boolean autoAck = false;
channel.basicConsume(QUQUE_NAME,autoAck,consumer);
}catch (Exception e){
e.printStackTrace();
}
}
}
第三步:编写Controller发送请求测试
@Controller
public class Ex_sendController {
@Autowired
private ExchangePublisher exchangePublisher;
@Autowired
private ExchangeReceiver exchangeReceiver;
@Autowired
private ExchangeReceiver2 exchangeReceiver2;
@RequestMapping("/exsender")
@ResponseBody
public void send(){
exchangePublisher.send();
}
@RequestMapping("/exreceiver")
@ResponseBody
public void exreceiver(){
exchangeReceiver.receiver();
}
@RequestMapping("/exreceiver2")
@ResponseBody
public void exreceiver2(){
exchangeReceiver2.receiver();
}
}
4、Routing(路由模式)
路由模式解读:
1)、我们的消息发送者,发送消息的时候要指定routting key(路由密钥),为了方便大家理解,下文中描述routting key的时候,我都会用中文“路由密钥”。
String routingKey = "info";//路由密钥
channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes());
2)、消费者绑定队列到转发器的时候,需要提供一个参数binding key(绑定密钥),同样为了方便大家理解,下文中描述binding key的时候,我都会用中文“绑定密钥”。
String bindKey = "info";//绑定密钥
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,bindKey);
一个队列是可以绑定多个不同绑定密钥的,不同的队列也可以绑定相同的绑定密钥(多重绑定),功能相当于Publish/Subscribe(订阅模式)。
3)、如果你想一个消息能到达这个队列,需要绑定密钥和路由密钥正好能匹配得上。需要注意的是:如果你的转发器类型是"fanout",会忽略绑定密钥。导致即使你的绑定密钥和路由密钥正好匹配,但是消费者也无法接受到生产者发送的消息。所以,我们需要创建一个"direct"类型的转换器。
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
完整代码如下:
第一步:创建生产者,指定路由密钥,发送消息
@Component
public class RoutingPublisher {
private static final String EXCHANGE_NAME = "routing_exchange";
public void send(){
Connection connection = ConnectionUtil.getInstance();
Channel channel = null;
try{
channel = connection.createChannel();
//公平分发
channel.basicQos(1);
//声明转发器
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
String message = "message for info!";
String routingKey = "info";//路由的key
channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes());
System.out.println("["+routingKey+"]发送的消息:"+message);
}catch (Exception e){
}
}
}
第二步:创建两个消费者,一个绑定密钥"info",另一个绑定密钥"error"。
@Component
public class RoutingReceiver {
private static final String EXCHANGE_NAME = "routing_exchange";
private static final String QUEUE_NAME ="routing_queue";
public void receiver(){
Connection connection = ConnectionUtil.getInstance();
Channel channel = null;
try{
channel = connection.createChannel();
channel.basicQos(1);
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到转发器
String bindKey = "info";//绑定的key
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,bindKey);
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("绑定队列["+bindKey+"]接收的消息:"+message);
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}catch (Exception e){
e.printStackTrace();
}
}
}
@Component
public class RoutingReceiver2 {
private static final String EXCHANGE_NAME = "routing_exchange";
private static final String QUEUE_NAME ="routing_queue2";
public void receiver(){
Connection connection = ConnectionUtil.getInstance();
Channel channel = null;
try{
channel = connection.createChannel();
channel.basicQos(1);
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到转发器
String bindKey = "error";//绑定的key
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,bindKey);
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("绑定队列["+bindKey+"]接收的消息:"+message);
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}catch (Exception e){
e.printStackTrace();
}
}
}
第三步:创建controller,发送请求测试
@Controller
public class RoutingController {
@Autowired
private RoutingPublisher routingPublisher;
@Autowired
private RoutingReceiver routingReceiver;
@Autowired
private RoutingReceiver2 routingReceiver2;
@RequestMapping("/rosender")
@ResponseBody
public void send(){
routingPublisher.send();
}
@RequestMapping("/roreceiver")
@ResponseBody
public void receiver(){
routingReceiver.receiver();
}
@RequestMapping("/roreceiver2")
@ResponseBody
public void receiver2(){
routingReceiver2.receiver();
}
}
发送请求会发现,只有绑定密钥为info的消费者可以收到消息。
5、Topic(主题模式)
主题模式解读:
主题模式实际上与路由模式大致相同,它俩的区别是:
1)、主题模式采用的转发器是topic类型的转发器。
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
2)、主题模式在队列绑定转发器的时候可以使用通配符 * 或 #来做为绑定密钥,达到能够匹配多个路由密钥的效果。
比如:一个商城的消息有:goods.add、goods.change、goods.delete三种路由密匙。
下面这种方法只能接受到goods.add路由密匙发送的消息:
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.add");
而下面只能接受到goods.add、goods.change、goods.delete三个路由密匙发送的消息:
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.*");
主题模式的通配符有两种:
* :可以替代一个单词
#:可以替代零个或多个单词
完整代码如下:
第一步:定义生产者和topic模式的转发器
@Component
public class TopicPublisher {
private static final String EXCHANGE_NAME = "topic_exchange";
public void send(){
Connection connection = ConnectionUtil.getInstance();
Channel channel = null;
try{
channel = connection.createChannel();
channel.basicQos(1);
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
String message = "good add message!";
channel.basicPublish(EXCHANGE_NAME,"goods.add",null,message.getBytes());
}catch (Exception e){
e.printStackTrace();
}
}
}
第二步:定义两个消费者,一个是绑定密钥为"goods.*",另一个绑定密钥为"goods.add"
@Component
public class TopicReceiver {
private static final String QUEUE_NAME = "topic_queue";
private static final String EXCHANGE_NAME = "topic_exchange";
public void receiver(){
Connection connection = ConnectionUtil.getInstance();
Channel channel = null;
try{
channel = connection.createChannel();
channel.basicQos(1);
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.*");
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("good.*:"+message);
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}catch (Exception e){
e.printStackTrace();
}
}
}
@Component
public class TopicReceiver2 {
private static final String QUEUE_NAME = "topic_queue2";
private static final String EXCHANGE_NAME = "topic_exchange";
public void receiver(){
Connection connection = ConnectionUtil.getInstance();
Channel channel = null;
try{
channel = connection.createChannel();
channel.basicQos(1);
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.add");
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("good.add:"+message);
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}catch (Exception e){
e.printStackTrace();
}
}
}
3、编写Controller测试
@Controller
public class TopicController {
@Autowired
private TopicPublisher topicPublisher;
@Autowired
private TopicReceiver topicReceiver;
@Autowired
private TopicReceiver2 topicReceiver2;
@RequestMapping("/tosender")
@ResponseBody
public void send(){
topicPublisher.send();
}
@RequestMapping("/toreceiver")
@ResponseBody
public void receiver(){
topicReceiver.receiver();
}
@RequestMapping("/toreceiver2")
@ResponseBody
public void receiver2(){
topicReceiver2.receiver();
}
}