rabbitMQ浅到深

RabbitMQ是采用erlang语言开发的,所以必须有erlang环境才可以运行

rabbitMQ浅到深_第1张图片

下载安装步骤


rabbitMQ浅到深_第2张图片


RabbitMQ 管理平台地址 http://127.0.0.1:15672

默认账号:guest/guest  用户可以自己创建新的账号

Virtual Hosts:

像mysql有数据库的概念并且可以指定用户对库和表等操作的权限。那RabbitMQ呢?RabbitMQ也有类似的权限管理。在RabbitMQ中可以虚拟消息服务器VirtualHost,每个VirtualHost相当月一个相对独立的RabbitMQ服务器,每个VirtualHost之间是相互隔离的。exchange、queue、message不能互通。

rabbitMQ的五种队列

                    1.点对点(简单)的队列

                    2.工作(公平性)队列模式

                    3.发布订阅模式(fanout)

                    4.路由模式Routing

                     5.通配符模式Topics


rabbitmq 关键名词

AMQP(高级消息队列协议)是一个异步消息传递所使用应用层协议规范,为面向消息中间件设计,基于此协议的客户端与消息中间件可以无视消息来源传递消息,不受客户端、消息中间件、不同的开发语言环境等条件的限制;

  涉及概念解释: 

 Server(Broker):接收客户端连接,实现AMQP协议的消息队列和路由功能的进程;

 Virtual Host:虚拟主机的概念,类似权限控制组,一个Virtual Host里可以有多个Exchange和Queue。   

 Exchange:交换机,接收生产者发送的消息,并根据Routing Key将消息路由到服务器中的队列Queue。

 ExchangeType:交换机类型决定了路由消息行为,RabbitMQ中有三种类型Exchange,分别是fanout、direct、topic;

 Message Queue:消息队列,用于存储还未被消费者消费的消息;

 Message:由Header和body组成,Header是由生产者添加的各种属性的集合,包括Message是否被持久化、优先级是多少、由哪个Message Queue接收等;body是真正需要发送的数据内容;

BindingKey:绑定关键字,将一个特定的Exchange和一个特定的Queue绑定起来。

RabbitMQ交换机的作用


rabbitMQ浅到深_第3张图片

RabbitMQ的公平转发

 目前消息转发机制是平均分配,这样就会出现俩个消费者,奇数的任务很耗时,偶数的任何工作量很小,造成的原因就是近当消息到达队列进行转发消息。并不在乎有多少任务消费者并未传递一个应答给RabbitMQ。仅仅盲目转发所有的奇数给一个消费者,偶数给另一个消费者。

  为了解决这样的问题,我们可以使用basicQos方法,传递参数为prefetchCount= 1。这样告诉RabbitMQ不要在同一时间给一个消费者超过一条消息。

  换句话说,只有在消费者空闲的时候会发送下一条信息。调度分发消息的方式,也就是告诉RabbitMQ每次只给消费者处理一条消息,也就是等待消费者处理完毕并自己对刚刚处理的消息进行确认之后,才发送下一条消息,防止消费者太过于忙碌,也防止它太过去清闲。

通过 设置channel.basicQos(1);

但是需要手动签收才可以

RabbitMQ的应答模式

案例:

生产者端代码不变,消费者端代码这部分就是用于开启手动应答模式的。

channel.basicConsume(QUEUE_NAME, false, defaultConsumer);

注:第二个参数值为false代表关闭RabbitMQ的自动应答机制,改为手动应答。

在处理完消息时,返回应答状态,true表示为自动应答模式。

channel.basicAck(envelope.getDeliveryTag(), false); 改为手动应答后要进入这行 告诉mq已经被消费了


发布/订阅模式Publish/Subscribe

这个可能是消息队列中最重要的队列了,其他的都是在它的基础上进行了扩展。

功能实现:一个生产者发送消息,多个消费者获取消息(同样的消息),包括一个生产者,一个交换机,多个队列,多个消费者。


rabbitMQ浅到深_第4张图片

 思路解读(重点理解): 

(1)一个生产者,多个消费者

(2)每一个消费者都有自己的一个队列                                       

(3)生产者没有直接发消息到队列中,而是发送到交换机

(4)每个消费者的队列都绑定到交换机上

(5)消息通过交换机到达每个消费者的队列

该模式就是Fanout Exchange(扇型交换机)将消息路由给绑定到它身上的所有队列

以用户发邮件案例讲解

注意:交换机没有存储消息功能,如果消息发送到没有绑定消费队列的交换机,消息则丢失。

路由模式RoutingKey

生产者发送消息到交换机并指定一个路由key,消费者队列绑定到交换机时要制定路由key(key匹配就能接受消息,key不匹配就不能接受消息)


rabbitMQ浅到深_第5张图片

例如:我们可以把路由key设置为insert ,那么消费者队列key指定包含insert才可以接收消息,消费者队列key定义为update或者delete就不能接收消息。很好的控制了更新,插入和删除的操作。

采用交换机direct模式

通配符模式Topics

说明:此模式实在路由key模式的基础上,使用了通配符来管理消费者接收消息。生产者P发送消息到交换机X,type=topic,交换机根据绑定队列的routing key的值进行通配符匹配;

符号#:匹配一个或者多个词lazy.# 可以匹配lazy.irs或者lazy.irs.cor

符号*:只能匹配一个词lazy.* 可以匹配lazy.irs或者lazy.cor


rabbitMQ浅到深_第6张图片


RabbitMQ消息确认机制

问题产生背景:

  生产者发送消息出去之后,不知道到底有没有发送到RabbitMQ服务器, 默认是不知道的。而且有的时候我们在发送消息之后,后面的逻辑出问题了,我们不想要发送之前的消息了,需要撤回该怎么做。

  解决方案:

  1.AMQP 事务机制

  2.Confirm 模式

事务模式:

txSelect  将当前channel设置为transaction模式

txCommit  提交当前事务

txRollback  事务回滚


SpringBoot整合RabbitMQ


生产


application.yml

spring:

  rabbitmq:

####连接地址

    host: 127.0.0.1

####端口号   

    port: 5672

####账号

    username: guest

####密码  

    password: guest

###地址

    virtual-host: /


交换机绑定队列


@Component

public class FanoutConfig {


//邮件队列

private String FANOUT_EMAIL_QUEUE = "fanout_eamil_queue";


//短信队列

private String FANOUT_SMS_QUEUE = "fanout_sms_queue";

//短信队列

private String EXCHANGE_NAME = "fanoutExchange";


// 1.定义队列邮件

@Bean

public Queue fanOutEamilQueue() {

return new Queue(FANOUT_EMAIL_QUEUE);

}


@Bean

public Queue fanOutSmsQueue() {

return new Queue(FANOUT_SMS_QUEUE);

}


// 2.定义交换机

@Bean

FanoutExchange fanoutExchange() {

return new FanoutExchange(EXCHANGE_NAME);

}


// 3.队列与交换机绑定邮件队列

@Bean

Binding bindingExchangeEamil(Queue fanOutEamilQueue, FanoutExchange fanoutExchange) {

return BindingBuilder.bind(fanOutEamilQueue).to(fanoutExchange);

}


// 4.队列与交换机绑定短信队列

@Bean

Binding bindingExchangeSms(Queue fanOutSmsQueue, FanoutExchange fanoutExchange) {

return BindingBuilder.bind(fanOutSmsQueue).to(fanoutExchange);

}

}



生产者投递消息

@Component

public class FanoutProducer {

@Autowired

private AmqpTemplate amqpTemplate;


public void send(String queueName) {

String msg = "my_fanout_msg:" + new Date();

System.out.println(msg + ":" + msg);

amqpTemplate.convertAndSend(queueName, msg);

}

}


控制层调用代码

@RestController

public class ProducerController {

@Autowired

private FanoutProducer fanoutProducer;


@RequestMapping("/sendFanout")

public String sendFanout(String queueName) {

fanoutProducer.send(queueName);

return "success";

}

}



消费


邮件消费者


@Component

@RabbitListener(queues = "fanout_eamil_queue")

public class FanoutEamilConsumer {

@RabbitHandler

public void process(String msg) throws Exception {

System.out.println("邮件消费者获取生产者消息msg:" + msg);

}

}


短信消费者

@Component

@RabbitListener(queues = "fanout_sms_queue")

public class FanoutSmsConsumer {

@RabbitHandler

public void process(String msg) {

System.out.println("短信消费者获取生产者消息msg:" + msg);

}

}

application.yml

spring:

  rabbitmq:

  ####连接地址

    host: 127.0.0.1

   ####端口号   

    port: 5672

   ####账号

    username: guest

   ####密码  

    password: guest

   ###地址

    virtual-host: /

rabbitMQ幂等问题

产生原因:网络延迟传输中,消费出现异常或者是消费延迟消费,会造成MQ进行重试补偿,在重试过程中,可能会造成重复消费。

消费者如何保证消息幂等性,不被重复消费

解决办法:

①使用全局MessageID判断消费方使用同一个,解决幂等性。

②或者使用业务逻辑保证唯一(比如订单号码)

生产者:

请求头设置消息id(messageId)

@Component

public class FanoutProducer {

@Autowired

private AmqpTemplate amqpTemplate;


public void send(String queueName) {

String msg = "my_fanout_msg:" + System.currentTimeMillis();

Message message = MessageBuilder.withBody(msg.getBytes()).setContentType(MessageProperties.CONTENT_TYPE_JSON)

.setContentEncoding("utf-8").setMessageId(UUID.randomUUID() + "").build();

System.out.println(msg + ":" + msg);

amqpTemplate.convertAndSend(queueName, message);

}

}


什么是死信呢?什么样的消息会变成死信呢?

消息被拒绝(basic.reject或basic.nack)并且requeue=false.

消息TTL过期

队列达到最大长度(队列满了,无法再添加数据到mq中)

应用场景分析

在定义业务队列的时候,可以考虑指定一个死信交换机,并绑定一个死信队列,当消息变成死信时,该消息就会被发送到该死信队列上,这样就方便我们查看消息失败的原因了


如何使用死信交换机呢?

定义业务(普通)队列的时候指定参数

x-dead-letter-exchange: 用来设置死信后发送的交换机

x-dead-letter-routing-key:用来设置死信的routingKey


RabbitMQ解决分布式事务问题


RabbitMQ解决分布式事务原理: 采用最终一致性原理。需要保证以下三要素1、确认生产者一定要将数据投递到MQ服务器中(采用MQ消息确认机制)2、MQ消费者消息能够正确消费消息,采用手动ACK模式(注意重试幂等性问题)3、如何保证第一个事务先执行,采用补偿机制,在创建一个补单消费者进行监听,如果订单没有创建成功,进行补单。

你可能感兴趣的:(rabbitMQ浅到深)