1rabbit介绍
(1)linux获取已经下载好的rabbit镜像tar包
docker load -i mq.tar
(2)启动容器,5672:MQ的通信端口,15672:管控台的端口
docker run \
-e RABBITMQ_DEFAULT_USER=itheima \
-e RABBITMQ_DEFAULT_PASS=itheima \
-v mq-plugins:/plugins \
--name mq \
--hostname mq \
--restart=always \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
(3)结构与角色
2、rabbit入门
介绍:rabbitmq的五种模式
(1)Simple简单模式:一个生产者,一个消费者,没有交换机
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置文件:
spring:
rabbitmq:
host: 192.168.138.100 # 主机名
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: itheima # 用户名
password: itheima # 密码
(2)WorkQueue:一个生产者,多个消费者,消费者之间是竞争关系
配置消费者:
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
(3)Fanout:一个生产者多个消费者,通过交换机,把消息路由到队列,实现群发
(4)Direct:一个生产者,多个消费者,通过交换机,通过路由key的精确名称把消息路由到队列,实现群发
(5)Topic:一个生产者,多个消费者,通过交换机,通过路由key的部分名称把消息路由到队列,实现群发.*:匹配单个字符,#:匹配一个或多个字符
3、实现
简单队列创建持久化队列和交换机并绑定
@Configuration
public class RabbitConfig {
@Bean
public Queue errorQueue(){
return QueueBuilder.durable("error.queue").build();
}
@Bean
public DirectExchange errorExchange(){
return new DirectExchange("error.direct",true,false);
}
@Bean
public Binding errorQueueDirect(Queue errorQueue,DirectExchange errorExchange){
return BindingBuilder.bind(errorQueue).to(errorExchange).with("error");
}
}
等价于,交换机默认持久化
@Component
@Slf4j
public class MessageListener {
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue(name = "direct.queue", durable = "true"),
exchange = @Exchange(name = "itcast.direct"),
key = {"red", "blue"}
)
})
public void listenerSimpleQueue(String message) {
}
4、保障消息可靠性
(1)、生产者开启confirm机制,保证消息正确到达交换机
String s = UUID.randomUUID().toString().replace("-","");
CorrelationData correlationData = new CorrelationData(s);
correlationData.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
@Override
public void onFailure(Throwable ex) {
log.error("发现送消息出问题了"+ex.getMessage()+correlationData.getId());
log.info("发现送消息出异常,重新发送消息");
}
@Override
public void onSuccess(CorrelationData.Confirm result) {
if (result.isAck()){
log.info("消息发送成功");
}
else {
log.error("消息发送失败");
log.info("重新发送消息");
}
}
});
MessageProperties messageProperties = new MessageProperties();
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
Message message = new Message("GoodMessage".getBytes(StandardCharsets.UTF_8), messageProperties);
rabbitTemplate.send("itcast.direct","blue",message,correlationData);
(2)、生产者开启return机制,保证消息正确到达队列
@Slf4j
@Configuration
public class CommonConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 获取RabbitTemplate
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
// 设置ReturnCallback
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
// 投递失败,记录日志
log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",
replyCode, replyText, exchange, routingKey, message.toString());
// 如果有业务需要,可以重发消息
});
}
}
(3)、交换机、队列、消息进行持久化,示例代码为消息持久化,剩下两个在3里提到
MessageProperties messageProperties = new MessageProperties();
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
Message message = new Message("GoodMessage".getBytes(StandardCharsets.UTF_8), messageProperties);
(4)、消费者开启手动ack,或者自动ack + 重试耗尽的失败策略,定义错误交换机队列,后期通过人工进行干预
完整配置文件:
spring:
rabbitmq:
host: 192.168.138.100
port: 5672
virtual-host: /
username: itheima
password: itheima
listener:
simple:
#每次拉取一条消息,等消费完了,再拉取另一条消息
prefetch: 1
#acknowledge-mode: manual #手动ack
acknowledge-mode: auto #自动ack
retry:
enabled: true #开启消费者失败重试
initial-interval: 1000 #初始的失败等待时长为1秒
multiplier: 2 #失败的等待时长倍数,下次等待时长 = multiplier * last-interval
max-attempts: 3 #最大重试次数
stateless: true #true无状态;false有状态。如果业务中包含事务,这里改为false
手动ack:
public void listenerSimpleQueue(String message,Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) Long tag) {
try {
log.info("direct.queue收到的消息:{}", message);
Thread.sleep(2000);
int i = 1 / 0;
channel.basicAck(tag, false);
} catch (Exception e) {
try {
channel.basicNack(tag, false,true);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
自动ack
public void listenerSimpleQueue(String message) {
log.info("direct.queue收到消息{}",message);
int i = 1 / 0;
}
5、消息重复消费问题
(1)幂等性:多次执行同一个操作,最终的结果是一样的、例如查询差多少次都是一样的,但是增删改就不可以不具有幂等性。
对于幂等性操作,多次消费消息,除开性能的影响 ,其他没有什么大问题,可以不管它
对于非幂等性操作,多次消费消息,会造成数据一致性的问题,所以要保证重复消费消息的问题
6、消息积压问题
(1)产生的原因?
生产者生产消息的速度 远高于 消费者消费消息的速度?于是就会造成消息积压在MQ中
(2)分析为什么会有消息积压?
<1>生产者消费者比例设计是否有问题?如果是,重新设置生产者与消费者数量匹配
<2>消费者出异常了,第一步:修复宕机的情况,第二步:临时开启多个消费者,来以多倍速消费积压的消息。当积压的消息消费的差不多的情况,关闭临时消费者
<3>惰性队列:可以放很多消息,还可以把多的消息持久化到本地
7、死信交换机
(1)死信:顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,生产者直接将消息投递给交换机或者队列,消费者从队列取出消息进行消费,但某些时候由于特定的原因导致队列中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。
可能导致的原因:
<1>消费者使用basic.reject 或 basic.nack声明消费失败,并且消息的requeue参数设置为false
<2>消息是一个过期消息,超时无人消费
<3>要投递的队列消息满了,无法投递
(2)死信交换机本质上就是一个普通的交换机,给他绑定一个属性
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。