RabbitMQ简明教程

介绍:

实现了AMQP协议,开源免费。保存消息的一个容器,用于两个系统之间消息传递。Erlang编写的。
producer生产消息-broker服务器(交换机和通道)-consumer:消费消息。
在rabbitmq中有两种消息处理的模式,一种是模式/订阅模式/投递模式(也叫push模式),消费者调用channel.basicConsume方法订阅队列后,由RabbitMQ主动将消息推送给订阅队列的消费者;另一种是拉模式/检索模式(也叫pull模式),需要消费者调用channel.basicGet方法,主动从指定队列中拉取消息

1:不能在循环中使用拉模式来模拟推模式,因为拉模式每次都需要去消息中间件中拉取消息来消费,所以会严重影响RabbitMQ性能。
2:要想实现高吞吐量,消费者需要使用推模式。特点:异步处理,解耦(生产者,消费者可使用不同语言),流量消峰,日志处理

场景:
  • 下单给MQ发消息-》加积分-》发红包-》发手机短信:
  • 系统解耦:多个系统之间交互,业务流转
  • 流量削峰:高负载流量写入MQ
  • ELK 日志处理解决方案

loger.error(…) -->logstash收集消息–> 发送消息的kafka --> elastic search

工作模型:

1.png
broker 相当于mysql服务器,virtual host相当于数据库(可以有多个数据库)queue相当于表,消息相当于记录。
消息队列有三个核心要素: 消息生产者消息队列消息消费者
生产者(Producer):发送消息的应用;(java程序,也可能是别的语言写的程序)
消费者(Consumer):接收消息的应用;(java程序,也可能是别的语言写的程序)
代理(Broker):就是消息服务器,RabbitMQ Server就是Message Broker;
连接(Connection):连接RabbitMQ服务器的TCP长连接;
信道(Channel):连接中的一个虚拟通道,消息队列发送或者接收消息时,都是通过信道进行的;
虚拟主机(Virtual host):一个虚拟分组,在代码中就是一个字符串,当多个不同的用户使用同一个RabbitMQ服务时,可以划分出多个Virtual host,每个用户在自己的Virtual host创建exchange/queue等;(分类比较清晰、相互隔离)
交换机(Exchange):交换机负责从生产者接收消息,并根据交换机类型分发到对应的消息队列中,起到一个路由的作用;
路由键(Routing Key):交换机根据路由键来决定消息分发到哪个队列,路由键是消息的目的地址;
绑定(Binding):绑定是队列和交换机的一个关联连接(关联关系);
队列(Queue):存储消息的缓存;
消息(Message):由生产者通过RabbitMQ发送给消费者的信息;(消息可以任何数据,字符串、user对象,json串等等)

交换机类型:

扇形交换机(Fanout Exchange):不要匹配,不需要绑定,往交换机中发数据,绑定队列都会接收到(广播)
直连交换机(Direct Exchange):精准匹配,需要交换机和队列绑定key
主题交换机(Topic Exchange):通配符匹配,相当于模糊匹配,#零个多个单词*一个多个单词 .用于匹配一个单词
**头部交换机(Headers Exchange):**headers进行匹配(type,status)一致进行匹配

死信队列,又叫死信交换机

消息死信设置TTL:

设置消息的过期时间,过期消息又叫死信消息
两种方式,

  1. 单条消息的TTL设置
  2. 设置队列的TTL设置

两者都设置以最小的为准。

死信情况:

死信的情况:

  1. 超出队列过期时间死信:设置队列过期时间:arg.put(“x-message-ttl”,10000);
  2. 超出消息过期时间死信:设置消息过期时间messageProperties.setExpiration(“10000”);
  3. 超出队列长度死信:设置队列容量arg.put(“x-max-length”, 5);
  4. 接收者拒绝了消息(订单情况下,不能自动确认,需要判断):开启手动确认配置。
  5. 接收者拒绝消息不放入原来队列。
listener:
  simple:
    acknowledge-mode: manual

队列长度变成最长的时候会变成死信。过期的信息也会变成死信。
队列长度使用参数:(x-max-length-bytes).先发的会先死信,FIFO。

队列过期:

9b288d5c-8058-4f48-aa63-333ebf3a8d95.png
死去的信息会进入死信交换机,死信交换机的就是死信队列。4队列没有消费者,定时过后进入死信交换机。可以用来做定时支付。让订单进入死信队列,过期时候取出来判断是否已经支付。从数据库中看已支付不处理,未支付直接取消订单,让别人购买。

消息过期:

就是通过设置消息的过期时间,本质上没有什么差别。

@Component
@Slf4j
public class ReceiveMessage {
    // 接收队列消息
    @RabbitListener(queues = {"queue.normal.4"})
    public void receiveMessage(Message message, Channel channel) throws IOException {
        MessageProperties messageProperties = message.getMessageProperties();
        long deliveryTag = messageProperties.getDeliveryTag();

        try {
            byte[] body = message.getBody();
            String msg = new String(body);
            System.out.println(msg);
            log.info("接收到的消息:{}",msg);
            int a=1/0;
            // false只却让当前消息,true表示全部进行确认
            channel.basicAck(deliveryTag,false);
        }catch (Exception e){
            log.error("接收者出现问题{}",e.getMessage());
            // 参数2表示只确认当前信息,参数3=true表示重新入队。这个不会进入死信队列:
//            channel.basicNack(deliveryTag,false,true);
            // 进入死信队列的代码,这样就会死信。
//            channel.basicNack(deliveryTag,false,false);
            // 拒绝消息,参数2表示不能重复入队。这个函数不能处理多个参数。只能处理一条消息。
            channel.basicReject(deliveryTag,false);
            throw new RuntimeException(e);
        }
    }
}

实现延迟队列:

通过队列进行延迟。
实现方案:

开启定时任务:每几秒种进行遍历处理
被动取消:用户请求来的是否进行判断
JDK延迟队列:DelayedQueue,实现简单,重启会丢失。只能的单机版。容易OOM。
消息中间件进行处理:本身不能实现,可以通过死信队列进行实现。

可以将上面的交换机进行合并为一个,通过不同的key进行转发路由标识。

延迟队列消息不一致问题:

对头消息过期时间长对后面消息过期时间的影响。
** 处理办法:**
将相同时间的放到同一个队列,这样就能够处理。也就是不同时间不同队列。

解决框架:rabbitmq-delayed-message-exchange:延迟框架。

这样会多一种交换机:
image.png
消息发送后不会直接投递到队列,
而是存储到 Mnesia(嵌入式数据库),检查 x-delay 时间(消息头部);
延迟插件在 RabbitMQ 3.5.7 及以上的版本才支持,依赖 Erlang/OPT 18.0 及以上运行环境;
Mnesia 是一个小型数据库,不适合于大量延迟消息的实现
解决了消息过期时间不一致出现的问题。
使用:创建自定义的交换机,将过期时间写在消息头部中。

@Component
@Slf4j
public class RabbitConfig {
    public static final String EXCHANGE = "exchange:plugin";
    public static final String QUEUE = "queue.plugin";
    public static final String KEY = "plugin";
    
    @Bean
    public CustomExchange customExchange() {
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-delayed-type", "direct");
        // CustomExchange(String name, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments)
        return new CustomExchange(EXCHANGE, "x-delayed-message", true, false, arguments);
    }

    @Bean
    public Queue queue() {
        return QueueBuilder.durable(QUEUE).build();
    }

    @Bean
    public Binding binding(CustomExchange customExchange, Queue queue) {
        return BindingBuilder.bind(queue).to(customExchange).with(KEY).noargs();
    }
}

Confirm和Return:

消息可靠性:

  1. 保证生产者发送给交换机(交换机要指定对),
  2. ** 保证交换机路由给队列(key要对)。**
  3. 将队列消息持久化,防止掉电消失
  4. 消费者开启手动确认,不能让队列自动确认。

发送者确认模式:

  1. 配置文件application.yml 开启确认模式:spring.rabbitmq.publisher-confirm-type=correlated
  2. 写一个类实现implements RabbitTemplate.ConfirmCallback,判断成功和失败的ack结果,可以根据具体的结果,如果ack为false,对消息进行重新发送或记录日志等处理;设置rabbitTemplate的确认回调方法
  3. rabbitTemplate.setConfirmCallback(messageConfirmCallBack);

具体实现ConfirmCallback可以有多种方式:

  1. 编写一个具体实现类MyConfirmCallback
  2. 在service中实现ConfirmCallback
  3. 由于是setConfirmCallback()可以编写匿名内部类
  4. 由于是setConfirmCallback可以编写lambda表达式。因为是函数表达式

保证交换机到达队列:

rabbitmq 整个消息投递的路径为:
producer —> exchange —> queue —> consumer

消息从 producer 到 exchange 则会返回一个 confirmCallback;
消息从 exchange –> queue 投递失败则会返回一个 returnCallback;
区别在于:改方法只有失败才会回调。
使用方法:

  • spring.rabbitmq.publisher-returns: true
  • 实现接口:
@Component
public class MessageReturnCallBack implements RabbitTemplate.ReturnsCallback {

    /**
     * 当消息从交换机 没有正确地 到达队列,则会触发该方法
     * 如果消息从交换机 正确地 到达队列了,那么就不会触发该方法
     *
     * @param returned
     */
    @Override
    public void returnedMessage(ReturnedMessage returned) {
        System.out.println("消息return模式:" + returned);
    }
}

  • 设置代码:
@Service
public class MessageService {
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private MessageReturnCallBack messageReturnCallBack;
    @PostConstruct //bean在初始化的时候,会调用一次该方法,只调用一次,起到初始化的作用
    public void init() {
        rabbitTemplate.setReturnsCallback(messageReturnCallBack);
    }
}

交换机属性:

image.png

  • 默认就是持久化,想要不持久化,设置.durable(false)为不持久化。
  • 自动删除,当没有队列和交换机绑定后,会删除交换机。
  • 设置Internal这样不会接收客户机的消息,只会获取交换机间信息
  • 设置备用交换机。当不能进行匹配路由就会进入备用交换机。(常用来判断代码是否写错,因为正常备用交换机不能有消息)备用交换机使用扇形交换机。使用主交换与备用交换机绑定。用.alternate(exchangeAlternateName)这样就能够绑定备用交换机。

队列属性:

image.png

Type:队列类型
Name:队列名称,就是一个字符串,随便一个字符串就可以;
Durability:声明队列是否持久化,代表队列在服务器重启后是否还存在;
Auto delete: 是否自动删除,如果为true,当没有消费者连接到这个队列的时候,队列会自动删除;
Exclusive:exclusive属性的队列只对首次声明它的连接可见,并且在连接断开时自动删除;
基本上不设置它,设置成false
Arguments:队列的其他属性,例如指定DLX(死信交换机等);
1、x-expires:Number
当Queue(队列)在指定的时间未被访问,则队列将被自动删除;
2、x-message-ttl:Number
发布的消息在队列中存在多长时间后被取消(单位毫秒);
3、x-overflow:String
设置队列溢出行为,当达到队列的最大长度时,消息会发生什么,有效值为Drop Head或Reject Publish;
4、x-max-length:Number
队列所能容下消息的最大长度,当超出长度后,新消息将会覆盖最前面的消息,类似于Redis的LRU算法;

5、 x-single-active-consumer:默认为false
激活单一的消费者,也就是该队列只能有一个消息者消费消息;
6、x-max-length-bytes:Number
限定队列的最大占用空间,当超出后也使用类似于Redis的LRU算法;
7、x-dead-letter-exchange:String
指定队列关联的死信交换机,有时候我们希望当队列的消息达到上限后溢出的消息不会被删除掉,而是走到另一个队列中保存起来;
8.x-dead-letter-routing-key:String
指定死信交换机的路由键,一般和6一起定义;
9.x-max-priority:Number
如果将一个队列加上优先级参数,那么该队列为优先级队列;
(1)、给队列加上优先级参数使其成为优先级队列
x-max-priority=10【0-255取值范围】
(2)、给消息加上优先级属性
通过优先级特性,将一个队列实现插队消费;
MessageProperties messageProperties=new MessageProperties();
messageProperties.setPriority(8);
10、x-queue-mode:String(理解下即可)
队列类型x-queue-mode=lazy懒队列,在磁盘上尽可能多地保留消息以减少RAM使用,如果未设置,则队列将保留内存缓存以尽可能快地传递消息;
11、x-queue-master-locator:String(用的较少,不讲)
在集群模式下设置队列分配到的主节点位置信息;
每个queue都有一个master节点,所有对于queue的操作都是事先在master上完成,之后再slave上进行相同的操作;
每个不同的queue可以坐落在不同的集群节点上,这些queue如果配置了镜像队列,那么会有1个master和多个slave。
基本上所有的操作都落在master上,那么如果这些queues的master都落在个别的服务节点上,而其他的节点又很空闲,这样就无法做到负载均衡,那么势必会影响性能;
关于master queue host 的分配有几种策略,可以在queue声明的时候使用x-queue-master-locator参数,或者在policy上设置queue-master-locator,或者直接在rabbitmq的配置文件中定义queue_master_locator,有三种可供选择的策略:
(1)min-masters:选择master queue数最少的那个服务节点host;
(2)client-local:选择与client相连接的那个服务节点host;
(3)random:随机分配;

消息可靠性:

消息的可靠性投递就是要保证消息投递过程中每一个环节都要成功,那么这肯定会牺牲一些性能,性能与可靠性是无法兼得的;2.png
如果业务实时一致性要求不是特别高的场景,可以牺牲一些可靠性来换取性能。
① 代表消息从生产者发送到Exchange; (confirm模式)(持久化)
② 代表消息从Exchange路由到Queue;** (return模式)(备用交换机)**
③ 代表消息在Queue中存储;(持久化)
④ 代表消费者监听Queue并消费消息;()
做集群:

幂等性:

如何避免消息的重复消费问题?(消息消费时的幂等性)
幂等性是:对于一个资源,不管你请求一次还是请求多次,对该资源本身造成的影响应该是相同的,不能因为重复的请求而对该资源重复造成影响;
消息在队列中,消费者消费请求返回后队列的指针才会前进,然而请求返回过程中请求由于网络延迟,同一个消息又给其他消费者消费了。RabbitMQ没有提供解决方法怎么解决?需要由消费者自行控制。rocketMQ会有一个messageID,但是kafka和rabbitMQ没有。我们最好是自己生成一个全局唯一的ID。下面是一些方式。

  • 使用全局ID和Redis做幂等性:
  • 使用redis通过key来进行幂等性。

全局唯一ID + Redis
生产者在发送消息时,为每条消息设置一个全局唯一的messageId,消费者拿到消息后,使用setnx命令,将messageId作为key放到redis中:setnx(messageId, 1),若返回1,说明之前没有消费过,正常消费;若返回0,说明这条消息之前已消费过,抛弃。

全局ID生成:

ID需要满足几个特性

  • 全局唯一
  • 趋势递增
  • 信息安全
  • 包含时间戳

生成系统的可用性要求:

  • 高可用
  • 低延迟
  • 高QPS

常见的ID方案:

  • UUID,特点无序
  • 数据库自增ID。信息安全
  • redis的incr,incrby生成步长。维护比较麻烦
  • Zookeeper。
  • 使用雪花算法

雪花算法:41位
第—个部分,是1个bit: o,这个是无意义的。
第二个部分是41个bit:表示的是时间戳。
第三个部分是5个bit:表示的是机房id,10001。
第四个部分是5个bit:表示的是机器id, 1 1001。
第五个部分是12个bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的id 的序号,000000000000.
然后再转为Long就可以了。

集群:

一个rabbitmq容易出现单点故障。
元数据:表命,字段名等就是元数据。

默认集群模式:

默认集群模式也叫 普通集群模式、或者 内置集群模式;
RabbitMQ默认集群模式,只会把交换机、队列、虚拟主机等元数据信息在各个节点同步,而具体队列中的消息内容不会在各个节点中同步;
image.png

镜像模式:

都是通过复制所有进行的。
安装erlang:

默认模式:

优势:节约内存磁盘,
劣势:存储结点死了,所有都死了。

镜像模式:

配置命令:/rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
-p Vhost: 可选参数,针对指定vhost下的queue进行设置;
Name: policy的名称;(可以自己取个名字就可以)
Pattern: queue的匹配模式(正则表达式);
Definition:镜像定义,包括三个部分ha-mode, ha-params, ha-sync-mode;(json格式)
{“ha-mode”:”exactly”,”ha-params”:2}
ha-mode:指明镜像队列的模式,有效值为 all/exactly/nodes
all:表示在集群中所有的节点上进行镜像
exactly:表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
nodes:表示在指定的节点上进行镜像,节点名称通过ha-params指定
ha-params:ha-mode模式需要用到的参数
ha-sync-mode:进行队列中消息的同步方式,有效值为automatic和manual
priority:可选参数,policy的优先级;
比如想配置所有名字开头为policy_的队列进行镜像,镜像数量为2,那么命令如下(在任意节点执行如下命令):
./rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
./rabbitmqctl set_policy -p powernode ha_policy “^policy_” ‘{“ha-mode”:“exactly”,“ha-params”:2,“ha-sync-mode”:“automatic”}’

如果要在所有节点所有队列上进行镜像,则(在任意节点执行如下命令):
所有节点、所有虚拟主机、所有队列 都进行镜像
./rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
./rabbitmqctl set_policy ha-all “^” ‘{“ha-mode”:“all”}’
针对某个虚拟主机进行镜像
rabbitmqctl set_policy -p powernode ha-all “^” ‘{“ha-mode”:“all”,“ha-sync-mode”:“automatic”}’

优势:高可用,预防宕机
劣势:占用了内存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值