Springboot 使用RabbitMQ

MQ(Message Queue简称MQ)作为Linux进程间八种(管道、命名管道、内存映射、消息队列、共享内存、信号量、信号、套接字)通信方式之一,它基于AMQP(Advanced Message Queuing Protocol)协议,实现了相互隔离的进程间的信息通信。主流的MQ框架有RabbitMQ、ActiveMQ、ZeroMq、kafka,以及阿里开源的RocketMQ,其中使用最为广泛的当属RabbitMQ了。

1. RabbitMQ介绍

Springboot 使用RabbitMQ_第1张图片

RabbitMQ使用Erlang(面向并发的一门编程)语言开发

2.环境搭建

RabbitMQ的安装需要依赖Erlang,所以需要先安装Erlang环境

RabbitMQ 3.7.7 windows安装文件https://pan.baidu.com/s/1KIceNVkAWU9NPNiEYGsqXw 提取码:xwg9

参照文档安装即可

Linux 版本和MacOX版本安装文件在官网下载安装即可,切记Erlang的版本要和RabbitMQ版本对应

3. 5种队列模式

Springboot 使用RabbitMQ_第2张图片

总的来说,RabbitMQ队列模式分为两大类,使用交换机与不使用交换机两大类;其中不使用交换机的有两种,一对一与一对多,使用交换机的有4种,Direct,Fanout,Topic,Headers;使用交换机时,生产者的消息不直接发送到消费者队列,而是先发送到交换机,再由交换机根据绑定的路由关系路由到消费者监听的队列;此外还有一种默认交换机,它实际上是一个由消息代理预先申明好的没有名字(名字为默认空字符串)的直连交换机,它使得每个新建队列(queue)都会自动绑定到默认交换机上,绑定的路由键(routing key)名称与队列名称相同。

3.RabbitMQ术语

RabbitMQ是基于AMQP协议的,所以协议规定的相关规范RabbitMQ都有实现,还做了一些自己的扩展。

AMQP模型

Springboot 使用RabbitMQ_第3张图片

Queue:生产者、消费者通过监听队列获取队列中的消息,队列在声明后才能被使用。如果一个队列不存在,声明一个队列会创建它。如果声明的队列已经存在,并且属性完全相同,那么此次声明不会对原有队列产生任何影响。如果声明中的属性与已存在队列的属性有差异,那么一个错误代码为 406 的通道级异常就会被抛出。

Exahange:交换机,不存储消息,负责将生产者生产的消息,按照绑定的路由键路由到指定的消费者队列。

RoutingKey:路由键,用来将队列和交换机进行绑定,生产者发送消息时,可以通过指定路由键让交换机将消息路由至指定的队列。

Vhost:类似于一个命名空间,Queue,Exchange都归属于Vhost,不同的Vhost中Queue,Exchange不同

4.RabbitMQ使用

RabbitMQ目前支持Java、Python、Ruby、Go、PHP、C#等多种语言,Java可单独使用RabbitMQ,也可通过Springboot集成

(1)Java使用原生RabbitMQ

引入依赖


    com.rabbitmq
    amqp-client
    5.4.3

获取连接工具类

package com.twp.spring.utils;

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class RabbitConnectionFactory {

    public static Connection getConnection(){

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("root");
        factory.setPassword("123456");
        factory.setVirtualHost("/");
        Connection connection = null;
        try {
            connection = factory.newConnection();
        }catch (Exception error){
            error.printStackTrace();
        }
        return connection;
    }
}

生产者

package com.twp.spring.producer;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.twp.spring.utils.RabbitConnectionFactory;

public class ProducerTest {

    private static final String QUEUE = "foo_queue";
    private static final String EXCHANGE = "foo_exchange";
    private static final String ROUTINGKEY = "foo_key";

    public static void main(String[] args) {
        Connection connection = RabbitConnectionFactory.getConnection();
        if(connection != null){
            try {
                //获取Channel,所有关于队列的操作都将在Channel中进行
                Channel channel = connection.createChannel();
                //声明交换机的名字和类型 DIRECT("direct"), FANOUT("fanout"), TOPIC("topic"), HEADERS("headers"),以及是否需要持久化,使用非交换机模式不需要进行交换机声明,
                channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.DIRECT,true);
                //声明队列,指定队列名,是否持久化(非持久化使用之后会自动删除),是否排他(排他只能当前连接使用),是否自动删除(生产者发送完消息后会自动删除),队列其他属性参数(Map)
                channel.queueDeclare(QUEUE,true,false,false,null);
                //将队列与交换机进行绑定
                channel.queueBind(QUEUE,EXCHANGE,ROUTINGKEY);
                String message = "Hello RabbitMQ";
                channel.basicPublish(EXCHANGE,ROUTINGKEY,null,message.getBytes());
                System.out.println("Producer 当前时间:" + System.currentTimeMillis());
                System.out.println("已发送消息:" + message);
                channel.close();
                connection.close();
            }catch (Exception error){
                error.printStackTrace();
            }
        }

    }
}

消费者

package com.twp.spring.consumer;

import com.rabbitmq.client.*;
import com.twp.spring.utils.RabbitConnectionFactory;
import java.io.IOException;

public class ConsumerTest {

    private static final String QUEUE = "foo_queue";

    public static void main(String[] args) {
        Connection connection = RabbitConnectionFactory.getConnection();
        if(connection != null){
            try {
                Channel channel = connection.createChannel();
                channel.queueDeclare(QUEUE,true,false,false,null);
                channel.basicConsume(QUEUE,true,new DefaultConsumer(channel){
                    @Override
                    public void handleDelivery(String consumerTag,
                                               Envelope envelope,
                                               AMQP.BasicProperties properties,
                                               byte[] body)
                            throws IOException {
                        System.out.println("Consumer 当前时间:" + System.currentTimeMillis());
                        System.out.println("RoutingKey:" + envelope.getRoutingKey());
                        System.out.println("Message ContentType:" + properties.getContentType());
                        System.out.println("Message Content:" + (new String(body)));
                    }
                });
            }catch (Exception error){
                error.printStackTrace();
            }

        }
    }
}

启动消费者,等待生产者发送消息

Springboot 使用RabbitMQ_第4张图片

RabbitMQ前端显示当前消费者已连接

Springboot 使用RabbitMQ_第5张图片

启动生产者发送消息

Springboot 使用RabbitMQ_第6张图片

Springboot 使用RabbitMQ_第7张图片

至此就完成了生产者生产消息,消费者消费消息,整条链路,下面来看看Springboot集成RabbitMQ

(2)Springboot对RabbitMQ已经做了专门的封装,简化开发人员对RabbitMQ的调用,springboot使用RabbitMQ极其简单,只需简单几步就能使用RabbitMQ进行消息发送,接收。

引入Springboot AMQP starter,它会帮我们自动引入RabbitMQ的相关依赖,并自动注入我们需要的相关bean


    org.springframework.boot
    spring-boot-starter-amqp

配置RabbitMQ连接信息

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: root
    password: 123456
    virtual-host: "/"

声明队列、交换机、绑定关系

package com.twp.spring.configuration;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static com.twp.spring.commons.RabbitConstants.*;

@Configuration
public class RabbitConfiguration {

    @Bean
    public Queue queue(){
        //默认持久化,非排他,不自动删除
        return new Queue(QUEUE);
    }

    @Bean
    public Exchange exchange(){
        //默认持久化,不自动删除
        return new DirectExchange(EXCHANGE);
    }

    @Bean
    public Binding binding(){
        return BindingBuilder.bind(queue()).to(exchange()).with(ROUTINGKEY).noargs();
    }
}

生产者

package com.twp.spring.producer;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import static com.twp.spring.commons.RabbitConstants.*;

@Service
public class RabbitPublisher {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMsg(String message){

        rabbitTemplate.convertAndSend(EXCHANGE,ROUTINGKEY,message);
        System.out.println("Send Success,message:" + message);
    }
}

消费者

package com.twp.spring.consumer;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.util.Map;
import static com.twp.spring.commons.RabbitConstants.*;

@Component
@RabbitListener(queues = QUEUE)
public class RabbitConsumer {

    /**
     * @Payload 接收消息体(消息中的body内容)
     * @Headers 接收消息头所有内容(Map),@Header接收单个消息头
     * @RabbitHandler 指定监听队列的处理方法
     * @RabbitListener(bindings = @QueueBinding(
     *              exchange = @Exchange(value="fant_exchange",type="fanout"),
     *              value = @Queue(value="foo.foo",durable="true"),
     *              key = "fant")
     *      )指明绑定关系,交换机等
     */
    @RabbitHandler
    public void handle(@Payload String message, @Headers Map headers){
        System.out.println("Receive Message: " + message);
    }
}

应用启动之后,通过自定义的http请求接口触发消息发送,监听队列的消费者就会实时消费队列中的消息

5.RabbitMQ常见问题处理

(1)防止消息丢失,消息丢失分为消费者丢失消息,RabbitMQ丢失消息,消费者丢失消息

消费者丢失消息处理方案:

开启RabbitMQ事务机制,会使RabbitMQ的性能下降10倍左右,一般不推荐使用

开启生产者消息确认(Confirm模式,异步),配置publisher-confirm-type指定确认方式,配置publisher-returns为true,设置RabbitTemplate.setMandatory(true),自定义消息确认回调类实现RabbitTemplate.ConfirmCallback(消息正确发送到Exchange后回调),实现RabbitTemplate.ReturnCallback接口(消息从Exchange路由到队列失败时回调)

RabbitMQ丢失消息处理方案:

设置消息持久化,Exchange,Queue,设置为持久化,Message持久化,消息deliveryMode=2

MQ采用集群镜像部署模式(队列、消息存在于多个节点上保证高可用)

消费者消息丢失处理方案:

开启消费者手动确认,自定义消息监听类实现ChannelAwareBatchMessageListener,处理消息消费确认、拒绝、重入队列,配置SimpleMessageListenerContainer,设置消息监听类,手动确认模式

使用第三方实现消息补偿

生产者发送消息之后同时异步将消息存在数据库,并设置状态,消费者成功消费消息之后,更改消息状态,消息补偿服务定期轮询消息状态或通过其他方式触发补偿服务,读取数据库中消费失败消息,重新将消息发往MQ

(2)防止消息重复消费

解决方案:保证消息的幂等性

为消息设置为一的ID,若消息需要入库,先通过唯一ID查询,存在不做处理

使用redis记录消息处理记录,处理消息前先查询,已处理则直接丢弃

业务处理逻辑本身是幂等的,如update,则无需处理

你可能感兴趣的:(Java,Springboot,RabbitMQ)