RabbitMQ接口分爲 通道、交換器、路由、和隊列4個模塊
生產者/消費者連接消息隊列,創建通道,創建交換器,創建隊列。把隊列綁定到交換器上。
生產者發送消息到交換器,交換器根據路由把消息轉到不同隊列上。
消費者創建通道後,指定交換器,路由,和隊列。
交換器:
DIRECT("direct"), FANOUT("fanout"), TOPIC("topic"), HEADERS("headers");
測試代碼
package com.cn.rabbitmq.cp1.exchange;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.impl.ErrorOnWriteListener;
import java.io.IOException;
public class BaseQueue {
public static Connection getConnection() throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setUsername("guest");
factory.setPassword("guest");
factory.setErrorOnWriteListener(new ErrorOnWriteListener() {
public void handle(Connection connection, IOException exception) throws IOException {
}
});
return factory.newConnection();
}
}
服務端發送消息:
package com.cn.rabbitmq.cp1.exchange.topic;
import com.cn.rabbitmq.cp1.exchange.BaseQueue;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* 隊列參數說明:
* 隊列名稱 queue :隊列名
* 是否持久隊列 durable :true 持久隊列,該隊列將在服務器重新啓動後繼續存在。當然了,隊列的消息內容也會被寫入磁盤
* 是否獨佔隊列 (單消費者隊列)exclusive : 如果需要消費者獨佔隊列,在隊列創建的時候,設定屬性exclusive爲true。 只能有一個消費者
* 是否自動刪除 autoDelete :自動刪除隊列,客戶端連接時自動刪除。 服務端會連接會不會刪除?
*
*
* 其他參數 arguments
* 參數名 目的
* x-dead-letter-exchange 死信交換器
* x-dead-letter-routing-key 死信消息的可選路由鍵
* x-expires 隊列在指定毫秒數後被刪除
* x-ha-policy 創建HA隊列
* x-ha-nodes HA隊列的分佈節點
* x-max-length 隊列的最大消息數
* x-message-ttl 毫秒爲單位的消息過期時間,隊列級別
* x-max-priority 最大優先值爲255的隊列優先排序功能
*
*/
public class TopicProduct extends BaseQueue {
public static final String TOPIC_EXCHANGE="topic_exchange"; //交換器名稱
public static final String BAK_EXCHANGE_NAME ="bak_exchange"; //備用交換器名稱
public static final String TOPIC_TOPIC = "topic_queue"; //隊列名稱
public static void main(String[] args) throws Exception {
Connection connection = getConnection(); //獲取連接
Channel channel =connection.createChannel(); //創建通道
Map<String, Object> exchangeArguments = new HashMap();
exchangeArguments.put("alternate-exchange",BAK_EXCHANGE_NAME); //設置備用交換器 ,備用開啓後,發送失敗和無路由會走備用交換器
channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC,false,false,exchangeArguments); //創建交換器, 名稱,類型
//備用交換器
channel.exchangeDeclare(BAK_EXCHANGE_NAME,BuiltinExchangeType.FANOUT, true,false,null);
Map<String, Object> arguments = new HashMap();
arguments.put("x-expires",45*1000); //隊列在超過一定時間沒有被使用,會被從RabbitMQ中刪除 這裏要注意是否有權限
arguments.put("x-message-ttl",20*1000);//設定了該隊列所有消息的存活時間,時間單位毫秒
//創建隊列 當然了,也可以再消費者方創建隊列
channel.queueDeclare(TOPIC_TOPIC,false,false,false,arguments); //隊列名稱,是否持久隊列,是否獨佔隊列,是否自動刪除,其他參數
//隊列綁定交換器
channel.queueBind(TOPIC_TOPIC,TOPIC_EXCHANGE,null,null);//隊列名稱,交換器名稱,路由key,arguments
channel.confirmSelect();//啓動生產者確認
// 3種確認方式 channel.waitForConfirms();普通發送方確認模式 channel.waitForConfirmsOrDie();批量確認模式 channel.addConfirmListener異步監聽發送方確認模式
channel.addConfirmListener(new ConfirmListener() { //發送結果監聽
public void handleAck(long deliveryTag, boolean multiple) throws IOException { //成功
System.out.println("消息發送成功");
}
public void handleNack(long deliveryTag, boolean multiple) throws IOException { //失敗
System.out.println("消息發送失敗");
}
});
for (int i=0;i<10;i++) {
String[] keys = new String[]{"topic.test","topic.mymsg","topic.mytest"};
Random random = new Random();
String routingKey =keys[random.nextInt(3)];
String message="這是個測試數據"+i+"使用的路由是"+routingKey;
//BasicProperties props消息屬性
//發送消息到交換器,路由器
channel.basicPublish(TOPIC_EXCHANGE,routingKey,null,message.getBytes("utf-8")); //交換器名稱,路由鍵,消息屬性,消息內容
System.out.println("發送了第"+i+"條消息。消息內容爲:"+message);
}
//不可路由到隊列的消息監聽
channel.addReturnListener(new ReturnListener() {
/**
* int replyCode, 響應嗎, 路由成沒成功
* String replyText, 回覆內容
* String exchange,
* String routingKey,
* AMQP.BasicProperties properties,
* byte[] body 實際的消息體內容
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
* @param properties
* @param body
* @throws IOException 跑出ioexception 異常
*/
public void handleReturn(int replyCode, String replyText,
String exchange, String routingKey,
AMQP.BasicProperties properties,
byte[] body) {
String message = new String(body);
System.out.println("RabbitMq返回的replyCode: "+replyCode+"RabbitMq返回的replyText: "+replyText);
System.out.println("RabbitMq返回的exchange: "+exchange+"RabbitMq返回的routingKey: "+routingKey);
System.out.println("RabbitMq返回的message: "+message);
}
});
//信道關閉監聽
channel.addShutdownListener(new ShutdownListener() {
public void shutdownCompleted(ShutdownSignalException cause) {
System.out.println("關閉了監聽連接");
}
});
//連接關閉監聽 不知道爲什麼創建連接的時候沒有連接創建監聽?
connection.addShutdownListener(new ShutdownListener() { //連接關閉加你聽
public void shutdownCompleted(ShutdownSignalException cause) {
}
});
/**
* 這個監聽器在服務器內存報警或者硬盤報警的情況下,使用發送接口會產生阻塞,如果發送和接受等使用同一個連接,則會都阻塞,因此我們應該爲消費者和生產者使用不同的CachingConnectionFactory,或者設置rabbitTemplate.setUsePublisherConnection(true);
*/
connection.addBlockedListener(new BlockedListener() { //連接阻塞情況的監聽器
public void handleBlocked(String reason) throws IOException {
System.out.println("connection blocked, reason: "+reason);
}
public void handleUnblocked() throws IOException { //需要同時解除內存和磁盤的報警纔會收到unblock的消息
System.out.println("==============================connection unblocked");
}
});
channel.close();
connection.close();
}
}
創建兩個消費端,都是從通道獲取的臨時隊列,並把隊列指定路由綁定到交換器上
public class TopicConsumer1 extends BaseQueue {
public static void main(String[] args) throws Exception{
Connection connection = getConnection();
Channel channel =connection.createChannel();
String queueName =channel.queueDeclare().getQueue();//從通道獲取隊列
channel.queueBind(queueName,TopicProduct.TOPIC_EXCHANGE,"topic.mymsg"); //指定路由綁定到交換器
channel.basicConsume(queueName,true,new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String str = new String(body,"utf-8");
System.out.println(str);
}
});
}
}
public class TopicConsumer2 extends BaseQueue {
public static void main(String[] args) throws Exception{
Connection connection = getConnection();
Channel channel =connection.createChannel();
String queueName =channel.queueDeclare().getQueue();//從通道獲取隊列
channel.queueBind(queueName,TopicProduct.TOPIC_EXCHANGE,"topic.mymsg"); //指定路由綁定到交換器
channel.basicConsume(queueName,true,new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String str = new String(body,"utf-8");
System.out.println(str);
}
});
}
}
每個客戶端都會消費一個臨時隊列裏的消息
springboot 下客戶端實現臨時隊列綁定到交換器路由
@Component(value = "testListener")
public class TestListener{
public final static String ROUTING_KEY = "test.queue";
public final static String TEST_EXCHANGE = "topic_exchange;
//指定監聽,綁定隊列隊列的路由和校驗器
@RabbitListener(bindings ={@QueueBinding(value = @Queue,
key = ROUTING_KEY,exchange = @Exchange(value=TEST_EXCHANGE,type = ExchangeTypes.TOPIC))})
@RabbitHandler
public void process(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel) throws IOException {
System.out.println(message);
channel.basicAck(deliveryTag,false);
}
}