1

前几篇我们介绍了如果通过RabbitMQ发布一个简单的消息,再到工作队列,多个消费者进行消费,最后再到工作队列的分发与消息的应答机制(ACK);

之前我们分享的这几种模式,都是被消费之后就从队列中被删除了,理想状态下不会被重复消费,试想我们另外一种场景,比如我之前做的小说业务,用户在登录成功后,需要将临时账户的金币和书架的书籍信息同步到正式账户。

如果我们跟登录融合在一块,登录成功之后,如果用户账户或者书架同步失败,那么势必影响我们整个登录的体验。为了更好地做到用户无感知,不需要用户做更多的操作,那么我们就使用消息队列的方式,来进行异步同步。

发布订阅模式

这就是我们一个用户数据同步的流程图,也是RabbitMQ发布订阅的流程图,大家可能注意到了中间怎么多了一个交换机

这里要注意,使用发布订阅模式,这里必须将交换机与队列进行绑定,如果不绑定,直接发送消息,这个消息是不会发送到任何队列的,更不会被消费。

交换机种类

交换机总共分四种类型:分别是direct、topic、headers、fanout。这次我们主要讲fanout,因为这是我们本次需要用到的交换机类型。

fanout顾名思义就是广播模式。它会把消息推送给所有订阅它的队列。

代码

生产者

public class Send {

    /**
     * 交换机名称
     */
    private final static String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] args) throws IOException, TimeoutException {

        // 获取连接
        Connection connection = MQConnectUtil.getConnection();

        // 创建通道
        Channel channel = connection.createChannel();

        // 声明交换机  fanout:分发模式,分裂
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

        // 消息内容
        String msg = "我是一个登录成功的消息";

        // 发送消息
        channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());

        System.out.println("消息发送成功:" + msg);

        channel.close();
        connection.close();
    }
}

消费者-同步账户

public class Consumer1 {

    /**
     * 交换机名称
     */
    private final static String EXCHANGE_NAME = "test_exchange_fanout";

    private final static String QUEUE_NAME = "test_topic_publish_account";

    public static void main(String[] args) throws IOException, TimeoutException {

        // 获取连接
        Connection connection = MQConnectUtil.getConnection();

        // 创建通道
        Channel channel = connection.createChannel();

        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 将队列绑定到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");

        // 保证一次只接收一个消息,保证rabbitMQ每次将消息发送给闲置的消费者
        channel.basicQos(1);

        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @SneakyThrows
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {

                String msg = new String(body, StandardCharsets.UTF_8);

                System.out.println("同步账户[1]:" + msg);

                Thread.sleep(1000);

                // 手动应答
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };

        // 监听队列
        channel.basicConsume(QUEUE_NAME, false, consumer);
    }
}

消费者-同步书架

public class Consumer2 {

    /**
     * 交换机名称
     */
    private final static String EXCHANGE_NAME = "test_exchange_fanout";

    private final static String QUEUE_NAME = "test_topic_publish_book_case";

    public static void main(String[] args) throws IOException, TimeoutException {

        // 获取连接
        Connection connection = MQConnectUtil.getConnection();

        // 创建通道
        Channel channel = connection.createChannel();

        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 将队列绑定到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");

        // 保证一次只接收一个消息,保证rabbitMQ每次将消息发送给闲置的消费者
        channel.basicQos(1);

        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @SneakyThrows
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {

                String msg = new String(body, StandardCharsets.UTF_8);

                System.out.println("同步书架[2]:" + msg);

                Thread.sleep(1000);

                // 手动应答
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };

        // 监听队列
        channel.basicConsume(QUEUE_NAME, false, consumer);
    }
}

总结

那么基于这样的需要同步用户数据的需求,那么为了保证各数据同步之间互不影响,降低耦合性,那么我们就可以使用多个队列,进行用户数据的同步。提升整个系统的高可用。

日拱一卒,功不唐捐

更多内容请关注


一个程序员的成长
308 声望7.3k 粉丝

日拱一卒,功不唐捐