首先要先部署好RabbitMQ,可以利用docker命令进行部署
docker run \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
-v mq-plugins:/plugins \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3.8-management
在spring中使用RabbitMQ使用的时AMQP统一规范,需要引入依赖
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
一、基本使用
推送端yml配置,virtual-host是配置rabbitmq的虚拟主机,类似于不同的服务器,这样当rabbitmq部署多个通道队列的时候,可以有效的进行隔离
spring:
rabbitmq:
host: 121.***.***.***
port: 5672
virtual-host: /
username: test
password: 123456
消费端yml配置,注意这里配置listener.simple.prefetch=1,意味着消费端每次只预取一个消息,消费过之后才能再次拉取消息
spring:
rabbitmq:
host: 121.***.***.***
port: 5672
virtual-host: /
username: test
password: 123456
listener:
simple:
prefetch: 1
(一)、消息队列方式一
1.推送端
推送端主要使用的是RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/sendMsg")
public String sendMsg(){
String queueName = "simple.queue";
String message = "hello world";
rabbitTemplate.convertAndSend(queueName,message);
return "send success";
}
2.消费端
@Component
public class MyRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listeneQueue1(String msg) throws InterruptedException {
System.out.println("收到msg:"+msg);
}
}
(二)、消息队列方式二
1.推送端
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/sendMsg")
public String sendMsg(){
String queueName = "simple.queue";
String message = "hello world";
rabbitTemplate.convertAndSend(queueName,message);
return "send success";
}
2.消费端
@Component
public class MyRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listeneQueue1(String msg) throws InterruptedException {
System.out.println("Listener111收到msg:"+msg);
Thread.sleep(1000);
}
@RabbitListener(queues = "simple.queue")
public void listeneQueue21(String msg) throws InterruptedException {
System.out.println("Listener222收到msg:"+msg);
Thread.sleep(20);
}
}
(三)、消息队列方式三(采用fanoutexchange交换机)
1.推送端
首先需要配置交换机并绑定队列
@Configuration
public class FanoutConfig {
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("itcast.fanout");
}
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
@Bean
public Binding fanoutBinding(Queue fanoutQueue1,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
//这里是绑定第二个队列,和上一个重复
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue2");
}
@Bean
public Binding fanoutBinding2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
之后再编写消息推送代码
@RequestMapping("/sendMsg2")
public String sendMsg2(){
String fanoutExchange = "itcast.fanout";
String message = "fanout test";
rabbitTemplate.convertAndSend(fanoutExchange,"aaa",message);
return "send success";
}
2消费端
@Component
public class MyRabbitListener {
@RabbitListener(queues = "fanout.queue1")
public void listeneQueue3(String msg) throws InterruptedException {
System.out.println("Listener333收到msg:"+msg);
}
@RabbitListener(queues = "fanout.queue2")
public void listeneQueue4(String msg) throws InterruptedException {
System.out.println("Listener444收到msg:"+msg);
}
}
(四)、消息队列方式四(采用directexchange交换机)
其实就是在上一个基础之上加入了bingkey,使得在推送数据的时候,交换机会根据bingkey添加到还有相同key的消息队列
1.推送端
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/sendMsg3")
public String sendMsg2(){
String directExchange = "itcast.direct";
String message = "direct test";
Map<String,String> map = new HashMap<>();
map.put("女生","柳岩");
map.put("男生","蔡徐坤222");
rabbitTemplate.convertAndSend(directExchange,"red",map);
return "send success";
}
2.消费端
由于上一个方式的绑定需要写非常多的bean,所以这里可以采用注解的方式进行交换机和队列的绑定
@Component
public class MyRabbitListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
key = {"red","blue"}
))
public void listeneQueue1(String msg) throws InterruptedException {
System.out.println("directListener111收到msg:"+msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
key = {"red","yellow"}
))
public void listeneQueue2(Map msg) throws InterruptedException {
System.out.println("directListener222收到msg:"+msg);
}
}
(五)、消息队列方式五(采用topicexchange交换机)
通配符用#表示,比如china.#或#.news
二、消息转换器
当我们使用AMQP发送Object对象时,默认会采取序列化的方法把对象转化为字节数据,这样一是数据庞大,二是不便于查阅,所以我们一般采用json的方式进行序列化
解决方法:
RabbitMQ高级
一、生产者确认机制
ReturnCallback应配置为一个单例的bean
ConfirmCallback应该放到每一个发送消息的方法中
二、消息持久化
实际上RabbitMQ会默认进行交换机、队列和消息的持久化,当然我们也可以进行更改。
三、消费者确认机制
消费者确认机制可以设置为auto模式
这种方式,如果消费端监听出现异常没有返回nack,则RabbitMQ会保留数据并再次向消费端推送,从而就会进入死循环,由于速度非常快,所以对RabbitMQ的消耗是非常大的,这种方式并不常用。
我们可以在消费端配置失败重试,在消费端进行失败重新循环,就可以环节RabbitMQ的压力
如果达到指定次数仍然失败,会自动返回nack给RabbitMQ,而RabbitMQ会再次向消费端推送,同样会进入死循环,只不过循环比较慢,对RabbitMQ开销比较小,我们可以更改为达到失败次数之后,直接向另一个交换机发送失败信息,由其他服务或者人工进行处理
四、死信交换机
这里与之前消费失败转发至error交换机有所不同,这里并不是在消费端转发至error交换机,而是消费端声明reject、nack或者超时未消费或者消息堆满了,由自身队列将信息打给dl队列
这里的basic.reject和basic.nack可以通过监听函数中的Channel实现
关于Channel的使用如下图
(一)设置延时TTL
方式一:在队列中设置延时时间和死信交换机
也可以在监听消息中绑定,这样的好处是在监听队列的同时就可以绑定死信队列,不会出现@bean声明绑定队列和@RabbitListener声明绑定队列重复报错的问题了
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "ttl.queue2",durable = "true",arguments = {
@Argument(name = "x-dead-letter-exchange", value = "dl.direct"),
@Argument(name = "x-dead-letter-routing-key", value = "dl"),
@Argument(name = "x-message-ttl", value = "10000")
}),
exchange = @Exchange(name = "ttl.direct"),
key = {"ttl"}
))
public void listeneTtlqueue(String msg, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws InterruptedException, IOException {
System.out.println("ttl:"+msg);
channel.basicReject(deliveryTag, false);
}
方式二:在发送消息的时候设置延时时间,但仍然需要在声明队列的时候先添加死信交换机
当自身消息队列不设置消费者监听时,则成了真正的延时队列,当然还有更加简便的方式实现延时队列,将在接下来简介。
(二)延时队列
1.下载插件
RabbitMQ有一个官方的插件社区,地址为:https://www.rabbitmq.com/community-plugins.html
2.上传插件
docker volume inspect mq-plugins
通过上述命令获取插件目录
将插件上传至对应目录
3.进入容器
docker exec -it mq bash
4.安装
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
5.使用
五、消息堆积和惰性队列
有时候消息来不及消费的话,就会产生堆积,本来消息是在内存中,但消息益处时,就将一部分消息转存在硬盘上,由于内存和硬盘读取的速度不一样,就会产生性能波动,对于消息较多的队列,我们可以直接设置为存储到硬盘上,也就是惰性队列