异步方式调用
什么是异步调用
类似于微信发消息,发送消息的人(发送者)发送到一个队列里面后不需要去关注接收者是否需要收到,等待接收者空闲后就可以接收消息。这样可以解耦,一个服务不需要直接去调用另外一个微服务,而是直接发送消息通知被调用者,被调用者接收到消息执行相应逻辑
异步调用的优点
耦合度低,吞吐量大,故障隔离
mq的实现rabbitmq
rabbitmq的部署
在linux可以使用Docker部署,可以通过ip地址加上端口号15672访问mq
mq的消息模型
使用SpringAMQP来操作mq
引入AMQP依赖
基本消息队列(一个发送者,一个接收者)
配置yml,其中prefetch:可以进行配置只读取一个消息,处理后才能在进行读取,这样可以保证,能力强的接收者,处理的消息多,此队列不涉及,下面的队列都涉及到这个问题
spring:
rabbitmq:
host: 192.168.163.135 # rabbitMQ的ip地址
port: 5672 # 端口
username: 配置的用户名
password: 配置的密码
virtual-host: 虚拟主机,初始默认为/
listener:
simple:
prefetch: 1 #每次只读取一条消息,处理完成才能获取下一条,可以做到能力好的处理的消息多
发送者:注入rabbitTemple,发送消息
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessageSimpleQueue() {
String queueName = "simple.queue";
String message = "hello,spring Amqp";
rabbitTemplate.convertAndSend(queueName, message);
}
接收者
@RabbitListener(queues = "simple.queue")//消息队列的名称
public void listenSimpleQueue(String mes){//和发送的类型相同
//业务逻辑
System.out.println("接收到的消息是:"+mes);
}
work Queue工作队列(一个发送者多个接收者)
代码和操作和上面的同理,不同的是配置yml,其中prefetch:可以进行配置只读取一个消息,处理后才能在进行读取,这样可以保证,能力强的接收者,处理的消息多。
发布订阅模型
发布订阅模型在整个流程里面加入的交换机,当发送者发送消息给交换机,交换机会路由到消息队列,从而在发送给接收者
Fanout Exchange广播订阅发布模型
特点:发送者把消息交给交换机,交换机去路由给多个消息队列并且给多个消费者。因此需要交换机与队列进行绑定
编写消费者配置配来配置构建消息队列和交换机并且进行绑定
package cn.itcast.mq.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 创建队列和交换机并且进行绑定,可以使用@RabbitListener注解代替
*/
@Configuration
public class FanoutConfig {
// itcast.fanout
//声明交换机
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("itcast.fanout");//起名字
}
// fanout.queue1
//声明队列
@Bean
public Queue fanoutQueue1() {
//是spring的queue
return new Queue("fanout.queue1");//起名字
}
// 绑定队列1到交换机
@Bean
public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
//将队列绑定交换机
return BindingBuilder
.bind(fanoutQueue1)
.to(fanoutExchange);
}
// fanout.queue2
@Bean
public Queue fanoutQueue2() {
return new Queue("fanout.queue2");
}
// 绑定队列2到交换机
@Bean
public Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
return BindingBuilder
.bind(fanoutQueue2)
.to(fanoutExchange);
}
@Bean
public Queue objectQueue() {
return new Queue("object.queue");
}
}
发送消息代码,需要加入一个参数,其中这个参数为空字符串下面会具体说明
/**
* 发送到交换机
*/
@Test
public void testSendFanoutExchange(){
//定义交换机名称
String exchangeName = "itcast.fanout";
//消息
String message = "hello,every one";
//发送消息到交换机(fanout类型的交换机,发送到每一个和它绑定的队列)
rabbitTemplate.convertAndSend(exchangeName,"",message);
}
消费者依然不变接收方式
DirectExchange直接发布订阅模型
特点:
在使用这个前先介绍一下@RabbitListener,由于交换机的出现,如果在配置类中每次都构建交换机的bean太过麻烦,交换机的属性比较简单时可以直接使用注解来构造队列和交换机并且进行绑定,并且指定交换机类型
消费者代码
//direct加入key发送者发送消息后相同的key的接收者才可以接收
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
key = {"red", "blue"}
))
public void listenDirectQueue1(String mes) {
System.out.println("消息接收者接收到direct.queue1的消息:【" + mes + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
key = {"red", "yellow"}
))
public void listenDirectQueue2(String mes) {
System.out.println("消息接收者接收到direct.queue2的消息:【" + mes + "】");
}
此时发送者的发送消息需要使用另外一个参数,参数是发送给指定的key
/**
* 发送到交换机,并且指定key,相同的key才能接收
*/
@Test
public void testSendTopicExchange(){
//定义交换机名称
String exchangeName = "itcast.topic";
//消息
String message = "weather";
//发送消息到交换机(fanout类型的交换机,发送到key为blue的接收者)
rabbitTemplate.convertAndSend(exchangeName,"china.weather",message);
}
TopicExchange话题发布订阅模型
特点:和Direct不同的是这里的key要求以.分割
BingKey消费者可以使用通配分符#:代指0个或者多个单词,*:代表一个单词
消费者代码
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
key = "#.news"
))
public void listenTopicQueue2(String mes) {
System.out.println("消息接收者接收到direct.topic2的消息:【" + mes + "】");
}
发送和接收Objcet类型
上面所有发送的消息都是字符串那怎么发送和接收Objcet呢?rabbitqm底层采用MessageConverter会自动序列化为字节,如果想要发送Object不是字节(乱码),则需要自定义序列方式,通常使用json进行序列化,因此需要自定义MessageConverter对象并且注入spring
发送者和消费者配置类代码
package cn.itcast.mq.config;
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 RabbitConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}