RabbitMQ高级特性

目录

前言

数据持久化原理

1. 数据持久化概述

2. 消息持久化

3. 队列持久化

4. 磁盘与内存持久化

RabbitMQ高级特性

1. 惰性队列(Lazy Queues)

2. 优先级队列(Priority Queues)

3. 死信队列(Dead Letter Exchanges, DLX)

4. 消息的延迟与 TTL(Time-To-Live)

5. 发布确认模式(Publisher Confirms)

6. 流控(Flow Control)

示例代码:观察流控状态

总结


前言

掌握 RabbitMQ 的数据持久化机制是理解其高级特性的基础。持久化机制提供了数据的可靠性保障、性能优化的依据、以及系统恢复的能力。在应用高级特性时,了解数据持久化机制可以帮助你做出更明智的配置决策、优化系统性能,并有效处理故障。希望大家可以多多给支持基于指正交流。

数据持久化原理

1. 数据持久化概述

RabbitMQ 的数据持久化机制确保消息在 RabbitMQ 服务器崩溃或重启后不会丢失。持久化是通过将消息和队列的元数据存储到磁盘来实现的。RabbitMQ 支持多种持久化机制,包括消息持久化和队列持久化。

2. 消息持久化
  • 定义: 消息持久化指的是将消息内容写入磁盘,以确保在 RabbitMQ 服务器崩溃后消息不会丢失。

  • 工作原理

    • 消息持久化设置:在生产者发送消息时,可以设置消息的 delivery_mode 属性为 2,表示消息是持久化的。
    • 存储过程
      • 当消息被标记为持久化时,RabbitMQ 将消息内容写入磁盘上的日志文件,而不是仅仅存储在内存中。
      • 消息在磁盘上持久化后,即使 RabbitMQ 崩溃,消息也不会丢失。消息会在 RabbitMQ 重启后重新加载到内存中。
  • 优缺点

    • 优点
      • 数据安全:确保消息不会因 RabbitMQ 崩溃而丢失,提高系统的可靠性和容错性。
      • 持久性:适用于需要确保消息在系统崩溃后的恢复的应用场景。
    • 缺点
      • 性能开销:写入磁盘的操作比仅存储在内存中要慢,可能会影响系统的吞吐量。
      • 磁盘 I/O:增加了磁盘 I/O 操作,可能会导致磁盘负载增加。
  • 配置方式

    • 在 RabbitMQ 客户端发布消息时,将 delivery_mode 属性设置为 2
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
    .deliveryMode(2)  // 2 表示持久化消息
    .build();
channel.basicPublish(exchange, routingKey, properties, message.getBytes());
3. 队列持久化
  • 定义: 队列持久化指的是将队列的定义和元数据存储到磁盘,以确保队列在 RabbitMQ 重启后能够恢复。

  • 工作原理

    • 队列持久化设置:在声明队列时,可以将队列的持久化属性设置为 true。这表示队列的元数据(如队列的定义)将存储到磁盘。
    • 存储过程
      • 当队列被声明为持久化时,队列的元数据(包括队列的名称、类型、持久化标志等)会被写入到磁盘上的数据库文件中。
      • 当 RabbitMQ 重启时,会从磁盘加载队列的定义,确保队列及其配置在重启后得以恢复。
  • 优缺点

    • 优点
      • 持久性:确保队列在 RabbitMQ 重启后能够恢复,防止因崩溃或重启导致的队列丢失。
      • 数据一致性:确保队列的元数据在系统崩溃后不会丢失,提高系统的可靠性。
    • 缺点
      • 性能开销:持久化操作会影响性能,尤其是在大量队列的情况下,可能会导致启动和恢复时间增加。
      • 存储需求:增加磁盘存储需求,可能需要更多的磁盘空间。
  • 配置方式

    • 在声明队列时,将 durable 属性设置为 true
channel.queueDeclare(queueName, true, false, false, null);

参数解释

  1. queueName (String):

    • 定义:队列的名称。
    • 作用:用于标识队列。所有操作(如消息发布、消费)都需要通过队列名称来进行。
  2. durable (boolean):

    • 定义:指定队列是否持久化。
    • 作用:如果设置为 true,则队列会被持久化到磁盘,RabbitMQ 在重启后会恢复这个队列。持久化的队列可以防止数据丢失,但可能会有性能开销。
    • 示例true 表示队列持久化;false 表示队列在服务器重启后不会恢复。
  3. exclusive (boolean):

    • 定义:指定队列是否为独占的。
    • 作用:如果设置为 true,则队列只对当前连接有效,连接关闭时队列会被自动删除。这通常用于临时队列。
    • 示例true 表示队列为独占;false 表示队列可以被多个连接共享。
  4. autoDelete (boolean):

    • 定义:指定队列是否为自动删除的。
    • 作用:如果设置为 true,则在没有消费者连接到该队列时,队列会自动删除。这通常用于临时队列。
    • 示例true 表示队列在没有消费者连接时会自动删除;false 表示队列不会自动删除,必须手动删除。
  5. arguments (Map<String, Object>):

    • 定义:用于指定队列的额外参数,如 TTL、死信队列等。
    • 作用:可以用来配置队列的特殊行为。通常用于设置队列的消息 TTL、死信交换机等。
    • 示例null 表示没有额外参数。如果需要设置额外参数,可以传递一个包含这些参数的 Map 对象如:
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-message-ttl", 60000);  // 设置消息 TTL 为 60000 毫秒
arguments.put("x-dead-letter-exchange", "dlx_exchange");  // 设置死信交换机
channel.queueDeclare(queueName, true, false, false, arguments);
4. 磁盘与内存持久化
  • 磁盘持久化

    • 定义:数据持久化到磁盘上,确保消息和队列在系统重启后能够恢复。
    • 优点
      • 数据持久性:提高了数据的可靠性和容错性。
    • 缺点
      • 性能:写入磁盘的操作比内存操作慢,影响系统吞吐量。
  • 内存持久化

    • 定义:数据存储在内存中,仅在 RabbitMQ 运行时有效。
    • 优点
      • 性能:内存操作速度快,能够提供高吞吐量。
    • 缺点
      • 数据丢失:系统崩溃时,存储在内存中的数据将丢失。

RabbitMQ高级特性

1. 惰性队列(Lazy Queues)
  • 定义: 惰性队列是一种将消息主要存储在磁盘上的队列类型,而不是内存中,只有在消费者请求时才从磁盘加载消息到内存中。

  • 工作原理

    • 消息存储:消息在写入队列时,首先被写入到磁盘上的日志文件。消息不立即加载到内存中。
    • 消息加载:当消费者需要处理消息时,RabbitMQ 从磁盘中读取消息并加载到内存中进行处理。
  • 优缺点

    • 优点
      • 内存节省:有效减少内存占用,适合处理大量消息。
      • 系统稳定性:降低因内存溢出而导致的系统崩溃风险。
    • 缺点
      • 性能影响:读取消息时需要从磁盘加载,可能导致消息处理延迟。
      • 磁盘 I/O:增加磁盘 I/O 操作,可能会影响性能。
  • 应用场景

    • 大规模数据处理系统,如日志系统、大数据处理任务等需要处理大量消息的场景。
  • 配置方式

    • 创建惰性队列时,通过设置 x-queue-mode 参数为 lazy
rabbitmqadmin declare queue name=my_lazy_queue arguments='{"x-queue-mode":"lazy"}'

Java 代码示例: 使用 RabbitMQ Java 客户端库创建惰性队列:

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

public class CreateLazyQueue {
    private final static String QUEUE_NAME = "my_lazy_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("123456");

        try (Connection connection = factory.newConnection(); 
             Channel channel = connection.createChannel()) {
            // 设置队列的属性
            channel.queueDeclare(QUEUE_NAME, true, false, false, 
                Map.of("x-queue-mode", "lazy"));
            System.out.println("Queue declared with lazy mode.");
        }
    }
}
2. 优先级队列(Priority Queues)
  • 定义: 优先级队列允许为队列中的消息设置不同的优先级,RabbitMQ 会优先处理优先级高的消息。

  • 工作原理

    • 优先级管理:消息通过 priority 属性设置优先级,队列中的消息按照优先级排序。
    • 队列设置:在队列创建时定义最大优先级值 x-max-priority,消息的优先级必须在此范围内。
  • 优缺点

    • 优点
      • 优先处理:允许根据消息重要性进行优先处理,提升关键任务的响应速度。
    • 缺点
      • 复杂性:优先级机制增加了队列的复杂性,处理逻辑可能更复杂。
      • 性能影响:优先级队列的排序可能会对性能产生影响,尤其是优先级较高的消息较多时。
  • 应用场景

    • 实时数据处理、任务调度系统、需要优先处理重要任务的场景。
  • 配置方式

    • 创建队列时设置 x-max-priority 属性,发送消息时设置 priority 属性。
rabbitmqadmin declare queue name=my_priority_queue arguments='{"x-max-priority":10}'

Java 代码示例: 使用 RabbitMQ Java 客户端库创建优先级队列:

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

public class CreatePriorityQueue {
    private final static String QUEUE_NAME = "my_priority_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("123456");

        try (Connection connection = factory.newConnection(); 
             Channel channel = connection.createChannel()) {
            // 设置队列的属性
            channel.queueDeclare(QUEUE_NAME, true, false, false, 
                Map.of("x-max-priority", 10));
            System.out.println("Queue declared with priority mode.");
        }
    }
}

发布优先级消息的代码:

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

public class PublishPriorityMessage {
    private final static String QUEUE_NAME = "my_priority_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("123456");
        
        try (Connection connection = factory.newConnection(); 
             Channel channel = connection.createChannel()) {
            // 发布消息并设置优先级
            channel.basicPublish("", QUEUE_NAME, 
                new AMQP.BasicProperties.Builder()
                    .priority(5)
                    .build(), 
                "Important message".getBytes());
            System.out.println("Message published with priority.");
        }
    }
}
3. 死信队列(Dead Letter Exchanges, DLX)
  • 定义: 死信队列用于处理因过期、队列满或消费者处理失败等原因无法正常处理的消息。这些消息被转发到指定的死信交换机,然后存储到一个或多个死信队列中。

  • 工作原理

    • 消息转发:当消息因无法处理(例如 TTL 到期、队列满、处理失败)时,会被转发到死信交换机。
    • 死信队列:消息被转发到配置的死信队列,便于后续处理或审查。
  • 优缺点

    • 优点
      • 容错处理:允许集中处理失败的消息,有助于错误排查和恢复。
      • 消息管理:避免消息丢失,使系统更健壮。
    • 缺点
      • 配置复杂性:需要额外配置死信交换机和队列,增加系统复杂性。
      • 管理开销:需要额外的逻辑来处理死信队列中的消息。
  • 应用场景

    • 消息处理失败后的重试、审查或记录,系统中出现错误时进行集中处理。
  • 配置方式

    • 在队列中设置 x-dead-letter-exchange 属性,指定死信交换机。
rabbitmqadmin declare queue name=my_queue arguments='{"x-dead-letter-exchange":"my_dl_exchange"}'
rabbitmqadmin declare exchange name=my_dl_exchange type=direct
rabbitmqadmin declare queue name=my_dl_queue
rabbitmqadmin declare binding source=my_dl_exchange destination=my_dl_queue

Java 代码示例: 设置死信队列:

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

public class CreateDLXQueue {
    private final static String QUEUE_NAME = "my_queue";
    private final static String DLX_EXCHANGE = "my_dl_exchange";
    private final static String DLX_QUEUE = "my_dl_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("123456");


        try (Connection connection = factory.newConnection(); 
             Channel channel = connection.createChannel()) {
            // 创建死信交换机和队列
            channel.exchangeDeclare(DLX_EXCHANGE, "direct");
            channel.queueDeclare(DLX_QUEUE, true, false, false, null);
            channel.queueBind(DLX_QUEUE, DLX_EXCHANGE, "dl_key");

            // 创建原队列并设置死信交换机
            channel.queueDeclare(QUEUE_NAME, true, false, false, 
                Map.of("x-dead-letter-exchange", DLX_EXCHANGE));
            System.out.println("Queue with DLX set up.");
        }
    }
}
4. 消息的延迟与 TTL(Time-To-Live)
  • 消息 TTL

    • 定义:消息 TTL 指定消息在队列中的生存时间,超过 TTL 的消息会被自动删除或转发到死信队列。
    • 工作原理:消息创建时设置 TTL 属性,TTL 到期后消息会被丢弃或发送到死信队列(如果配置了)。
    • 优缺点
      • 优点
        • 自动清理:自动清理过期消息,保持队列的整洁。
      • 缺点
        • 复杂性:需要合理配置 TTL 和死信队列,避免消息丢失。
    • 应用场景:处理过期数据、自动清理过时消息。
  • 延迟消息

    • 定义:延迟消息允许在消息发送时指定一个延迟时间,消息在延迟时间结束后才会进入队列。
    • 工作原理:RabbitMQ 本身不直接支持延迟消息,但可以通过插件(如 rabbitmq_delayed_message_exchange)来实现。
    • 优缺点
      • 优点
        • 任务调度:用于任务调度或延迟通知。
      • 缺点
        • 额外插件:需要安装和配置额外的插件,增加系统复杂性。
  • 配置方式

    • 设置消息 TTL:
rabbitmqadmin declare queue name=my_ttl_queue arguments='{"x-message-ttl":60000}'

使用插件设置延迟消息(需要安装 rabbitmq_delayed_message_exchange 插件)。

  • Java 代码示例: 设置消息 TTL:

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

public class CreateTTLQueue {
    private final static String QUEUE_NAME = "my_ttl_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("123456");


        try (Connection connection = factory.newConnection(); 
             Channel channel = connection.createChannel()) {
            // 创建 TTL 队列
            channel.queueDeclare(QUEUE_NAME, true, false, false, 
                Map.of("x-message-ttl", 60000));
            System.out.println("Queue with TTL set up.");
        }
    }
}

发布延迟消息(假设使用了插件):

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

public class PublishDelayedMessage {
    private final static String EXCHANGE_NAME = "delayed_exchange";
    private final static String QUEUE_NAME = "my_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("123456");

        try (Connection connection = factory.newConnection(); 
             Channel channel = connection.createChannel()) {
            // 创建延迟交换机
            channel.exchangeDeclare(EXCHANGE_NAME, "x-delayed-message", 
                Map.of("x-delayed-type", "direct"));
            channel.queueDeclare(QUEUE_NAME, true, false, false, null);
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "routing_key");

            // 发布延迟消息
            AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                .headers(Map.of("x-delay", 60000))
                .build();
            channel.basicPublish(EXCHANGE_NAME, "routing_key", properties, 
                "Delayed message".getBytes());
            System.out.println("Delayed message published.");
        }
    }
}
5. 发布确认模式(Publisher Confirms)
  • 定义: 发布确认模式用于确保生产者发布的消息确实被 RabbitMQ 接收到。生产者在发布消息后,可以获得 RabbitMQ 的确认或否定回应。

  • 工作原理

    • 确认机制:生产者发布消息后,RabbitMQ 会异步发送确认消息给生产者,确认消息已经持久化到磁盘。如果消息发布失败,RabbitMQ 会发送否定响应。
    • 异步操作:发布确认是异步操作,生产者可以通过回调或同步等待的方式接收确认响应。
  • 优缺点

    • 优点
      • 数据可靠性:确保消息确实被 RabbitMQ 接收到,防止消息丢失。
    • 缺点
      • 性能开销:增加额外的网络开销和延迟,特别是在高吞吐量场景中。
  • 应用场景

    • 高可靠性系统,要求确认消息成功传递的场景,如金融交易系统、重要通知系统。
  • 配置方式

    • 在创建 RabbitMQ 连接时启用发布确认模式。

Java 代码示例: 启用发布确认模式:

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

public class PublisherConfirmsExample {
    private final static String EXCHANGE_NAME = "my_exchange";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("123456");
        try (Connection connection = factory.newConnection(); 
             Channel channel = connection.createChannel()) {
            // 启用发布确认模式
            channel.confirmSelect();
            
            // 发布消息
            channel.basicPublish(EXCHANGE_NAME, "", null, "Message".getBytes());
            // 等待确认
            if (channel.waitForConfirms()) {
                System.out.println("Message confirmed.");
            } else {
                System.out.println("Message not confirmed.");
            }
        }
    }
}
6. 流控(Flow Control)
  • 定义: 流控用于管理消息流入和流出 RabbitMQ 队列的速率,以防止系统过载。

  • 工作原理

    • 控制机制:RabbitMQ 在系统负载过高时,会进行流控,限制生产者发布消息的速率或消费者拉取消息的速率。
    • 流控信号:RabbitMQ 会向生产者发送流控信号,通知其减少消息发布速率。
  • 优缺点

    • 优点
      • 系统稳定性:防止系统因负载过高而崩溃,保持系统的稳定性。
    • 缺点
      • 性能影响:流控可能导致消息处理延迟,影响系统的吞吐量。
  • 应用场景

    • 高负载场景,如大规模数据处理系统,防止因负载过高导致系统性能下降。
  • 配置方式

    • RabbitMQ 的流控机制是自动的,不需要额外配置。可以通过监控系统指标(如内存使用、队列长度)来调整 RabbitMQ 的配置。
    • RabbitMQ 的流控机制主要由系统自动处理,但可以通过一些配置和监控来管理和优化流控的行为:

    • 配置 RabbitMQ 资源限制

      • 内存限制:设置 RabbitMQ 的内存限制,可以间接影响流控行为。当 RabbitMQ 的内存使用超过设置的阈值时,系统会触发流控来减少消息的处理速度。
      • 磁盘空间限制:设置磁盘空间限制,确保 RabbitMQ 在磁盘空间不足时能够进行流控。

      这些设置通常通过 RabbitMQ 的配置文件 rabbitmq.conf 来配置。例如:

# rabbitmq.conf

# 设置内存高水位线(默认值为 0.4)
vm_memory_high_watermark = 0.4

# 设置内存低水位线(默认值为 0.3)
vm_memory_high_watermark_paging_ratio = 0.3

# 设置磁盘空间高水位线(默认值为 0.5)
disk_free_limit = 500MB
  • 上述配置表示当内存使用超过 40% 时触发流控,内存使用降低到 30% 时恢复流控,磁盘空间低于 500MB 时也会触发流控。

  • 生产者流控

    • 在生产者端,可以使用流控机制的回调来处理流控状态。例如,RabbitMQ 客户端库通常提供了流控事件的回调接口来处理流控信号。
  • 流控的观察
    • RabbitMQ 提供了多种方式来观察流控状态和相关指标:
  • RabbitMQ 管理插件

    打开管理插件 Web 界面,通常可以通过 http://localhost:15672 访问,登录后进入“Overview”或“Queues”面板查看相关信息。

    • RabbitMQ 提供了管理插件,可以通过 Web 界面实时观察流控状态。在管理插件中,你可以查看以下信息:
      • 内存使用情况:在“Overview”面板中,可以看到当前的内存使用情况和流控状态。
      • 磁盘使用情况:可以查看当前的磁盘空间使用情况和流控状态。
      • 队列长度:观察队列的消息数量和流控对消息处理的影响。
  • RabbitMQ 的命令行工具

    • 使用 rabbitmq-diagnostics 命令可以获取系统的诊断信息,包括流控状态。例如:

rabbitmq-diagnostics -q memory
  • 可以用来检查当前的内存使用情况。

  • 使用 rabbitmqctl 工具查看队列信息和流控状态:

rabbitmqctl status
    • 这将显示 RabbitMQ 的当前状态,包括内存和磁盘使用情况等。

  • 监控系统

    • 将 RabbitMQ 集成到监控系统中,如 Prometheus、Grafana,可以通过这些监控工具获取实时的流控指标和图表。

    配置监控系统需要:

    • RabbitMQ Exporter:安装 RabbitMQ Exporter,并将其配置为与 Prometheus 集成。
    • Grafana Dashboards:使用 Grafana 创建仪表盘来可视化 RabbitMQ 的监控数据,包括流控状态。
示例代码:观察流控状态

虽然 RabbitMQ 的流控机制是自动处理的,但可以通过以下示例代码来处理生产者流控事件(假设使用 Java 客户端):

import com.rabbitmq.client.*;

public class ProducerWithFlowControl {
    private final static String EXCHANGE_NAME = "my_exchange";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("123456");

        try (Connection connection = factory.newConnection(); 
             Channel channel = connection.createChannel()) {
            // 启用发布确认模式
            channel.confirmSelect();

            // 发布消息
            for (int i = 0; i < 1000; i++) {
                channel.basicPublish(EXCHANGE_NAME, "", null, ("Message " + i).getBytes());

                // 等待确认
                if (channel.waitForConfirms()) {
                    System.out.println("Message " + i + " confirmed.");
                } else {
                    System.out.println("Message " + i + " not confirmed.");
                }
            }
        }
    }
}

上述代码在发布每条消息后会等待 RabbitMQ 的确认响应。如果系统处于流控状态,可能会导致确认延迟或失败,这可以作为观察流控的一种方式。


总结

了解 RabbitMQ 的数据持久化机制是理解其高级特性的基础。数据持久化确保了消息和队列的可靠性,而高级特性如队列模式、死信队列、消息 TTL、发布确认模式和流控则提供了额外的功能和优化,帮助满足不同的应用场景需求。掌握这些机制和特性有助于提高 RabbitMQ 的可靠性、性能和灵活性。

  • 27
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值