Ubutun安裝使用命令:
-
apt-get install erlang-nox # 安裝erlang 需要erlang環境 erl # 查看relang語言版本,成功執行則說明relang安裝成功
-
apt-get install rabbitmq-server #安裝成功自動啓動
-
service rabbitmq-server start # 啓動 service rabbitmq-server stop # 停止 service rabbitmq-server restart # 重啓
-
rabbitmq-plugins enable rabbitmq_management # 啓用插件 service rabbitmq-server restart # 重啓
-
啓動後,mq只能在本地登陸web端控制
-
rabbitmqctl list_users #查看用戶
-
rabbitmqctl add_user admin yourpassword
-
rabbitmqctl set_user_tags admin administrator # 給普通用戶分配管理員角色
注意RabbitMQ的端口爲15672,如果虛擬在在遠程的話,需要開放端口
http://39.108.254.46:15672/就可以訪問了.
一:基本消息模型
如何避免消息丟失?
1種方案):
消費者消息的確認機制
問題:消息一旦被消費者接收,隊列中的消息會被刪除,那麼RabbitMQ怎麼知道消息被接受了?
這就需要通過消息確認機制
如果消費者領取消息後,還沒執行操作就掛掉了呢?或者拋出了異常?消息消費失敗,但是RabbitMQ無從得知,這樣消息就丟失了!
因此,RabbitMQ有一個ACK機制。當消費者獲取消息後,會向RabbitMQ發送回執ACK,告知消息已經被接收。不過這種回執ACK分兩種情況:
- 自動ACK:消息一旦被接收,消費者自動發送ACK
- 手動ACK:消息接收後,不會發送ACK,需要手動調用
- 如果消息不太重要,丟失也沒有影響,那麼自動ACK會比較方便
- 如果消息非常重要,不容丟失。那麼最好在消費完成後手動ACK,否則接收消息後就自動ACK,RabbitMQ就會把消息從隊列中刪除。如果此時消費者宕機,那麼消息就丟失了。
<dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.3.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies>
public class ConnectionUtil { /** * 建立與RabbitMQ的連接 * @return * @throws Exception */ public static Connection getConnection() throws Exception { //定義連接工廠 ConnectionFactory factory = new ConnectionFactory(); //設置服務地址 factory.setHost("39.108.254.46"); //端口 factory.setPort(5672); //設置賬號信息,用戶名、密碼、vhost factory.setVirtualHost("/kf"); factory.setUsername("kf"); factory.setPassword("kf"); // 通過工程獲取連接 Connection connection = factory.newConnection(); return connection; } }
public class Send { private final static String QUEUE_NAME = "simple_queue"; public static void main(String[] argv) throws Exception { // 獲取到連接以及mq通道 Connection connection = ConnectionUtil.getConnection(); // 從連接中創建通道,這是完成大部分API的地方。 Channel channel = connection.createChannel(); // 聲明(創建)隊列,必須聲明隊列才能夠發送消息,我們可以把消息發送到隊列中。 // 聲明一個隊列是冪等的 - 只有當它不存在時纔會被創建 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 消息內容 String message = "Hello World!"; channel.basicPublish("", QUEUE_NAME, null, message.getBytes()); System.out.println(" [x] Sent '" + message + "'"); //關閉通道和連接 channel.close(); connection.close(); } }
public class Recv { private final static String QUEUE_NAME = "simple_queue"; public static void main(String[] argv) throws Exception { // 獲取到連接 Connection connection = ConnectionUtil.getConnection(); // 創建通道 final Channel channel = connection.createChannel(); // 聲明隊列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 定義隊列的消費者 DefaultConsumer consumer = new DefaultConsumer(channel) { // 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用 @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { // body 即消息體 String msg = new String(body); System.out.println(" msg : " + msg + "!"); // 手動進行ACK channel.basicAck(envelope.getDeliveryTag(), false); } }; // 監聽隊列,第二個參數false,手動進行ACK channel.basicConsume(QUEUE_NAME, false, consumer); } }
二:WORK消息模型
生產消息過快,消費消息過慢,導致消息堆積
避免消息堆積?
1) 採用workqueue,多個消費者監聽同一隊列。
2)接收到消息以後,而是通過線程池,異步消費。
public class Send { //循環發送50條消息 private final static String QUEUE_NAME = "test_work_queue"; public static void main(String[] argv) throws Exception { // 獲取到連接 Connection connection = ConnectionUtil.getConnection(); // 獲取通道 Channel channel = connection.createChannel(); // 聲明隊列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 循環發佈任務 for (int i = 0; i < 50; i++) { // 消息內容 String message = "task .. " + i; channel.basicPublish("", QUEUE_NAME, null, message.getBytes()); System.out.println(" [x] Sent '" + message + "'"); Thread.sleep(i * 2); } // 關閉通道和連接 channel.close(); connection.close(); } } 消費者: 添加:channel.basicQos(1);//一條一條處理消息,並不是原來的RabbitMQ默認平均分配消息,能者多勞。 // 消費者1 public class Recv { private final static String QUEUE_NAME = "test_work_queue"; public static void main(String[] argv) throws Exception { // 獲取到連接 Connection connection = ConnectionUtil.getConnection(); // 獲取通道 final Channel channel = connection.createChannel(); // 聲明隊列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 設置每個消費者同時只能處理一條消息 channel.basicQos(1); // 定義隊列的消費者 DefaultConsumer consumer = new DefaultConsumer(channel) { // 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用 @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { // body 即消息體 String msg = new String(body); System.out.println(" [消費者1] received : " + msg + "!"); try { // 模擬完成任務的耗時:1000ms Thread.sleep(1000); } catch (InterruptedException e) { } // 手動ACK channel.basicAck(envelope.getDeliveryTag(), false); } }; // 監聽隊列。 channel.basicConsume(QUEUE_NAME, false, consumer); } } //消費者2 public class Recv2 { private final static String QUEUE_NAME = "test_work_queue"; public static void main(String[] argv) throws Exception { // 獲取到連接 Connection connection = ConnectionUtil.getConnection(); // 獲取通道 final Channel channel = connection.createChannel(); // 聲明隊列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 設置每個消費者同時只能處理一條消息 channel.basicQos(1); // 定義隊列的消費者 DefaultConsumer consumer = new DefaultConsumer(channel) { // 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用 @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { // body 即消息體 String msg = new String(body); System.out.println(" [消費者2] received : " + msg + "!"); // 手動ACK channel.basicAck(envelope.getDeliveryTag(), false); } }; // 監聽隊列。 channel.basicConsume(QUEUE_NAME, false, consumer); } }
其他模型:
1、1個生產者,多個消費者
2、每一個消費者都有自己的一個隊列
3、生產者沒有將消息直接發送到隊列,而是發送到了交換機
4、每個隊列都要綁定到交換機
5、生產者發送的消息,經過交換機到達隊列,實現一個消息被多個消費者獲取的目的
X(Exchanges):交換機一方面:接收生產者發送的消息。另一方面:知道如何處理消息,例如遞交給某個特別隊列、遞交給所有隊列、或是將消息丟棄。到底如何操作,取決於Exchange的類型。
Exchange類型有以下幾種:
Fanout:廣播,將消息交給所有綁定到交換機的隊列
Direct:定向,把消息交給符合指定routing key 的隊列
Topic:通配符,把消息交給符合routing pattern(路由模式) 的隊列
三.訂閱模型-Fanout
在廣播模式下,消息發送流程是這樣的:
- 1) 可以有多個消費者
- 2) 每個**消費者有自己的queue**(隊列)
- 3) 每個**隊列都要綁定到Exchange**(交換機)
- 4) **生產者發送的消息,只能發送到交換機**,交換機來決定要發給哪個隊列,生產者無法決定。
- 5) 交換機把消息發送給綁定過的所有隊列
- 6) 隊列的消費者都能拿到消息。實現一條消息被多個消費者消費。
實現demo:
public class Send { private final static String EXCHANGE_NAME = "fanout_exchange_test"; public static void main(String[] argv) throws Exception { // 獲取到連接 Connection connection = ConnectionUtil.getConnection(); // 獲取通道 Channel channel = connection.createChannel(); // 聲明exchange,指定類型爲fanout channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); // 消息內容 String message = "Hello everyone"; // 發佈消息到Exchange channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes()); System.out.println(" [生產者] Sent '" + message + "'"); channel.close(); connection.close(); } }
// 消費者2 public class Recv2 { private final static String QUEUE_NAME = "fanout_exchange_queue_2"; private final static String EXCHANGE_NAME = "fanout_exchange_test"; public static void main(String[] argv) throws Exception { // 獲取到連接 Connection connection = ConnectionUtil.getConnection(); // 獲取通道 Channel channel = connection.createChannel(); // 聲明隊列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 綁定隊列到交換機 channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ""); // 定義隊列的消費者 DefaultConsumer consumer = new DefaultConsumer(channel) { // 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用 @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { // body 即消息體 String msg = new String(body); System.out.println(" [消費者2] received : " + msg + "!"); } }; // 監聽隊列,手動返回完成 channel.basicConsume(QUEUE_NAME, true, consumer); } }
//消費者1 public class Recv { private final static String QUEUE_NAME = "fanout_exchange_queue_1"; private final static String EXCHANGE_NAME = "fanout_exchange_test"; public static void main(String[] argv) throws Exception { // 獲取到連接 Connection connection = ConnectionUtil.getConnection(); // 獲取通道 Channel channel = connection.createChannel(); // 聲明隊列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 綁定隊列到交換機 channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ""); // 定義隊列的消費者 DefaultConsumer consumer = new DefaultConsumer(channel) { // 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用 @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { // body 即消息體 String msg = new String(body); System.out.println(" [消費者1] received : " + msg + "!"); } }; // 監聽隊列,自動返回完成 channel.basicConsume(QUEUE_NAME, true, consumer); } }