注意:版本号中带有management的是带有管理端界面的,没有带的是访问不到15672端口的
docker run \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=123456 \
-v 服务器地址:/var/lib/rabbitmq \
-v mq-plugins(数据卷名称):/plugins \
--name rabbit \
--hostname rabbbitHost \
--network bridge \
-p 15672:15672 \
-p 5672:5672 \
rabbitmq:3.12-management
-e 声明环境变量
-e RABBITMQ_DEFAULT_USER 设置rabbbitmq默认用户名
-e RABBITMQ_DEFAULT_PASS 设置rabbbitmq默认用户密码
-v 磁盘映射
-v 服务器地址:/var/lib/rabbitmq 持久化 rabbitmq 数据,数据映射到服务器的地址
--name 容器名称
--hostname 其他容器访问当前容器时,可通过hostname进行访问
--network 指定容器的网络 bridge网桥模式
-p 端口映射
- 15672:RabbitMQ提供的管理控制台的端口
- 5672:RabbitMQ的消息发送处理接口
ps:
topic模式routingKey使用通配符时,一对多,一个routingKey可以监听多个不带通配符的routingKey
direct模式routingKey是一对一的
交换机的作用:
生产者发送消息时可直接发送到队列中或者发送到交换机中(交换机通过routingKey来将消息路由到正确的队列中)
Work queues,任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息,多个消费者绑定到同一队列,同一条消息只能被一个消费者消费,可以使用prefetch来控制预获取的消息数量
rabbitmq采用AMQP的协议,因此它具备了跨语言的特性,任何语言只要尊徐AMQP协议来收发消息,都可以与rabbitmq互通,rabbitmq提供了java客户端编码,不过相对比较复杂,spring官方基于rabbitmq提供的了一套消息收发的模板工具:springAMQP. SpringAMQP提供了三个功能:
# 引入springAMQP
org.springframework.boot
spring-boot-starter-amqp
对rabbitMq中消息的序列化,默认是使用jdk序列化,众所周知,jdk序列化存在:数据体积过大,有安全漏洞,可读性查的问题,所以我们可以使用json的方式进行序列化或者反序列化,首先我们要引入jackson依赖,如果项目中引入了spring-boot-starter-web依赖,就无需要重新引入jackson依赖
com.fasterxml.jackson.dataformat
jackson-dataformat-xml
2.9.10
需要在启动类型注入bean方法或者通过applicationContectAware两种方式实现
@Bean
public Jackson2JsonMessageConverter jsonMessageConverter(){
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
// 给每个消息的头部的Properties中添加message_id,可用于区分不同消息,在实现业务的幂等性中,可以通过唯一id来判断消息是否被处理
jackson2JsonMessageConverter.setCreateMessageIds(true);
return jackson2JsonMessageConverter;
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
/**
* 描述:
* @author
*/
@Slf4j
@Configuration
public class RabbitMqJsonConverter implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RabbitTemplate bean = applicationContext.getBean(RabbitTemplate.class);
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
jackson2JsonMessageConverter.setCreateMessageIds(true);
// 设置对象序列化方式,默认使用的是jdk的序列化方式
bean.setMessageConverter(jackson2JsonMessageConverter);
// 设置publish return
bean.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
log.info("returnedMessage:exchange:{},message:{},replyCode:{},replyText:{},routingKey:{}",
returnedMessage.getExchange(),returnedMessage.getMessage(),
returnedMessage.getReplyCode(),returnedMessage.getReplyText(),
returnedMessage.getRoutingKey());
}
});
}
}
生产者配置:需要在application的配置文件中对rabbitMq进行配置
为了保证生产者一定能把消息发送到MQ可以使用:
ack和nack是 Publisher confirm机制的,ack
是投递成功;nack
是投递失败。而return
则属于Publisher Return机制。 默认两种机制都是关闭状态,需要通过配置文件来开启。每个RabbitTemplate
只能配置一个ReturnCallback,在上边代码中RabbitMqJsonConverter,已对return进行设置
spring:
rabbitmq:
host: rabbit主机地址
username:用户名
password: 密码
virtual-host: hmall
port: 5673
template: # 生产者重试机制 springAmqp的重试机制是阻塞的,所以建议使用异步线程来执行消息发送代码
retry:
initial-interval: 1000ms # 失败后的初始等待时间
enabled: true # 开启重试机制
multiplier: 1 # 等待时间倍数 失败后下次的等待时长倍数,下次等待时长 = initial-interval * multiplier+connection-timeout
max-attempts: 3 # 最大重试次数
connection-timeout: 1s # rabbitTemplate链接超时时间
publisher-confirm-type: correlated # 开启publish confirm机制 correlated :mq异步回调返回回执 simple:mq同步回调返回回执 none关闭confirm机制
publisher-returns: true # 开启publisher return机制
发送消息:
@Slf4j
@SpringBootTest
public class SpringAmqpTest {
@Resource
private RabbitTemplate rabbitTemplate;
@Test
public void test1() throws InterruptedException {
// 生产者消息确认机制
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
correlationData.getFuture().addCallback(new ListenableFutureCallback() {
@Override
public void onFailure(Throwable ex) {
// spring处理失败报错,与mq无关
log.error("程序处理失败",ex);
}
@Override
public void onSuccess(CorrelationData.Confirm result) {
if (result.isAck()) {
log.debug("操作成功ack");
}else{
log.error("操作失败nack{}",result.getReason());
}
}
});
// rabbitTemplate.convertAndSend("direct.hmall","direct","你好啊,direct");
rabbitTemplate.convertAndSend("topic.exchange.hmall","topic.*","你好啊,topic.*",correlationData);
Thread.sleep(300);
}
@Test
public void sendObject(){
Map map = new HashMap<>(2);
map.put("name","刘星");
map.put("sex","男");
map.put("age",12);
rabbitTemplate.convertAndSend("direct.hmall.queue1",map);
}
}
在开启持久化机制以后,如果同时还开启了生产者确认,那么MQ会在消息持久化以后才发送ACK回执,进一步确保消息的可靠性。 不过出于性能考虑,为了减少IO次数,发送到MQ的消息并不是逐条持久化到数据库的,而是每隔一段时间批量持久化。一般间隔在100毫秒左右,这就会导致ACK有一定的延迟,因此建议生产者确认全部采用异步方式
rabbitmq消费者确认机制,当消费者处理消息结束后,应该向RabbitMQ发送一个回执,告知RabbitMQ自己消息处理状态。回执有三种可选值
logging:
pattern:
dateformat: MM-dd HH:mm:ss:SSS
level:
com.itheima: debug
spring:
rabbitmq:
host: 47.96.6.18
port: 5672
virtual-host: hmall
username: hmallUser
password: 123456
listener:
simple:
prefetch: 1 # 消费者每次只可以获取一条消息,只有处理完当前的消息后,
# 才可以获取下一条消息。如果未设置,则当前队列的所有消费者会平分队列中消息条数即
#(队列会轮询分配消息到与之绑定的消费者上,不管消费者是否处理完毕)
acknowledge-mode: auto # 消费者确认机制
# **none**:不处理。即消息投递给消费者后立刻ack,消息会立刻从MQ删除。非常不安全,不建议使用
# **manual**:手动模式。需要自己在业务代码中调用api,发送ack或reject,存在业务入侵,但更灵活
# **auto**:自动模式。SpringAMQP利用AOP对我们的消息处理逻辑做了环绕增强,当业务正常执行时则自动返回ack. 当业务出现异常时,根据异常判断返回不同结果:
# 如果是业务异常,会自动返回nack;
# 如果是消息处理或校验异常,自动返回reject;
retry:
# 消费者在失败后消息没有重新回到MQ无限重新投递,而是在本地重试了3次
# 本地重试3次以后,默认的失败处理策略会抛出了AmqpRejectAndDontRequeueException异常。查看RabbitMQ控制台,发现消息被删除了,说明最后SpringAMQP返回的是reject
enabled: true # 开启消费者重试机制,当消费者出现异常时利用本地重试,而并不是无限制的requeue到mq中
initial-interval: 1s # 初次失败等待时长为1秒
max-attempts: 3 # 最大重试次数
multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false
max-interval: 1000ms
Spring允许我们自定义重试次数耗尽后的消息处理策略,这个策略是由MessageRecovery
接口来定义的,它有3个不同实现:
RejectAndDontRequeueRecoverer
:重试耗尽后,直接reject
,丢弃消息。默认就是这种方式ImmediateRequeueMessageRecoverer
:重试耗尽后,返回nack
,消息重新入队RepublishMessageRecoverer
:重试耗尽后,将失败消息投递到指定的交换机 RepublishMessageRecoverer的实现
/**
* 描述:消息处理异常时,本地重试次数用尽后,将失败消息投递到指定交换机
* 当spring.rabbitmq.listener.simple.retry.enabled为true时,才会启用当前类中的bean注册
* @author guomengsi
*/
@Configuration
@ConditionalOnProperty(name = "spring.rabbitmq.listener.simple.retry.enabled",havingValue = "true")
public class MqFailTacticsConfiguration {
@Resource
private RabbitTemplate rabbitTemplate;
@Bean
public Queue failQueue(){
return new Queue("MQ_FAIL_QUEUE");
}
/**
* 返回类型需要是具体的交换机类型
* @return FanoutExchange 广播类型
*/
@Bean
public DirectExchange failExchange(){
return new DirectExchange("MQ_FAIL_EXCHANGE");
}
/**
* 交换机和队列可以通过参数传递,即参数名使用上边bean的函数名,也可以直接在bind(failQueue()).to(failExchange())
* 因为被bean注释的函数只会实例化一次,下次再使用时,会判断是否被实例化,如果实例化则直接使用
* @param failQueue 队列
* @param failExchange 交换机
* @return Binding
*/
@Bean
public Binding exchangeBindToQueue(Queue failQueue,DirectExchange failExchange){
return BindingBuilder.bind(failQueue).to(failExchange).with("fail");
}
@Bean
public RepublishMessageRecoverer republishMessageRecoverer(){
return new RepublishMessageRecoverer(rabbitTemplate,"MQ_FAIL_EXCHANGE","fail");
}
}
开启消费者确认机制,由spring确认消息处理成功后返回ack,异常返回nack
开启消费者失败重试机制,并设置messageRecoverer,多次本地重试,失败后,会将消息投递到异常交换机,由人工处理
不能保证mq百分百的可以处理成功,我们可以通过定时任务去主动查询
业务的幂等性是指在程序开发中,同一个业务执行一次或者多次对业务的影响状态是一样的。处理业务幂等性的方法有两种:
业务判断就是基于业务本身的逻辑或状态来判断是否是重复的请求或消息,不同的业务场景判断的思路也不一样。 例如,处理消息的业务逻辑是把订单状态从未支付修改为已支付。因此我们就可以在执行业务时判断订单状态是否是未支付,如果不是则证明订单已经被处理过,无需重复处理。
在一段时间后才会被执行的任务(例如,订单超时未支付,需要更改订单状态,库存等),称为延迟任务,而实现延迟任务最简单的方法则是MQ中的延迟消息
在RabbitMQ中实现延迟消息的两种方法: