RabbitMQ,即消息队列,遵循FIFQ(先进先出)
应用场景
1.提高系统响应速度:将不需要同步处理且耗时长的操作由消息队列通知消息接收方异步处理
2.提高系统稳定性:系统挂了关系,操作内容放到消息队列
3.服务调用异步化:服务没有直接的调用关系,而是通过队列进行服务通信
4.服务解耦:应用程序解耦合 MQ相当于一个中介,生产方通过MQ与消费方交互,它将引用程序进行解耦
5.消除峰值:服务器能接受多少个请求就接受多少个,接受不了的放消息队列中等待
为什么使用RabbitMQ?
1.使用简单,功能强大
2.基于AMQP协议(一套公开的消息队列协议)
3.社区活跃,文档完善
4.springBoot默认已集成RabbitMQ
安装后启动服务,然后到rabbitmq安装目录下的F:\RabbitMQ\rabbitMQ\rabbitmq_server-3.7.4\sbin
下运行cmd,下载管理工具rabbitmq-plugins enable rabbitmq_management
进入浏览器localhost:15672
游客登录,账号密码都是guest
通讯原理
2.4 实时监听队列,一旦有消息就取出来
工具类
public class ConnectionUtils {
public static Connection getConn(){
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
Connection connection = factory.newConnection();
return connection;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
生产者
public static void main(String [] args)throws Exception{
//创建连接
Connection conn = ConnectionUtils.getConn();
//创建通道
Channel channel = conn.createChannel();
//创建队列
// 各参数意义 1.队列名称 2.是否持久化,重启后没有消费的消息会继续存在
// 3.队列是否独占此连接 4.队列不在使用时是否删除此队列
// 5.队列参数
channel.queueDeclare(HELLO_NAME,false,false,false,null);
//发布消息
String massage = "Hello World";
//参数1.交换机,不写就发布到默认的交换机
//参数2.路由的key,理解为队列的key
//参数3.要发布的消息
channel.basicPublish("",HELLO_NAME,null,massage.getBytes());
}
消费者
public static void main(String [] args)throws Exception{
//创建连接
Connection conn = ConnectionUtils.getConn();
//创建通道
Channel channel = conn.createChannel();
//创建消费者
Consumer consumer = new DefaultConsumer(channel){
//处理交互
//consumerTag :消费者唯一标识
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body, "utf-8"));
}
};
//监听队列--取消息
//参数1 队列名字
//参数2 是否自动签收
//参数3 消费者
channel.basicConsume(SendTest.HELLO_NAME,true,consumer);
}
什么情况下会造成消息丢失?
在自动签收时,程序出现错误
所有进行手动签收
public static void main(String [] args)throws Exception{
//创建连接
Connection conn = ConnectionUtils.getConn();
//创建通道
Channel channel = conn.createChannel();
//设置最大处理数
channel.basicQos(1);
//创建消费者
Consumer consumer = new DefaultConsumer(channel){
//处理交互
//consumerTag :消费者唯一标识
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body, "utf-8"));
channel.basicAck(envelope.getDeliveryTag(), false);//false表示只签收当前id的消息,true则签收所有消息
}
};
//监听队列--取消息
//参数1 队列名字
//参数2 是否自动签收
//参数3 消费者
channel.basicConsume(SendTest.HELLO_NAME,false,consumer);
}
当生产者只有1个,消费者有多个的时候,就会轮流处理消息,但是这不合理,应该能者多劳
进行配置,
//设置最大处理数
channel.basicQos(1);
这样子每个消费者都最多处理一个消息,比如2消费者线程睡眠2分钟,1正常,那么生产者的消息在2没有处理完当前消息的时候,就会一直分配给处理好的1
订阅模式-FANOUT
多个交换机,多个消费者,一个生产者,只要消费者绑定了这个交换机,就可以接受到这个消息
生产者不用创建队列,而是创建交换机
public static final String FANOUT_NAME="fanout_name";
public static void main(String[] args) throws Exception{
Connection conn = ConnectionUtils.getConn();
Channel channel = conn.createChannel();
//创建交换机
channel.exchangeDeclare(FANOUT_NAME, BuiltinExchangeType.FANOUT);
System.out.println("发布消息");
String massage = "这是消息";
channel.basicPublish(FANOUT_NAME,"",null,massage.getBytes());
}
消费者要创建队列,然后和交换机绑定
public static final String QUEUE_NAME = "queue_name1";
public static void main(String[] args) throws Exception{
Connection conn = ConnectionUtils.getConn();
Channel channel = conn.createChannel();
//创建队列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//把队列和交换机绑定
channel.queueBind(QUEUE_NAME,SendTest.FANOUT_NAME,"");
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String s, Envelope envelope, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
//取消息
channel.basicAck(envelope.getDeliveryTag(),false);
System.out.println("消息ID"+envelope.getDeliveryTag());
System.out.println("交换机"+envelope.getExchange());
}
};
//监听队列
channel.basicConsume("QUEUE_NAME",false,consumer);
}
路由模式-DERECT(太局限了)
与FANOUT不同的是,他在把交换机和队列绑定的时候多给了一个参数,且交换机类型为DERECT
//消费者中
channel.queueBind(QUEUE_NAME,SendTest.FANOUT_NAME,"error");
channel.queueBind(QUEUE_NAME,SendTest.FANOUT_NAME,"info");
//生产者中
channel.basicPublish(FANOUT_NAME,"error",null,massage.getBytes());
如果生产者发布的error,在消费者中有绑定,则此消费者可见此消息,如果两个消费者都是error,则都可见
统配符模式-TOPIC
与FANOUT不同的是,交换机类型为TOPIC,且key不同
//消费者中
channel.queueBind(QUEUE_NAME,SendTest.FANOUT_NAME,"User.#");
//生产者中
channel.basicPublish(FANOUT_NAME,"User.Insert",null,massage.getBytes());
//只要生产者是User. 那么该消费者就可以接收到消息
使用springboot整合rabbitMQ
1.配置application.yml
server:
port: 44000
spring:
application:
name: test-rabbitmq-producer
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtualHost: /
listener:
simple:
acknowledge-mode: manual #手动签收
prefetch: 1 #最大同时接受数
publisher-returns: true #消息发送到队列失败回调
publisher-confirm-type: correlated
template:
mandatory: true #消息路由失败通知监听者,而不是丢失
2.配置类RabbitMqConfig @Qualifier注解是解决选择哪个bean问题
package org.tree.config;
import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMqConfig {
public static final String QUEUE_INFORM_EMAIL="queue_inform_email";
public static final String QUEUE_INFORM_SMS="queue_inform_sms";
public static final String EXCHANGE_TOPIC_INFORM="exchange_topic_inform";
//创建交换机
@Bean
public Exchange creatExchange(){
return ExchangeBuilder.topicExchange(EXCHANGE_TOPIC_INFORM).durable(true).build();
}
//创建队列
@Bean(QUEUE_INFORM_EMAIL)
public Queue creatQueuq1(){
return new Queue(QUEUE_INFORM_EMAIL,true);
}
@Bean(QUEUE_INFORM_SMS)
public Queue creatQueuq2(){
return new Queue(QUEUE_INFORM_SMS,true);
}
//绑定交换机和队列
@Bean
public Binding QUEUE_INFORM_EMAIL(@Qualifier(QUEUE_INFORM_EMAIL) Queue queue,Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("email.#").noargs();
}
@Bean
public Binding QUEUE_INFORM_SMS(@Qualifier(QUEUE_INFORM_SMS) Queue queue,Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("sms.#").noargs();
}
}
3.生产者
package org.tree.producer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.tree.Application;
import org.tree.config.RabbitMqConfig;
import org.tree.domain.User;
@SpringBootTest(classes = Application.class)
@RunWith(SpringRunner.class)
public class SendTest {
/*
消息队列不能直接传对象,解决方式
1.转成json
2.实现RabbitListenerConfigurer接口,重写所有方法(太麻烦)
*/
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void sendTestByTopic(){
User user = new User(1l,"张三",15);
String s = JSON.toJSONString(user);
rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE_TOPIC_INFORM, "email.updata",s);
System.out.println("消息发送成功");
}
}
4.消费者
package org.tree.producer;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import org.tree.config.RabbitMqConfig;
import org.tree.domain.User;
@Component
public class Cums {
@RabbitListener(queues = {RabbitMqConfig.QUEUE_INFORM_EMAIL})
public void cums1(String msg, Message message, Channel channel)throws Exception{
//手动签收
long deliveryTag = message.getMessageProperties().getDeliveryTag();
channel.basicAck(deliveryTag,false);//签收
System.out.println("+++++++++++"+msg);
}
@RabbitListener(queues = {RabbitMqConfig.QUEUE_INFORM_SMS})
public void cums2(String msg, Message message, Channel channel)throws Exception{
long deliveryTag = message.getMessageProperties().getDeliveryTag();
channel.basicAck(deliveryTag,false);//签收
System.out.println("+++++++++++"+msg);
}
}
5.pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.66</version>
</dependency>
总结:
简单模式
work模式
订阅模式 fanout
路由模式 direct 给参数error,info,waring
统配符模式 topic