RabbitMQ实战总结

一 RabbitMQ介绍

源码地址,更改配置文件,每个测试案例都可以独立运行

1. RabbitMQ概念

 RabbitMQ是AMQP(高级消息队列协议)的一种实现,主要用来对消息进行发送,存储和转发

下图是RabbitMQ的整体架构模型:

从整体架构模型理解RabbitMQ,RabbitMQ主要包括下面的组成部分

1. 生产者Producer,用来生产消息,可以是普通文本,或是JSON字符串,或是XML消息,会作为MQ的消息来源。

2. 消费者Consumer,消息接收方,用来获取MQ中的消息(普通文本、JSON或XML格式等等),进行业务逻辑处理。

3. Exchange交换机和路由键BindingKey,用来接收生产者的消息,通过不同的组合来确定将消息发送到哪个队列。

4.Queue队列用来存储生产的消息,并提供给消费段进行消息处理。

2. RabbitMQ特点

  • 异步消息处理,Queue可以对消息进行持久化存储,当Consumer需要的时候,再拉取消息进行处理
  • 流量削峰,Consumer在拉取消息消费是,可以指定每秒消费的消息条数,减缓消费端的压力。
  • 系统解耦,上下游的生产端和消费端完全无关,可以用不同的语言编写,可以是不同的系统。
  • 横向扩展,RabbitMQ支持横向扩展机制,大流量情况下,可以增大服务器的平均负载能力。
  • 其他特点,比如RabbitMQ提供丰富的插件,并且支持集群负载均衡和高可用等等。

二 RabbitMQ Client

1. 交换机声明

public DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments) throws IOException {...}

 参数解释:

exchange,交换机的名字,自定义。

BuiltinExchangeType ,交换机的类型。

durable,是否持久化,这里设置成true。

autoDelete,是否自动删除,这里设置成false。

internal,内部使用,这里设置为false。

arguments, 交换机可能会有的其他参数,设置成NULL。

 交换机的类型介绍,交换机类型决定消息的发送规则

direct(直连交换机),绑定了该交换机,并且绑定了指定路由键的队列才能接收到消息。

fanout(扇形交换机), 不需要路由键,绑定了该交换机的队列,都能接收到消息。

top(发布订阅交换机),绑定了交换机,并且绑定了指定路由键(支持正则表达式匹配)的队列能收到消息。

2. 队列声明

   public com.rabbitmq.client.impl.AMQImpl.Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException{...}

参数解释:

queue,队列名字,自定义。

durable,队列是否持久化,建议为true(将更多的消息放在磁盘存储,然后再消费,节约内存)。

exclusive,排他队列,设置为false。

autoDelete,设置为false,队列不能自动删除,不然数据咋办?

arguments,附加的参数。

arguments附加参数具体有哪些:

  • x-message-ttl 消息过期时间,队列中的消息最长存活时间。
  • x-expires 队列的过期时间。
  • x-max-length 队列能够保存消息的最大条数
  • x-max-length-bytes 队列中消息大小限定的最大值。
  • x-dead-letter-exchange 死信队列的交换机,若队列需要绑定到私信队列,则需要设置该参数。
  • x-dead-letter-routing-key 死信队列的路由键,若队列需要私信队列,则需要制定键,否则用自己的路由键进行匹配
  • x-max-priority 队列支持的消息最大优先级(设置为数字), 发送消息时,单独设置消息的优先级,比该数字小

3. 绑定(队列)

 public com.rabbitmq.client.impl.AMQImpl.Queue.BindOk queueBind(String queue, String exchange, String routingKey) throws IOException {...}

4. 消息发送

public void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body) throws IOException {...}

exchange, 要发送的交换机名字

routingKey,指定路由键

mandatory, 发送消息应答机制,true表示MQ服务器会对消息返回是否收到的信息,false表示不做任何回应

immediate,设置成false

BasicProperties, 单条消息的其他属性。

byte[], 消息体,可以使JSON或者XML或者String

 BasicProperties,消息其他属性介绍(主要设置两个参数):

deliveryMode 设置成2 表示,消息可以持久化存储,设置成1 重启RabbitMQ,消息将丢失

contentType,设置消息体的格式,比如JSON格式为"applicaion/json"

messageId, 消息唯一标识符

5. 生产者消息确认机制

在消息未发送到交换机上的时候,可以在发送之前注入ReturnListener,异步回调进入该接口内

channel.addReturnListener((replyCode, replyText, exchange, routingKey, properties, body) -> {});

在消息发送到交换机时,可以设置信道Channel为confirm模式,MQ收到消息会异步回调该接口

channel.confirmSelect();
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long seg, boolean ack) throws IOException {}

            @Override
            public void handleNack(long seg, boolean ack) throws IOException {}
        });

6. 消费端处理

 消费端消息限流,高并发情况下,消费端可以设置每秒消费消息的条数。

    public void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException {
        this.exnWrappingRpc(new Qos(prefetchSize, prefetchCount, global));
    }

basicRecover:是路由不成功的消息可以使用recovery重新发送到队列中。

basicReject:是接收端告诉服务器这个消息我拒绝接收,不处理,可以设置是否放回到队列中还是丢掉,而且只能一次拒绝一个消息,官网中有明确说明不能批量拒绝消息,为解决批量拒绝消息才有了basicNack。

basicNack:可以一次拒绝N条消息,客户端可以设置basicNack方法的multiple参数为true,服务器会拒绝指定了delivery_tag的所有未确认的消息(tag是一个64位的long值,最大值是9223372036854775807)。

三 springboot整合RabbitMQ

1. 引入依赖文件

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2. yml配置文件配置

server:
  port: 8081

spring:
  rabbitmq:
    host: 192.168.0.21
    port: 5672
    addresses: 192.168.0.21:5672
    username: pengjunjie
    password: pengjunjie
    virtual-host: /pengjunjie
    cache:
      connection:
        size: 1
      channel:
        size: 30
    publisher-confirms: true
    publisher-returns: true

3. RabbitConfig.java

生产者配置,ConnectionFactory配置。

  • 生产者mandatory设置成true,增加消息confirm机制
  • 添加ReturnCallback和ConfirmCallback,进行消息发送异步确认
  • 有必要的情况下,可以对发送的消息进行缓存,收到确认之后清除消息。
/**
 * @author pengjunjie
 */
public class QueueConstant {


    /** 订单队列 */
    public static final String ORDER_EXCHANGE = "order_exchange";
    public static final String ORDER_BIND_KEY = "order_bind_key";
    public static final String ORDER_QUEUE = "order";


    /** 死信队列 */
    public static final String DEAD_EXCHANGE = "dead_exchange";
    public static final String DEAD_BIND_KEY = "dead_bind_key";
    public static final String DEAD_QUEUE = "dead";
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author pengjunjie
 * @date 2019.2.27
 */
@Configuration
public class RabbitConfig {
    private static final Logger logger = LoggerFactory.getLogger(RabbitConfig.class);


    /** ConnectionFactory配置 */
    @Bean
    public ConnectionFactory connectionFactory(RabbitProperties properties) throws Exception {
        RabbitConnectionFactoryBean factoryBean = new RabbitConnectionFactoryBean();
        factoryBean.setHost(properties.getHost());
        factoryBean.setVirtualHost(properties.getVirtualHost());
        factoryBean.setPort(properties.getPort());
        factoryBean.setUsername(properties.getUsername());
        factoryBean.setPassword(properties.getPassword());
        factoryBean.afterPropertiesSet();

        CachingConnectionFactory cachingConnectionFactory =
                new CachingConnectionFactory(factoryBean.getObject());
        if (properties.getAddresses() != null) {
            cachingConnectionFactory.setAddresses(properties.getAddresses());
        }

        if (properties.isPublisherConfirms()) {
            cachingConnectionFactory.setPublisherConfirms(true);
        }

        if (properties.isPublisherReturns()) {
            cachingConnectionFactory.setPublisherReturns(true);
        }

        RabbitProperties.Cache cache = properties.getCache();
        if (cache.getChannel().getSize() != null) {
            cachingConnectionFactory.setChannelCacheSize(cache.getChannel().getSize());
        }

        if (cache.getConnection().getSize() != null) {
            cachingConnectionFactory.setConnectionCacheSize(cache.getConnection().getSize());
        }

        return cachingConnectionFactory;
    }

    /** RabbitTemplate生产者配置 */
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        Jackson2JsonMessageConverter jackson2JsonMessageConverter =
                new Jackson2JsonMessageConverter();
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setEncoding("UTF-8");
        rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);


        /** return message */
        rabbitTemplate.setReturnCallback((Message message, int replyCode, String replyText,
                                          String exchange, String routingKey) -> {
            /** Redis缓存处理 */
            logger.error("===> 消息未发送到交换机 message={}, replyCode={}, replyText={}, " +
                            "exchange={}, routingKey={}",
                    message, replyCode, replyText, exchange, routingKey);
        });

        /** confirm listener */
        rabbitTemplate.setConfirmCallback((CorrelationData correlationData, boolean ack,
                                           String cause) -> {
            /** Redis缓存处理 */
            if (ack) {
                logger.info("===> 消息发送成功 id={}, message={}",
                        correlationData.getId(),
                        correlationData.getReturnedMessage());
            } else {
                logger.error("===> 消息发送失败 id={}, message={}, cause={}",
                        correlationData.getId(),
                        correlationData.getReturnedMessage(),
                        cause);
            }
        });
        return rabbitTemplate;
    }

}

4. 发送消息

封装自己的RabbitMessage.java

/**
 * @author pengjunjie
 */
public class RabbitMessage {

    private String routeKey;
    private String bindKey;
    private Object source;

    public String getRouteKey() {
        return routeKey;
    }

    public void setRouteKey(String routeKey) {
        this.routeKey = routeKey;
    }

    public String getBindKey() {
        return bindKey;
    }

    public void setBindKey(String bindKey) {
        this.bindKey = bindKey;
    }

    public Object getSource() {
        return source;
    }

    public void setSource(Object source) {
        this.source = source;
    }

    @Override
    public String toString() {
        return "RabbitMessage{" +
                "routeKey='" + routeKey + '\'' +
                ", bindKey='" + bindKey + '\'' +
                ", source=" + source +
                '}';
    }
}

消息发送器RabbitSender.java

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
 * @author pengjunjie
 */
@Component
public class RabbitSender {
    private static final Logger logger = LoggerFactory.getLogger(RabbitSender.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;


    public String send(RabbitMessage rabbitMessage) {
        String msgId = UUID.randomUUID().toString();
        MessageProperties properties = new MessageProperties();
        properties.setContentType("application/json");
        properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        properties.setMessageId(msgId);
        properties.setContentEncoding("utf-8");

        CorrelationData data = new CorrelationData();
        data.setId(msgId);

        ObjectMapper objectMapper = new ObjectMapper();
        byte[] bytes = new byte[0];
        try {
            bytes = objectMapper.writeValueAsBytes(rabbitMessage.getSource());
        } catch (JsonProcessingException e) {
            logger.error("===> 消息发送失败 {}", e.toString());
        }

        Message message = new Message(bytes, properties);
        rabbitTemplate.convertAndSend(rabbitMessage.getRouteKey(), rabbitMessage.getBindKey(),
                message, msg -> msg, data);

        logger.info("====> 消息发送成功 {}", message);
        return msgId;
    }
}

5. 创建死信队列和订单队列

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author pengjunjie
 */
@Configuration
public class QueueConfig {

    @Bean
    public Queue order() {
        Map<String, Object> args = new HashMap<>(2);
        args.put("x-dead-letter-exchange", QueueConstant.DEAD_EXCHANGE);
        args.put("x-dead-letter-routing-key", QueueConstant.DEAD_BIND_KEY);
        Queue queue = new Queue(QueueConstant.ORDER_QUEUE,
                true, false, false, args);
        return queue;
    }

    @Bean
    public DirectExchange orderExchange() {
        DirectExchange directExchange = new DirectExchange(QueueConstant.ORDER_EXCHANGE,
                true, false);
        return directExchange;
    }

    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(order()).to(orderExchange()).with(QueueConstant.ORDER_BIND_KEY);
    }

    @Bean
    public Queue dead() {
        Queue queue = new Queue(QueueConstant.DEAD_QUEUE,
                true, false, false);
        return queue;
    }

    @Bean
    public DirectExchange deadExchange() {
        DirectExchange directExchange = new DirectExchange(QueueConstant.DEAD_EXCHANGE,
                true, false);
        return directExchange;
    }

    @Bean
    public Binding deadBinding() {
        return BindingBuilder.bind(dead()).to(deadExchange()).with(QueueConstant.DEAD_BIND_KEY);
    }
}

6. 消费端消费消息

import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @author pengjunjie
 */
@Component
@RabbitListener(queues = {QueueConstant.ORDER_QUEUE})
public class OrderConsumer1 {

    @RabbitHandler
    public void process(Object msg) {
        System.out.println(msg);
    }
}
/** 第二种消费方式 */
@Component
class OrderConsumer2 {

    @Bean
    public SimpleMessageListenerContainer orderListener(ConnectionFactory connectionFactory) {
        SimpleMessageListenerContainer listenerContainer = 
                new SimpleMessageListenerContainer(connectionFactory);
        /** 设置消费的队列 */
        listenerContainer.addQueueNames(QueueConstant.ORDER_QUEUE);
        /** 设置同时几个消费者线程进行消费 */
        listenerContainer.setConcurrentConsumers(3);
        /** 最多多少个线程进行消费 */
        listenerContainer.setMaxConcurrentConsumers(10);
        /** 手动应答模式 */
        listenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        /** 每次拉取100条消息 */
        listenerContainer.setPrefetchCount(100);
        listenerContainer.setMessageConverter(new Jackson2JsonMessageConverter());
        listenerContainer.setMessageListener((ChannelAwareMessageListener) (message, channel) -> {
            /** 消息的业务逻辑在这里处理,手动应答机制 */
            System.out.println(message);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        });
        return listenerContainer;
    }
}

 

posted @ 2019-03-05 18:28  陌生。  阅读(458)  评论(0)    收藏  举报