简述:
流控机制是用来避免消息的发送速率过快而导致服务器难以支撑的情形。内存和磁盘警告相当于全局的流控,一旦触发会阻塞集群中所有的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();
}
}
}
}