RabbitMQ入门案例之Work模式

这篇具有很好参考价值的文章主要介绍了RabbitMQ入门案例之Work模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本文章将介绍RabbitMQ的Work模式,其中这个模式又细分为轮询分发和公平分发,本文将会用Java代码结合RabbitMQ的web管理界面进行实操演示。

官网文档地址:https://rabbitmq.com/getstarted.html

什么是Work模式

RabbitMQ的Work模式是一种简单的消息队列模式,也叫做“竞争消费者模式”或“任务分发模式”。在这种模式下,多个消费者同时监听同一个队列,当队列中有消息时,多个消费者之间会进行竞争,只有一个消费者能够获得这个消息并进行处理。其他消费者则需要等待下一个消息的到来。

工作原理是这样的:生产者向队列中发送任务消息,多个消费者同时监听队列,当队列中有消息时,RabbitMQ会将消息分发给其中的一个消费者。每个消费者都会独立处理自己分配到的任务,消费者之间的工作是平等的,不会区分谁优先谁没有优先,直到队列中的任务被消费完或者没有消费者在处理为止。

在工作模式中,RabbitMQ允许多个消费者同时监听同一个队列,并且每个消费者只能接收一条消息,这使得消息在执行过程中可以分布到多个消费者中,并且每个消费者可以执行自己的任务。这种模式广泛应用于分布式系统中的任务调度或者并行处理等场景中。

Work模式中的轮询分发

在消息队列中,轮询是指多个消费者从消息队列中获取消息的方式。消费者将以轮询的方式获取消息,也就是每个消费者按照顺序逐个接收消息。当一个消费者接收到一条消息时,它会对这个消息进行处理,然后确认已经处理完成,随后再尝试获取下一条消息,如果当前消费者在处理消息时,消息队列对其它消费者进行了轮询派发,则消息队列会将该消息重新分配给其他消费者。

比如说,消息队列中有消息 A、B、C、D、E 五条消息,同时有三个消费者(消费者 1、消费者 2 和消费者 3)在等待消息,那么轮询的过程大致如下:

  1. 消费者 1 获取消息 A,并进行处理,确认已经处理完毕,然后尝试获取下一条消息;
  2. 消费者 2 获取消息 B,并进行处理,确认已经处理完毕,然后尝试获取下一条消息;
  3. 消费者 3 获取消息 C,并进行处理,确认已经处理完毕,然后尝试获取下一条消息;
  4. 消费者 1 尝试获取下一条消息,但此时没有消息可供获取;
  5. 消费者 2 尝试获取下一条消息,但此时没有消息可供获取;
  6. 消费者 3 尝试获取下一条消息,此时消息队列轮询派发了消息 D 给消费者 3,因此消费者 3 获取消息 D 并进行处理;
  7. 消费者 1 尝试获取下一条消息,此时消息队列轮询派发了消息 E 给消费者 1,因此消费者 1 获取消息 E 并进行处理;
  8. 消费者 2 尝试获取下一条消息,但此时没有消息可供获取。

这样一直轮询下去,直到所有的消息都被处理完毕。

代码实现

创建一个maven项目,导入依赖

	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--RabbitMQ依赖-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.10.0</version>
        </dependency>
    </dependencies>

创建生产者,代码如下:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("ip地址");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            //===============================end topic模式==================================
            for (int i = 1; i <= 20; i++) {
                //消息的内容
                String msg = "学习:" + i + "号";
                // 7: 发送消息给中间件rabbitmq-server
                // @params1: 交换机exchange
                // @params2: 队列名称/routingkey
                // @params3: 属性配置
                // @params4: 发送消息的内容
                channel.basicPublish("", "queue1", null, msg.getBytes());
            }
            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

对于消费者,我们需要创建两个来进行演示轮询分发,并且这两个消费者还是指向同一个消息队列:

//work1
import com.rabbitmq.client.*;
import java.io.IOException;

public class Work1 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("ip地址");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work1");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
//            channel.queueDeclare("queue1", false, false, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(2000);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work1-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
import com.rabbitmq.client.*;
import java.io.IOException;

public class Work2 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("ip地址");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work2");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, true, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            //channel.basicQos(1);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;

            finalChannel.basicQos(1);

            finalChannel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(200);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work2-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

注意:执行上面三段代码的开始执行顺序,work1和work2先开启,后面在开始producer分发任务。可以想想为什么要这样(doge)

我们需要注意到的是,work1和work2的处理速度是不一样的,work1每次处理消息后,会使线程睡眠2秒,而work2的处理速度是0.2秒,当我们执行开始后,你会发现,work2已经结束了消息的接收,但是work1还在工作,work2不会继续到消息队列中获取消息,它是忙完就下班了!但是最终结果一定是均分的,结果如下:
RabbitMQ入门案例之Work模式RabbitMQ入门案例之Work模式
在这些work1 和 work2 的两端代码中,有一个代码是值得我们了解一下的

Channel finalChannel = channel;
finalChannel.basicQos(1);

这段代码是用来设置RabbitMQ消费者的最大并发处理量(max prefetch count),即同时从队列中预取的消息的个数。通过这个属性的设置,我们可以让RabbitMQ在可控的范围内平衡系统的负载,避免所有消息都在一瞬间被一台消费端处理完而造成其他消费端处于闲置状态的情况。

一般来讲,如果没有显式的设置 prefetch count,RabbitMQ会默认设置一个 prefetch count 值为 0,这时候它将会帮助你处理尽可能多的消息。如果你需要其他值,可以使用channel.basicQos 方法进行设置。在多个消费者队列的情况下,不同队列可能分配不同的 max prefetch count 值。

在上面的代码中,可以看到我们设置了 prefetch count 值为 1。这意味着在当前消费者从队列中预取的消息达到了 1 条之后,它需要等待确认(acknowledge)之前的消息处理完后才能预取下一条消息。这样可以避免当前消费者占用过多的 RabbitMQ 资源,也可以保证程序的稳定性。

Work模式中的公平分发

与轮询分发不同的是,公平分发是一种类似于能力越大责任越大的机制,这个分发模式下,如果某个消费者的带宽大处理速度快,处理的消息数量就多,反之亦然。简而言之,按劳分配

代码

生产者代码与轮询模式的一样,就不在放出来

消费者代码如下:

import com.rabbitmq.client.*;
import java.io.IOException;

public class Work1 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("43.139.42.244");
        connectionFactory.setPort(5678);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work1");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
//            channel.queueDeclare("queue1", false, false, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", false, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(2000);
                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work1-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
import com.rabbitmq.client.*;
import java.io.IOException;

public class Work2 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("43.139.42.244");
        connectionFactory.setPort(5678);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work2");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, true, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            //channel.basicQos(1);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", false, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(200);
                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work2-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

公平分发的代码与轮询代码不同的地方如下两张图对比

※公平分发
RabbitMQ入门案例之Work模式
※轮询分发
RabbitMQ入门案例之Work模式
这个不同地方的参数为:**autoAck,它表示的意义为是否开启自动确认模式。**如果为true,表示一旦消费端成功收到消息,就立即确认消息,RabbitMQ就会将其从队列中删除。如果为false,则需要手动确认消息。

这个参数的设置意义就是实现消息的手动确认,处理不同线程处理任务不同的耗时而导致一个线程忙死一个线程闲死的资源没有充分利用问题。使用这个手动确认,我们就可以做到“按劳分配”的机制

按照work1+work2->producer的先后,执行代码,结果如下:
RabbitMQ入门案例之Work模式
RabbitMQ入门案例之Work模式
因为work1的线程睡眠为2秒,work2为0.2秒,所以work2 能力大,处理的消息就越多。

总结

(1)当队列里消息较多时,我们通常会开启多个消费者处理消息;公平分发和轮询分发都是我们经常使用的模式。

(2)轮询分发的主要思想是“按均分配”,不考虑消费者的处理能力,所有消费者均分;这种情况下,处理能力弱的服务器,一直都在处理消息,而处理能力强的服务器,在处理完消息后,处于空闲状态;

(3)公平分发的主要思想是”能者多劳”,按需分配,能力强的干的多。文章来源地址https://www.toymoban.com/news/detail-501665.html

到这里文章就结束了,感谢阅读
如有错误,欢迎指出

到了这里,关于RabbitMQ入门案例之Work模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【RabbitMQ教程】前言 —— 中间件介绍

                                                                       💧 【 R a b b i t M Q 教程】前言——中间件介绍 color{#FF1493}{【RabbitMQ教程】前言 —— 中间件介绍} 【 R abbi tMQ 教程】前言 —— 中间件介绍 💧           🌷 仰望天空,妳

    2024年02月08日
    浏览(70)
  • RabbitMQ详解(三):消息模式(fanout、direct、topic、work)

    参考官网:https://www.rabbitmq.com/getstarted.html 简单模式 Simple, 参考RabbitMQ详解(二):消息模式 Simple(简单)模式 简单模式是最简单的消息模式,它包含一个生产者、一个消费者和一个队列。生产者向队列里发送消息,消费者从队列中获取消息并消费。 发布订阅模式 fanout 同时向

    2024年02月10日
    浏览(48)
  • RabbitMQ入门到实战一篇文章就够了

    课程内容 认识RabbitMQ 安装RabbitMQ SpringBoot使用RabbitMQ 其他特性 1.RabbitMQ认识 1.1.RabbitMQ是什么 MQ全称为Message Queue,即消息队列. 它也是一个队列,遵循FIFO原则 。RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue Protocol高级消息队列协议)协议实现的消息队列,它是一种应用程

    2024年03月09日
    浏览(50)
  • rabbitmq的介绍、使用、案例

    rabbitmq简单来说就是个消息中间件,可以让不同的应用程序之间进行异步的通信,通过消息传递来实现解耦和分布式处理。 消息队列:允许将消息发到队列,然后进行取出、处理等操作,使得生产者和消费者之间能够解耦,异步地进行通信。 持久性,可靠性的消息传递机制。

    2024年01月20日
    浏览(53)
  • RabbitMQ特性介绍和使用案例

    ❤ 作者主页:李奕赫揍小邰的博客 ❀ 个人介绍:大家好,我是李奕赫!( ̄▽ ̄)~* 🍊 记得点赞、收藏、评论⭐️⭐️⭐️ 📣 认真学习!!!🎉🎉   RabbitMQ特性 AMQP (高级消息队列协议) 是一个异步消息传递所使用的应用层协议规范,作为线路层协议,而不是API(例如JMS),

    2024年02月11日
    浏览(32)
  • RabbitMQ--04--发布订阅模式 (fanout)-案例

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 @RabbitListener和@RabbitHandler的使用 OrderService OrderServiceImpl 在项目的test中发送请求 访问网址: http://localhost:15672/#/queues yml配置 SmsConsumerService、SmsConsumerServiceImpl EmailConsumerService、EmailConsumerServiceImpl DuanxinCo

    2024年04月14日
    浏览(44)
  • RabbitMQ的SpringAMQP的各种模式的案例

    目录 Basic Queue 简单队列模型  任务模型(Work queues,也被称为(Task queues))  发布/订阅的广播(Fanout)模式  发布/订阅的定向(Direct)模式 发布订阅的通配(Topic)模式 导入依赖 配置yml 消息发送  消息接收  消息发送  消息接收  交换机 消息发送  消息接收 交换机  消

    2024年02月04日
    浏览(34)
  • RabbitMQ:work结构

    只需要在消费者端,添加Qos能力以及更改为手动ack即可让消费者,根据自己的能力去消费指定的消息,而不是默认情况下由RabbitMQ平均分配了,生产者不变,正常发布消息到默认的exchange  消费者指定Qoa和手动ack

    2024年02月09日
    浏览(70)
  • RabbitMQ ---- Work Queues

    工作队列(又称任务队列)的注销思想是避免立即执行资源密集型任务,而不得不等待它完成。相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时,这些工作线程将一起处理这些

    2024年02月15日
    浏览(68)
  • rabbitMQ Work Queues

    工作队列(又称任务队列)的主要思想是避免立即执行资源密集型任务,而不得不等待它完成。相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。 当有多个工作线程时,这些工作线程将一起处理这些任

    2024年01月24日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包