RabbitMQ系列【三】RabbitMQ消息队列实践

RabbitMQ

消息队列目前常用的有很多种,比如ActiveMQ、Kafka、RocketMQ,甚至使用Redis简单实现一个消息队列。这里主要记录一下RabbitMQ的相关知识。
1、RabbitMQ的使用场景
2、RabbitMQ的缺点
3、RabbitMQ数据传递过程
4、RabbitMQ的Exchange类型
5、RabbitMQ使用实例

RabbitMQ的使用场景

消息队列的使用通常有三个重点:异步、削峰、解耦

1、当系统耦合较高时,不利于我们维护与扩展,例如,AB两个系统都需要C系统的数据,C系统通过接口调用的方式传递给AB,如果后期又有D系统需要,则要对C系统进行修改,这时可以使用消息队列,谁需要数据谁来消费即可。

2、当某个接口不需要实时返回时,允许出现短时间的不一致性时可以使用,这样可以降低响应时间,带来更好的体验,例如用户注册时,只需要往数据库中插入一条数据即可返回响应,而需要做的其他操作就可以发到队列里慢慢完成。

3、当一个接口的瞬时请求量非常巨大的时候,我们的系统可能根本承受不住,直接就挂掉了,这时候我们可以将请求全部发送到队列当中,让系统根据自己的能力来处理,一秒钟5000个请求进来,只有2000个交给系统来处理,剩下的挤压等待处理。

RabbitMQ的缺点

MQ的缺点在于,如何保证MQ可用性,数据的一致性,系统的复杂性。
1、现在都是通过搭建集群来保证MQ的可用性,一台挂掉了,不会影响系统的正常运行
2、数据一致性问题是最重要的,一条消息由多个消费者处理,其中一个消费者处理失败,会不会导致数据不一致。如果消费者处理消息后挂掉了,没有返回执行结果,下次重启会不会再消费一次?如果在MQ没有消费的时候,会不会有可能消息丢失?
3、如何保证消息是顺序消费的?比如一个消息队列有多个消费者?
这些常见的问题,我们会在文章的最后来解决。

RabbitMQ数据传递过程

图示消息传递过程(图片来源于网络)

从图片上可以看出,一条消息并不是直接发送到队列当中的,而是通过Exchange建立的通道channel,将消息首先发送到Exchange中,然后由Exchange定义的路由规则转发到消息队列Queue中,而消费者则通过channel从队列中获取消息。根据路由规则的不同选择不同的Exchange。

RabbitMQ的Exchange类型

Exchange在转发消息的时候会通过路由规则来进行匹配,如果没有匹配的队列,消息会被丢弃。
根据路由规则的不同,我们可以选用4个类型:

1、direct: Exchange在和Queue进行绑定Binding时会设定一个routing key,而消息生产者发送消息时会指定一个参数routing key,只有在完全匹配的情况下才会发送到队列当中。

2、topic: 与direct类似, Exchange在设置routing key时可以指定通配符 和 “#” ,其中“ ”匹配一个单词,“ # ”可以匹配多个单词或者无

3、fanout:这个路由规则就是直接匹配到所有绑定的队列,不需要routing key匹配,设置了也是无效的,通常用来做广播。

4、header:其路由的规则是根据header来判断,这种方式很少使用,了解即可

默认Defualt: 如果定义Exchange时没有指定类型和名称, RabbitMQ将会为每个消息队列设定一个Default Exchange,它的Routing Key是消息队列名称。

RabbitMQ使用实例

Defualt Exchange

1、创建链接,设置MQ连接信息

    private static ConnectionFactory connectionFactory = null;
    private static Connection connection = null;
/***
 * 设置MQ连接信息
 * @return MQ连接
 */
private static ConnectionFactory getConnectionFactory() {
    // 为防止重复采用一个connectionFactory
    if (connectionFactory != null){
        return connectionFactory;
    }
    connectionFactory = new ConnectionFactory();
    // 配置连接信息
    connectionFactory.setHost("127.0.0.1");
    connectionFactory.setPort(5672);
    connectionFactory.setVirtualHost("/");
    connectionFactory.setUsername("admin");
    connectionFactory.setPassword("admin");
    // 网络异常自动连接恢复
    connectionFactory.setAutomaticRecoveryEnabled(true);
    // 每10秒尝试重试连接一次
    connectionFactory.setNetworkRecoveryInterval(10000);
    Map<String, Object> connectionFactoryPropertiesMap = new HashMap<>();

    // 设置MQ属性信息
    connectionFactoryPropertiesMap.put("user", "J");
    connectionFactoryPropertiesMap.put("description", "My test MQ");
    connectionFactoryPropertiesMap.put("emailAddress", "abc@hotmail.com");
    connectionFactory.setClientProperties(connectionFactoryPropertiesMap);
    return connectionFactory;
}

2、获取channel通道

/***
     * 获取生产者Channel
     * @param connectionDescription 生产者名称
     * @return 消息通道
     */
    public static Channel getChannelInstance(String connectionDescription){
        try {
            ConnectionFactory connectionFactory = getConnectionFactory();
            // 创建一个连接,这里采用1个connection
            if (connection == null) {
                connection = connectionFactory.newConnection(connectionDescription);
            }
            // 创建一个channel通道
            return connection.createChannel();
        } catch (Exception e) {
            throw new RuntimeException("获取Channel连接失败");
        }
    }

3、设置默认的Default Exchange和创建消息队列

/**
     * 设置默认的生产Default Exchange
     * @param connectionDescription 生产者连接名称
     * @param queueName 消息队列名称
     * @return channel连接
     */
    private static Channel getDefaultExchangeChannel(String connectionDescription, String queueName){
        Channel channel = getChannelInstance(connectionDescription);
        try {
            // 参数,
            // 1.队列名称
            // 2.是否持久化
            // 3.是否只适用于当前TCP连接
            // 4.队列不使用时是否自动删除
            // 5.定义了队列的一些参数信息,主要用于Headers Exchange进行消息匹配
            channel.queueDeclare(queueName,true, false, false, null);
        } catch (Exception e) {
            System.out.println(e);
        }
        return channel;
    }

4、封装发送消息接口

/**
     * 向Default Exchange queue发送消息
     * @param connectionDesc 生产者名称
     * @param queueName 消息队列名称
     * @param message 消息内容
     */
    private static void pushMessageToDefaultExchange(String connectionDesc,String queueName, String message){
        try {
            Channel channel = getDefaultExchangeChannel(connectionDesc, queueName);
            // 参数
            // 1.Exchange名称,如果没有指定,则使用Default Exchange
            // 2.routingKey是消息的路由Key,是用于Exchange将消息路由到指定的消息队列时使用
            // 3.props是消息包含的属性信息。RabbitMQ的消息属性和消息体是分开的
            // 4.body是RabbitMQ消息体
            channel.basicPublish("", queueName, null, message.getBytes());
            System.out.println("生产者生产消息给队列 "+ queueName + ",内容为:" + message);
        } catch (Exception e) {
            System.out.println("消息发送到" + queueName + "失败:" + message);
        }
    }

5、消费者

/**
     * 消费默认DefaultExchange的指定队列信息
     * @param consumerName 消费者连接名称
     * @param queueName 消息队列名称
     */
    public static void consumeDefaultExchangeMessage(String consumerName,String queueName){
        Channel channel = MQProductsUtils.getChannelInstance(consumerName);
        try {
            Consumer consumer = new DefaultConsumer(channel){
                /**
                 * 重写消费处理过程
                 * @param consumerTag 接收到消息时的消费者Tag(随机生成),可以在basicConsume中指定
                 * @param envelope 消息的属性,包含
                 *                 1.deliveryTag 消息发送编号,
                 *                 2.redeliver 重传标志,确认在收到对消息的失败确认后,是否需要重发这条消息,这里的值为false,不需要重发。
                 *                 3.exchange exchange名称,默认为""
                 *                 4.消息发送的路由Key,这里是发送消息时设置的queueName
                 * @param properties basicPublish中的props参数
                 * @param body 消息内容
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    System.out.println("消费者消费消息:" + new String(body));
                    //System.out.println(consumerTag);
                    //System.out.println(envelope.toString());
                    //System.out.println(properties.toString());
                    // 需要手动确认消息时,通过Channel.basicAck方法,发送确认消息给消息队列
                    // 参数 1.消息的发送编号 2.确认方式,true为确认所有编号小于等于参数1的消息,false只确认当前消息
                    // this.getChannel().basicAck(envelope.getDeliveryTag(), false);
                }
            };
            // 这里与消息队列queueName进行绑定,否则无法从队列中消费信息
            // 参数 1.绑定的队列名称 2. 自动确认标志,true时自动发送确认消息ACK给队列 3.consume对象,执行处理消息逻辑
            channel.basicConsume(queueName, true, consumer);
        } catch (Exception e) {
            System.out.println(consumerName + "消费" + queueName + "队列消息失败");
        }
    }

6、测试接口

public static void main(String[] args) {
        // 使用默认Exchange,发送一条消息到队列当中
        pushMessageToDefaultExchange("测试生产者" ,"FirstQueue", "I am frist queue's message ");
        pushMessageToDefaultExchange("测试生产者2" ,"SecondQueue", "I am second queue's message);
        consumeDefaultExchangeMessage("测试消费者", "FirstQueue");
        consumeDefaultExchangeMessage("测试消费者2", "SecondQueue");
    }
Direct Exchange

1、添加 Direct Exchange设置方法

/**
     * 设置默认的生产Direct Exchange
     * @param connectionDescription 生产者连接名称
     * @param queueName 消息队列名称
     * @return channel连接
     */
    private static Channel getDirectExchangeChannel(String connectionDescription, String queueName){
        Channel channel = getChannelInstance(connectionDescription);
        try {
            // 设置exchange
            // 参数1、Exchange名称,2、Exchange类型N
            channel.exchangeDeclare("directExchange", "direct");
            channel.queueDeclare(queueName, true, false, false, null);
            // 绑定exchange
            // 参数1、消息队列名称,2、Exchange名称 3、路由匹配规则RoutingKey
            channel.queueBind(queueName, "directExchange", "directRoutingKey");
        } catch (Exception e) {
            System.out.println(e);
        }
        return channel;
    }

2、添加发送消息方法

/**
     * 向DirectExchange queue发送消息
     * @param connectionDesc 生产者名称
     * @param queueName 消息队列名称
     * @param message 消息内容
     */
    private static void pushMessageToDirectExchange(String connectionDesc,String queueName, String message){
        try {
            Channel channel = getDirectExchangeChannel(connectionDesc, queueName);
            // 参数
            // 1.Exchange名称,如果没有指定,则使用Default Exchange
            // 2.routingKey是消息的路由Key,是用于Exchange将消息路由到指定的消息队列时使用
            // 3.props是消息包含的属性信息。RabbitMQ的消息属性和消息体是分开的
            // 4.body是RabbitMQ消息体
            channel.basicPublish("directExchange", "directRoutingKey", null, message.getBytes());
            System.out.println("生产者生产消息给队列 "+ queueName + ",内容为:" + message);
        } catch (Exception e) {
            System.out.println("消息发送到" + queueName + "失败:" + message);
        }
    }

3、消费者
与Default一致,不赘述

其他两种方式这里不再赘述,与Direct基本类似,HeaderExchange了解即可。

以上就是RabbitMQ的全部内容
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值