消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,由消息系统来确保消息的可靠传递。消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的。这样发布者和使用者都不用知道对方的存在。在项目中,将一些无需即时返回且耗时的操作提取出来,进行了异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。
ConnectionFactory、Connection、Channel都是RabbitMQ对外提供的API中最基本的对象。
Connection是RabbitMQ的socket链接,它封装了socket协议相关部分逻辑。
ConnectionFactory为Connection的制造工厂。
Channel是我们与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。
Queue(队列)RabbitMQ的作用是存储消息,队列的特性是先进先出。上图可以清晰地看到Client A和Client B是生产者,生产者生产消息最终被送到RabbitMQ的内部对象Queue中去,而消费者则是从Queue队列中取出数据。可以简化成表示为:
生产者Send Message “A”被传送到Queue中,消费者发现消息队列Queue中有订阅的消息,就会将这条消息A读取出来进行一些列的业务操作。这里只是一个消费正对应一个队列Queue,也可以多个消费者订阅同一个队列Queue,当然这里就会将Queue里面的消息平分给其他的消费者,但是会存在一个一个问题就是如果每个消息的处理时间不同,就会导致某些消费者一直在忙碌中,而有的消费者处理完了消息后一直处于空闲状态,因为前面已经提及到了Queue会平分这些消息给相应的消费者。这里我们就可以使用prefetchCount来限制每次发送给消费者消息的个数。
这里的prefetchCount=1是指每次从Queue中发送一条消息来。等消费者处理完这条消息后Queue会再发送一条消息给消费者。
//普通模式
public static void main(String[] args) throws Exception {
Connection connection = Utils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 同一时刻服务器只会发一条消息给消费者,只有当前消费者将消息处理完成后才会获取到下一条消息
//注释掉后可以获取多条消息,但是会一条一条处理
channel.basicQos(1);//告诉服务器,在我们没有确认当前消息完成之前,不要给我发新的消息
DefaultConsumer consumer =new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//当我们收到消息的时候调用
System.out.println("消费者1 收到的内容是:"+new String(body));
//确认
try {
Thread.sleep(10);//模拟耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
channel.basicAck(envelope.getDeliveryTag(), false);//参数2,false 为确认收到消息, true 为拒接收到消息
}
};
//注册消费者, 参数2 手动确认,代表我们收到消息后需要手动告诉服务器,我收到消息了
channel.basicConsume(QUEUE_NAME,false,consumer);
}
测试结果:
1、消费者1和消费者2获取到的消息内容是不同的,同一个消息只能被一个消费者获取。
2消费者从队列中获取消息,服务端如何知道消息已经被消费呢?
模式1:自动确认
只要消息从队列中获取,无论消费者获取到消息后是否成功消息,都认为是消息已经成功消费。
模式2:手动确认
消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态。
//订阅模式
//生产者
public class send2 {
public final static String EXCHANGE_NAME="message";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接,通道
Connection connection= Utils.getConnection();
Channel channel=connection.createChannel();
//声明exchange
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
String message="exchange message";
//将消息发送到交换机,如果此时没有队列绑定的话,消息会丢失,因为交换机没有存储消息的能力
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes());
System.out.println("发送消息"+message);
channel.close();
connection.close();
}
}
//消费者1
public class exchangeRecver1 {
private final static String EXCHANGE_NAME = "message";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接,通道
Connection connection= Utils.getConnection();
Channel channel=connection.createChannel();
//声明队列
channel.queueDeclare("testqueue1",false,false,false,null);
//绑定队列到交换机
channel.exchangeBind("testqueue1",EXCHANGE_NAME,"");
// 同一时刻服务器只会发一条消息给消费者,只有当前消费者将消息处理完成后才会获取到下一条消息
//注释掉后可以获取多条消息,但是会一条一条处理
channel.basicQos(1);//告诉服务器,在我们没有确认当前消息完成之前,不要给我发新的消息
DefaultConsumer consumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//当我们收到消息的时候调用
System.out.println("消费者11111111:"+new String(body));
channel.basicAck(envelope.getDeliveryTag(), false);//参数2,false 为确认收到消息, true 为拒接收到消息
}
};
//注册消费者, 参数2 手动确认,代表我们收到消息后需要手动告诉服务器,我收到消息了
channel.basicConsume("testqueue1",false,consumer);
}
}
测试结果
一条消息可以被多个消费者同时获取
生产者将消息发送到交换机
消费者将自己对应的队列组册到交换机
当消息发送后,所有组册的队列的消费者都可以收到消息
1、fanout类型
fanout类型的Exchange路由规则非常简单,它会把所有发送到fanout Exchange的消息都会被转发到与该Exchange 绑定(Binding)的所有Queue上。Fanout Exchange 不需要处理RouteKey 。只需要简单的将队列绑定到exchange 上。这样发送到exchange的消息都会被转发到与该交换机绑定的所有队列上。类似子网广播,每台子网内的主机都获得了一份复制的消息。所以,Fanout Exchange 转发消息是最快的。
2、direct类型
direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。direct Exchange是RabbitMQ Broker的默认Exchange,它有一个特别的属性对一些简单的应用来说是非常有用的,在使用这个类型的Exchange时,可以不必指定routing key的名字,在此类型下创建的Queue有一个默认的routing key,这个routing key一般同Queue同名。direct模式,可以使用rabbitMQ自带的Exchange:default Exchange 。所以不需要将Exchange进行任何绑定(binding)操作 。消息传递时,RouteKey必须完全匹配,才会被队列接收,否则该消息会被抛弃。
3、topic类型
前面讲到direct类型的Exchange路由规则是完全匹配binding key与routing key,但这种严格的匹配方式在很多情况下不能满足实际业务需求。topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同,它约定:
a)、routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
b)、binding key与routing key一样也是句点号“. ”分隔的字符串
c)、binding key中可以存在两种特殊字符“”与“#”,用于做模糊匹配,其中“”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
所有发送到Topic Exchange的消息被转发到所有关心RouteKey中指定Topic的Queue上,Exchange 将RouteKey 和某Topic 进行模糊匹配。此时队列需要绑定一个Topic。可以使用通配符进行模糊匹配,符号“#”匹配一个或多个词,符号“”匹配不多不少一个词。因此“log.#”能够匹配到“log.info.oa”,但是“log.” 只会匹配到“log.error”。