文章目錄
一、Maven依賴添加
<!-- rabbitmq相關依賴 -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.0.4</version>
</dependency>
二、七種工作模式的java實例
1、簡單模式
最簡單的一個消費者和一個生成者模式,生產者生成消息,消費者監聽消息,若是消費者監聽到它所需要的消息,就會消費該消息,這種消息是次性的,被消費了就沒有了。
1.1.1、EasyRecv.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
public class EasyRecv {
//隊列名稱
private final static String QUEUE_NAME = "hello world";
public static void main(String[] argv) throws java.io.IOException,java.lang.InterruptedException {
//打開連接和創建頻道,與發送端一樣
ConnectionFactory factory = new ConnectionFactory();
//設置RabbitMQ所在主機ip或者主機名
factory.setHost("127.0.0.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//聲明隊列,主要爲了防止消息接收者先運行此程序,隊列還不存在時創建隊列。
/**
* 隊列名
* 是否持久化
* 是否排外 即只允許該channel訪問該隊列 一般等於true的話用於一個隊列只能有一個消費者來消費的場景
* 是否自動刪除 消費完刪除
* 其他屬性
*
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println("Waiting for messages. To exit press CTRL+C");
//創建隊列消費者
QueueingConsumer consumer = new QueueingConsumer(channel);
//指定消費隊列
/**
* 隊列名
* 其他屬性 路由
* 消息body
*/
channel.basicConsume(QUEUE_NAME, true, consumer);
while (true)
{
//nextDelivery是一個阻塞方法(內部實現其實是阻塞隊列的take方法)
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("Received '" + message + "'");
}
}
}
1.1.2、EasySend.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.Scanner;
public class EasySend {
//隊列名稱
private final static String QUEUE_NAME = "hello world";
public static void main(String[] argv) throws java.io.IOException
{
/**
* 創建連接連接到MabbitMQ
*/
ConnectionFactory factory = new ConnectionFactory();
//設置MabbitMQ所在主機ip或者主機名
factory.setHost("127.0.0.1");
while (true){
//創建一個連接
Connection connection = factory.newConnection();
//創建一個頻道
Channel channel = connection.createChannel();
//指定一個隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//發送的消息
Scanner scanner = new Scanner(System.in);
String ms = scanner.nextLine();
//String message = "hello world!";
//往隊列中發出一條消息
channel.basicPublish("", QUEUE_NAME, null, ms.getBytes());
System.out.println("Sent '" + ms + "'");
//關閉頻道和連接
channel.close();
connection.close();
}
}
以上兩個已經可以進行通信了,下面同樣是簡單的實例,但是我們可以看到在代碼層面上,連接的代碼都是一樣的,所以我們可以創建一個連接的工具類。
1.2.1、RabbitmqConnectionUtil .java
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
public class RabbitmqConnectionUtil {
public static Connection getConnection() throws IOException {
//連接工廠
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
//連接5672端口 注意15672爲工具界面端口 25672爲集羣端口
factory.setPort(5672);
//factory.setVirtualHost("/xxxxx");
// factory.setUsername("xxxxxx");
// factory.setPassword("123456");
//獲取連接
Connection connection = factory.newConnection();
return connection;
}
}
1.2.2、UtilSend.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
public class UtilSend {
private final static String QUEUE_NAME = "UtilConn";
public static void main(String[] args) throws IOException {
Connection connection = RabbitmqConnectionUtil.getConnection();
//創建通道
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//消息內容
String message = "這裏是lbw廣場";
channel.basicPublish("", QUEUE_NAME,null,message.getBytes());
System.out.println("[x]Sent '"+message + "'");
//最後關閉通關和連接
channel.close();
connection.close();
}
}
1.2.3、UtilRecv.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
public class UtilRecv {
private final static String QUEUE_NAME = "UtilConn";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = null;
connection = RabbitmqConnectionUtil.getConnection();
//創建通道
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
channel.basicConsume(QUEUE_NAME,true,queueingConsumer);
while(true){
//該方法會阻塞
QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("[x] Received '"+message+"'");
}
}
}
2、工作隊列
工作隊列也就是簡單模式的強化版,一個隊列是可以多個生產者,也可以有多個消費者來競爭消費消息,但是我們仍需保證隊列的冪等性,隊列存在就不能再創建同名隊列。
下面的每個進程都控制其主線程休眠,讓我們可以更好的看到結果。
2.1.1、Sender1.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
public class Sender1 {
private final static String QUEUE_NAME = "queue_work";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = RabbitmqConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for(int i = 0; i < 100; i++){
String message = "lbw" + i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("[x] Sent '"+message + "'");
Thread.sleep(i*10);
}
channel.close();
connection.close();
}
}
2.1.2、Sender2.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
public class Sender2 {
private final static String QUEUE_NAME = "queue_work";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = RabbitmqConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for(int i = 0; i < 100; i++){
String message = "nb" + i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("[x] Sent '"+message + "'");
Thread.sleep(i*10);
}
channel.close();
connection.close();
}
}
2.1.3、Receiver1.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
/**
* Created by san
*/
public class Receiver1 {
private final static String QUEUE_NAME = "queue_work";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = RabbitmqConnectionUtil.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("[x] Received1 '"+message+"'");
Thread.sleep(10);
//返回確認狀態
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
2.1.4、Receiver2.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
/**
* Created by san
*/
public class Receiver2 {
private final static String QUEUE_NAME = "queue_work";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = RabbitmqConnectionUtil.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("[x] Received2 '"+message+"'");
Thread.sleep(1000);
//返回確認狀態
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
2.1.5、結果
上面的四個程序都運行起來,結果可以看到如下,依據結果分析,可知,同一個消息隊列,是可以有多個生產者和消費者的。
3、發佈/訂閱(fanout)
3.1.1、Sender.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
public class Sender {
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args)
{
try
{
//獲取連接
Connection connection = RabbitmqConnectionUtil.getConnection();
//從連接中獲取一個通道
Channel channel = connection.createChannel();
//聲明交換機(分發:發佈/訂閱模式)
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//發送消息
for (int i = 0; i < 5; 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();
}
}
}
3.1.2、Receiver1.java
import com.rabbitmq.client.*;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
public class Receiver1 {
//交換機名稱
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 = RabbitmqConnectionUtil.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 e)
{
e.printStackTrace();
}
}
}
3.1.3、Receiver2.java
import com.rabbitmq.client.*;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
public class Receiver2 {
//交換機名稱
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 = RabbitmqConnectionUtil.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
{
//消費者休息1s處理業務
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 e)
{
e.printStackTrace();
}
}
}
3.1.4、結果
從程序運行結果和RabbitMq的後臺看出,這樣的消息屬於廣播型,兩個不同名的隊列的都能收到該消息,只需它們都將自己綁定到同一個交換機,而且,該消息是持久的,只要交換機還在,消費者啥時候上線都能消費它所綁定的交換機,而且只會一個消費者只會消費一次。
4、路由(direct)
- 在前面的示例中,我們已經在創建綁定。您可能會想起類似的代碼:
channel.queueBind(queueName,EXCHANGE_NAME,“”);
綁定是交換和隊列之間的關係。可以簡單地理解爲:隊列對來自此交換的消息感興趣。
- 綁定可以採用額外的routingKey參數。爲了避免與basic_publish參數混淆,我們將其稱爲 綁定鍵。這是我們可以創建帶有鍵的綁定的方法:
channel.queueBind(queueName,EXCHANGE_NAME,“ black”);
- 直接綁定(密鑰直接綁定到單個隊列)
- 多重綁定(相同的綁定密鑰綁定多個隊列)
- 不同密鑰綁定不同的隊列,可以發揮出不同日誌級別發送到不同的隊列的效果。
4.1.1、Sender
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
public class Sender {
private final static String EXCHANGE_NAME = "exchange_direct";
private final static String EXCHANGE_TYPE = "direct";
public static void main(String[] args) throws IOException {
Connection connection = RabbitmqConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME,EXCHANGE_TYPE);
String message = "那一定是藍色";
channel.basicPublish(EXCHANGE_NAME,"key2", null, message.getBytes());
System.out.println("[x] Sent '"+message+"'");
channel.close();
connection.close();
}
}
4.1.2、Receiver1.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
/**
* Created by san
*/
public class Receiver1 {
private final static String QUEUE_NAME = "queue_routing";
private final static String EXCHANGE_NAME = "exchange_direct";
public static void main(String[] args) throws IOException, InterruptedException {
// 獲取到連接以及mq通道
Connection connection = RabbitmqConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"key");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"key2");
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("[x] Received1 "+message);
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
4.1.3、Receiver2.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
/**
* Created by san
*/
public class Receiver2 {
private final static String QUEUE_NAME = "queue_routing2";
private final static String EXCHANGE_NAME = "exchange_direct";
public static void main(String[] args) throws IOException, InterruptedException {
// 獲取到連接以及mq通道
Connection connection = RabbitmqConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"key2");
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("[x] Received2 "+message);
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
4.1.4、結果-總結
有一點要注意是:在direct下,必須是Exchange(交換機)已經存在,消費端的隊列才能綁定到Exchange,否則會報錯。也就說上面的程序第一次運行時,需先啓Sender,才能成功啓動Reciver。
5、話題(topic)
話題也是一個持久的消息,只要交換機還在,每個上線的消費者都可以消費一次自己感興趣的topic。
- *(星號)可以代替一個單詞。
- #(哈希)可以替代零個或多個單詞。
5.1.1、Sender.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
public class Sender {
private final static String EXCHANGE_NAME = "exchange_topic";
private final static String EXCHANGE_TYPE = "topic";
public static void main(String[] args) throws IOException {
Connection connection = RabbitmqConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, EXCHANGE_TYPE);
//消息內容
String message = "這裏是盧本偉廣場";
//第二個參數是topic匹配值
channel.basicPublish(EXCHANGE_NAME,"lbw.nb",null,message.getBytes());
System.out.println("[x] Sent '"+message+"'");
//關通道 關連接
channel.close();
connection.close();
}
}
5.1.2、Receiver1.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
public class Receiver1 {
private final static String QUEUE_NAME = "queue_topic";
private final static String EXCHANGE_NAME = "exchange_topic";
private final static String EXCHANGE_TYPE = "topic";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = RabbitmqConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false,false, null);
//第二參數就是去匹配我興趣的topic
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "lbw.nb.*");
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("[x] Received1 '"+message + "'");
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
5.1.3、Receiver2.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import top.san.RabbitMq.util.RabbitmqConnectionUtil;
import java.io.IOException;
public class Receiver2 {
private final static String QUEUE_NAME = "queue_topic2";
private final static String EXCHANGE_NAME = "exchange_topic";
private final static String EXCHANGE_TYPE = "topic";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = RabbitmqConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false,false, null);
//第二參數就是去匹配我興趣的topic
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "lbw.#");
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("[x] Received2 '"+message + "'");
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
5.1.4、結果-分析
話題的特色就是隊列可以獲取自己感興趣的話題消息,可以通過通配符*或#來表示匹配所有的感興趣的字符串。
6、RPC(遠程過程調用)
給張圖自己體會吧(官網沒給示例代碼,我也就不寫了),就是通過兩個交換機實現一個可回調的過程吧。
7、發佈者確認
官網沒給畫圖,哭了。
三、RabbitMq的交換機
RabbitMq是有一個交換機的概念的, 消息(Message)由Client發送,RabbitMQ接收到消息之後通過交換機轉發到對應的隊列上面。Worker會從隊列中獲取未被讀取的數據處理。這樣就可以實現消息的發送者無需知道消息使用者的存在,反之亦然。
Direct exchange:直連(路由)交換機,轉發消息到routigKey指定的隊列
Fanout exchange:扇形交換機,轉發消息到所有綁定隊列(相當於廣播)
Topic exchange:主題交換機,按規則轉發消息(很靈活)
Headers exchange:首部交換機
前面的簡單類型我們都是忽略了交換機的參數的,如該方法:channel.basicPublish("", QUEUE_NAME, null, message.getBytes());就是這個方法的第一個參數,置空說明使用了默認的交換機。
有幾種交換類型可用:direct,topic,headers 和fanout。