Flink 内容分享(一):Fink原理、实战与性能优化(一)_flink原理、实战与性能优化

Hadoop是一个开源的分布式存储和计算框架,最初由Apache开发,用于处理大规模数据集。Hadoop的核心组件包括:

  1. Hadoop Distributed File System(HDFS): HDFS是一种分布式文件系统,用于存储大规模数据。它将数据分成多个块,并将这些块分散存储在集群中的不同节点上。HDFS支持高可靠性、冗余存储和数据复制。
  2. MapReduce: MapReduce是Hadoop的计算模型,用于处理分布式数据。它将计算任务分成Map和Reduce两个阶段,分布在集群中的节点上并行执行。Map阶段负责数据的拆分和处理,Reduce阶段负责数据的汇总和计算。
YARN (Yet Another Resource Negotiator)

YARN是Hadoop的资源管理器,它负责集群资源的管理和分配。YARN将集群资源划分为容器(Containers),并分配给不同的应用程序。这种资源的隔离和管理允许多个应用程序同时在同一个Hadoop集群上运行,从而提高了资源利用率和集群的多租户能力。

Spark:

Apache Spark是一个通用的分布式计算引擎,旨在提供高性能、易用性和多功能性。与传统的Hadoop MapReduce相比,Spark具有更快的执行速度,因为它将数据加载到内存中并进行内存计算。Spark支持多种计算模式,包括批处理、交互式查询、流处理和机器学习。

Spark的主要特点和组件包括:

  1. RDD(Resilient Distributed Dataset): RDD是Spark的核心数据抽象,表示分布式的数据集。RDD支持并行操作和容错性,可以在计算过程中重新计算丢失的分区。
  2. Spark SQL: Spark SQL是用于处理结构化数据的组件,支持SQL查询和操作。它能够将RDD和传统的数据源(如Hive)无缝集成。
  3. Spark Streaming: Spark Streaming是用于处理实时流数据的模块,支持微批处理模式。它能够将实时数据流分割成小批次并进行处理。
  4. MLlib: MLlib是Spark的机器学习库,提供了常见的机器学习算法和工具,用于训练和评估模型。
  5. GraphX: GraphX是Spark的图计算库,用于处理图数据和图算法。
Flink:

Apache Flink是一个流式处理引擎和分布式批处理框架,具有低延迟、高吞吐量和容错性。Flink支持流批一体化,能够实现实时流处理和批处理作业的无缝切换。它的核心特点包括:

  1. DataStream API: Flink的DataStream API用于处理实时流数据,支持事件时间处理、窗口操作和状态管理。它能够处理高吞吐量的实时数据流。
  2. DataSet API: Flink的DataSet API用于批处理作业,类似于Hadoop的MapReduce。它支持丰富的操作符和优化技术。
  3. Stateful Stream Processing: Flink支持有状态的流式处理,可以在处理过程中保存和管理状态。这对于实现复杂的数据处理逻辑很有用。
  4. Event Time Processing: Flink支持事件时间处理,能够处理乱序事件并准确计算窗口操作的结果。
  5. Table API和SQL: Flink提供了Table API和SQL查询,使开发人员可以使用类似SQL的语法来查询和分析数据。
  6. 可以连接大数据生态圈各类组件,包括Kafka、Elasticsearch、JDBC、HDFS和Amazon S3
  7. 可以运行在Kubernetes、YARN、Mesos和独立(Standalone)集群上。

Flink在流处理上的几个主要优势如下:

  1. 真正的流计算引擎:Flink具有更好的streaming计算模型,可以进行非常高效的状态运算和窗口操作。Spark Streaming仍然是微批处理引擎。
  2. 更低延迟:Flink可以实现毫秒级的低延迟处理,而Spark Streaming延迟较高。
  3. 更好的容错机制:Flink支持更细粒度的状态管理和检查点机制,可以实现精确一次的状态一致性语义。Spark较难做到确保exactly once。
  4. 支持有限数据流和无限数据流:Flink可处理有开始和结束的有限数据流,也能处理无限不断增长的数据流。Spark Streaming更适合有限数据集。
  5. 更易统一批处理和流处理:Flink提供了DataStream和DataSet API,可以轻松统一批处理和流处理。Spark需要联合Spark SQL使用。
  6. 更优秀的内存管理:Flink具有自己的内存管理,可以根据不同查询优化内存使用。Spark依赖Hadoop YARN进行资源调度。
  7. 更高性能:在部分场景下,Flink拥有比Spark Streaming更高的吞吐和低的延迟。

总体来说,Flink作为新一代流处理引擎,在延迟、容错、易用性方面优于Spark Streaming。但Spark生态更加完善,也在努力减小与Flink的差距。需要根据具体场景选择最优的框架。

总的来说,Flink在流处理领域的优势主要体现在事件时间处理、低延迟、精确一次语义和状态管理等方面。这些特性使得Flink在处理实时流数据时能够更好地满足复杂的业务需求,特别是对于需要高准确性和可靠性的应用场景。

Flink 部署

Apache Flink在1.7版本中进行了重大的架构重构,引入了Master-Worker架构,这使得Flink能够更好地适应不同的集群基础设施,包括Standalone、Hadoop YARN和Kubernetes等。下面会详细介绍一下Flink 1.7版本引入的Master-Worker架构以及其在不同集群基础设施中的适应性。

Master-Worker架构:

Flink 1.7版本中引入的Master-Worker架构是为了解决之前版本中存在的一些问题,如资源管理、高可用性等。在这个架构中,Flink将任务管理和资源管理分离,引入了JobManager和ResourceManager两个主要角色。

  • JobManager: 负责接受和调度任务,维护任务的状态和元数据信息,还负责处理容错机制。JobManager分为两种:JobManager(高可用模式)和StandaloneJobManager(非高可用模式)。
  • ResourceManager: 负责管理集群中的资源,包括分配任务的资源、维护资源池等。

这种架构的优势在于解耦任务的管理和资源的管理,使得Flink能够更好地适应不同的集群环境和基础设施。

兼容性:

Flink的Master-Worker架构设计使其能够兼容几乎所有主流信息系统的基础设施,包括:

  • Standalone集群: 在Standalone模式下,Flink的JobManager和ResourceManager都运行在同一个进程中,适用于简单的开发和测试场景。
  • Hadoop YARN集群: Flink可以部署在现有的Hadoop YARN集群上,通过ResourceManager与YARN ResourceManager进行交互,实现资源管理。
  • Kubernetes集群: Flink还支持在Kubernetes集群中部署,通过Kubernetes提供的资源管理能力来管理任务和资源。

这种兼容性使得Flink可以灵活地在不同的集群环境中运行,满足不同场景下的需求。

总之,Flink在1.7版本中引入的Master-Worker架构使其在资源管理、高可用性等方面有了更好的表现,同时也使得Flink能够更好地适应各种不同的集群基础设施,包括Standalone、Hadoop YARN和Kubernetes等。这为Flink的部署和使用带来了更多的灵活性和选择性。

Standalone集群是Apache Flink中一种简单的部署模式,适用于开发、测试和小规模应用场景。下面我将详细介绍Standalone集群的特点以及部署方式。

Standalone集群的特点:

  1. 简单部署: Standalone集群是Flink的最简单部署模式之一,不需要依赖其他集群管理工具,可以在单个机器上部署。
  2. 资源共享: Standalone集群中的JobManager和TaskManager共享同一份资源,例如内存和CPU。这使得资源管理相对简单,但也可能在资源竞争时影响任务的性能。
  3. 适用于开发和测试: Standalone集群适用于开发和测试阶段,可以在本地机器上模拟Flink集群环境,方便开发人员进行调试和测试。
  4. 不支持高可用性: Standalone集群默认情况下不支持高可用性,即不具备故障恢复和任务迁移的能力。如果需要高可用性,可以通过运行多个JobManager实例来实现。
Standalone集群的部署方式:
  1. 安装Flink: 首先,需要下载并安装Flink。可以从官方网站下载预编译的二进制文件,解压到指定目录。也可以从以下网站下载:

apache-flink安装包下载_开源镜像站-阿里云 (aliyun.com)(https://mirrors.aliyun.com/apache/flink/)

图片

  1. 配置Flink: 进入Flink的安装目录,修改conf/flink-conf.yaml配置文件。主要配置项包括jobmanager.rpc.addresstaskmanager.numberOfTaskSlots等。
  2. 启动JobManager: 打开终端,进入Flink安装目录,执行以下命令启动JobManager:
./bin/start-cluster.sh

  1. 启动TaskManager: 打开终端,进入Flink安装目录,执行以下命令启动TaskManager:
./bin/taskmanager.sh start

  1. 提交作业: 使用Flink客户端工具提交作业。可以使用以下命令提交JAR文件中的作业:
./bin/flink run -c your.main.Class ./path/to/your.jar

  1. 停止集群: 可以使用以下命令停止整个Standalone集群:
./bin/stop-cluster.sh

总之,Standalone集群是一个简单且易于部署的Flink集群模式,适用于开发、测试和小规模应用场景。然而,由于其资源共享和不支持高可用性的特点,不适合部署在生产环境中。

下面提供利用Docker部署flink standalone简单集群。

Docker部署flink简单集群

Flink程序可以作为集群内的分布式系统运行,也可以以独立模式或在YARN、Mesos、基于Docker的环境和其他资源管理框架下进行部署。

1、在服务器创建flink目录
mkdir flink

目录的结构如下:

2、docker-compose.yml脚本创建

docker 容器的编排文件,具体如下

3、启动flink

(1)后台运行

一般推荐生产环境下使用该选项。

docker-compose up -d

(2)前台运行

控制台将会同时打印所有容器的输出信息,可以很方便进行调试。

docker-compose up

4、浏览器上查看页面dashboard

访问web界面

http://cdh1:8081/

Flink快速应用

通过一个单词统计的案例,快速上手应用Flink,进行流处理(Streaming)和批处理(Batch)

实操1:单词统计案例(批数据)
1.1 需求

统计一个文件中各个单词出现的次数,把统计结果输出到文件

步骤:1、读取数据源 2、处理数据源

a、将读到的数据源文件中的每一行根据空格切分

b、将切分好的每个单词拼接1

c、根据单词聚合(将相同的单词放在一起)

d、累加相同的单词(单词后面的1进行累加)

3、保存处理结果

1.2 代码实现
  • 引入依赖
<!--flink核心包-->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-java</artifactId>
    <version>1.7.2</version>
</dependency>
<!--flink流处理包-->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-streaming-java_2.12</artifactId>
    <version>1.7.2</version>
    <scope>provided</scope>
</dependency> 

  • Java程序
package com.crazymaker.bigdata.wordcount.batch;

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.operators.AggregateOperator;
import org.apache.flink.api.java.operators.FlatMapOperator;
import org.apache.flink.api.java.operators.UnsortedGrouping;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.util.Collector;

/**
   * 1、读取数据源
   * 2、处理数据源
   *  a、将读到的数据源文件中的每一行根据空格切分
   *  b、将切分好的每个单词拼接1
   *  c、根据单词聚合(将相同的单词放在一起)
   *  d、累加相同的单词(单词后面的1进行累加)
   * 3、保存处理结果
   */
public class WordCountJavaBatch {
    public static void main(String[] args) throws Exception {
        String inputPath="D:\\data\\input\\hello.txt";
        String outputPath="D:\\data\\output\\hello.txt";

        //获取flink的运行环境
        ExecutionEnvironment executionEnvironment = ExecutionEnvironment.getExecutionEnvironment();
        DataSet<String> text = executionEnvironment.readTextFile(inputPath);
        FlatMapOperator<String, Tuple2<String, Integer>> wordOndOnes = text.flatMap(new SplitClz());

        //0代表第1个元素
        UnsortedGrouping<Tuple2<String, Integer>> groupedWordAndOne = wordOndOnes.groupBy(0);
        //1代表第1个元素
        AggregateOperator<Tuple2<String, Integer>> out = groupedWordAndOne.sum(1);

        out.writeAsCsv(outputPath, "\n", " ").setParallelism(1);//设置并行度
        executionEnvironment.execute();//人为调用执行方法

    }

    static class SplitClz implements FlatMapFunction<String,Tuple2<String,Integer>>{

        public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {
            String[] s1 = s.split(" ");
            for (String word:s1) {
                collector.collect(new Tuple2<String,Integer>(word,1));//发送到下游

            }

        }
    }
}

源文件的内容

统计的结果

图片

实操2:单词统计案例(流数据)

nc

netcat:

flink开发时候,经常用socket作为source;使用linux/mac环境开发,可以在终端中开启 nc -l 9000(开启netcat程序,作为服务端,发送数据);

nc是netcat的缩写,有着网络界的瑞士军刀美誉。因为它短小精悍、功能实用,被设计为一个简单、可靠的网络工具。

nc作用

  • 数据传输
  • 文件传输
  • 机器之间网络测速
2.1 需求

Socket模拟实时发送单词,

使用Flink实时接收数据,对指定时间窗口内(如5s)的数据进行聚合统计,每隔1s汇总计算一次,并且把时间窗口内计算结果打印出来。

2.2 代码实现
package com.crazymaker.bigdata.wordcount.stream;

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;

/**
 *  Socket模拟实时发送单词,使用Flink实时接收数据
 */
public class WordCountStream {

    public static void main(String[] args) throws Exception {
        // 监听的ip和端口号,以main参数形式传入,约定第一个参数为ip,第二个参数为端口
//        String ip = args[0];
        String ip = "127.0.0.1";
//        int port = Integer.parseInt(args[1]);
        int port = 9000;
        // 获取Flink流执行环境
        StreamExecutionEnvironment streamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment();
        // 获取socket输入数据
        DataStreamSource<String> textStream = streamExecutionEnvironment.socketTextStream(ip, port, "\n");

        SingleOutputStreamOperator<Tuple2<String, Long>> tuple2SingleOutputStreamOperator = textStream.flatMap(new FlatMapFunction<String, Tuple2<String, Long>>() {
            public void flatMap(String s, Collector<Tuple2<String, Long>> collector) throws Exception {
                String[] splits = s.split("\\s");
                for (String word : splits) {
                    collector.collect(Tuple2.of(word, 1l));
                }
            }
        });

        SingleOutputStreamOperator<Tuple2<String, Long>> word = tuple2SingleOutputStreamOperator.keyBy(0)
                .sum(1);
        // 打印数据
        word.print();
        // 触发任务执行
        streamExecutionEnvironment.execute("wordcount stream process");

    }
}

Flink程序开发的流程总结

Flink程序开发的流程总结如下:

1)获得一个执行环境

2)加载/创建初始化数据

3)指定数据操作的算子

4)指定结果数据存放位置

5)调用execute()触发执行程序

注意:Flink程序是延迟计算的,只有最后调用execute()方法的时候才会真正触发执行程序

Flink分布式架构与核心组件

Flink作业提交过程

standalone模式下的作业提交过程如下:

在一个作业提交前,Master和TaskManager等进程需要先被启动。

我们可以在Flink主目录中执行脚本来启动这些进程:

bin/start-cluster.sh。

Master和TaskManager被启动后,TaskManager需要将自己注册给Master中的ResourceManager。

这个初始化和资源注册过程发生在单个作业提交前,我们称之为第0步。

接下来,我们将逐步解析Flink作业的提交过程,具体步骤如下:

① 用户编写应用程序代码,并使用Flink客户端(Client)提交该作业。通常,这些程序会使用Java或Scala语言编写,并调用Flink API 构建出逻辑视图。这些代码以及相关配置文件被编译并打包,然后被提交至Master节点的Dispatcher,形成一个应用作业(Application)。

② Dispatcher接收到提交的作业后,会启动一个JobManager,该JobManager负责协调这个作业的各项任务。

③ JobManager向ResourceManager申请所需的作业资源,这些资源可能包括CPU、内存等。

④ 由于在前面的步骤中,TaskManager已经向ResourceManager注册了可供使用的资源,这时处于空闲状态的TaskManager将被分配给JobManager。

⑤ JobManager将用户作业中的逻辑视图转化为物理执行图,如图3-3所示,该图显示了作业被并行化后的执行过程。JobManager将计算任务分配并部署到多个TaskManager上。此时,一个Flink作业正式开始执行。

在计算任务执行过程中,TaskManager可能会与其他TaskManager交换数据,使用特定的数据交换策略。同时,TaskManager还会将任务的状态信息传递给JobManager,这些状态信息包括任务的启动、执行和终止状态,以及快照的元数据等。

Flink核心组件

在这个作业提交流程的基础上,我们可以更详细地介绍涉及的各个组件的功能和角色:

  1. Client(客户端): 用户通常使用Flink提供的客户端工具(如位于Flink主目录下的bin目录中的命令行工具)来提交作业。客户端会对用户提交的Flink作业进行预处理,并将作业提交到Flink集群中。在提交作业时,客户端需要配置一些必要的参数,例如使用Standalone集群还是YARN集群等。整个作业会被打包成一个JAR文件,DataStream API会被转换成一个JobGraph,该图类似于逻辑视图(如图3-2所示)。
  2. Dispatcher(调度器): Dispatcher可以接收多个作业,每次接收作业时,会为该作业分配一个JobManager。Dispatcher通过提供表述性状态转移(REST)式的接口,使用超文本传输协议(HTTP)来对外提供服务。
  3. JobManager(作业管理器): JobManager是单个Flink作业的协调者。每个作业都有一个对应的JobManager负责管理。JobManager将客户端提交的JobGraph转化为ExecutionGraph,该图类似于并行物理执行图(如图3-3所示)。JobManager会向ResourceManager申请所需的资源。一旦获取足够的资源,JobManager会将ExecutionGraph及其计算任务分发到多个TaskManager上。此外,JobManager还管理多个TaskManager,包括收集作业状态信息、生成检查点、必要时进行故障恢复等。
  4. ResourceManager(资源管理器): Flink可以在Standalone、YARN、Kubernetes等环境中部署,而不同环境对计算资源的管理模式有所不同。为了解决资源分配问题,Flink引入了ResourceManager模块。在Flink中,计算资源的基本单位是TaskManager上的任务槽位(Slot)。ResourceManager的主要职责是从资源提供方(如YARN)获取计算资源。当JobManager需要计算资源时,ResourceManager会将空闲的Slot分配给JobManager。在计算任务结束后,ResourceManager会回收这些空闲Slot。
  5. TaskManager(任务管理器): TaskManager是实际执行计算任务的节点。一般来说,一个Flink作业会分布在多个TaskManager上执行,每个TaskManager提供一定数量的Slot。当一个TaskManager启动后,相关的Slot信息会被注册到ResourceManager中。当Flink作业提交后,ResourceManager会将空闲的Slot分配给JobManager。一旦JobManager获取了空闲Slot,它会将具体的计算任务部署到这些Slot上,并在这些Slot上执行。在执行过程中,TaskManager可能需要与其他TaskManager进行数据交换,因此需要进行必要的数据通信。总之,TaskManager负责具体计算任务的执行,它会在启动时将Slot资源向ResourceManager注册。
Flink组件栈

1. 部署层:
  • Local模式: Flink支持本地模式,包括单节点(SingleNode)和单虚拟机(SingleJVM)模式。在SingleNode模式中,JobManager和TaskManager运行在同一个节点上;在SingleJVM模式中,所有角色都在同一个JVM中运行。
  • Cluster模式: Flink可以部署在Standalone、YARN、Mesos和Kubernetes集群上。Standalone集群需要配置JobManager和TaskManager的节点,然后通过Flink提供的脚本启动。YARN、Mesos和Kubernetes集群提供了更强大的资源管理和集群扩展能力。
  • Cloud模式: Flink还可以部署在各大云平台上,如AWS、谷歌云和阿里云,使用户能够在云环境中灵活地部署和运行作业。
2. 运行时层:
  • 运行时层是Flink的核心组件,支持分布式执行和处理。该层负责将用户提交的作业转化为任务,并分发到相应的JobManager和TaskManager上执行。运行时层还涵盖了检查点和故障恢复机制,确保作业的容错性和稳定性。
3. API层:
  • Flink的API层提供了DataStream API和DataSet API,分别用于流式处理和批处理。这两个API允许开发者使用各种操作符和转换来处理数据,包括转换、连接、聚合、窗口等计算任务。
4. 上层工具:

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

强大的资源管理和集群扩展能力。

  • Cloud模式: Flink还可以部署在各大云平台上,如AWS、谷歌云和阿里云,使用户能够在云环境中灵活地部署和运行作业。
2. 运行时层:
  • 运行时层是Flink的核心组件,支持分布式执行和处理。该层负责将用户提交的作业转化为任务,并分发到相应的JobManager和TaskManager上执行。运行时层还涵盖了检查点和故障恢复机制,确保作业的容错性和稳定性。
3. API层:
  • Flink的API层提供了DataStream API和DataSet API,分别用于流式处理和批处理。这两个API允许开发者使用各种操作符和转换来处理数据,包括转换、连接、聚合、窗口等计算任务。
4. 上层工具:

[外链图片转存中…(img-4xIqAWK4-4702038019525)]
[外链图片转存中…(img-N8042iA9-4702038019526)]
[外链图片转存中…(img-a5AJaVIS-4702038019526)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flink是一个开源的分布式流处理框架,它的运行原理是将数据流分成不同的子任务,这些子任务会在不同的计算节点上并行执行。Flink将数据流看作是一个无限的事件流,每当有新的事件到来时,Flink会将这些事件收集起来,交给对应的算子进行处理,并将结果传递给下一个算子。Flink的运行原理主要包括以下几个方面: 1. 数据流划分:Flink会将数据流划分为多个子任务,并将这些子任务分配到不同的计算节点上。 2. 算子执行:每个子任务会在对应的计算节点上并行执行,数据会经过一系列的算子进行处理,每个算子都会将处理后的数据再次输出到下一个算子。 3. 状态管理:Flink支持对算子状态的管理,可以将算子的状态存储在内存或外部存储中,以便在出现故障时进行恢复。 4. 检查点:Flink会定期生成检查点,用于保存算子的状态以及数据流的位置信息,以便在出现故障时进行恢复。 5. 任务协调:Flink会对所有子任务进行统一的协调和调度,确保数据流的正确处理。 在实际应用中,为了保证Flink的性能,需要进行性能优化。常见的性能优化包括: 1. 调整并行度:适当调整算子的并行度可以提高Flink的性能。 2. 减少数据倾斜:数据倾斜会导致某些节点的负载过高,可以通过数据重分区等方式来减少数据倾斜。 3. 使用状态后端:选择合适的状态后端可以提高Flink的性能,常用的状态后端包括内存和RocksDB。 4. 减少网络开销:减少网络开销可以提高Flink的性能,可以通过调整数据分区、使用压缩算法等方式来减少网络开销。 5. 避免不必要的计算:避免不必要的计算可以提高Flink的性能,可以通过过滤掉不需要处理的数据等方式来避免不必要的计算。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值