簡述:
流控機制是用來避免消息的發送速率過快而導致服務器難以支撐的情形。內存和磁盤警告相當於全局的流控,一旦觸發會阻塞集羣中所有的Connection,而本節的流程是針對單個Connection的.
原理:
1.Erlang進程之間並不共享內存(binary類型的除外),而是通過消息傳遞來通信,每個進程都有自己的進程郵箱。
2.默認情況下,Erlang並沒有對進程郵箱的大小進程限制,所以當有大量消息持續發往某個進程時,會導致該進程郵箱過大,最終內存溢出並崩潰。
解決思路:
1.RabbitMQ使用了一種基於信用證算法的流控機制來限制發送消息的速率以解決前面嗦剔除的問題。
2.它通過監控各個進程的進程郵箱,當某個進程負載過高而來不及處理消息時,這個進程的郵箱就會開始堆積消息。當堆積一定量時,就會阻塞而不接收上游的新消息。從而慢慢地,上游進程的進程郵箱也會開始堆積消息。當堆積到一定量時也會阻塞而停止接收上游的消息,最後就會使負責網絡數據包接收的進程阻塞而暫停接收新的消息。
3. 如圖所示,進程A接收消息轉發至進程B,進程B接收消息轉發至進程C。每一個進程都有一對關於收發消息的credit值(這裏以B爲當前進程):
~credit_from表示能發送多少消息,每發一條消息,值減1,爲0時不再往C進程發送消息,
時,也不接收A進程的消息.
~credit_to表示再接收多少消息就向A進程發送增加credit值的通知,A接收到通知就增加
credit_from的值,這樣進程A就能持續發送消息.
~當進程A發送速率高於進程B接收速率時,進程A的credit_from值會逐漸耗光,這時A進程就會
堵塞,並且阻塞的情況會一直傳遞到最上游.
~當進程A開始接收進程B的增加credit_from值的通知時,如果進程A處理堵塞狀態則會接觸阻
塞,開始接收更上游的消息,並依次解除上游堵塞狀態.
~這樣,基於信用證的流控機制最終將消息發送進程的發送速率限制在消息處理進程的處理能力
範圍內
4.一個連接(Connection)觸發流控會處於“flow”的狀態,也意味着這個連接的狀態每秒在blocked和unblocked之間來回切換數次,這樣就可以將消息發送的速率控制在服務器能夠支撐的範圍之內.
5.處於flow狀態和running狀態的Connection沒有什麼不同,flow只是告訴系統管理員相應的發送速率受限了。而對客戶端而言,它看到的只是服務器的帶寬要比正常情況下要小一些.
擴展知識:
1.流控機制不只是作用於Connection,同樣作用於信道和隊列。連接到消息持久化存儲形成了一個完整的流控鏈,對於處於整個流控鏈中的任意進程,只要該進程堵塞,必然導致上游所有進程堵塞.
案例,打破隊列的瓶頸:
1.一般情況下,向一個隊列裏推送消息時,往往在rabbit_amqqueue_process中(即隊列進程中)產生性能瓶頸。
2.在向一個隊列中快速發送消息的時候,Connection和channel都會處於flow狀態,而隊列處於running狀態。
解決方案:
1.開啓Erlang語言的HiPE功能,這樣保守估計可以提高30%~40%的性能,不過在較舊版本的Erlang中,這個功能不太穩定,建議使用至少是18.x版本的Erlang。
2.尋求打破rabbit_amqqueue_process的性能瓶頸,指以多個rabbit_amqqueue_process替換單個rabbit_amqqueue_process,這樣可以充分利用上rabbit_reader和rabbit_channel進程中被流控的性能。
代碼封裝:
鏈接RabbitMq
package com.ly.liyong.publicrmq;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeoutException;
public class RmqEncapsulation {
private static String host = "47.105.121.99";
private static int port = 5672;
private static String vhost = "/";
private static String username = "lpadmin";
private static String password = "lpadmin";
private static Connection connection;
private int subdivisionNum;//分片數,表示一個邏輯隊列背後的實際隊列
public RmqEncapsulation(int _subdivisionNum) {
this.subdivisionNum = _subdivisionNum;
}
/**
* 創建Connection
*
* @throws IOException
* @throws TimeoutException
*/
public static void newConnection() throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(host);
connectionFactory.setVirtualHost(vhost);
connectionFactory.setPort(port);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
connection = connectionFactory.newConnection();
}
/**
* 獲取Connection,若爲null,則調用newConnection進行創建
*
* @return
* @throws IOException
* @throws TimeoutException
*/
public static Connection getConnection() throws IOException, TimeoutException {
if (connection == null) {
newConnection();
}
return connection;
}
/**
* 關閉Connection
*
* @throws IOException
* @throws TimeoutException
*/
public static void closeConnection() throws IOException, TimeoutException {
if (connection != null) {
connection.close();
}
}
/**
* 聲明交換器
*
* @param channel
* @param exchange
* @param type
* @param durable
* @param autoDelete
* @param arguments
* @throws IOException
*/
public void exchangeDeclarer(Channel channel, String exchange, String type, boolean durable,
boolean autoDelete, Map<String, Object> arguments)
throws IOException {
channel.exchangeDeclare(exchange, type, durable, autoDelete, autoDelete, arguments);
}
/**
* 聲明隊列
*
* @param channel
* @param queue
* @param durable
* @param exclusive
* @param autoDelete
* @param arguments
* @throws IOException
*/
public void queueDeclare(Channel channel, String queue, boolean durable, boolean exclusive,
boolean autoDelete, Map<String, Object> arguments)
throws IOException {
for (int i = 0; i < subdivisionNum; i++) {
String queueName = queue + "_" + i;
channel.queueDeclare(queueName, durable, exclusive, autoDelete, arguments);
}
}
/**
* 創建綁定關係
*
* @param channel
* @param queue
* @param exchange
* @param routingKey
* @param arguments
* @throws IOException
*/
public void queueBind(Channel channel, String queue, String exchange, String routingKey,
Map<String, Object> arguments)
throws IOException {
for (int i = 0; i < subdivisionNum; i++) {
String rkName = routingKey + "_" + i;
String queueName = queue + "_" + i;
channel.queueBind(queueName, exchange, rkName, arguments);
}
}
/**
* 普通發送消息
*
* @param channel
* @param exchange
* @param routingKey
* @param mandatory
* @param props
* @param body
* @throws IOException
*/
public void basicPublish(Channel channel, String exchange, String routingKey,
boolean mandatory, AMQP.BasicProperties props, byte[] body)
throws IOException {
//隨機挑選一個隊列發送
Random random = new Random();
int index = random.nextInt(subdivisionNum);
String rkName = routingKey + "_" + index;
System.out.println("send msg " + rkName);
channel.basicPublish(exchange, rkName, mandatory, props, body);
}
}
消息處理
package com.ly.liyong.publicrmq;
import java.io.*;
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
private long msgSeq;
private String msgBody;
private long deliveryTag;
public void setMsgSeq(long msgSeq) {
this.msgSeq = msgSeq;
}
public void setMsgBody(String msgBody) {
this.msgBody = msgBody;
}
public void setDeliveryTag(long deliveryTag) {
this.deliveryTag = deliveryTag;
}
public static long getSerialVersionUID() {
return serialVersionUID;
}
public long getMsgSeq() {
return msgSeq;
}
public String getMsgBody() {
return msgBody;
}
public long getDeliveryTag() {
return deliveryTag;
}
public String toString() {
return "[msgSeq" + getMsgSeq()
+ ", msgBody=" + getMsgBody()
+ ", deliveryTag=" + getDeliveryTag()
+ "]";
}
/**
* 對象轉爲字節數組
*
* @param object
* @return
* @throws IOException
*/
public static byte[] getBytesFromObject(Object object) throws IOException {
if (object == null) {
return null;
}
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(object);
oo.close();
bo.close();
return bo.toByteArray();
}
/**
* 字節數組轉爲對象
*
* @param body
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static Object getObjectFromBytes(byte[] body) throws IOException, ClassNotFoundException {
if (body == null || body.length == 0) {
return null;
}
ByteArrayInputStream bi = new ByteArrayInputStream(body);
ObjectInputStream oi = new ObjectInputStream(bi);
oi.close();
bi.close();
return oi.readObject();
}
}
生產者
package com.ly.liyong.rabbitmq;
import com.ly.liyong.publicrmq.Message;
import com.ly.liyong.publicrmq.RmqEncapsulation;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class TestPublisherController {
public static void main(String[] args) {
RmqEncapsulation rmqEncapsulation = new RmqEncapsulation(4);
try {
Connection connection = RmqEncapsulation.getConnection();
Channel channel = connection.createChannel();
rmqEncapsulation.exchangeDeclarer(channel, "exchange", "direct", true, false, null);
rmqEncapsulation.queueDeclare(channel, "queue", true, false, false, null);
rmqEncapsulation.queueBind(channel, "queue", "exchange", "rk", null);
for (int i = 0; i < 100; i++) {
Message message = new Message();
message.setMsgSeq(i);
message.setMsgBody("rabbitmq encapsulation");
byte[] body = message.getBytesFromObject(message);
rmqEncapsulation.basicPublish(channel, "exchange", "rk", false, MessageProperties.PERSISTENT_TEXT_PLAIN, body);
}
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
try {
RmqEncapsulation.closeConnection();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
}