1.消息隊列的應用場景有哪些?
答:消息隊列的應用場景如下。
- 應用解耦,比如,用戶下單後,訂單系統需要通知庫存系統,假如庫存系統無法訪問,則訂單減庫存將失敗,從而導致訂單失敗。訂單系統與庫存系統耦合,這個時候如果使用消息隊列,可以返回給用戶成功,先把消息持久化,等庫存系統恢復後,就可以正常消費減去庫存了。
- 削峯填谷,比如,秒殺活動,一般會因爲流量過大,從而導致流量暴增,應用掛掉,這個時候加上消息隊列,服務器接收到用戶的請求後,首先寫入消息隊列,假如消息隊列長度超過最大數量,則直接拋棄用戶請求或跳轉到錯誤頁面。
- 日誌系統,比如,客戶端負責將日誌採集,然後定時寫入消息隊列,消息隊列再統一將日誌數據存儲和轉發。
2.RabbitMQ 有哪些優點?
答:RabbitMQ 的優點如下:
- 可靠性,RabbitMQ 的持久化支持,保證了消息的穩定性;
- 高併發,RabbitMQ 使用了 Erlang 開發語言,Erlang 是爲電話交換機開發的語言,天生自帶高併發光環和高可用特性;
- 集羣部署簡單,正是因爲 Erlang 使得 RabbitMQ 集羣部署變的非常簡單;
- 社區活躍度高,因爲 RabbitMQ 應用比較廣泛,所以社區的活躍度也很高;
- 解決問題成本低,因爲資料比較多,所以解決問題的成本也很低;
- 支持多種語言,主流的編程語言都支持,如 Java、.NET、PHP、Python、JavaScript、Ruby、Go 等;
- 插件多方便使用,如網頁控制檯消息管理插件、消息延遲插件等。
3.RabbitMQ 有哪些重要的角色?
答:RabbitMQ 包含以下三個重要的角色:
- 生產者:消息的創建者,負責創建和推送數據到消息服務器;
- 消費者:消息的接收方,用於處理數據和確認消息;
- 代理:就是 RabbitMQ 本身,用於扮演“快遞”的角色,本身不生產消息,只是扮演“快遞”的角色。
4.RabbitMQ 有哪些重要的組件?它們有什麼作用?
答:RabbitMQ 包含的重要組件有:ConnectionFactory(連接管理器)、Channel(信道)、Exchange(交換器)、Queue(隊列)、RoutingKey(路由鍵)、BindingKey(綁定鍵) 等重要的組件,它們的作用如下:
- ConnectionFactory(連接管理器):應用程序與 RabbitMQ 之間建立連接的管理器,程序代碼中使用;
- Channel(信道):消息推送使用的通道;
- Exchange(交換器):用於接受、分配消息;
- Queue(隊列):用於存儲生產者的消息;
- RoutingKey(路由鍵):用於把生成者的數據分配到交換器上;
- BindingKey(綁定鍵):用於把交換器的消息綁定到隊列上。
運行流程,如下圖所示:
5.什麼是消息持久化?
答:消息持久化是把消息保存到物理介質上,以防止消息的丟失。
6.RabbitMQ 要實現消息持久化,需要滿足哪些條件?
答:RabbitMQ 要實現消息持久化,必須滿足以下 4 個條件:
- 投遞消息的時候 durable 設置爲 true,消息持久化,代碼:
channel.queueDeclare(x, true, false, false, null)
,參數 2 設置爲 true 持久化; - 設置投遞模式 deliveryMode 設置爲 2(持久),代碼:
channel.basicPublish(x, x, MessageProperties.PERSISTENTTEXTPLAIN,x)
,參數 3 設置爲存儲純文本到磁盤; - 消息已經到達持久化交換器上;
- 消息已經到達持久化的隊列。
7.消息持久化有哪些缺點?如何緩解?
答:消息持久化的缺點是很消耗性能,因爲要寫入硬盤要比寫入內存性能較低很多,從而降低了服務器的吞吐量。可使用固態硬盤來提高讀寫速度,以達到緩解消息持久化的缺點。
8.如何使用 Java 代碼連接 RabbitMQ?
答:使用 Java 代碼連接 RabbitMQ 有以下兩種方式:
方式一:
public static Connection GetRabbitConnection() {
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(Config.UserName);
factory.setPassword(Config.Password);
factory.setVirtualHost(Config.VHost);
factory.setHost(Config.Host);
factory.setPort(Config.Port);
Connection conn = null;
try {
conn = factory.newConnection();
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
方式二:
public static Connection GetRabbitConnection2() {
ConnectionFactory factory = new ConnectionFactory();
// 連接格式:amqp://userName:password@hostName:portNumber/virtualHost
String uri = String.format("amqp://%s:%s@%s:%d%s", Config.UserName, Config.Password, Config.Host, Config.Port,
Config.VHost);
Connection conn = null;
try {
factory.setUri(uri);
factory.setVirtualHost(Config.VHost);
conn = factory.newConnection();
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
9.使用 Java 代碼編寫一個 RabbitMQ 消費和生產的示例?
答:代碼如下:
public static void main(String[] args) {
publisher(); // 生產消息
consumer(); // 消費消息
}
/**
* 推送消息
*/
public static void publisher() {
// 創建一個連接
Connection conn = ConnectionFactoryUtil.GetRabbitConnection();
if (conn != null) {
try {
// 創建通道
Channel channel = conn.createChannel();
// 聲明隊列【參數說明:參數一:隊列名稱,參數二:是否持久化;參數三:是否獨佔模式;參數四:消費者斷開連接時是否刪除隊列;參數五:消息其他參數】
channel.queueDeclare(Config.QueueName, false, false, false, null);
String content = String.format("當前時間:%s", new Date().getTime());
// 發送內容【參數說明:參數一:交換機名稱;參數二:隊列名稱,參數三:消息的其他屬性-routing headers,此屬性爲MessageProperties.PERSISTENT_TEXT_PLAIN用於設置純文本消息存儲到硬盤;參數四:消息主體】
channel.basicPublish("", Config.QueueName, null, content.getBytes("UTF-8"));
System.out.println("已發送消息:" + content);
// 關閉連接
channel.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 消費消息
*/
public static void consumer() {
// 創建一個連接
Connection conn = ConnectionFactoryUtil.GetRabbitConnection();
if (conn != null) {
try {
// 創建通道
Channel channel = conn.createChannel();
// 聲明隊列【參數說明:參數一:隊列名稱,參數二:是否持久化;參數三:是否獨佔模式;參數四:消費者斷開連接時是否刪除隊列;參數五:消息其他參數】
channel.queueDeclare(Config.QueueName, false, false, false, null);
// 創建訂閱器,並接受消息
channel.basicConsume(Config.QueueName, false, "", new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
String routingKey = envelope.getRoutingKey(); // 隊列名稱
String contentType = properties.getContentType(); // 內容類型
String content = new String(body, "utf-8"); // 消息正文
System.out.println("消息正文:" + content);
channel.basicAck(envelope.getDeliveryTag(), false); // 手動確認消息【參數說明:參數一:該消息的index;參數二:是否批量應答,true批量確認小於index的消息】
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
10.RabbitMQ 交換器類型有哪些?
答:RabbitMQ 消費類型也就是交換器(Exchange)類型有以下四種:
- direct:輪詢方式
- headers:輪詢方式,允許使用 header 而非路由鍵匹配消息,性能差,幾乎不用
- fanout:廣播方式,發送給所有訂閱者
- topic:匹配模式,允許使用正則表達式匹配消息
RabbitMQ 默認的是 direct 方式。
11.RabbitMQ 如何確保每個消息能被消費?
答:RabbitMQ 使用 ack 消息確認的方式保證每個消息都能被消費,開發者可根據自己的實際業務,選擇 channel.basicAck() 方法手動確認消息被消費。
12.RabbitMQ 接收到消息之後必須消費嗎?
答:RabbitMQ 接收到消息之後可以不消費,在消息確認消費之前,可以做以下兩件事:
- 拒絕消息消費,使用 channel.basicReject(消息編號, true) 方法,消息會被分配給其他訂閱者;
- 設置爲死信隊列,死信隊列是用於專門存放被拒絕的消息隊列。
13.topic 模式下發布了一個路由鍵爲“com.mq.rabbit.error”的消息,請問以下不能接收到消息的是?
A:cn.mq.rabbit.*
B:#.error
C:cn.mq.*
D:cn.mq.#
答:C
題目解析:“*”用於匹配一個分段(用“.”分割)的內容,“#”用於匹配 0 和多個字符。
14.以下可以獲取歷史消息的是?
A:topic 交換器
B:fanout 交換器
C:direct 交換器
D:以上都不是
答:C
題目解析:fanout 和 topic 都是廣播形式的,因此無法獲取歷史消息,而 direct 可以。
15.RabbitMQ 包含事務功能嗎?如何使用?
答:RabbitMQ 包含事務功能,主要是對信道(Channel)的設置,主要方法有以下三個:
- channel.txSelect() 聲明啓動事務模式;
- channel.txComment() 提交事務;
- channel.txRollback() 回滾事務。
16.RabbitMQ 的事務在什麼情況下是無效的?
答:RabbitMQ 的事務在 autoAck=true 也就是自動消費確認的時候,事務是無效的。因爲如果是自動消費確認,RabbitMQ 會直接把消息從隊列中移除,即使後面事務回滾也不能起到任何作用。
17.Kafka 可以脫離 ZooKeeper 單獨使用嗎?
答:Kafka 不能脫離 ZooKeeper 單獨使用,因爲 Kafka 使用 ZooKeeper 管理和協調 Kafka 的節點服務器。
18.Kafka 有幾種數據保留的策略?
答:Kafka 有兩種數據保存策略:按照過期時間保留和按照存儲的消息大小保留。
19.Kafka 同時設置了 7 天和 10G 清除數據,到第五天的時候消息達到了 10G,這個時候 Kafka 將如何處理?
答:這個時候 Kafka 會執行數據清除工作,時間和大小不論哪個滿足條件,都會清空數據。
20.什麼情況會導致 Kafka 運行變慢?
答:以下情況可導致 Kafka 運行變慢:
- CPU 性能瓶頸
- 磁盤讀寫瓶頸
- 網絡瓶頸
21.使用 Kafka 集羣需要注意什麼?
答:Kafka 集羣使用需要注意以下事項:
- 集羣的數量不是越多越好,最好不要超過 7 個,因爲節點越多,消息複製需要的時間就越長,整個羣組的吞吐量就越低;
- 集羣數量最好是單數,因爲超過一半故障集羣就不能用了,設置爲單數容錯率更高。