微服务间基于Feign的调用就属于同步方式,存在一些问题。
异步调用常见实现就是事件驱动模式
优势:
缺点:
MQ,中文是消息队列,字面来看即使存放消息队列。也就是事件驱动架构中的Broker。
RabbitMQ是基于Erlang语言开发的开源消息中间件
官网:Messaging that just works — RabbitMQ
我们在Centos7虚拟机中使用Docker来安装。
方式一:在线拉取
docker pull rabbitmq:3-management
方式二:从本地加载
在课前资料已经提供了镜像包:
上传到虚拟机中后,使用命令加载镜像即可:
docker load -i mq.tar
执行下面的命令来运行MQ容器:
docker run \
-e RABBITMQ_DEFAULT_USER=xcxc \
-e RABBITMQ_DEFAULT_PASS=xcxc666 \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:latest
如果无法访问,则需要开启管理插件
# 进入mq的内部
docker exec -it mq /bin/bash
# 开启管理插件
rabbitmq-plugins enable rabbitmq_management
接下来,我们看看如何安装RabbitMQ的集群。
在RabbitMQ的官方文档中,讲述了两种集群的配置方式:
我们先来看普通模式集群。
首先,我们需要让3台MQ互相知道对方的存在。
分别在3台机器中,设置 /etc/hosts文件,添加如下内容:
192.168.150.101 mq1
192.168.150.102 mq2
192.168.150.103 mq3
并在每台机器上测试,是否可以ping通对方:
发布订阅,又根据交换机类型不同分为三种:
官方的HelloWorld是基于最基础的消息队列模型来实现的,只包括三个角色:
基本消息队列的消息发送流程:
基本消息队列的消息接受流程:
官网:https://spring.io/projects/spring-amqp
发送消息
引入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
在publisher服务编写application.yml,添加mq连接信息
spring:
rabbitmq:
host: 192.168.72.133
port: 5672
username: xcxc
password: xcxc666
virtual-host: /
编写测试类
package cn.itcast.mq.spring;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author xc
* @date 2023/5/8 7:00
*/
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSend(){
String queueName = "simple.queue";
String message = "hello xc";
rabbitTemplate.convertAndSend(queueName,message);
}
}
接受消息
package cn.itcast.mq.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author xc
* @date 2023/5/8 7:08
*/
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue(String msg){
System.out.println("消费者接受到simple.queue的消息:【"+msg+"】");
}
}
@Test
public void testSend1() throws InterruptedException {
String queueName = "simple.queue";
String message = "hello xc-";
for (int i = 0; i < 50; i++) {
Thread.sleep(20);
rabbitTemplate.convertAndSend(queueName,message+i);
}
}
package cn.itcast.mq.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author xc
* @date 2023/5/8 7:08
*/
@Component
@Slf4j
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue1(String msg) throws InterruptedException {
log.info("消费者1接受到simple.queue的消息:【"+msg+"】");
Thread.sleep(20);
}
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue2(String msg) throws InterruptedException {
log.error("消费者2接受到simple.queue的消息:【"+msg+"】");
Thread.sleep(200);
}
}
logging:
pattern:
dateformat: MM-dd HH:mm:ss:SSS
spring:
rabbitmq:
host: 192.168.72.133
port: 5672
username: xcxc
password: xcxc666
virtual-host: /
listener:
simple:
prefetch: 1 # 每次只取一条消息,处理完进行下一条消息
发布订阅与之前的区别就是允许将同一个消息发送给多个消费者。实现方式是加入了exchange
常见的exchange类型包括:
注意 :exchange负责消息路由,而不是存储,路由失败则消息丢失
Fanout Exchange会将接收到的消息路由到每一个跟其绑定的queue
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;
/**
* @author xc
* @date 2023/5/8 7:48
*/
@Configuration
public class FanoutConfig {
/**
* 声明交换机
*/
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("xc.fanout");
}
/**
* 声明队列
*/
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue2");
}
/**
* 绑定交换机与队列
*/
@Bean
public Binding fanoutBinding1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
@Bean
public Binding fanoutBinding2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
package cn.itcast.mq.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author xc
* @date 2023/5/8 7:08
*/
@Component
@Slf4j
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue1")
public void listenFanoutQueue1(String msg) throws InterruptedException {
log.info("消费者1接受到simple.queue1的消息:【"+msg+"】");
}
@RabbitListener(queues = "simple.queue2")
public void listenFanoutQueue2(String msg) throws InterruptedException {
log.error("消费者2接受到simple.queue2的消息:【"+msg+"】");
}
}
@Test
public void testSendExchange() throws InterruptedException {
String exchangeName = "xc.fanout";
String message = "hello xc-";
for (int i = 0; i < 50; i++) {
Thread.sleep(20);
rabbitTemplate.convertAndSend(exchangeName,"",message+i);
}
}
Direct Exchange会将接收到的消息根据规则路由到指定的Queue,因此称为路由模式
绑定交换机和队列
package cn.itcast.mq.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author xc
* @date 2023/5/8 7:08
*/
@Component
@Slf4j
public class SpringRabbitListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "xc.direct",type = ExchangeTypes.DIRECT),
key = {"red","blue"}
))
public void listenSimpleQueue1(String msg) {
log.info("消费者接受到direct.queue1的消息:【"+msg+"】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "xc.direct",type = ExchangeTypes.DIRECT),
key = {"red","yellow"}
))
public void listenSimpleQueue2(String msg) {
log.info("消费者接受到direct.queue2的消息:【"+msg+"】");
}
}
发布测试
@Test
public void testSendDirectExchange() throws InterruptedException {
String exchangeName = "xc.direct";
String message = "hello xc-";
for (int i = 0; i < 50; i++) {
rabbitTemplate.convertAndSend(exchangeName,"red",message+i);
}
}
TopicExchange与DirectExchange类似,区别在于routingKey必须是多个单词的列表,并且以 . 分割
Queue与Exchange指定BindingKey时可以使用通配符:
#:代表0个或多个单词
*:代指一个单词
绑定交换机和队列
package cn.itcast.mq.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author xc
* @date 2023/5/8 7:08
*/
@Component
@Slf4j
public class SpringRabbitListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "xc.topic",type = ExchangeTypes.TOPIC),
key = "china.#"
))
public void listenTopicQueue1(String msg) {
log.info("消费者接受到direct.queue1的消息:【"+msg+"】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "xc.topic",type = ExchangeTypes.TOPIC),
key = "*.news"
))
public void listenTopicQueue2(String msg) {
log.info("消费者接受到direct.queue2的消息:【"+msg+"】");
}
}
发布测试
@Test
public void testSendTopicExchange1() throws InterruptedException {
String exchangeName = "xc.topic";
String message = "hello red";
rabbitTemplate.convertAndSend(exchangeName,"china.news",message);
}
@Test
public void testSendTopicExchange2() throws InterruptedException {
String exchangeName = "xc.topic";
String message = "hello red";
rabbitTemplate.convertAndSend(exchangeName,"am.news",message);
}
@Test
public void testSendTopicExchange3() throws InterruptedException {
String exchangeName = "xc.topic";
String message = "hello red";
rabbitTemplate.convertAndSend(exchangeName,"china.weahter",message);
}
发送消息
引入依赖
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
dependency>
声明bean
package cn.itcast.mq;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class PublisherApplication {
public static void main(String[] args) {
SpringApplication.run(PublisherApplication.class);
}
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
因为消息转换是在底层做的,所以消息直接发就行
接受消息
引入依赖
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
dependency>
声明bean
package cn.itcast.mq;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
因为消息转换是在底层做的,所以消息直接收就行
定义收消费者
@RabbitListener(queues = "object.queue")
public void listenObjQueue(Map<String,Object> msg){
System.out.println(msg);
}
;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
因为消息转换是在底层做的,所以消息直接收就行
定义收消费者
@RabbitListener(queues = "object.queue")
public void listenObjQueue(Map<String,Object> msg){
System.out.println(msg);
}