RabbitMQ学习

为什么要使用MQ

  1. 异步通信
  2. 系统解耦
  3. 流量削峰

工作模型

  1. Broker

  按照RabbitMQ的服务器,代理/中介,帮主我们存储、转发消息。

  1. Connection

  生产者发送消息,消费者接受消息,都必须要跟Broker建立一个TCP长连接。

  1. Channel

  虚拟连接,Channel是RabbitMQ原生API里最重要的编程接口,我们定义交换机、队列、绑定管理,消息发送,消费都在这上面。

  1. Queue

  队列用来存储消息,独立的进程拥有自己的数据库,消费者有两种模式获取消息,一种是Push,一种是Pull

  1. Exchange

  交换机是一个绑定列表,用来查找匹配的绑定关系,队列使用绑定键跟交换机建立绑定关系。生产者发送的消息需要携带路由键,交换机根据绑定关系,路由到指定的队列上。

  1. Vhose

  RabbitMQ提供了虚拟主机的概念,提高硬件资源利用率,实现资源的隔离和权限的控制。

路由方式

直连-精确指定绑定键

channel.basicPublish("MY_DIRECT_EXCHANGE","spring","msg");

直接路由到绑定关系为spring的队列上

主题

使用通配符#(0或多个单词)和*(一个单词)

广播Fanout

不指定绑定键,所有绑定的队列都会收到消息

基本使用

安装

RabbitMQ基于Erlang语言编写,需要先安装Erlang

JavaAPI

消费者

        private final String EXCHANGE_NAME = "SIMPLE_EXCHANGE";
        private final String QUEUE_NAME = "SIMPLE_QUEUE";
        
        ConnectionFactory factory = new ConnectionFactory();
        // 连接IP
        factory.setHost("127.0.0.1");
        // 默认监听端口
        factory.setPort(5672);
        // 虚拟机
        factory.setVirtualHost("/");

        // 设置访问的用户
        factory.setUsername("guest");
        factory.setPassword("guest");
        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        // 声明交换机
        // String exchange, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments
        channel.exchangeDeclare(EXCHANGE_NAME,"direct",false, false, null);

        // 声明队列
        // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" Waiting for message....");

        // 绑定队列和交换机
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"best");

        // 创建消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("Received message : '" + msg + "'");
                System.out.println("consumerTag : " + consumerTag );
                System.out.println("deliveryTag : " + envelope.getDeliveryTag() );
            }
        };

        // 开始获取消息
        // String queue, boolean autoAck, Consumer callback
        channel.basicConsume(QUEUE_NAME, true, consumer);

生产者

        private final String EXCHANGE_NAME = "SIMPLE_EXCHANGE";
        ConnectionFactory factory = new ConnectionFactory();
        // 连接IP
        factory.setHost("127.0.0.1");
        // 连接端口
        factory.setPort(5672);
        // 虚拟机
        factory.setVirtualHost("/");
        // 用户
        factory.setUsername("guest");
        factory.setPassword("guest");

        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        // 发送消息
        String msg = "Hello world, Rabbit MQ";

        // String exchange, String routingKey, BasicProperties props, byte[] body
        channel.basicPublish(EXCHANGE_NAME, "best", null, msg.getBytes());

        channel.close();
        conn.close();

参数详解

  1. 交换机
// String exchange, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments
channel.exchangeDeclare(EXCHANGE_NAME,"direct",false, false, null);

交换机类型包括:direct、topic、fanout

durable:是否持久化,表示服务器重启后是否还存在

  1. 队列参数
// String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
channel.queueDeclare(QUEUE_NAME, false, false, false, null);

durable:是否持久化,表示服务器重启后是否还存在

exclustive:是否排他性队列,只有在声明它的Connection中,连接断开时自动删除

autoDelete:是否自动删除,消费者断开后,自动删除

arguments:其他属性

属性

含义

x-message-ttl

队列中消息的存活时间,单位毫秒

x-expires

多久没有消费者访问后会被删除

x-max-length

队列最大消息数

x-max-length-bytes

队列最大容量,单位Byte

x-dead-letter-exchange

死信交换机

x-dead-letter-routing-key

死信交换机路由键

x-max-priority

队列中消息的最大优先级,消息的优先级不能超过它

UI界面

windows

rabbitmq-plugins.bat enable rabbitmq_management

linux

./rabbitmq-plugins enable rabbitmq_management

默认端口15672,默认用户guest,密码guest,远程访问需创建其他用户。

创建用户命令

用户名密码:admin admin

rabbitmqctl add_user admin admin
rabbitmqctl set_user_tags admin administrator
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

RabbitMQ进阶

TTL

消息过期时间

  1. 通过队列属性设置过期时间

所有队列中的消息超过时间未被消费都会过期

Map<String,Object> map = new HashMap<String,Object>();
map.put("x-message-ttl",1000);//1秒后队列未被消费的消息过期
new Queue("TTL_QUEUE",true,false,false,map);
  1. 设置单条过期时间

发送消息的时候指定消息属性

MessageProperties messageProperties = new MessageProperties();
messageProperties.setExpiration("4000");//4秒后过期
Message message = new Message("ddd".getBytes(),messageProperties);
rabbitTemplate.send("TTL","ttl",message);

消息队列和消息的过期时间按照小的生效

死信队列

当发生以下情况后,消息会进入死信队列:

  1. 消息被消费者拒绝并且未设置重回队列
  2. 消息过期
  3. 队列达到最大长度,超过Max length或者Max length bytes,最先入队的会被送到DLX

创建死信队列

 @Bean("oriUseExchange")
    public DirectExchange exchange() {

        return new DirectExchange("ORI_USE_EXCHANGE", true, false, new HashMap<>());
    }

    @Bean("oriUseQueue")
    public Queue queue() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("x-message-ttl", 10000); // 10秒钟后成为死信
        map.put("x-dead-letter-exchange", "DEAD_LETTER_EXCHANGE"); // 队列中的消息变成死信后,进入死信交换机
        return new Queue("ORI_USE_QUEUE", true, false, false, map);
    }

    @Bean
    public Binding binding(@Qualifier("oriUseQueue") Queue queue,@Qualifier("oriUseExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("ori.use");
    }
    
        @Bean
    public Binding binding(@Qualifier("oriUseQueue") Queue queue,@Qualifier("oriUseExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("gupao.ori.use");
    }

    /**
     * 队列的死信交换机
     * @return
     */
    @Bean("deatLetterExchange")
    public TopicExchange deadLetterExchange() {
        return new TopicExchange("GP_DEAD_LETTER_EXCHANGE", true, false, new HashMap<>());
    }

    @Bean("deatLetterQueue")
    public Queue deadLetterQueue() {
        return new Queue("GP_DEAD_LETTER_QUEUE", true, false, false, new HashMap<>());
    }

    @Bean
    public Binding bindingDead(@Qualifier("deatLetterQueue") Queue queue,@Qualifier("deatLetterExchange") TopicExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("#"); // 无条件路由
    }

消息流转图

延迟队列

TTL+DLX实现

利用超时时间和死信队列实现延迟队列方式。

缺点:

  • 易造成阻塞,前一条未消费,后面的无法投递(第一条30min到期,第二条10min到期,第一条未出队,导致第二条无法出队)
  • 延迟梯度多需要大量的交换机和队列
  • 存在时间误差

基于延迟队列插件

RabbitMQ3.5.7之后添加了rabbitmq-delayed-message-exchange来实现延迟队列功能。依赖Erlang/OPT18.0及以上版本

消费端限流

可以基于Consumer或者channel设置prefetch count,即当超过这个数值未被确认,则停止投递新消息给消费者。

channel.basicQos(2);//超过2条没有ACK,则不接受队列消息
channel.basicConsume("name",false,consumer);

Spring AMQP

依赖

<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
    <version>1.3.5.RELEASE</version>
</dependency>

核心组件

ConnectionFactory

用于创建连接,CachingConnectionFactory是其一个实现。

RabbitAdmin

AmqpAdmin的实现,封装了RabbitMQ的基本管理操作,RabbitAdmin实现了InitializingBean接口。
如:

//声明交换机
rabbitAdmin.declareExchange(new DirectExchange("exchange",false,false));
//声明队列
rabbitAdmin.declareQueue(new Queue("queue",false,false));
//绑定
rabbitAdmin.declareBinding(new Binding("queue",Binding.DestinationType.QUEUE,"exchange","admin",null));

Message

消息的封装

RabbitTemplate

AmqpTemplate的实现,用来简化消息的收发,支持消息的确认与返回。

MessageListener消息侦听

用来处理推送过来的消息

MessageConvertor消息转换器

消息在网络中是byte[]数组,处理Message的消息体body对象。

如果消息已经是byte[]格式,就不需要转换。

如果是String会转换成byte[]。

如果是对象,则使用JDK序列化为byte[]。

在RabbitTemplate的convertAndSend()方法会使用MessageConvertor序列化,默认是SimpleMessageConvert。

可靠性投递

消息发送到Broker

RabbitMQ中提供了两种机制,事务模式和确认模式,来确认消息投递成功。

  • Transaction事务模式

通过设置channel.txSelect()将信道设置成事务模式,然后就可以发布消息给RabbitMQ了,如果channel.txCommit()调用成功,说明事务提交成功。否则通过channel.txRollback()回滚。缺点是会阻塞。

rabbitTemplate.setChannelTransacted(true);//springboot设置
  • Confirm确认模式
    • 普通确认模式
      调用channel.confirmSelect()将信道设置成Confirm模式,然后发送消息。一旦投递到队列,RabbitMQ就会发送一个确认给生产者,即调用channel.waitForConfirms()返回true。
    • 批量确认
      channel.waitForConfirmsOrDie();没有抛异常则表示服务端接受成功。缺点:批量确认的数量无法控制。
    • 异步确认模式
      添加Confirmistener,并用SortedSet来维护没有被确认的消息

rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback(){
    public void confirm(CorrelationData correlationData,boolean ack,String cause){
        if(!ack){
            System.out.println("发送消息失败");
            throw new RuntimeException("异常:"+cause);
        }
    }
})

交换机到队列

路由键设置错误,或队列不存在,则消息会发送失败,此时可以选择让服务端重发给生产者或路由到另一个备份的交换机。

消息回发

rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback(){
    public void returnedMessage(Message message,int replyCode,String replyText,String exchange,Strnig routingKey){
        
    }
})

备份的路由

Map<String,Object> arguments = new HashMap<String,Object>();
arguments.put("alternate-exchange","BACK_ECCHANGE");
channel.exchangeDeclare("EXCHANGE","topic",false,false,false,arguments);

消息在队列存储

消息如果没有被消费则一直存储在队列的数据库中。如果服务器发生故障,可能导致内存数据丢失。

  • 队列持久化
  • 交换机持久化
  • 消息持久化
  • 集群

投递到消费者

RabbitMQ提供了消费者的消息确认机制,消费者可以自动或手动发送ACK给服务端。如果没有收到消费者ACK,消费者断开连接后,RabbitMQ会发送给其他消费者,如果没有其他消费者,消费者重启后会重新消费这条消息。

消费者在订阅队列时,可以指定autoAck参数,如果autoAck=false,则会显示的等待回复确认信号后才从队列中移除。

消费者回调

  • 调用生产者API
  • 发送响应消息给生产者

补偿机制

生产者与消费者之间约定一个超时时间,对于超出这个超时时间没有得到响应的,可以设置一个重发机制,同时需要控制重发时间和次数。

消息幂等

为了避免相同消息的重复处理,因此必须采取一定的措施。RabbitMQ服务端没有这种控制,因此只能在消费端控制。对于重复发送的消息通过日志或状态来做重复控制。

集群

节点类型

RabbitMQ支持两种节点类型,一种是磁盘节点,一种是内存节点。

磁盘节点:将元数据存在磁盘上。默认节点

内存节点:将元数据存在内存上。

内存节点将地址存放在磁盘上。如果是持久化消息,会同时存在磁盘和内存。且集群中至少存在一个磁盘节点

集群通过25672端口两两通信。

普通集群

普通集群模式下,不同的节点只会互相同步元数据。因此普通模式无法保证高可用

镜像集群

与普通模式不同的是镜像模式会同步消息,但整体性能降低。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苜蓿花乐园

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值