rabbitMQ二之工作模式

目录

1.工具类抽取

2.简单队列

2.1消息生产者

2.2消息消费者

2.2.1客户端自动应答

2.2.2客户端手动应答

3.工作队列

3.1消息生产者

3.2消息消费者

4.发布订阅

4.1消息生产者

4.2消息消费者

5.路由

5.1消息生产者

5.2消息消费者

6.话题

6.1消息生产者

6.2消息消费者

7.RPC

8.发布者确认

8.1事务机制

8.2confirm机制

8.2.1同步确认机制

8.2.2异步确认机制


rabbitMQ提供一下几种工作模式:

  1. 简单队列
  2. 工作队列
  3. 发布订阅
  4. 路由
  5. 话题
  6. RPC
  7. 发布者确认

1.工具类抽取

为了方便代码能直接使用,先将代码中用到的公告部分抽取取来。这个工具栏的作用很简单,就是回去一个Connection,代码如下:

public class ConnectionUtil {

	public static Connection getConnection() throws Exception {
		// 定义连接工厂
		ConnectionFactory factory = new ConnectionFactory();
		// 设置服务地址
		factory.setHost("localhost");
		// 端口
		factory.setPort(5672);
		// 设置账号信息,用户名、密码、vhost
		factory.setVirtualHost("testhost");
		factory.setUsername("admin");
		factory.setPassword("admin");
		// 通过工程获取连接
		Connection connection = factory.newConnection();
		return connection;
	}
}

 

 

2.简单队列

简单队列,就如名字一样,简单到这个队列,只有一个生产者,只对应一个消费者了。

2.1消息生产者

    public boolean sendMessage(String queueName, String message) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明(创建)队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 消息内容
        channel.basicPublish("", queueName, null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
        // 关闭通道和连接
        channel.close();
        connection.close();
        return false;
    }

声明队列的时候,有四个参数,应为后面经常出现,现在提前说明一下这四个参数的含义:

channel.queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
                                 Map<String, Object> arguments);
  1. queue:我们使用的队列名称
  2. durable:是否将消息持久化;假如我们在创建queue的时候没有指定其持久化,后期不可以在代码中修改其为持久化队列,只能删除重建。rabbitmq不允许重新定义(不同参数)一个已存在的队列
  3. exclusive:是否声明为独占队列
  4. autoDelete:消息投递到消费者后,server上是否自动删除消息
  5. arguments:队列的配置信息

2.2消息消费者

消费者消费信息的时候,对服务器有两种应答方式:自动应答,手动应答

自动应答:当队列服务器将消息投递给消费者后,不管消费者是否成功处理,都自行将队列删除。

手动应答:当队列服务器将消息投递给消费者后,需要消费者确认是否在服务器上删除该消息。

2.2.1客户端自动应答

    /**
     * 客户端自动应答消费消息
     */
    public void reciveByAutoMode(String queueName) throws Exception {

        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 定义消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("receiveMessage:" + message);
            }
        };
        // 监听队列
        channel.basicConsume(queueName, true, consumer);
    }

 代码中监听队列的第二个参数设置为true,表示自动应答。

basicConsume(String queue, boolean autoAck, Consumer callback)

2.2.2客户端手动应答

    /**
     * 客户端手动应答
     */
    public void reciveByHandleMode(String queueName) throws Exception {

        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 定义消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("receiveMessage:" + message);
                // 程序确认,而不是rabbit的自动确认(手动回执)
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 监听队列,改成false则表示手动应答
        channel.basicConsume(queueName, false, consumer);
    }

手动应答,首先需要将监听队列的第二个参数设置为false,

basicConsume(String queue, boolean autoAck, Consumer callback)

 同时,在consumer中,需要手动发送一个确认信息。

  channel.basicAck(envelope.getDeliveryTag(), false);

3.工作队列

消息生产者可以不断的向服务器发送消息,但是消费者不一定能够及时或者快速处理掉消息,这就会导致消息的积压。

工作队列的出现,在某种程度上降低了消息积压发生的可能性。

工作队列,通过多个消费者消费同一个队列的数据,加快了消息的处理速度。

同时,队列提供了两种消息消费类型:公平消费,轮训消费

公平消费:服务器根据消费者的能力来投递消息,在消费者没有完成对消息的处理时,不会再次发送一条消息至消费者。

轮训消费:服务器将消息轮训投递给消费者,如果消费者有一个处理能力较差,会导致最后的压力都集中在能力差的机器上。

生产中,我们毫无疑问的提倡公平分发消费。

3.1消息生产者

    public boolean sendMessage2WorkQueue(String queueName, List<String> messages) throws Exception {

        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明(创建)队列
        channel.queueDeclare(queueName, false, false, false, null);
        for (String message : messages) {
            //发送消息
            channel.basicPublish("", queueName, null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");
        }

        // 此行代码做公平分发用
        // 每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者,一次只处理一个消息
        // 限制发送给同一个消费者不得超过一条消息
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);

        // 关闭通道和连接
        channel.close();
        return true;
    }

3.2消息消费者

   /**
     * 客户端手动应答
     * 当采用公平分发的时候,消费端要关闭自动应答,改为手动应答
     *
     * @param queueName
     * @throws Exception
     */
    public void reciveByHandleMode(String queueName) throws Exception {

        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        final Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 定义消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("receiveMessage:" + message);
                // 程序确认,而不是rabbit的自动确认(手动回执)
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 监听队列,改成false则表示手动应答
        channel.basicConsume(queueName, false, consumer);
    }

 

4.发布订阅

 

从这开始接触一个新的概念:交换机

交换机相当于一个路由,对生产者发来的消息,根据配置进行路由,其本身并不存储消息。在rabbitmq中,只有队列能存储消息。

路由的方式有三种:fanout(对应发布订阅模式),direct(对应路由模式),topic(对应主题模式)

  1. fanout:发布订阅模式,顾名思义,像扇子一样(扇出),对绑定到该交换机的所有队列,都能收到消息。此时routingKey不生效。
  2. direct:路由模式,对绑定到该交换机的队列,通过routingKey匹配,只有完全匹配,交换机才会将消息路由到该队列。
  3. topic:主题模式,将路由和某个模式进行匹配(规则匹配, #:匹配一个或者多个; *:匹配一个)。匹配上的队列,交换机才会路由消息至该队列

 

1.一个生产者,多个消费者
2. 每一个消费者都有自己的队列 </br>
3. 生产者没有直接把消息发送到队列,而是发到了交换机(转发器 exchange)
4. 每个队列都要绑定到交换机上
5. 生产者发送的消息,经过交换机,到达队列,就能实现一个消息被多个消费者消费

4.1消息生产者

    public void sendMessage(String exchange, String message, String routingKey) throws Exception {

        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明交换机(交换机没有存储的能力,在rabbitmq中只有队列才有存储能力)
        // fanout:不处理路由键,会将消息转发给所有绑定该交换机的队列,因此将routingKey设置为""
        // direct:处理路由键,将消息转发给指定了routingKey的队列上
        channel.exchangeDeclare(exchange, "fanout");
        // 发送消息
        channel.basicPublish(exchange, "", null, message.getBytes());
        // 关闭通道
        channel.close();
        connection.close();
    }

4.2消息消费者

    public void reciveByHandleMode(String exchange, String queueName, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 绑定到交换机
        channel.queueBind(queueName, exchange, "");
        // 可绑定多个
        // channel.queueBind(queueName, exchange, routingKey);
        // channel.queueBind(queueName, exchange, routingKey);
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        // 定义消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("receiveMessage:" + message);
                // 程序确认,而不是rabbit的自动确认(手动回执)
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 监听队列,改成false则表示手动应答
        boolean autoAck = false;
        channel.basicConsume(queueName, autoAck, consumer);
    }

5.路由

5.1消息生产者

    public void sendMessage(String exchange, String message, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明交换机(交换机没有存储的能力,在rabbitmq中只有队列才有存储能力)
        // fanout:不处理路由键,会将消息转发给所有绑定该交换机的队列,因此将routingKey设置为""
        // direct:处理路由键,将消息转发给指定了routingKey的队列上
        channel.exchangeDeclare(exchange, "direct");
        // 发送消息
        channel.basicPublish(exchange, routingKey, null, message.getBytes());
        // 关闭通道
        channel.close();
        connection.close();
    }

5.2消息消费者

    /**
     * 将队列绑定到交换机
     *
     * @param exchange
     * @param queueName
     * @throws Exception
     */
    public void reciveByHandleMode(String exchange, String queueName, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 绑定到交换机
        channel.queueBind(queueName, exchange, routingKey);
        // 可绑定多个
        // channel.queueBind(queueName, exchange, routingKey);
        // channel.queueBind(queueName, exchange, routingKey);
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        // 定义消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("receiveMessage:" + message);
                // 程序确认,而不是rabbit的自动确认(手动回执)
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 监听队列,改成false则表示手动应答
        channel.basicConsume(queueName, false, consumer);
    }

6.话题

 

6.1消息生产者

    public void sendMessage(String exchange, String message, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明交换机(交换机没有存储的能力,在rabbitmq中只有队列才有存储能力)
        // fanout:不处理路由键,会将消息转发给所有绑定该交换机的队列,因此将routingKey设置为""
        // direct:处理路由键,将消息转发给指定了routingKey的队列上
        //topic:将路由和某个模式进行匹配(规则匹配)。 #:匹配一个或者多个; *:匹配一个
        channel.exchangeDeclare(exchange, "topic");
        // 发送消息routingKey,such as :goods.add
        channel.basicPublish(exchange, routingKey, null, message.getBytes());
        // 关闭通道
        channel.close();
    }

6.2消息消费者

    /**
     * 将队列绑定到交换机
     *
     * @param exchange
     * @param queueName
     * @throws Exception
     */
    public void reciveByHandleMode(String exchange, String queueName, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 绑定到交换机,发送消息routingKey,such as :goods.#
        channel.queueBind(queueName, exchange, routingKey);
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        // 定义消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("receiveMessage:" + message);
                // 程序确认,而不是rabbit的自动确认(手动回执)
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 监听队列,改成false则表示手动应答
        boolean autoAck = false;
        channel.basicConsume(queueName, autoAck, consumer);
    }

7.RPC

8.发布者确认

生产者将消息发送至broker以后,生产者并不知道消息是否真的正确达到了目标队列,rabbitMQ当然不会这么傻,它提供了两种方式,来告知生产者,消息是否正确投递出去了。他们分别是:事务机制,确认机制。

8.1事务机制

投递前开启事务,投递结束后提交事务,若发送异常,则回滚事务。

    public void createTransactionProducer(String exchange, String message, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明交换机(交换机没有存储的能力,在rabbitmq中只有队列才有存储能力)
        // fanout:不处理路由键
        // direct:处理路由键
        channel.exchangeDeclare(exchange, "topic");
        try {
            // 发送消息routingKey,such as :goods.add
            channel.txSelect();
            channel.basicPublish(exchange, routingKey, null, message.getBytes());
            channel.txCommit();
        } catch (Exception e) {
            channel.txRollback();
        } finally {
            // 关闭通道
            channel.close();
        }
    }

8.2confirm机制

生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上发布的消息都会被指派一个唯一的ID(默认从1开始),
一旦消息被投递到所匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确的到达目标队列了。
如果消息和队列是可持久化的,那么确认消息会在消息写入磁盘之后发出,broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号。
此外,broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。

8.2.1同步确认机制

    /**
     * @param exchange
     * @param message
     * @param routingKey
     * @throws Exception
     */
    public void createConfirmProducerSync(String exchange, String message, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明交换机(交换机没有存储的能力,在rabbitmq中只有队列才有存储能力)
        // fanout:不处理路由键
        // direct:处理路由键
        channel.exchangeDeclare(exchange, "topic");
        try {
            // 发送消息routingKey,such as :goods.add
            channel.confirmSelect();
            channel.basicPublish(exchange, routingKey, null, message.getBytes());
            if (!channel.waitForConfirms()) {
                System.out.println("send message fail");
            } else {
                System.out.println("send message success");
            }
        } catch (Exception e) {

        } finally {
            // 关闭通道
            channel.close();
        }
    }

8.2.2异步确认机制

    public void createConfirmProducerAsync(String exchange, String message, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();
        // 声明交换机(交换机没有存储的能力,在rabbitmq中只有队列才有存储能力)
        // fanout:不处理路由键
        // direct:处理路由键
        channel.exchangeDeclare(exchange, "topic");
        // 发送消息routingKey,such as :goods.add
        channel.confirmSelect();
        final SortedSet<Long> confirmSets = Collections.synchronizedSortedSet(new TreeSet<Long>());
        channel.addConfirmListener(new ConfirmListener() {

            // 没有问题的handleAck
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                if (multiple) {
                    confirmSets.headSet(deliveryTag + 1).clear();
                } else {
                    confirmSets.remove(deliveryTag);
                }

            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                if (multiple) {
                    confirmSets.headSet(deliveryTag + 1).clear();
                } else {
                    confirmSets.remove(deliveryTag);
                }
            }

        });

        channel.basicPublish(exchange, routingKey, null, message.getBytes());
        // 关闭通道
        channel.close();
    }

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值