MQ
1.同步通讯和异步通讯
同步调用的问题
异步调用方案
异步通信的优点:
-
耦合度低
-
吞吐量提升
-
故障隔离
-
流量削峰
异步通信的缺点:
-
依赖于Broker的可靠性、安全性、吞吐能力
-
架构复杂了,业务没有明显的流程线,不好追踪管理
2.MQ
消息队列
3.RabbitMQ
Docker安装
Docker拉取
docker pull rabbitmq:management
创建容器
docker run \ -e RABBITMQ_DEFAULT_USER=root \ -e RABBITMQ_DEFAULT_PASS=123456 \ --name mq \ --hostname mq1 \ -p 15672:15672 \ -p 5672:5672 \ -d rabbitmq:management
RabbitMQ中的几个概念:
-
channel:操作MQ的工具
-
exchange:路由消息到队列中
-
queue:缓存消息
-
virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组
消息模型
SpringAMQP
使用步骤:
-
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
-
导入RabbitMQ的配置信息
spring: rabbitmq: host: # rabbitMQ的ip地址 port: 5672 # 端口 username: root password: #密码 virtual-host: /
-
利用RabbitMQTemplate中的ConvertAndSend方法
@SpringBootTest public class SpringAmqpTest { @Autowired private RabbitTemplate rabbitTemplate; @Test public void testSendMessageSimpleQueue() { String queueName = "simple.queue"; String message = "hello, spring amqp!"; rabbitTemplate.convertAndSend(queueName, message); } }
基本消息模型
只有publisher、queue、consumer三个角色。
在测试类中编写publisher
@SpringBootTest public class SpringAmqpTest { @Autowired private RabbitTemplate rabbitTemplate; @Test /** * @description 向simple.queue消息队列中发送消息数据 */ public void testSendMessageSimpleQueue() { String queueName = "simple.queue"; String message = "hello, spring amqp!"; rabbitTemplate.convertAndSend(queueName, message); } }
编写consumer
@Component public class SpringRabbitListener { @RabbitListener(queues = "simple.queue") public void listerWorkQueue(String msg) throws InterruptedException { System.out.println("消费者1接受到消息:["+msg+"]"+ LocalDateTime.now()); Thread.sleep(200); } }
工作消息队列
设置消息预取上限
spring: rabbitmq: host: # rabbitMQ的ip地址 port: 5672 # 端口 username: root password: #密码 virtual-host: / listener: simple: prefetch: 1 #控制预取消息上限,每次只能获取一条消息,处理完成才能获取下一条消息
两个consumer
@Component public class SpringRabbitListener { @RabbitListener(queues = "simple.queue") public void listerWorkQueue1(String msg) throws InterruptedException { System.out.println("消费者1接受到消息:["+msg+"]"+ LocalDateTime.now()); Thread.sleep(20); } @RabbitListener(queues = "simple.queue") public void listerWorkQueue2(String msg) throws InterruptedException { System.out.println("消费者2接受到消息:["+msg+"]"+ LocalDateTime.now()); Thread.sleep(200); } }
发布订阅
根据exchange的不同可以划分为广播、路由、主题三种消息模型
广播
实现步骤:
-
在consumer服务中声明队列、交换机,并且将两者进行绑定。
@Configuration public class FanoutConfig { @Bean public FanoutExchange fanoutExchange(){ return new FanoutExchange("wyj.fanout"); } @Bean public Queue fanoutQueue1(){ return new Queue("fanout.queue1"); } @Bean public Binding fanoutBinding1(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); } @Bean public Queue objectQueue(){ return new Queue("object.queue"); } }
-
在consumer服务中编写两个方法,分别监听fanout.queue1,fanout.queue2。
@RabbitListener(queues = "fanout.queue1") public void listenFanoutQueue1(String msg) { System.out.println("消费者1接受到消息:["+msg+"]"+ LocalDateTime.now()); } @RabbitListener(queues = "fanout.queue2") public void listenFanoutQueue2(String msg) { System.out.println("消费者2接受到消息:["+msg+"]"+ LocalDateTime.now()); }
-
在publisher中编写测试方法,向交换机发送数据。
@Test public void testSendFanoutExchange() { // 交换机名称 String exchangeName = "wyj.fanout"; // 消息 String message = "hello, every one!"; // 发送消息 rabbitTemplate.convertAndSend(exchangeName, "", message); }
路由
实现步骤:
-
利用@RabbitListener声明Exchange、Queue、RoutingKey
@RabbitListener(bindings = @QueueBinding( value = @Queue(name = "direct.queue1"),//绑定queue exchange = @Exchange(name = "wyj.direct",type = ExchangeTypes.DIRECT),//指定exchange类型 key = {"red","yellow"}//指定routingkey )) public void listenDirectQueue1(String msg){ System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】"); }
-
在consumer服务中编写方法监听direct.queue1和direct.queue2。
@RabbitListener(bindings = @QueueBinding( value = @Queue(name = "direct.queue1"), exchange = @Exchange(name = "wyj.direct",type = ExchangeTypes.DIRECT), key = {"red","yellow"} )) public void listenDirectQueue1(String msg){ System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】"); }
-
在publisher中编写方法向,wyj.direct发送数据。
@Test public void testSendDirectExchange() { // 交换机名称 String exchangeName = "wyj.direct"; // 消息 String message = "hello, red!"; // 发送消息 rabbitTemplate.convertAndSend(exchangeName, "red", message); }
主题
Queue与Exchange指定BindingKey时可以使用通配符:
#:代指0个或多个单词
*:代指一个单词
实现步骤:
-
利用@RabbitListener声明Exchange、Queue、RoutingKey
-
在consumer服务中编写方法监听topic.queue1和topic.queue2。
@RabbitListener(bindings = @QueueBinding( value = @Queue(name = "topic.queue1"), exchange = @Exchange(name = "wyj.topic",type = ExchangeTypes.TOPIC), key = {"wyj.*"})) public void listenTopicQueue1(String msg) { System.out.println("消费者1接收到topic.queue1的消息:【" + msg + "】"); } @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "topic.queue2"), exchange = @Exchange(name = "wyj.topic",type = ExchangeTypes.TOPIC), key = {"*.wyj"})) public void listenTopicQueue2(String msg) { System.out.println("消费者2接收到topic.queue2的消息:【" + msg + "】"); }
-
在publisher中编写方法向,wyj.topic发送数据。
@Test public void testSendTopicExchange() { // 交换机名称 String exchangeName = "wyj.topic"; // 消息 String message = "今天天气不错,我的心情好极了!"; // 发送消息 rabbitTemplate.convertAndSend(exchangeName, "jj.china", message); }
消息转换器
在SpringAMQP的发送中,接收消息的类型是Object,也就是说我们可以发送任意对象类型的消息,SpringAMQP会帮我们序列化为字节后发送。
查看消息,中文被java序列化。 此序列化数据长度太长,有安全等问题,建议调整。
在父工程引入依赖:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>
在publisher和consumer中加入:
@Bean public MessageConverter messageConverter(){ return new Jackson2JsonMessageConverter(); }
在consumer定义MessageConvert(因为消息发送进行了转换,这里接收反过来也要转换)
就是将默认的JDK序列化转换为Json格式序列化。