RabbitMQ简介
MQ全称是Message Queue,可以理解为消息队列的意思,简单来说就是消息以管道的方式进行传递,RabbitMQ是一个实现了AMQP(Advanced Message Queuing Protocol)高级消息队列协议的消息队列服务,用Erlang语言的。
使用阿里云rabbitmq的优势
相比自己手动linux系统搭建rabbitmq, 使用阿里云提供的rabbitmq可免去Linux安装的各种流程,直接在阿里控制台配置路由交换机队列,更为方便,且只需将工作重心放到代码逻辑编写上来,更为方便快捷。免去系统安全性mq稳定性等等操作。
使用场景
电商平台预支付订单延迟付款操作,用户下单后五分钟内需付款,若用户各种情况未付款的话,则自动取消预支付订单,则执行将库存等数据返回数据库等操作。
代码逻辑中的各种异步操作,将必须执行但可异步延迟执行的逻辑放到队列里面。通过交换机路由分发至对应的队列执行逻辑操作。
在我们秒杀抢购商品的时候,系统会提醒我们稍等排队中,而不是像几年前一样页面卡死或报错给用户。像这种排队结算就用到了消息队列机制,放入通道里面一个一个结算处理,而不是某个时间断突然涌入大批量的查询新增把数据库给搞宕机,所以RabbitMQ本质上起到的作用就是削峰填谷,为业务保驾护航。
ConnectionFactory(连接管理器):应用程序与Rabbit之间建立连接的管理器,程序代码中使用;
Channel(信道):消息推送使用的通道;
Exchange(交换器):用于接受、分配消息;
Queue(队列):用于存储生产者的消息;
RoutingKey(路由键):用于把生成者的数据分配到交换器上;
BindingKey(绑定键):用于把交换器的消息绑定到队列上;
实际操作:
进入阿里云服务器mq管理控制台界面:
实例ID, 代理接入点url,AK管理(用户名和密码) 等信息在代码中配置文件会用到。
点击左边导航栏vhost,在对应实例下创建一个vhost
然后我们点击左边Exchange管理创建路由,路由的类型有如下几种:
fanout:该类型路由规则非常简单,会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,相当于广播功能。
topic:与direct类型相似,只是规则没有那么严格,可以模糊匹配和多条件匹配。
direct:该类型路由规则会将消息路由到Bindingkey与Routingkey完全匹配的Queue中。
headers:该类型与direct类型相似,只是Headers Exchange使用Headers属性代替Routing Key进行路由匹配,在绑定Headers Exchange和Queue时,设置绑定属性的键值对;在向Headers Exchange发送消息时,设置消息的Headers属性键值对,使用消息Headers属性键值对和绑定属性键值对比较的方式将消息路由至绑定的Queue。
x-jms-topic:适用于通过消息队列RabbitMQ版提供的JMS接口接入消息队列RabbitMQ版的JMS应用,该类型路由规则会将消息路由到Binding Key与Routing Key通配符匹配的Queue中。
我们创建一个常用的fanout广播类型路由当示例, 名称叫aaa
还可以自定义创建死信延迟队列及路由(也挺常用,用来处理延迟订单)等,想做的私我。
路由创建好了我们需要创建一个queue队列,用来接收从exchange路由中分配匹配而来的消息,将消息按顺序放到匹配的队列中,执行对应的代码逻辑处理。 queue名称就叫bbb
接下来我们需要将创建的queue队列和 exchange路由绑定在一起,需要用到自定义的Bingkey。
如图绑定即可。
到这里我们控制台的操作基本就弄完了,接下来我们开始配置代码中的配置文件。
java代码示例,首先在pom文件中导入依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.5.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.mq-amqp</groupId>
<artifactId>mq-amqp-client</artifactId>
<version>1.0.5</version>
</dependency>
配置文件
#阿里AMQP 配置
spring.rabbitmq.host=amqp-cn-*****************************aliyuncs.com
spring.rabbitmq.port=5672
spring.rabbitmq.username=**************
spring.rabbitmq.password=***********
spring.rabbitmq.virtual-host=*********
# 触发returnedMessage回调必须设置mandatory=true, 否则Exchange没有找到Queue就会丢弃掉消息, 而不会触发回调
spring.rabbitmq.template.mandatory=true
# 是否启用【发布确认】
spring.rabbitmq.publisher-confirms=true
# 是否启用【发布返回】
spring.rabbitmq.publisher-returns=true
#消费者消费失败,自动重新入队
# spring.rabbitmq.listener.simple.default-requeue-rejected=true
#消息推送相关创建路由
aaa=aaa #路由
bbb=bbb #队列
aaabbb-key=aaabbb-key #Bingkey
然后将官方提供的工具类代码导入项目中
package com.*********.rabbitmq;
import com.alibaba.mq.amqp.utils.UserUtils;
import com.rabbitmq.client.impl.CredentialsProvider;
import org.apache.commons.lang3.StringUtils;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* 阿里云 UserName、Password 生成类(动态变化)
*/
public class AliyunCredentialsProvider implements CredentialsProvider {
/**
* Access Key ID.
*/
private final String accessKeyId;
/**
* Access Key Secret.
*/
private final String accessKeySecret;
/**
* security temp token. (optional)
*/
private final String securityToken;
/**
* 实例 id(从阿里云 AMQP 版控制台获取)
*/
private final String instanceId;
public AliyunCredentialsProvider(final String accessKeyId, final String accessKeySecret,
final String instanceId) {
this(accessKeyId, accessKeySecret, null, instanceId);
}
public AliyunCredentialsProvider(final String accessKeyId, final String accessKeySecret,
final String securityToken, final String instanceId) {
this.accessKeyId = accessKeyId;
this.accessKeySecret = accessKeySecret;
this.securityToken = securityToken;
this.instanceId = instanceId;
}
@Override
public String getUsername() {
if(StringUtils.isNotEmpty(securityToken)) {
return UserUtils.getUserName(accessKeyId, instanceId, securityToken);
} else {
return UserUtils.getUserName(accessKeyId, instanceId);
}
}
@Override
public String getPassword() {
try {
return UserUtils.getPassord(accessKeySecret);
} catch (InvalidKeyException e) {
//todo
} catch (NoSuchAlgorithmException e) {
//todo
}
return null;
}
}
package com.************.rabbitmq;
import com.jianlet.constant.Configure;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
@Configuration
public class RabbitConfig {
@Resource
private RabbitProperties rabbitProperties;
@Bean
public ConnectionFactory getConnectionFactory() {
com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory =
new com.rabbitmq.client.ConnectionFactory();
rabbitConnectionFactory.setHost(rabbitProperties.getHost());
rabbitConnectionFactory.setPort(rabbitProperties.getPort());
rabbitConnectionFactory.setVirtualHost(rabbitProperties.getVirtualHost());
AliyunCredentialsProvider credentialsProvider = new AliyunCredentialsProvider(
rabbitProperties.getUsername(), rabbitProperties.getPassword(), Configure.getInstanceId());
rabbitConnectionFactory.setCredentialsProvider(credentialsProvider);
rabbitConnectionFactory.setAutomaticRecoveryEnabled(true);
rabbitConnectionFactory.setNetworkRecoveryInterval(5000);
ConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitConnectionFactory);
((CachingConnectionFactory)connectionFactory).setPublisherConfirms(rabbitProperties.isPublisherConfirms());
((CachingConnectionFactory)connectionFactory).setPublisherReturns(rabbitProperties.isPublisherReturns());
return connectionFactory;
}
}
package com.****.rabbitmq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
/**
* 生产者端将消息发送出去,消息到达RabbitMQ之后,会返回一个到达确认。
* 这个确认实际上就是官方常说的ConfirmCallback,我们通过在生产者端使用一个回调类来监听RabbiMQ返回的消息确认。
* Spring AMQP中我们通过设置RabbitTemplate的ConfirmCallback属性来实现消息确认回调,通过一个实现了ConfirmCallback的类来实现回调逻辑。
*/
public class RabbitConfirmCallback implements RabbitTemplate.ConfirmCallback {
Logger log= LoggerFactory.getLogger(RabbitConfirmCallback.class);
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("MessageConfirm correlationData:"+correlationData+",ack:"+ack+",cause:"+cause);
}
}
package com.**********.rabbitmq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
/**
* 设置 ReturnCallback 回调
* 如果发送到交换器成功,但是没有匹配的队列,就会触发这个回调 在ConfirmCallback之前执行
*/
public class RabbitReturnCallback implements RabbitTemplate.ReturnCallback {
Logger log= LoggerFactory.getLogger(RabbitReturnCallback.class);
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("message return message:"+message+",replyCode:"+replyCode+",replyText:"+replyText+",exchange:"+exchange+",routingKey:"+routingKey);
}
}
package com.*********.rabbitmq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
@Component
public class SenderWithCallback {
Logger log= LoggerFactory.getLogger(SenderWithCallback.class);
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void initRabbitTemplate() {
// 设置生产者消息确认
rabbitTemplate.setConfirmCallback(new RabbitConfirmCallback());
rabbitTemplate.setReturnCallback(new RabbitReturnCallback());
}
}
在代码逻辑中创建消息发送者,将消息发送至路由交换机,接着由交换机匹配至对应的队列执行你要处理的代码操作。 注意这里创建的发送者这几行代码是异步操作,所以即使报错也不会影响正常流程。
@Resource
private RabbitTemplate rabbitTemplate;
@RequestMapping(value = "/rabbitmqtest")
@ResponseBody
public void rabbitmqtest(@RequestParam Map<String, Object> map) {
rabbitTemplate.convertAndSend(env.getProperty("aaa"),
env.getProperty("aaa-bbb-key"),"你要发送的String类型数据,会在消息接收者里接收,然后进行后续逻辑判断",
new CorrelationData("unRouting-" + UUID.randomUUID().toString()));
}
创建消息接收者,用来接收消息处理异步逻辑
package com.*;
@Component
public class RabbitmqPeceiver {
@RabbitListener(queues = "${bbb}")
public void process(String msg) {
JSONObject jsonObject = JSONObject.parseObject(msg);
//获取需要的参数进行你的逻辑处理
}
}
到这儿大致的主要流程就创建完了,基本的流程操作就这些,如果不行的话私我。