文章目錄
demo代碼在ssm-demo中
一、rabbitmq的幾種工作模式
1、simple
三個對象:生產者、隊列、消費者
代碼:
Sender:
package com.my.test.rabbitmq.simple;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-18
*/
public class Send {
static final String QUEUE_NAME = "test_simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//從連接中創建一個管道
Channel channel = connection.createChannel();
//創建隊列聲明
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String msg = "hello simple";
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
System.out.println();
channel.close();
connection.close();
}
}
Receiver:
package com.my.test.rabbitmq.simple;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-18
*/
public class Receive {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Send.QUEUE_NAME, false, false, false, null);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, StandardCharsets.UTF_8);
System.out.println(Send.QUEUE_NAME + "receive:" + msg);
}
};
channel.basicConsume(Send.QUEUE_NAME, true, consumer);
}
}
缺點:耦合性高,生產者一一對應消費者,如果我想有多個消費者消費隊列中的消息,這就不行了;如果對列名變更,這時要把生產者和消費者同時變更
2、work queue 工作隊列
- 模型:
-
爲什麼會出現工作隊列
-
simple隊列是一一對應的,而且實際開發中,生產者發送消息是毫不費力的,而消費者一般是要跟業務相結合的,消費者接收到消息之後就需要處理,可能會需要花費很多時間,這時候隊列可能會積壓很多消息
-
代碼:
sender:
package com.my.test.rabbitmq.workqueue;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
public class Send {
protected final static String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 50; i++) {
String msg = "hello " + i;
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
TimeUnit.MICROSECONDS.sleep(i * 20);
}
channel.close();
connection.close();
}
}
receive1:
package com.my.test.rabbitmq.workqueue;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
public class Receive1 {
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(Send.QUEUE_NAME, false, false, false, null);
//定義消費者
DefaultConsumer consumer1 = new DefaultConsumer(channel) {
//消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, StandardCharsets.UTF_8);
System.out.println("[1] receive msg :" + msg);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[1] receive msg :" + msg);
}
}
};
boolean autoAck = true;
channel.basicConsume(Send.QUEUE_NAME, autoAck, consumer1);
}
}
receive2:
package com.my.test.rabbitmq.workqueue;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
public class Receive2 {
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(Send.QUEUE_NAME, false, false, false, null);
//定義消費者
DefaultConsumer consumer1 = new DefaultConsumer(channel) {
//消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, StandardCharsets.UTF_8);
System.out.println("[2] receive msg :" + msg);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
boolean autoAck = true;
channel.basicConsume(Send.QUEUE_NAME, autoAck, consumer1);
}
}
- 現象:
消費者1和消費者2處理的消息數是一樣的,消費者1都是偶數 消費者2都是奇數,這種方式是輪詢分發(round-robin)
不管誰忙或者誰清閒,都不會多給一個消息,任務消息總是輪詢的
3、workqueue公平分發(fair dipatch)
-
必須關閉自動應答ack,改成手動
-
代碼:
send:
package com.my.test.rabbitmq.workfairqueue;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
@SuppressWarnings("DuplicatedCode")
public class Send {
final static String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
/**
* 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
*/
channel.basicQos(1);
for (int i = 0; i < 50; i++) {
String msg = "hello " + i;
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
TimeUnit.MICROSECONDS.sleep(i * 20);
}
channel.close();
connection.close();
}
}
receive1:
package com.my.test.rabbitmq.workfairqueue;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
public class Receive1 {
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(Send.QUEUE_NAME, false, false, false, null);
/**
* 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
*/
channel.basicQos(1);
//定義消費者
DefaultConsumer consumer1 = new DefaultConsumer(channel) {
//消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, StandardCharsets.UTF_8);
System.out.println("[1] receive msg :" + msg);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[1] done");
/**
* 手動回執一個消息
* deliveryTag:該消息的index
* multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
/**
* 自動應答改成false
*/
boolean autoAck = false;
channel.basicConsume(Send.QUEUE_NAME, autoAck, consumer1);
}
}
receive2:
package com.my.test.rabbitmq.workfairqueue;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
@SuppressWarnings("DuplicatedCode")
public class Receive2 {
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(Send.QUEUE_NAME, false, false, false, null);
//定義消費者
DefaultConsumer consumer1 = new DefaultConsumer(channel) {
//消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, StandardCharsets.UTF_8);
System.out.println("[2] receive msg :" + msg);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[1] done");
/**
* 手動回執一個消息
* deliveryTag:該消息的index
* multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
/**
* 自動應答改成false
*/
boolean autoAck = false;
channel.basicConsume(Send.QUEUE_NAME, autoAck, consumer1);
}
}
現象:
消費之2處理的消息比消費者1多,能者多勞
-
autoAck消息應答:
boolean autoAck = false;
channel.basicConsume(Send.QUEUE_NAME, autoAck, consumer1);①如果ack爲true
自動確認模式,一旦rabbitmq把消息給到消費者,會自動從內存中吧消息刪除
這種情況下,如果殺死正在執行的消費者,就會丟失正在處理的消息
②如果ack爲false
手動確認模式,如果有一個消費者掛掉,就會交付給其他消費者,消費者執行成功後可以發送一個消息應答,告知mq已經消費完畢,可以刪除消息了
消息應答默認是打開的,false
Message Acknowkedgment
-
消息的持久化
如果mq掛了,因爲消息在內存中存儲,我們的消息仍然會丟失
//聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException;
durable 就是是否持久化,生產端和消費端都要,聲明好的隊列不能修改,true就是持久化,這是隊列持久化
channel.basicPublish(EXCHANGE_NAME, routingKey, MessageProperties.MINIMAL_PERSISTENT_BASIC//持久化消息 , msg.getBytes());
增加properties,這個properties 就是消費端 callback函數中的properties
delivery_mode = 2 持久化消息
4、訂閱模式(publish/subscribe)
RocketMQ的設計上,是不考慮消息去重的問題,即不考慮消息是否會重複的消費的問題,而是將這個問題拋給業務端自己去處理冪等的問題。
- 模型:
work模式,一個生產者對應一個隊列 一個隊列對應多個消費者
訂閱模式,一個生產者對應多個隊列 一個隊列對應一個消費者
生產者沒有直接把消息發到隊列中,而是發送到了交換機
- send:
package com.my.test.rabbitmq.publish_subscribe;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
@SuppressWarnings("DuplicatedCode")
public class Send {
final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明交換機
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");//分發
//發送消息
String msg = "hello ps";
channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
System.out.println("send :" + msg);
channel.close();
connection.close();
}
}
消息哪裏去了?
丟失了,rabbitmq中只有隊列有存儲能力,因爲這時候還沒有隊列綁定到這個交換機,所以消息就丟失了
- receive:
package com.my.test.rabbitmq.publish_subscribe;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
@SuppressWarnings("DuplicatedCode")
public class Receive1 {
final static String QUEUE_NAME = "test_ps_fanout_queue1";
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//綁定隊列到交換機
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "");
/**
* 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
*/
channel.basicQos(1);
//定義消費者
DefaultConsumer consumer1 = new DefaultConsumer(channel) {
//消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, StandardCharsets.UTF_8);
System.out.println("[1] receive msg :" + msg);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[1] done");
/**
* 手動回執一個消息
* deliveryTag:該消息的index
* multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
/**
* 自動應答改成false
*/
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer1);
}
}
package com.my.test.rabbitmq.publish_subscribe;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
@SuppressWarnings("DuplicatedCode")
public class Receive2 {
final static String QUEUE_NAME = "test_ps_fanout_queue2";
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//綁定隊列到交換機
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "");
/**
* 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
*/
channel.basicQos(1);
//定義消費者
DefaultConsumer consumer1 = new DefaultConsumer(channel) {
//消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, StandardCharsets.UTF_8);
System.out.println("[2] receive msg :" + msg);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[2] done");
/**
* 手動回執一個消息
* deliveryTag:該消息的index
* multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
/**
* 自動應答改成false
*/
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer1);
}
}
-
Exchange 交換機 轉換器
①一方面是接收生產者的消息,另一方面是向隊列推送消息
②匿名轉發 exchange=""
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
③fanout(不處理路由鍵)只要是跟exchange綁定的隊列都可以接收到消息
//聲明交換機 channel.exchangeDeclare(EXCHANGE_NAME, "fanout");//分發
④direct(處理路由鍵)
5、路由模式
send:
package com.my.test.rabbitmq.routing;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
@SuppressWarnings("DuplicatedCode")
public class Send {
final static String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明交換機
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);//direct
//發送消息
String msg = "hello direct";
//routing key
String routingKey = "error";
channel.basicPublish(EXCHANGE_NAME, routingKey, null, msg.getBytes());
System.out.println("send :" + msg);
channel.close();
connection.close();
}
}
receive1:
package com.my.test.rabbitmq.routing;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
@SuppressWarnings("DuplicatedCode")
public class Receive1 {
final static String QUEUE_NAME = "test_routing_queue1";
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//綁定隊列到交換機
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "error");
/**
* 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
*/
channel.basicQos(1);
//定義消費者
DefaultConsumer consumer1 = new DefaultConsumer(channel) {
//消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, StandardCharsets.UTF_8);
System.out.println("[1] receive msg :" + msg);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[1] done");
/**
* 手動回執一個消息
* deliveryTag:該消息的index
* multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
/**
* 自動應答改成false
*/
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer1);
}
}
receive2:
package com.my.test.rabbitmq.routing;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
@SuppressWarnings("DuplicatedCode")
public class Receive2 {
final static String QUEUE_NAME = "test_routing_queue2";
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//綁定隊列到交換機
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "error");
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "info");
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "warning");
/**
* 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
*/
channel.basicQos(1);
//定義消費者
DefaultConsumer consumer1 = new DefaultConsumer(channel) {
//消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, StandardCharsets.UTF_8);
System.out.println("[2] receive msg :" + msg);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[2] done");
/**
* 手動回執一個消息
* deliveryTag:該消息的index
* multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
/**
* 自動應答改成false
*/
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer1);
}
}
6、topic主題模式
-
將路由和某個模式匹配
-
topic exchange
-
#:匹配一個或者多個
-
*:匹配一個
-
商品 Goods.# (Goods.fruit)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KfiY97xd-1570516233843)(E:\anotes_and_information\一些後端服務\rabbitmq\Snipaste_2019-09-30_11-46-24.png)]
send:
package com.my.test.rabbitmq.topic;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
@SuppressWarnings("DuplicatedCode")
public class Send {
final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明交換機
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);//topic
//發送消息
String msg = "商品......";
//routing key
String routingKey = "goods.add";
// String routingKey = "goods.delete";
// String routingKey = "goods.update";
channel.basicPublish(EXCHANGE_NAME,
routingKey,
MessageProperties.MINIMAL_PERSISTENT_BASIC//持久化消息
, msg.getBytes());
System.out.println("------send :" + msg);
channel.close();
connection.close();
}
}
receive1:
package com.my.test.rabbitmq.topic;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
@SuppressWarnings("DuplicatedCode")
public class Receive1 {
private final static String QUEUE_NAME = "test_topic_queue1";
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//綁定隊列到交換機
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "goods.add");
/**
* 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
*/
channel.basicQos(1);
//定義消費者
DefaultConsumer consumer1 = new DefaultConsumer(channel) {
//消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, StandardCharsets.UTF_8);
System.out.println("[1] receive msg :" + msg);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[1] done");
/**
* 手動回執一個消息
* deliveryTag:該消息的index
* multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
/**
* 自動應答改成false
*/
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer1);
}
}
receive2:
package com.my.test.rabbitmq.topic;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-29
*/
@SuppressWarnings("DuplicatedCode")
public class Receive2 {
/**
* 如果和receive1是同一個隊列名,則每個消息只會有一個消費者消費
*/
private final static String QUEUE_NAME = "test_topic_queue2";
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//獲取channel
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//綁定隊列到交換機
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "goods.#");
/**
* 每個消費者 發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一條消息
*/
channel.basicQos(1);
//定義消費者
DefaultConsumer consumer1 = new DefaultConsumer(channel) {
//消息到達 觸發這個方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, StandardCharsets.UTF_8);
System.out.println("[2] receive msg :" + msg);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("[2] done");
/**
* 手動回執一個消息
* deliveryTag:該消息的index
* multiple:是否批量.true:將一次性ack所有小於deliveryTag的消息。
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
/**
* 自動應答改成false
*/
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer1);
}
}
7、rabbitmq消息確認機制
在rabbitmq中,我們可以通過持久化數據 解決rabbitmq服務器的異常的數據丟失問題
問題:生產者將消息發送出去之後,消息到底有沒有到達rabbitmq服務器
兩種方式:
①、AMQP實現了事務機制
txSelect txCommit txRollback
txSelect :用於將當前channel設置成transaction模式
txCommit :用於提交事務
txRollback:回滾事務
send:
package com.my.test.rabbitmq.transaction;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-18
*/
@SuppressWarnings("DuplicatedCode")
public class Send {
static final String QUEUE_NAME = "test_simple_queue_tx";
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//從連接中創建一個管道
Channel channel = connection.createChannel();
//創建隊列聲明
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
String msg = "hello tx";
try {
/**
* 開啓事務
*/
channel.txSelect();
/**
* 發送消息
*/
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
/**
* 提交事務
*/
channel.txCommit();
System.out.println("send " + msg);
} catch (Exception e) {
e.printStackTrace();
/**
* 回滾事務
*/
channel.txRollback();
System.out.println("send rollback " + msg);
}
channel.close();
connection.close();
}
}
receive:
package com.my.test.rabbitmq.transaction;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-18
*/
@SuppressWarnings("DuplicatedCode")
public class Receive {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Send.QUEUE_NAME, true, false, false, null);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, StandardCharsets.UTF_8);
System.out.println(Send.QUEUE_NAME + "receive:" + msg);
}
};
channel.basicConsume(Send.QUEUE_NAME, true, consumer);
}
}
②、confirm模式
生產者端confirm模式的實現原理:
-
生產者將信道設置成confirm模式,一旦信道進入confirm模式,所有在該信道上發佈的消息都會被指派一個唯一的ID(從1開始),一旦消息被投遞到所有匹配的隊列之後,broker就會發送一個確認給生產者(包含消息的唯一ID),這就使得生產者知道消息已經正確到達目的隊列了,如果消息和隊列是可持久化的,那麼確認消息會將消息寫入磁盤之後發送給生產者。broker回傳給生產者的確認消息中deliver-tag域中包含了確認消息的序列號,此外broker也可以設置basic.ack的multiple域,表示到這個序列號之前的所有消息都已經得到了處理。
-
confirm模式最大的好處在於異步
開啓confirm模式:channel.confirmSelect()
編程模式:
1、普通 發一條 waitForConfirms()
package com.my.test.rabbitmq.confirm;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-18
* 單條確認
*/
@SuppressWarnings("DuplicatedCode")
public class Send1 {
static final String QUEUE_NAME = "test_queue_confirm1";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//從連接中創建一個管道
Channel channel = connection.createChannel();
//創建隊列聲明
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
/**
* 將channel設爲confirm模式,注意不能同時設爲事務模式
*/
channel.confirmSelect();
String msg = "hello confirm";
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
if (!channel.waitForConfirms()) {
System.out.println("message send failed");
} else {
System.out.println("send success " + msg);
}
channel.close();
connection.close();
}
}
2、批量的 發一批 waitForConfirms()
package com.my.test.rabbitmq.confirm;
import com.my.test.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author QinHe at 2019-09-18
* 批量確認
*/
@SuppressWarnings("DuplicatedCode")
public class Send2 {
static final String QUEUE_NAME = "test_queue_confirm2";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();
//從連接中創建一個管道
Channel channel = connection.createChannel();
//創建隊列聲明
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
/**
* 將channel設爲confirm模式,注意不能同時設爲事務模式
*/
channel.confirmSelect();
String msg = "hello confirm";
//批量發送
for (int i = 0; i < 10; i++) {
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
}
//確認
if (!channel.waitForConfirms()) {
System.out.println("message send failed");
} else {
System.out.println("send success " + msg);
}
channel.close();
connection.close();
}
}
3、異步confirm模式 提供一個回調方法
channel對象提供的ConfirmListen()回調方法中質保函deliveryTag(當前channel發出的消息序號),我們需要自己爲每一個channel維護一個unconfirm的消息序號集合,每publish一條數據,集合中元素加1,每回調一次handleAck方法,unconfirm集合刪掉相應的一條(multiple=false) 或者多條(multiple=true)記錄。從程序運行效率上看,這個unconfirm集合最好採用有序集合SortedSet存儲結構
thor QinHe at 2019-09-18
-
批量確認
*/
@SuppressWarnings(“DuplicatedCode”)
public class Send2 {static final String QUEUE_NAME = “test_queue_confirm2”;
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//獲取連接
Connection connection = ConnectionUtils.getConnection();//從連接中創建一個管道 Channel channel = connection.createChannel(); //創建隊列聲明 channel.queueDeclare(QUEUE_NAME, true, false, false, null); /** * 將channel設爲confirm模式,注意不能同時設爲事務模式 */ channel.confirmSelect(); String msg = "hello confirm"; //批量發送 for (int i = 0; i < 10; i++) { channel.basicPublish("", QUEUE_NAME, null, msg.getBytes()); } //確認 if (!channel.waitForConfirms()) { System.out.println("message send failed"); } else { System.out.println("send success " + msg); } channel.close(); connection.close();
}
}
3、異步confirm模式 提供一個回調方法
channel對象提供的ConfirmListen()回調方法中質保函deliveryTag(當前channel發出的消息序號),我們需要自己爲每一個channel維護一個unconfirm的消息序號集合,每publish一條數據,集合中元素加1,每回調一次handleAck方法,unconfirm集合刪掉相應的一條(multiple=false) 或者多條(multiple=true)記錄。從程序運行效率上看,這個unconfirm集合最好採用有序集合SortedSet存儲結構