作用:
消息中间件
使用场景:
主要用于程序的解耦、异步处理、消息缓存作用。
eg1:打开快递柜取出快递时,设备相关模块会调用短信模块的发送短信接口,此时可以使用rabbitmq实现“异步处理”(如果采用同步处理,可能出现阻塞的问题)
eg2:消息缓存、异步处理以解决大并发的请求
例如:
举一些我们平常生活中的消费场景,例如:火车票、机票、门票等,通常来说这些服务在下单之后,后续的出票结果都是异步通知的,如果服务本身只支持每秒1000访问量,由于外部服务的原因突然访问量增加到每秒2000并发,这个时候服务接收者因为流量的剧增,超过了自己系统本身所能处理的最大峰值,如果没有对消息做限流措施,系统在这段时间内就会造成不可用,在生产环境这是一个很 严重
的问题,实际应用场景不止于这些例如之前做的报表系统,由于单个报表包括很多请求,然后用户会同时打开多个报表,所以在报表使用高峰期时,即使使用集群部署+redis集群做缓存,但是还是可能导致服务会挂掉。此时使用rabbitmq来实现消息缓存
简单模式:
工作队列:
发布订阅模式:
路由模式:
主题模式:
rabbitmq控制台:
//todo
概念:
概念:
Brocker:消息队列服务器实体。
exchange:消息交换机,指定消息按照什么规则路由到那个队列
queue:消息队列,存储用户发送的消息(数据)
binding:把exchange和queue按照路由规则binding起来
routing key:路由关键字,exchange根据这个关键字进行消息投递到不同的队列
channel:消息通道,生产者和消费者的通信是通过channel
//定义通道对象
Channel channel = connection.createChannel();//定义队列
channel.queueDeclare("queue_work", false, false, false, null);producer、consumer
rabbitmq的工作模式:
简单模式:
工作的流程:
一个消费者和一个生产者模式,生产者生成消息,消费者监听消息。若是消费者监听到它所需要的消息,就会消费该消息,这种消息是次性的,被消费了就没有了。
工作队列:
流程:一个队列是可以多个生产者,也可以有多个消费者来竞争消费消息
应用场景:抢红包机制、秒杀业务
流程:生产者只能把消息发送给一个exchange ;
Exchange作用:接收从生产者发送过来的消息;把接收到的数据推送给队列
有4种类型的交换器:
Direct
Topic 主题模式
Redirect 路由模式
Fanout 发布订阅模式
发布订阅fanout
这样的消息属于广播型,两个不同名的队列的都能收到该消息,只需它们都将自己绑定到同一个交换机,
该消息是持久的,只要交换机还在,消费者啥时候上线都能消费它所绑定的交换机,
而且只会一个消费者只会消费一次。
应用场景:群发、微信公众号订阅
//生产者: //定义通道对象 Channel channel = connection.createChannel(); //定义队列 channel.queueDeclare("queue_work", false, false, false, null); //定义广播的消息 String msg = "我是工作模式"; //发送消息 channel.basicPublish("", "queue_work", null, msg.getBytes());
//消费者 //定义消费者 @Test public void consumer1() throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException{ //定义通道 Channel channel = connection.createChannel(); //定义队列 channel.queueDeclare("queue_work", false, false, false, null); //定义消费数 每次只能消费一条记录.当消息执行后需要返回ack确认消息 才能执行下一条 //当由于网络原因,出现返回延时的现象,允许最多3次没有返回ack确认消息 //当达到3次时,则不允许再次获取消息 channel.basicQos(1); // //定义消费者 QueueingConsumer consumer = new QueueingConsumer(channel); //将队列和消费者绑定 false表示手动返回ack channel.basicConsume("queue_work", false, consumer); while(true){ QueueingConsumer.Delivery delivery = consumer.nextDelivery(); String msg = new String(delivery.getBody()); System.out.println("队列A获取消息:"+msg); //deliveryTag 队列下标位置 //multiple是否批量返回 channel.basicAck(delivery.getEnvelope().getDeliveryTag(), true); } }
路由模式: 根据不同的路由key发往不同的队列
生产者发送消息时需要配置交换机名称、路由key和发送消息
//定义发布订阅模式 fanout direct 路由模式 topic 主题模式
channel.exchangeDeclare(exchange_name, "direct");
/**exchange:交换机名称 routingKey:路由key props:参数 body:发送消息*/
channel.basicPublish(exchange_name, rontKey, null, msg.getBytes());
消费者需要定义队列名称 并且与交换机绑定(绑定路由key)
channel.queueBind(queue, exchange, routingKey)应用场景:根据功能模块的业务不同,发往不同的队列中
//定义生产者
@Test
public void proverder() throws IOException{
Channel channel = connection.createChannel();
//定义交换机名称
String exchange_name = "redirect";
//定义发布订阅模式 fanout direct 路由模式 topic 主题模式
channel.exchangeDeclare(exchange_name, "direct");
for(int i=0;i<10; i++){
String msg = "生产者发送消息"+i;
String rontKey = "1707B";
/**
* exchange:交换机名称
* routingKey:路由key
* props:参数
* body:发送消息
*/
channel.basicPublish(exchange_name, rontKey, null, msg.getBytes());
}
channel.close();
connection.close();
}
/**
* 消费者需要定义队列名称 并且与交换机绑定
* @throws IOException
* @throws InterruptedException
* @throws ConsumerCancelledException
* @throws ShutdownSignalException
*/
@Test
public void consumer1() throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException{
//定义通道
Channel channel = connection.createChannel();
//定义交换机名称
String exchange_name = "redirect";
//定义队列名称
String queue_name = "c_r_1";
//定义交换机模式
channel.exchangeDeclare(exchange_name, "direct");
//定义队列
channel.queueDeclare(queue_name, false, false, false, null);
//将队列和交换机绑定
/**
* 参数介绍:
* queue:队列名称
* exchange:交换机名称
* routingKey:路由key
*/
//channel.queueBind(queue, exchange, routingKey)
channel.queueBind(queue_name, exchange_name, "1707A");
//定义消费个数
channel.basicQos(1);
//定义消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
//绑定消息与消费者
channel.basicConsume(queue_name, false, consumer);
while(true){
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = "路由模式-消费者1"+new String(delivery.getBody());
System.out.println(msg);
//手动回复 一个一个回复
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
主题模式topic:
说明:主题模式是路由模式的升级版,只要是满足条件的路由key,消息队列都可以接收消息.
通配符的格式:
*号:匹配一个或一组字符(以.分割)
#号.能够匹配任意的字符.
例子:
Item.update.aabb item.# 可以匹配
Item.update item.# item.* 都可以匹配
消费者:
//将交换机和队列进行绑定
//参数1.队列名称 参数2交换机名称 参数3 路由key #号匹配多个字符
channel.queueBind(queue_name, exchange_name, "item.#");//定义生产者 @Test public void proverder() throws Exception{ //获取通道 Channel channel = connection.createChannel(); //定义交换机的名称 String exchange_name = "TOP"; //创建交换机队列 //exchange 交换机名称 //type 定义类型 fanout 发布订阅模式 direct-路由模式 topic-主题模式 channel.exchangeDeclare(exchange_name, "topic"); //主题模式 for (int i = 0; i < 100; i++) { String msg = "主题模式"+i; /** * 参数说明: * exchange:交换机名称 * routingKey:路由key * props:参数 * body:数据字节码 */ //channel.basicPublish(exchange, routingKey, props, body); channel.basicPublish(exchange_name,"item.update.abc", null, msg.getBytes()); } channel.close(); connection.close(); }
@Test public void consumer1() throws Exception{ //定义通道 Channel channel = connection.createChannel(); //定义交换机名称 String exchange_name = "TOP"; //定义队列名称 String queue_name = "TOP1"; //声明交换机名称以及主题模式 channel.exchangeDeclare(exchange_name, "topic"); //定义队列 channel.queueDeclare(queue_name, false, false, false, null); //将交换机和队列进行绑定 //参数1.队列名称 参数2交换机名称 参数3 路由key #号匹配多个字符 channel.queueBind(queue_name, exchange_name, "item.#"); channel.basicQos(1); //定义消费数量 1 //定义消费者 QueueingConsumer consumer = new QueueingConsumer(channel); //将队列和消费者绑定 channel.basicConsume(queue_name, false, consumer); //定义手动回复 while(true){ QueueingConsumer.Delivery delivery = consumer.nextDelivery(); //获取消息队列中的数据 String msg = new String(delivery.getBody()); System.out.println("item.#消费者1:"+msg); //手动回复 channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } }
参考