消息队列
1,消息队列
1.1应用场景
大多数应用中,可通过消息服务中间件来提升系统异步通信,扩展解耦能力
1.2,主要内容
1.2.1异步处理
等同于注册用户将注册信息写入数据库,再以极短的时间写入消息队列。立马就可以返回消息给用户。然后发送邮件和发送短信的这些操作。再通过异步从消息队列中读取信息,进行操作
1.2.2应用解耦
订单系统–消息队列–库存系统
1.2.3流量削锋
比如10万个请求直接访问数据库不行,就让他直接访问消息队列。消息队列可设置它最多处理1万条信息。将其余9万条挡掉,秒杀失败。然后再处理秒杀业务
1.3,重要概念
message 消息
destination 目的地
点对点模式:
消息只有唯一的发送者和接受者。但并不是只有一个接收者。
同一时间,一个发送者发送消息到消息队列。多个消费者可以享受接收这个消息的权利。但只能有一个消费者消费这条信息
发布订阅模式:
发布者发送消息到topic。多个接收者订阅这主题。接收者能同时收到消息
1.4 消息服务规范
jms
基于JVM消息代理的规范,不能跨平台跨语言的。activeMq
AMQP
高级消息队列协议,兼容JMS的,rabbitmq是AMQP的实现
跨平台,跨语言。发送数据都是以byte[]将消息序列化后发送
rabbitmq的springboot starter是 spring-boot-starter-amqp
1.5 rabbitMQ
1.5.1核心概念
message
消息:由消息头和消息体组成
publisher
消息的生产者,也是将message发送至Exchange
Exchange
交换器:用于接收生产者发送的消息并将这些消息路由给服务器的队列
Queue
队列:保存消息直到发送给消费者
Binding
绑定:路由键将交换器和队列连接起来的路由规则
connection
网络连接:比如tcp连接
channel
信道:发送数据,订阅队列,接受消息的通道
1.5.2,整个流程
amqp跟jms的差别就是多了binding跟exchange
exchange有四种策略:direct,fanout,topic和headers,只有header不是通过路由键绑定,而是通过消息头
1.5.2.1
1,direct:转发,交换器的key和queue的key相同才会转发
2,fanout:交换器是fanout则广播所有绑定的队列
3,topic是模糊匹配。exchange是路由键和绑定键的单词切分以.分隔。而queue是以#(匹配0或者多个单词)。*(匹配一个单词)。。例如:usa.news 匹配queue中的usa.#
1.5.3,安装,使用,项目
1,hub.docker.com
2,docker pull rabbitmq:3.7.26-management
3,docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq rabbitmq:3.7.26-management
4,进入图形化界面,创建交换器:exchange.direct,exchange.fanout,exchange.topic
5,创建队列:atguigu,atguigu.news,atguigu.emps,gulixueyuan.news
6,回到exchange绑定路由键和queue (1)direct:queue:atguigu。rounting key:atguigu queue:atguigu.news routing key:atguigu.news queue:atguigu.emps routing key:atguigu.emps queue:gulixueyuan.news routing key:gulixueyuan.news #(2)fanout 交换器也是绑定这四个queue (3)topic queue:atguigu rounting key:atguigu.# (atguigu开头的routing key绑定atguigu.# new结尾的绑定 *.news一共绑5个)
7,测试direct
publish message选项填写routing key=atguigu payload为发送消息的内容随便写,点击发送。发现就一条队列收到消息,并且精确匹配。到atguigu
8,测试fanout
全收到消息
9,测试topic
routing key=atguigu.news 由于规则匹配atguigu.#和*.news的队列都能发过去。所以这边全部发过去
10,queue可以点击get message按钮获取到发送过来的消息,默认是nack。。。就接受一条发送过来的消息。如果选择ack,则删除掉本条消息,再获取下一条消息
11,项目整合rabbitmq
11.1,创建一个springboot项目,
1,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.1.13.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
2,
spring.rabbitmq.host=xxxx
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
3,test类
package com.atguigu.springboot02amqp;
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 java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
@RunWith(SpringRunner.class)
public class Springboot02AmqpApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void contextLoads() {
//点对点模式
//rabbitmq有两种方法消息的方法
//1,rabbitTemplate.send(exChange,routekey,message)
//2,rabbitTemplate.covertAndSend(exchange,routekey,Object)
Map<String,Object>map=new HashMap<>();
map.put("msg","这是第一个消息");
map.put("data", Arrays.asList("helloworld",123,true));
rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",map);
}
}
这时候直接去rabbit里面去get消息。发现是序列化策略不对的
4,test类继续
写一个receive的方法
@Test
public void receive(){
Object o=rabbitTemplate.receiveAndConvert("atguigu.news");
System.out.println(o.getClass());
System.out.println(o);
}
发现可以读取到控制台。并且消费了消息。但是图形化界面的序列化机制有些不对。怎么改成json的
5,config
package com.atguigu.springboot02amqp.config;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyAMQPConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
6,这下发送消息和取消息都正常了
7,rabbitmq操作java-bean
package com.atguigu.springboot02amqp.bean;
public class Book {
private String bookName;
private String author;
Book(){}
public Book(String bookName, String author) {
this.bookName = bookName;
this.author = author;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "Book{" +
"bookName='" + bookName + '\'' +
", author='" + author + '\'' +
'}';
}
}
修改test类
package com.atguigu.springboot02amqp;
import com.atguigu.springboot02amqp.bean.Book;
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 java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
@RunWith(SpringRunner.class)
public class Springboot02AmqpApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void contextLoads() {
//点对点模式
//rabbitmq有两种方法消息的方法
//1,rabbitTemplate.send(exChange,routekey,message)
//2,rabbitTemplate.covertAndSend(exchange,routekey,Object)
Map<String,Object>map=new HashMap<>();
map.put("msg","这是第一个消息");
map.put("data", Arrays.asList("helloworld",123,true));
rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",new Book("java 8新特性","ljs"));
}
@Test
public void receive(){
Object o=rabbitTemplate.receiveAndConvert("atguigu.news");
System.out.println(o.getClass());
System.out.println(o);
}
}
8,广播队列(不用管rountkey)
修改test类
9,生产者消费者监听问题
9.1,创建service
package com.atguigu.springboot02amqp.service;
import com.atguigu.springboot02amqp.bean.Book;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
public class BookService {
@RabbitListener(queues = "atguigu.news")
public void receive(Book book){
System.out.println("收到消息:"+book);
}
}
9.2,启动类加@EnableRabbit
9.3启动项目
9.4,使用单元测试再发一条消息到消息队列
9.5,回到项目发现控制台监听到消息
9.6,如果既想接受到消息,又想获得消息头,新增方法service里
@RabbitListener(queues = "atguigu")
public void receive02(Message message){
System.out.println(message.getBody());
System.out.println(message.getMessageProperties());
}
10,以上是本来rabbitMQ已经创建好了exchange和queue。如果没创建。则使用AmqpAdmin操作消息
test类
//创建exchange
@Test
public void createExchange(){
amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
System.out.println("创建交换器完成");
}
//创建queue
@Test
public void createQueue(){
amqpAdmin.declareQueue(new Queue("amqpadmin.queue",true));
}
//将创建出的交换器跟队列绑定
@Test
public void binding(){
amqpAdmin.declareBinding(new Binding("amqpadmin.queue",Binding.DestinationType.QUEUE,"amqpadmin.exchange","amqp:haha",null));
}
exchange参数:指定名称,是否持久化,是否自动删除。queue一样。binding:从左到右:绑定的队列名称,绑定队列,exchange名称,路由键随便写,消息可以指定null
#####rabbitMQ的自动配置
1,rabbitAutoConfiguration
2,有自动配置了连接工厂ConnectionFactory
3,rabbitProperties封装了rabbitMQ的配置
4,rabbitTemplate,给rabbitMq发送和接受消息
5,AmqpAdmin:rabbitMq系统管理功能组件