RabbitMQ笔记

303 阅读4分钟

一,死信队列

/**
     * 原始交换机
     */
    @Bean("oriUseExchange")
    public DirectExchange exchange() {
        //Exchange的构造参数:String name, boolean durable, boolean autoDelete, Map<String, Object> arguments
        return new DirectExchange("GP_ORI_USE_EXCHANGE", true, false, new HashMap<>());
    }

    /**
     * 原始queue
     */
    @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", "GP_DEAD_LETTER_EXCHANGE"); // 队列中的消息变成死信后,进入死信交换机
        //Queue的构造函数参数:String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        return new Queue("GP_ORI_USE_QUEUE", true, false, false, map);
    }

    /**
     * 队列和交换器绑定
     */
    @Bean
    public Binding binding(@Qualifier("oriUseQueue") Queue queue,@Qualifier("oriUseExchange") DirectExchange exchange) {
        //注意with的参数是:String routingKey
        return BindingBuilder.bind(queue).to(exchange).with("gupao.ori.use");
    }

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

    /**
     * 死信queue
     */
    @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("#"); // 无条件路由
    }

二,延迟队列

  • 1,使用 RabbitMQ 的死信队列(Dead Letter Queue)实现
消息的流转流程:
生产者——原交换机——原队列(超过 TTL 之后)——死信交换机——死信队列— —最终消费者

缺点:
1),如果统一用队列来设置消息的 TTL,当梯度非常多的情况下,比如 1 分钟,2 分钟,5 分钟,10 分钟,20 分钟,30 分钟......需要创建很多交换机和队列来路由消息。
2),如果单独设置消息的 TTL,则可能会造成队列中的消息阻塞——前一条消息没 有出队(没有被消费),后面的消息无法投递
	(比如第一条消息过期 TTL 是 30min,第二条消息 TTL 是 10min。10 分钟后,即使第二条消息应该投递了,但是由于第一条消息 还未出队,所以无法投递)。
3),可能存在一定的时间误差。
  • 2,利用 rabbitmq-delayed-message-exchange 插件
在 RabbitMQ 3.5.7 及以后的版本提供了一个插件 (rabbitmq-delayed-message-exchange)来实现延时队列功能。同时插件依赖 Erlang/OPT 18.0 及以上。
插件源码地址: https://github.com/rabbitmq/rabbitmq-delayed-message-exchange 插件下载地址: https://bintray.com/rabbitmq/community-plugins/rabbitmq_delayed_message_exchange

1、进入插件目录
    whereis rabbitmq
    cd /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.12/plugins
2、下载插件
  wget https://bintray.com/rabbitmq/community-plugins/download_file?file_path=rabbitmq_delayed_message_exchange-0.0.1.ez
  如果下载的文件名带问号则需要改名:
  mv download_file?file_path=rabbitmq_delayed_message_exchange-0.0.1.ez rabbitmq_delayed_message_exchange-0.0.1.ez
3、启用插件
	rabbitmq-plugins enable rabbitmq_delayed_message_exchange
4、停用插件
	rabbitmq-plugins disable rabbitmq_delayed_message_exchange
5、插件使用
	通过声明一个 x-delayed-message 类型的 Exchange 来使用 delayed-messaging 特性。
    x-delayed-message 是插件提供的类型,并不是 rabbitmq 本身的(区别于 direct、 topic、fanout、headers)。

代码Demo:

/***
 * 延时队列的交换机和queue的配置和绑定
 */
@Configuration
public class DelayPluginConfig {
    @Bean
    public ConnectionFactory connectionFactory() throws Exception {
        CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
        cachingConnectionFactory.setUri("amqp://guest:guest@192.168.8.133:5672");
        return cachingConnectionFactory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        return new RabbitTemplate(connectionFactory);
    }

    @Bean("delayExchange")
    public TopicExchange exchange() {
        Map<String, Object> argss = new HashMap<String, Object>();
        argss.put("x-delayed-type", "direct");
        return new TopicExchange("GP_DELAY_EXCHANGE", true, false, argss);
    }

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

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

生产者:
/**
 * 延时消息插件,去管控台队列看有无收到消息
 * 不能在本地测试,必须发送消息到安装了插件的服务端
 */
@ComponentScan(basePackages = "com.gupaoedu.dlx.delayplugin")
public class DelayPluginProducer {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DelayPluginProducer.class);
        RabbitAdmin rabbitAdmin = context.getBean(RabbitAdmin.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        // 延时投递,比如延时4秒
        Date now = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, +4);
        Date delayTime = calendar.getTime();

        // 定时投递,把这个值替换delayTime即可
        // Date exactDealyTime = new Date("2019/06/24,22:30:00");
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        String msg = "延时插件测试消息,发送时间:" + sf.format(now) + ",理论路由时间:" + sf.format(delayTime);

        MessageProperties messageProperties = new MessageProperties();
        // 延迟的间隔时间,目标时刻减去当前时刻
        messageProperties.setHeader("x-delay", delayTime.getTime() - now.getTime());
        Message message = new Message(msg.getBytes(), messageProperties);

        // 不能在本地测试,必须发送消息到安装了插件的服务端
        rabbitTemplate.send("GP_DELAY_EXCHANGE", "#", message);
    }
}

消费者:
public class DelayPluginConsumer {
    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri("amqp://guest:guest@192.168.8.133:5672");
        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        // 创建消费者
        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");
                SimpleDateFormat sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                System.out.println("收到消息:[" + msg + "]\n接收时间:" +sf.format(new Date()));
            }
        };

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

三,服务端流控 (Flow Control)

当 RabbitMQ 生产 MQ 消息的速度远大于消费消息的速度时,会产生大量的消息堆积,占用系统资源,导致机器的性能下降。我们想要控制服务端接收的消息的数量,应该怎么做呢?

队列有两个控制长度的属性: x-max-length:队列中最大存储最大消息数,超过这个数量,队头的消息会被丢 弃。 x-max-length-bytes:队列中存储的最大消息容量(单位 bytes),超过这个容 量,队头的消息会被丢弃。

** 需要注意的是,设置队列长度只在消息堆积的情况下有意义,而且会删除先入队的 消息,不能真正地实现服务端限流。**

具体可以用:内存控制和磁盘控制(具体不多叙述)

四,消费端限流

  • 1),channel.basicQos(2); 如果超过 2 条消息没有发送 ACK,当前消费者不再接受队列消息 使用:channel.basicConsume(QUEUE_NAME, false, consumer);

  • 2),channel 设置 prefetch count 的值 channel 的 prefetch count 设置为 5。当消费者有 5 条消息没有给 Broker 发送 ACK 后,RabbitMQ 不再给这个消费者投递消息。

import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
...
container.setPrefetchCount(5);