前言
本次講的是一箇中間件的概念。
也就是rabbitmq…
此次我們只給大家來介紹一下有哪些中間件,和rabbit中間件的的基本使用方式。
hh
消息中間件
所有消息中間件
1.1 RocketMQ
阿里系下開源的一款分佈式、隊列模型的消息中間件,原名Metaq,3.0版本名稱改爲RocketMQ,是阿里參照kafka設計思想使用java實現的一套mq。同時將阿里系內部多款mq產品(Notify、metaq)進行整合,只維護核心功能,去除了所有其他運行時依賴,保證核心功能最簡化,在此基礎上配合阿里上述其他開源產品實現不同場景下mq的架構,目前主要多用於訂單交易系統。
具有以下特點:
- 能夠保證嚴格的消息順序
- 提供針對消息的過濾功能
- 提供豐富的消息拉取模式
- 高效的訂閱者水平擴展能力
- 實時的消息訂閱機制
- 億級消息堆積能力
- 官方提供了一些不同於kafka的對比差異:
https://rocketmq.apache.org/docs/motivation/
1.2 RabbitMQ
使用Erlang編寫的一個開源的消息隊列,本身支持很多的協議:AMQP,XMPP, SMTP,STOMP,也正是如此,使的它變的非常重量級,更適合於企業級的開發。同時實現了Broker架構,核心思想是生產者不會將消息直接發送給隊列,消息在發送給客戶端時先在中心隊列排隊。對路由(Routing),負載均衡(Load balance)、數據持久化都有很好的支持。多用於進行企業級的ESB整合。
1.3 ActiveMQ
Apache下的一個子項目。使用Java完全支持JMS1.1和J2EE 1.4規範的 JMS Provider實現,少量代碼就可以高效地實現高級應用場景。可插拔的傳輸協議支持,比如:in-VM, TCP, SSL, NIO, UDP, multicast, JGroups and JXTA transports。RabbitMQ、ZeroMQ、ActiveMQ均支持常用的多種語言客戶端 C++、Java、.Net,、Python、 Php、 Ruby等。
1.4 Redis
使用C語言開發的一個Key-Value的NoSQL數據庫,開發維護很活躍,雖然它是一個Key-Value數據庫存儲系統,但它本身支持MQ功能,所以完全可以當做一個輕量級的隊列服務來使用。對於RabbitMQ和Redis的入隊和出隊操作,各執行100萬次,每10萬次記錄一次執行時間。測試數據分爲128Bytes、512Bytes、1K和10K四個不同大小的數據。實驗表明:入隊時,當數據比較小時Redis的性能要高於RabbitMQ,而如果數據大小超過了10K,Redis則慢的無法忍受;出隊時,無論數據大小,Redis都表現出非常好的性能,而RabbitMQ的出隊性能則遠低於Redis。
1.5 Kafka
Apache下的一個子項目,使用scala實現的一個高性能分佈式Publish/Subscribe消息隊列系統,具有以下特性:
- 快速持久化:通過磁盤順序讀寫與零拷貝機制,可以在O(1)的系統開銷下進行消息持久化;
- 高吞吐:在一臺普通的服務器上既可以達到10W/s的吞吐速率;
- 高堆積:支持topic下消費者較長時間離線,消息堆積量大;
- 完全的分佈式系統:Broker、Producer、Consumer都原生自動支持分佈式,依賴zookeeper自動實現複雜均衡;
- 支持Hadoop數據並行加載:對於像Hadoop的一樣的日誌數據和離線分析系統,但又要求實時處理的限制,這是一個可行的解決方案。
大大
1.6 ZeroMQ
號稱最快的消息隊列系統,專門爲高吞吐量/低延遲的場景開發,在金融界的應用中經常使用,偏重於實時數據通信場景。ZMQ能夠實現RabbitMQ不擅長的高級/複雜的隊列,但是開發人員需要自己組合多種技術框架,開發成本高。因此ZeroMQ具有一個獨特的非中間件的模式,更像一個socket library,你不需要安裝和運行一個消息服務器或中間件,因爲你的應用程序本身就是使用ZeroMQ API完成邏輯服務的角色。但是ZeroMQ僅提供非持久性的隊列,如果down機,數據將會丟失。如:Twitter的Storm中使用ZeroMQ作爲數據流的傳輸。
ZeroMQ套接字是與傳輸層無關的:ZeroMQ套接字對所有傳輸層協議定義了統一的API接口。默認支持 進程內(inproc) ,進程間(IPC) ,多播,TCP協議,在不同的協議之間切換隻要簡單的改變連接字符串的前綴。可以在任何時候以最小的代價從進程間的本地通信切換到分佈式下的TCP通信。ZeroMQ在背後處理連接建立,斷開和重連邏輯。
特性:
-
無鎖的隊列模型:對於跨線程間的交互(用戶端和session)之間的數據交換通道pipe,採用無鎖的隊列算法CAS;在pipe的兩端註冊有異步事件,在讀或者寫消息到pipe的時,會自動觸發讀寫事件。
-
批量處理的算法:對於批量的消息,進行了適應性的優化,可以批量的接收和發送消息。
-
多核下的線程綁定,無須CPU切換:區別於傳統的多線程併發模式,信號量或者臨界區,zeroMQ充 分利用多核的優勢,每個核綁定運行一個工作者線程,避免多線程之間的CPU切換開銷。
重要的三大塊
結論:
-
activiMq老牌消息中間件,api全面,但是吞吐量不大
-
Kafaka吞吐量大,但是數據無法保證不丟失,主要面向大數據
-
rokectMQ:吞吐量大,保證數據不丟失,並且支持分佈式事物,但是商業版需要收費
-
rabbitMQ:吞吐量大,數據不易丟失
本次的主題Rabbit
RabbitMQ:
RabbitMQ是—個開源的消息代理和隊列服務器,用來通過普通協議 在完全不同的應用之間共享數據,RabbitMQ是使用Erlang語言來編寫 的,並且RabbitMQ是基於AMQP協議的。
大概的圖解,而不是全部:
AMQP核心概念(重點!!!)
Server:又稱Broker,接受客戶端的連接,實現AMQP實體服務
Connection:連接:應用程序與Broker的網絡連接
Channel:網絡通道,幾乎所有的操作都在Channel中進行,Channel是進行消息讀寫的通道;客戶端可建立多個Channel,每個Channel代表一個會話任務;
Message:消息,服務器與應用程序之間傳遞的數據,由Properties和Body組成。Properties可以對消息進行裝飾,比如消息的優先級、延遲等高級特性;Body則就是消息體內容;
Virtual host:虛擬地址,用於進行邏輯隔離,最上層的消息路由;一個Virtual Host裏面可以有若干個Exchange和Queue,同一個Virtual Host裏面不能有相同名稱的Exchange或Queue;
Exchange:交換機,交換消息,根據路由鍵轉發消息到綁定的隊列;
Binding:Exchange和Queue之間的虛擬連接,binding中可以包含routing key;
Routing key:一個路由規則,虛擬機可用它來確定如何路由一個特定消息
Queue:也稱爲Message Queue,消息隊列,保存消息並將它們轉發給消費者
RabbitMQ的安裝和使用
先說我們的環境也就是之前的docker 裏面進行一個安裝
1、直接拉下來:
docker pull rabbitmq:management
2、運行
2.1、第一種運行方式
docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management
2.2、第二種運行方式
docker run -d \
--name my-rabbitmq \
-p 5672:5672 -p 15672:15672 \
-v /data:/var/lib/rabbitmq \
--hostname my-rabbitmq-host \
-e RABBITMQ_DEFAULT_VHOST=my_vhost \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin \
--restart=always \
rabbitmq:management
注意事項!!!
-d:後臺運行容器
-name:指定容器名
-p:指定服務運行的端口(5672:應用訪問端口;15672:控制檯Web端口號)
-v:映射目錄或文件,啓動了一個數據卷容器,數據卷路徑爲:/var/lib/rabbitmq,再將此數據卷映射到住宿主機的/data目錄
--hostname:主機名(RabbitMQ的一個重要注意事項是它根據所謂的 “節點名稱” 存儲數據,默認爲主機名)
-e:指定環境變量;(RABBITMQ_DEFAULT_VHOST:默認虛擬機名;RABBITMQ_DEFAULT_USER:默認的用戶名;RABBITMQ_DEFAULT_PASS:默認用戶名的密碼)
--restart=always:當Docker重啓時,容器能自動啓動
rabbitmq:management:鏡像名
注1:RABBITMQ_DEFAULT_VHOST=my_vhost,my_vhost名字請記好,在之後的編程中要用到,
如果啓動時沒指定,默認值爲/
注2:容器啓動後,可以通過docker logs 窗口ID/容器名字 查看日誌
docker logs my-rabbitmq
注3:停止並刪除所有容器
docker stop $(docker ps -aq) && docker rm $(docker ps -aq)
3、輸入地址(賬戶密碼默認爲guest:guest)
Connections
Channels
Exchanges
Queues
賬戶
測試
基本的流程(沒有交換機)
圖解:
測試:先開消費者,再開生產者
Consumer
package com.liwangwang.rabbitmq01.quickstart;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception {
//1 創建一個ConnectionFactory, 並進行配置
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.47.133");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2 通過連接工廠創建連接
Connection connection = connectionFactory.newConnection();
//3 通過connection創建一個Channel
Channel channel = connection.createChannel();
//4 聲明(創建)一個隊列
String queueName = "test001";
// 參數:隊列名稱、持久化與否、獨佔與否、無消息隊列是否自動刪除、消息參數
// queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
channel.queueDeclare(queueName, true, false, false, null);
//5 創建消費者
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
//6 設置Channel
// 參數:隊列名稱、自動簽收、消費者回調
// basicConsume(String queue, boolean autoAck, Consumer callback)
channel.basicConsume(queueName, true, queueingConsumer);
while(true){
//7 獲取消息(Delivery:傳送)
QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
String msg = new String(delivery.getBody());
System.err.println("消費端: " + msg);
//Envelope envelope = delivery.getEnvelope();
}
}
}
Procuder
package com.liwangwang.rabbitmq01.quickstart;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @author 小李飛刀
* @site www.javaxl.com
* @company
* @create 2019-11-18 8:58
*/
public class Procuder {
public static void main(String[] args) throws Exception {
//1 創建一個ConnectionFactory, 並進行配置
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.47.133");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2 通過連接工廠創建連接
Connection connection = connectionFactory.newConnection();
//3 通過connection創建一個Channel
Channel channel = connection.createChannel();
//4 通過Channel發送數據
for(int i=0; i < 5; i++){
String msg = "Hello RabbitMQ!";
//1 exchange 2 routingKey
channel.basicPublish("", "test001", null, msg.getBytes());
}
//5 記得要關閉相關的連接
channel.close();
connection.close();
}
}
交換機流程(有交換機)
直流交換機
測試:先開消費者,再開生產者
Consumer4DirectExchange
package com.liwangwang.rabbitmq01.exchange.direct;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
public class Consumer4DirectExchange {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory() ;
connectionFactory.setHost("192.168.47.133");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setNetworkRecoveryInterval(3000);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//4 聲明
String exchangeName = "test_direct_exchange";
String exchangeType = "direct";
String queueName = "test_direct_queue";
String routingKey = "test.direct";
//表示聲明瞭一個交換機
channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
//表示聲明瞭一個隊列
channel.queueDeclare(queueName, false, false, false, null);
//建立一個綁定關係:
channel.queueBind(queueName, exchangeName, routingKey);
//durable 是否持久化消息
QueueingConsumer consumer = new QueueingConsumer(channel);
//參數:隊列名稱、是否自動ACK、Consumer
channel.basicConsume(queueName, true, consumer);
//循環獲取消息
while(true){
//獲取消息,如果沒有消息,這一步將會一直阻塞
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("收到消息:" + msg);
}
}
}
Producer4DirectExchange
package com.liwangwang.rabbitmq01.exchange.direct;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer4DirectExchange {
public static void main(String[] args) throws Exception {
//1 創建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.47.133");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2 創建Connection
Connection connection = connectionFactory.newConnection();
//3 創建Channel
Channel channel = connection.createChannel();
//4 聲明
String exchangeName = "test_direct_exchange";
String routingKey = "test.direct";
// String routingKey = "test.direct111"; //收不到
//5 發送
String msg = "Hello World RabbitMQ 4 Direct Exchange Message 111 ... ";
channel.basicPublish(exchangeName, routingKey , null , msg.getBytes());
}
}
代碼的區別:
控制檯:
主題交換機
測試:先開消費者再開服務者
Consumer4TopicExchange
package com.liwangwang.rabbitmq01.exchange.topic;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
public class Consumer4TopicExchange {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory() ;
connectionFactory.setHost("192.168.47.133");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setNetworkRecoveryInterval(3000);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//4 聲明
String exchangeName = "test_topic_exchange";
String exchangeType = "topic";
String queueName = "test_topic_queue";
String routingKey = "user.#";
// String routingKey = "user.*";
// 1 聲明交換機
channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
// 2 聲明隊列
channel.queueDeclare(queueName, false, false, false, null);
// 3 建立交換機和隊列的綁定關係:
channel.queueBind(queueName, exchangeName, routingKey);
//durable 是否持久化消息
QueueingConsumer consumer = new QueueingConsumer(channel);
//參數:隊列名稱、是否自動ACK、Consumer
channel.basicConsume(queueName, true, consumer);
//循環獲取消息
while(true){
//獲取消息,如果沒有消息,這一步將會一直阻塞
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("收到消息:" + msg);
}
}
}
Producer4TopicExchange
package com.liwangwang.rabbitmq01.exchange.topic;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer4TopicExchange {
public static void main(String[] args) throws Exception {
//1 創建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.47.133");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2 創建Connection
Connection connection = connectionFactory.newConnection();
//3 創建Channel
Channel channel = connection.createChannel();
//4 聲明
String exchangeName = "test_topic_exchange";
String routingKey1 = "user.save";
String routingKey2 = "user.update";
String routingKey3 = "user.delete.abc";
//5 發送
String msg = "Hello World RabbitMQ 4 Topic Exchange Message ...";
channel.basicPublish(exchangeName, routingKey1 , null , msg.getBytes());
channel.basicPublish(exchangeName, routingKey2 , null , msg.getBytes());
channel.basicPublish(exchangeName, routingKey3 , null , msg.getBytes());
channel.close();
connection.close();
}
}
消費端:
生產端:
控制檯:
輸出交換機
測試:先開消費端再打開服務端
Consumer4FanoutExchange
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
/**
* @author 小李飛刀
* @site www.javaxl.com
* @company
* @create 2019-11-18 10:40
*/
public class Consumer4FanoutExchange {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory() ;
connectionFactory.setHost("192.168.147.146");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setNetworkRecoveryInterval(3000);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//4 聲明
String exchangeName = "test_fanout_exchange";
String exchangeType = "fanout";
String queueName = "test_fanout_queue";
String routingKey = ""; //不設置路由鍵
channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);
//durable 是否持久化消息
QueueingConsumer consumer = new QueueingConsumer(channel);
//參數:隊列名稱、是否自動ACK、Consumer
channel.basicConsume(queueName, true, consumer);
//循環獲取消息
while(true){
//獲取消息,如果沒有消息,這一步將會一直阻塞
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("收到消息:" + msg);
}
}
}
Producer4FanoutExchange
package com.liwangwang.rabbitmq01.exchange.fanout;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer4FanoutExchange {
public static void main(String[] args) throws Exception {
//1 創建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.47.133");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2 創建Connection
Connection connection = connectionFactory.newConnection();
//3 創建Channel
Channel channel = connection.createChannel();
//4 聲明
String exchangeName = "test_fanout_exchange";
//5 發送
for(int i = 0; i < 10; i ++) {
String msg = "Hello World RabbitMQ 4 FANOUT Exchange Message ...";
channel.basicPublish(exchangeName, "", null , msg.getBytes());
}
channel.close();
connection.close();
}
}
消費端:
生產端:
控制檯:
後記
這個的核心也就是交換機的概念,
在沒有交換機的時候,我們的消息隊列會處理所有發給這個消息隊列的消息,然後由消費者一個一個消費這個隊列裏面的消息,如果由集羣的話還會分攤對這個消息隊列的處理。只不過這裏面有一個
Message acknowledgment的概念,這將會導致嚴重的bug——Queue中堆積的消息會越來越多
當然一般的消息中間件都不會這麼幹,我們使用了交換機後,我們看到我們的三種策略,其實都可以說由交換機去找跟它所綁定的消息隊列,如果生產端的路由鍵不符合要求或找不到消息隊列定好的路由鍵的話就會進行其他處理。
可以參考博客:https://www.sojson.com/blog/48.html