一、MQ的工作原理?
(1):RabbitMQ作为消息代理,在开发中主要负责转发消息!
(2):对RabbitMQ的理解需要明白以下几个节点的意义:
--------生产者:消息的制造者与发起者,消息的最初是从此处向MQ发送的,类似于制造商品的工厂
--------交换机:直接与生产者沟通,生产者的每个消息都有一个路由键(字符串),交换机根据这个路由键选择 不同队列进行分发
--------消费者:消息的接收者与处理者,从MQ中获取消息并将消息处理掉的,类似于从商店购买商品的客人
--------MQ队列:消息的仓库与流水线,理论上是一个无限大的缓冲区,生产者可以将消息发送到队列中存储起来,也满足消费者逐渐从队列中取出处理掉的流水线!多个生产者可以将消息发送到同一个队列中,多个消费者也可以只从同一个队列接收数据!MQ队列可以直接接收生产者信息(MQ默认一个空白字符串命名的交换器),也可以通过交换机[--exchnge--]接收! 在有交换机的环境下,交换机通过多种方式用绑定键与消息队列互相绑定!
注意:队列和消费者两者之间没有必然的联系,在生产中因为对列中转站的特性,要负载很多的数据,为了避免内存不够,通常是与消费者部署在不同服务器上的!
(3):基本操作流程?
-------创建ConnectionFactory对象,利用抽象工厂模式获取连接
-------从获取的连接中获取渠道
-------创建队列,将渠道与队列绑定
-------生产者通过将消息放入队列中,消费者创建消费对象从队列中获取消息
注意:此处为基本通讯原理,后面有其他改变也是基于此进行封装改造,不会再违背此基础~
二、初步了解MQ的机制?
当我们处理一条消息的时候,网络异常是时刻都可能会发生的!网络问题,系统异常问题,甚至MQ问题,硬件问题等等,这样的后果都可能造成正在处理的消息或者任务因突然中断而丢失。MQ为了确保消息或者任务不会丢失,使用了一个确认消息机制,消息确认-ACK。MQ在推送消息给消费者这个过程中,被推送的数据会被一直保存在容器中,消费者收到队列处理完后会反馈
一个ACK消息给MQ进行确认,MQ确认完成后会将容器中的消息给删除掉。如果消费者在这个处理过程中挂了或者有某个消费者挂了,任务也不会消失也不会超时,而是会被一直保存在队列容器中,只有收到消费者返回的ACK才会进行删除。但是有一点需要注意,在使用MQ时候,如果消费者已经消失,生产者再次推送消息会显示失败!
注意:我们需要明白正常情况下“消息确认机制”是默认打开的,但如果存在忘记的情况或则认为处理的情况,则会导致MQ因迟迟收不到确认消息,而不断保存任务,最终MQ爆满而阻塞!
final Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("我是消费者,我从队列里面接收到消息:'" + message + "'");
try {
doSomeThing(message);
} finally {
System.out.println(" [x] Done");
channel.basicAck(envelope.getDeliveryTag(), false);//ACK确认手动关闭
}
}
};
==========================================================================================================================================
消息持久化:分为两部分,通过消息确认来保证消费者挂掉消息不丢失,通过消息持久化来保证MQ挂掉消息不丢失queueDeclare()是一个队列,如果需要保证消息的持久化,那么必须保持队列的持久化。也即是说我们需要同时设置队列持久化和消息持久化,单独只设置一个都是无效的!
-------队列持久化:
channel.queueDeclare("queue.persistent.name", dectable, false, false, null);
--------保持dectable=true
==================================================================================================================================================================
源码:Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,Map<String, Object> arguments) throws IOException;
解释:其中queus是指消息队列,exclusive=true的时候,队列为排他队列,允许首次访问他的排他队列基于该连接可见且持久化,一旦连接断开则队列持久化失效并且队列被删除。
排他队列也允许该连接的不同信道访问,但如果该对列没有任何使用中的消费者也会被自动删除,该队列适用于临时对列,并且具备唯一性,不可同时存在
-------消息持久化:
channel.basicPublish("exchange.persistent", "persistent", MessageProperties.PERSISTENT_TEXT_PLAIN, "persistent_test_message".getBytes());
=========================================================================================================================================
源码:
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
exchange:交换机
routingKey:路由键
props:属性信息
body:消息
交换机持久化:交换机持久化与否对消息不会造成什么影响,但是当MQ重启后,生产者将可能无法正常发送数据,因此将交换机持久化,可以保证即使是MQ重启也不会影响到用户!
channel.exchangeDeclare(exchangeName, “direct/topic/header/fanout”, true);
注意:对于一个正常而稳定的MQ消息,需要保证ACK确认机制,持久化机制
MQ镜像? 除了以上问题外,MQ仍然存在着几个问题可能导致数据丢失?
正常情况下:消息正确存入MQ后,还需要一段时间才能存入磁盘,MQ并没有为每条消息都做异步存储处理,而是先保存在缓存区里面,定时刷新或者缓存区提前满了才会刷新近存储区中持久化! 如果在没有持久化之前MQ挂掉同样也会造成数据丢失,这种情况需要MQ的mirrored-queue来缓解,除非整个集群都出问题,不然能非常有效地避免这种情况。此外也可以引入事务机制来确保生产者消息已经到MQ端!
在写入存储区之前会设置一个大小为1024K的缓存区,每隔25毫秒,刷新一次。如果缓存区满了也会刷新入存储区。每次消息写入缓存区后会等待一定时间,如果进程箱里没有消息,则会直接触发超时写入存储!
开发层面可以注意的问题,以避免数据丢失? 当MQ将消息推送给消费者,消费者刚拿到消息但未处理便挂了,这样情况的数据丢失也算的,尽管存在着MQ的确认机制,但长时间以往,仍然可能导致阻塞,这种情况下需要手动处理:
1.默认结果将ACK给关闭,置为false
channel.basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException; autoAck=false;
2.在正确处理完后,主动确认,置为true
channel.basicAck(envelope.getDeliveryTag(), false);
负载均衡机制?
由于某些时候,某台机器的负载高,处理任务的性能比较低,而另外一台的性能高,处理任务的效率也高。但对MQ来说,她是不会做这些智能化的区分的,也不会根据你ACK的返回速度来决定是推送给谁,一般都是有数据来了就推送!那么这样子则容易造成一种情况,负载高的机器任务繁重,负载低的机器被闲置~,这样子无疑是非常浪费~,MQ提供了一种设置每次推送数据量的方法来认为避免:
channel.basicQos(1);// 每次从队列中获取数量 ,负载均衡设置,告诉MQ不要一次将消息发送给某个队列,要等待逐步完成确认后再处理
结果:处理完一条并成功响应才会推送下一条,但是需要注意的是,当消息量非常大,而消费者处理效率比较低数量有不足的时候,将会导致队列积压的消息爆满而产生阻塞!
==============================================================================================
三、初步使用与API封装
---------生产者基础模块?
public class CreateProduct {
private static final String TASK_QUEUE_NAME = "create_queue";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);//队列持久化
//分发消息
for(int i = 0 ; i < 15; i++){
String message = "Hello World! " + i;
channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());//消息持久化
System.out.println(" 我是生产者,发送消息到队列:'" + message + "'");
}
channel.close();
connection.close();
}
}
======================================================================================================================
此处为生产者,也即是向交换机发送数据,生产数据的一方!
-----------消费者基础模块?
public class HandlerProduct {
private static final String TASK_QUEUE_NAME = "create_queue";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
System.out.println("消费者2: Waiting for messages. To exit press CTRL+C");
// 每次从队列中获取数量
channel.basicQos(1);
final Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("我是消费者2号,从队列中接受消息: '" + message + "'");
try {
doWork(message);
} finally {
System.out.println("消费者2号完成任务");
// 消息处理完成确认
channel.basicAck(envelope.getDeliveryTag(), true);//手动确认完成
}
}
};
// 消息消费完成确认
channel.basicConsume(TASK_QUEUE_NAME, false, consumer);//有任何问题都是false,表示ACK无确认
}
//处理模块
private static void doWork(String task) {
try {
Thread.sleep(1000); // 暂停1秒钟
} catch (InterruptedException _ignored) {
Thread.currentThread().interrupt();
}
}
}
=========================================================================================================================
此处为消费者,基础模块