同步调用
Feign客户端可以实现服务间的通信,但是Feign是同步调用,也就是说A服务调用B服务之后,会进入阻塞/等待状态,直到B服务返回调用结果给A服务,A服务才会继续往后执行
在特定的业务场景中:用户注册成功之后,发送短息通知用户〈A服务为用户注册,B服务发送短信)A服务在完成用户注册之后(代码1),调用B服务发送短信,A服务完成B服务调用之后无需等待B服务的执行接口,直接执行提示用户注册从公(代码2),在这种需求下A服务调用B服务如果使用同步调用,必然降低A服务的执行效率,因此在这种场景下A服务需要通过异步调用调用B服务异步调用:
当A服务调用B服务之后,无需等待B的调用结果,可以继续往下执行;那么服务间的异步通信该如何实现呢?
服务之间可以通过消息队列实现异步调用
保证可靠性
:使用一些机制来保证可靠性,如持久化、传输确认、发布确认。高可用性
:RabbitMo集群中的某个节点出现问题时队列任然可用。https://note.youdao.com/ynoteshare1/index.html?id=b01d0f9ed0499610fc8f7b2fc76ef17e&type=note
一个生产者对应一个消费者
一个生产者对应多个消费者
通过交换机来转发消息
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
<version>4.10.0version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.25version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.9version>
dependency>
# 全局日志配置
# 日志的级别:
# error, warn, info, debug, TRACE按照优先级别越来越低
# 注意:如果设置的优先级别比较高,只能看到高优先级的日志,如果设置的优先级比较低,可以看到本优先级的日志以及高优先级的日志信息
log4j.rootLogger=DEBUG, stdout, F
# MyBatis 日志配置
log4j.logger.com.qfedu.dao.IUserDao.getAllUsers=TRACE
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
log4j.appender.F=org.apache.log4j.DailyRollingFileAppender
log4j.appender.F.File=myproj.log
log4j.appender.F.Append=true
log4j.appender.F.Threshold=DEBUG
log4j.appender.F.layout=org.apache.log4j.PatternLayout
log4j.appender.F.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss}-[%p %F\:%L] %m%n
package qf.util;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class RabbitConnection {
public static Connection getConnection() throws Exception{
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setVirtualHost("host1");
factory.setUsername("dalaolao");
factory.setPassword("a443496487");
return factory.newConnection();
}
}
package com.qf.produce;
import com.qf.util.RabbitConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.nio.charset.StandardCharsets;
public class Produce {
public static void main(String[] args) throws Exception {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
//String var1,交换机名名称,如果消息直接发送到队列 那么直接给个空串
// String var2, 目标消息队列名称
// BasicProperties var3, 设置当前的消息的参数(过期时间)
// byte[] var4 消息
for (int i = 0; i < 10; i++) {
String msg = "测试消息1,我是dalaolao"+i;
channel.basicPublish("ex2","b",null,msg.getBytes(StandardCharsets.UTF_8));
}
channel.close();
connection.close();
}
}
package qf.Consumer;
import com.rabbitmq.client.*;
import qf.util.RabbitConnection;
import java.io.IOException;
public class ResavingMsg {
public static void main(String[] args) throws Exception {
Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 处理消息的方法。body表示从消息队列接收到的消息
String msg = new String(body);
System.out.println("消费者"+msg);
}
};
channel.basicConsume("queue5",true,consumer);
}
}
## 应用名称
#spring.application.name=RabbitMQ-Springboot
## 应用服务 WEB 访问端口
#server.port=8080
server:
port: 9998
spring:
application:
name: producer
rabbitmq:
host: localhost
port: 5672
username: dalaolao
password: a443496487
virtual-host: host1
package com.qf.service.impl;
import com.qf.service.ProducerService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProducerServiceImpl implements ProducerService {
@Autowired
private AmqpTemplate amqpTemplate;
@Override
public String send(String msg) {
// 发送到队列(简单模式)
amqpTemplate.convertAndSend("queue1",msg);
// 发送到交换机(订阅模式)
amqpTemplate.convertAndSend("ex1","",msg);
// 发送到交换机(路由模式)
amqpTemplate.convertAndSend("ex2","a",msg);
return "发送成功";
}
}
前两步同上
接收端代码:
package com.qf.service;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
@RabbitListener(queues = {"queue1","queue2","queue3","queue4","queue5","queue6"})
public class Queue1 {
@RabbitHandler
public void handlerMsg(String msg){
System.out.println(msg);
}
}
RabbitMQ消息队列,发送/接收都是字符串/字节数组组类型的消息
- 必须实现Serializable接口
- 对象的包名,类名,属性名必须相同
package com.qf.service.impl;
import com.qf.service.ProducerService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProducerServiceImpl implements ProducerService {
@Autowired
private AmqpTemplate amqpTemplate;
@Override
public String send(String msg) {
amqpTemplate.convertAndSend("queue1",Goods);
}
}
@Service
@RabbitListener(queues = {"queue1","queue2","queue3","queue4","queue5","queue6"})
public class Queue1 {
@RabbitHandler
public void handlerMsg(String msg){
System.out.println(msg);
}
}
要求对象中的属性名、类型相同即可
发送端发送json串,接收端将接收到的json串转换成对象即可
import com.fasterxml.jackson.databind.ObjectMapper;//这个包中有一个ObjectMapper类 //ObjectMapper类有两个方法可以将对象转换成json串/将json串转换成对象 String s = new ObjectMapper().writeValueAsString(goods);//将对象转换成json串 Goods goods1 = new ObjectMapper().readValue(goods, Goods.class);//将json串转换成对象
//定义队列(使用Java代码在MQ中新建一个队列)
//参数1:定义的队列名称
//参数2:队列中的数据是否持久化(如果选择了持久化)
//参数3:是否排外(当前队列是否为当前连接私有)
//参数4:自动删除(当此队列的连接数为8时,此队列会销毁(无论队列中是否还有数据))
//参数5:设置当前队列的参数
channel. queueDeclare( " queue7" ,false,false, false , null);
//定义一个“订阅交换机”
channel.exchangeDeclare( "ex3",BuiltinExchangeType .FANOUT);
//定义一个“路由交换机”
channel.exchangeDeclare( "ex4", BuiltinExchangeType.DIRECT);
//绑定队列
//参数1:队列名称
//参数2:目标交换机
//参数3:如果绑定订阅交换机参数为"",如果绑定路由交换机则表示设置队列的key
channel.queueBind( "queue7" , "ex4" , "k1" );
channel.queueBind( "queue8" , "ex4" , "k2" );
主要是生产端的创建队列、创建交换机、绑定交换机与队列;消费端只需监听即可
package com.qf.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
//创建队列
@Bean(name = "queue8")
public Queue Queue8() {
return new Queue("queue8");
}
@Bean(name = "queue9")
public Queue Queue9() {
return new Queue("queue9");
}
@Bean(name = "queue10")
public Queue Queue10() {
return new Queue("queue10");
}
@Bean(name = "queue11")
public Queue Queue11() {
return new Queue("queue11");
}
//创建订阅交换机
@Bean
public FanoutExchange ex3(){
return new FanoutExchange("ex3");
}
//创建路由交换机
@Bean
public DirectExchange ex4(){
return new DirectExchange("ex4");
}
@Bean
public Binding binding1(FanoutExchange ex3,Queue queue8){
//将queue8绑定到订阅交换机ex3
return BindingBuilder.bind(queue8).to(ex3);
}
@Bean
public Binding binding2(FanoutExchange ex3,Queue queue9){
//将queue9绑定到订阅交换机ex3
return BindingBuilder.bind(queue9).to(ex3);
}
@Bean
public Binding binding3(DirectExchange ex4,Queue queue10){
//将queue10绑定到路由交换机ex4并设置key为a
return BindingBuilder.bind(queue10).to(ex4).with("a");
}
@Bean
public Binding binding4(DirectExchange ex4,Queue queue9){
//将queue10绑定到路由交换机ex4并设置key为b
return BindingBuilder.bind(queue9).to(ex4).with("b");
}
}
#在rabbitmq配置下追加以下配置
publisher-confirm-type: simple #开启消息确认机制
publisher-returns: true #开启return机制
package com.qf.config;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class MyConfirmListener implements RabbitTemplate.ConfirmCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private AmqpTemplate amqpTemplate;
@PostConstruct//在创建完对象后加载此方法
public void setConfirm() {
rabbitTemplate.setConfirmCallback(this);
}
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
// 参数b 表示消息确认结果
// 参数s 表示发送的消息
if (b) {
System.out.println("消息发送到交换机成功");
} else {
System.out.println("消息发送到交换机失败");
amqpTemplate.convertAndSend("queue1",s);
}
}
}
package com.qf.config;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class MyReturnListener implements RabbitTemplate.ReturnCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void setReturn() {
rabbitTemplate.setReturnCallback(this);
}
/**
* @param message 消息主体
* @param i replyCode
* @param s 错误描述
* @param s1 发送到的交换机
* @param s2 路由交换机的key
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("分发到队列失败");
}
}
消费端给队列的反馈
package com.qf.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qf.dto.Goods;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
@RabbitListener(queues = {"queue1"})
public class Queue1 {
@RabbitHandler
public void handlerMsg(String goods, Message message, Channel channel) throws IOException {
try {
Goods goods1 = new ObjectMapper().readValue(goods, Goods.class);//将json串转换成对象
System.out.println(goods1);
//参数1 :当前消息的索引
//参数2 :是否开启批处理
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
//参数1 :当前消息的索引
//参数2 :是否开启批处理
//参数3 :是否重新加入队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
}
消息消费的幂等性――多次消费的执行结果时相同的(避免重复消费)
解决方案
:处理成功的消息setnx到redis