Spark基础篇-初识Spark

Spark系列文章目录

第一章 初识Spark
第二章 Spark-Core核心模型(一)
第二章 Spark-Core核心模型(二)
第三章 Spark-Core编程进阶(一)
第三章 Spark-Core编程进阶(二)
第四章 Spark-SQL基础(一)
第四章 Spark-SQL基础(二)
第五章 Spark-SQL进阶(一)
第五章 Spark-SQL进阶(二)
第五章 Spark-SQL进阶(三)


第一章 初识Spark

1.认识Spark

Spark是加州大学伯克利分校AMP实验室开发基于内存通用并行计算框架。

思考:已经学习了MapReduce,为什么要学习Spark?

1.1并行计算

并行计算(Parallel Computing)是指同时使用多种计算资源解决计算问题的过程,是提高计算机系统计算速度和处理能力的一种有效手段。

它的基本思想是:用多个处理器来协同求解同一问题

注意,其实就是将被求解的问题分解成若干个部分,各部分均由一个独立的处理机来并行计算

并行计算系统:

  • 既可以是专门设计的、含有多个处理器的超级计算
  • 也可以是以某种方式互连的若干台的独立计算机构成的集群

通过并行计算集群完成数据的处理,再将处理的结果返回给用

1.2 MapReduce

MapReduce是面向大数据并行处理的计算模型、框架和平台,它隐含了以下三层含义:

  1. MapReduce是一个基于集群的高性能并行计算平台(Cluster Infrastructure)

    它允许用市场上普通的商用服务器构成一个包含数十、数百至数千个节点的分布和并行计算集群。

  2. MapReduce是一个并行计算与运行软件框架(Software Framework)

    • 能自动完成计算任务的并行化处理
    • 能自动划分计算数据和计算任务
    • 能自动分配和执行任务
    • 能收集计算结果
    • 能将数据分布存储、数据通信、容错处理等并行计算涉的很多系统底层的复杂细节处理
    • 能大大减少了软件开发人员的负担
  3. MapReduce是一个并行程序设计模型与方法(Programming Model & Methodology)

    它借助于函数式程序设计语言Lisp的设计思想。

    • 提供了一种简便的并行程序设计方法,用Map和Reduce两个函数编程实现基本的并行计算任务
    • 提供了抽象的操作和并行编程接口,以简单方便地完成大规模数据的编程和计算处理
1.2.1 优点
  • Mapreduce易于编程

    • 简单的实现一些接口,就可以完成一个分布式程序
  • 良好的扩展性

    • 当项目计算资源得不到满足的时候,可以简单的通过增加机器来扩展它的计算能力
  • 高容错性

    • 当一个机器挂了,可以把上面的计算任务转移到另一个节点上运行,此过程由框架自行完成
  • 适合PB级以上海量数据的离线处理

1.2.2 缺点
  • 实时计算

    • MapReduce无法像Oracle或MySQL那样在毫米或秒级内返回结果,如果需要大数据量的毫秒级响应,可以考虑使用HBase。
  • 流式计算

    • 流计算的输入数据是动态的,而MapReduce的输入数据是静态的,不能动态变化,这是因为MapReduce自身的设计特点决定了数据源必须是静态的。如果需要处理流式数据可以用Storm、Spark Steaming、Flink。
  • 数据挖掘、 DAG(有向图)、机器学习等迭代计算

    • 多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MapReduce并不是不能做,而是使用后,每个MapReduce作业的输出结果都会写入磁盘,会造成大量的磁盘IO导致性能非常低下,此时可以考虑用Spark等迭代计算框架。

1.3 Spark由来

发展历程

  • 2009年由 Berkeley’s AMPLab 开始编写最初的源代码;

  • 2010年开放源代码;

  • 2013年6月进入Apache孵化器项目;

  • 2014年2月成为Apache的顶级项目(8 个月时间);

  • 2014年5月底Spark1.0.0发布;

    ……………………

  • 2016年3月Spark1.6.1发布;

  • 2016年7月Spark2.0发布;

    ……………………

  • 2020年2月Spark2.4.5发布;

  • 2020年6月Spark3.0.0发布;

Spark官网地址

在这里插入图片描述

1.4 Spark特点

在Spark官网上介绍,它具有运行速度快、易用性好、通用性强和随处运行等特点。

  1. 运行速度快

Spark拥有DAG执行引擎,支持在内存中对数据进行迭代计算。官方提供的数据表明,如果数据由磁盘读取,速度是Hadoop MapReduce的10倍以上,如果数据从内存中读取,速度可以高达100多倍。

在这里插入图片描述

  1. 易用性好

支持4种语⾔言的API:Scala、Java、Python、R。特别是Scala是一种高效、可拓展的语言,能够用简洁的代码处理较为复杂的处理工作。

//Sprark-Core RDD
//词频统计 SparkContext
val text_file:RDD[String]=sc.textFile("hdfs://...")
text_file.flatMap(_.split(" ")).map(word => (word,1)).reduceByKey(_+_)

//Sprark-Sql  Dataset SparkSession
//从Spark2.2.0之后,推荐使用 Dataset API
val textFile:Dataset[String] = spark.read.textFile("README.md")
val wordCounts = textFile.flatMap(line => line.split(" ")).groupByKey(identity).count()
  1. 通用性强

Spark技术栈是由以下五个组件组成,分别为:

在这里插入图片描述

这些组件分别提供以下功能:

  • Spark Core提供内存计算
  • SparkStreaming提供实时计算
  • Spark SQL提供即时查询
  • MLlib提供机器学习
  • GraphX提供图处理

它们都是由AMP实验室提供,能够无缝的集成并提供一站式解决平台。

注意,Spark其他项目都会依赖于核心项目 Spark-Core的中的组件模块。

  1. 随处运行
  • Spark可以读取HDFS、HBase、Cassandra S3中的数据
  • 可以运行在Mesos、YARN和Standalone等资源管理调度器
    在这里插入图片描述

Spark生态圈:也称为BDAS伯克利数据分析栈),是伯克利APMLab实验室打造的,力图在算法(Algorithms)、机器(Machines)、(People)之间通过大规模集成来展现大数据应用的一个平台。

运用大数据、云计算、通信等各种资源以及各种灵活的技术方案,对海量不透明的数据进行甄别并转化为有用的信息,以供人们更好的理解世界。

该生态圈已经涉及到机器学习、数据挖掘、数据库、信息检索、自然语言处理和语音识别等多个领域。

2.Spark安装

2.1 Spark模式

Spark的部署/运行模式默认为local[*]。

2.1.1 本地模式

Local模式是在一台计算机上的运行模式,适合于学习与测试。

设置本地模式的三种方式:

  1. local 用单线程来运行

  2. local[Number] 用Number个线程模式并行

  3. local[*] *代表该计算机的CPU核数

2.1.2 Yarn模式

Spark-On-Yarn模式是在Yarn集群中运行,适合于生产环境。

yarn

2.1.3 Standalone模式

Spark-Standalone模式是在Spark独立集群中运行,适合于生产环境。需要提前搭建好Spark集群,国内使用较少。

spark://host:port

2.1.4 Mesos模式

MesosApache下的开源分布式资源管理框架,它被称为是分布式系统的内核。Mesos最初是由加州大学伯克利分校的AMPLab开发的,后在Twitter得到广泛使用。

mesos://host:port

2.1.5 K8S模式

kubernetes,简称K8s,是用8代替8个字符“ubernete”而成的缩写。是一个开源的,用于管理云平台中多个主机上的容器化的应用。

k8s://https://host:port

2.2 Spark安装

2.2.1 单机配置
  1. 解压

tar -xvf spark.2.2.1-bin-hadoop2.7.tar

  1. 移动到opt/software目录中

mv spark.2.4.5-bin-hadoop2.7 /opt/software/

  1. 创建软连接

sudo ln -s software/spark.2.4.5-bin-hadoop2.7 spark

  1. 修改环境变量:

vi ~/.bashrc

export SPARK_HOME=/opt/spark
export PATH=$PATH:$SPARK_HOME/bin:$PATH:$SPARK_HOME/sbin

source ~/.bashrc

  1. 可选-修改 $SPARK_HOME/conf/log4j.properties下的log4j的级别

  2. 可选-拷贝一份hive-site.xml到spark安装目录的conf目录下

  3. 测试spark是否安装成功

    在终端输入:spark-shell --version

  4. 成功效果
    在这里插入图片描述


2.2.2 Spark-on-Yarn环境搭建
  1. 事先准备:Hadoop集群上的三个配置文件
  • core-site.xml
  • yarn-site.xml
  • hdfs-site.xml

将三个文件存储到一个空目录中。

例如:/opt/spark/spark-yarn/目录下。

  1. 在$SPARK_HOME/conf/spark-env.sh环境配置文件中添加如下配置

export HADOOP_CONF_DIR =/opt/spark/spark-yarn/

export SPARK_LOCAL_IP=机器IP地址或者是主机名

  1. 优化配置

spark-defaults.conf配置文件里添加如下配置:

spark.yarn.jars hdfs://HDFS集群的主节点IP:9000/spark_lib/jars/*
​ 或者
spark.yarn.archive hdfs://HDFS集群的主节点IP:9000/spark_lib/jars

注意,此配置需先将Spark的家目录的jars文件夹上传到hdfs上。

上传操作具体如下:

hdfs dfs -mkdir /spark_lib
hdfs dfs -put /opt/spark/jars /spark_lib/


2.2.3 Spark-Standalone环境搭建
  1. 修改Spark配置文件 (路径为$SPARK_HOME/conf/下)
    复制slaves.template和 spark-env.sh.template各一份

    cp spark-env.sh.template spark-env.sh

    cp slaves.template slaves

  2. vi slaves,此文件是指定子节点的主机,直接添加从节点主机名即可(前提是在/etc/hosts里面配置了).例如:

    briup0

    briup1

    briup2

  3. 在spark-env.sh末端添加如下几行:

必须配置

#主节点的IP地址
export SPARK_MASTER_IP=192.168.1.200

可选配置

#主节点的端口号
export SPARK_MASTER_PORT=7077
#指定Spark用来混洗数据的本地存储路径
export SPARK_LOCAL_DIRS=/data/spark/dirs,/home/briup/spark/dirs
#(一定要注意这个混洗数据的路径的权限)		
#Worker的WebUI端口号
export SPARK_WORKER_WEBUI_PORT=8081 
#主节点的WEBUI端口号
export SPARK_MASTER_WEBUI_PORT=8099
#每个Worker使用的CPU核数,默认1个
export SPARK_WORKER_CORES=2 
#每个Slave中启动几个Worker实例,默认1个
export SPARK_WORKER_INSTANCES=2
#每个Worker使用多大的内存,默认1g
export SPARK_WORKER_MEMORY=2g 
#驱动器节点Driver使用的内存
export SPARK_DRIVER_MEMORY=2g
  1. 启动start-all.sh命令或者是

    start-master.sh/start-slave.sh spark://host:7077

  2. 测试是否成功,在集群的所有机器上输入

    jps

  3. 打开浏览器输入http://localhost:8080查看集群资源页面

    测试spark-submit命令
    $Spark_home/spark-submit --master spark://localhost:7077 --class org.apache.spark.examples.SparkPi examples/jars/spark-examples_2.11-2.2.1.jar 10


2.2.4 Spark-on-Hive环境搭建
  1. 将hive-site.xml添加到$SPARK_HOME/conf/目录下

hive-site.xml文件内容如下:

<configuration>
  <property>
    <name>javax.jdo.option.ConnectionDriverName</name>
    <value>com.mysql.jdbc.Driver</value>
  </property>
  <property>
    <name>javax.jdo.option.ConnectionURL</name>
    <value>jdbc:mysql://127.0.0.1:3306/hive_spark?createDatabaseIfNotExist=true</value>
  </property>
  <property>
    <name>javax.jdo.option.ConnectionUserName</name>
    <value>spark</value>
  </property>
  <property>
    <name>javax.jdo.option.ConnectionPassword</name>
    <value>spark</value>
  </property>
  <property>
    <name>hive.metastore.schema.verification</name>
    <value>false</value>
  </property>
  <property>
    <name>hive.server2.thrift.port</name>
    <value>10000</value>
  </property>
  <property>
    <name>hive.server2.thrift.bind.host</name>
    <value>0.0.0.0</value>
  </property>
  <property>
    <name>hive.metastore.warehouse.dir</name>
    <value>/user/hive/warehouse</value>
  </property>
</configuration>
  1. 修改spark-defaults.conf文件
#集群运行模式下,需要将Hive的元数据的连接jar包配置到执行器节点
spark.executor.extraClassPath     $HIVE_HOME/lib/mysql-connector-java-5.1.22.jar
#设置warehouse
spark.sql.warehouse.dir hdfs://computer1.cloud.briup.com:9000/user/hive/warehouse/
  1. 添加Mysql连接jar包

    将mysql的依赖mysql-connector-java-5.1.22.jar添加到spark的安装目录,并上传到HDFS的/spark_lib/jars目录下

  2. 命令行测试

    spark-sql --driver-class-path $HIVE_HOME/lib/mysql-connector-java-5.1.22.jar

  3. 编码测试

//构建支持Spark-On-Hive的SparkSession对象
object FirstSpark{
  def main(args:Array[String])={
    val spark=SparkSession.builder()
      .appName("应用名称")
      .master("运行模式")
      .config("spark.sql.warehouse.dir","Hive数据仓库地址")
    	//需要先在hive服务器中启动该命令hive --service metastore 
      .config("hive.metastore.uris","thrift://Hive所在服务器的IP地址:9083")
      .enableHiveSupport()
      .getOrCreate();
     import spark.implicits._
  }
}

注意 ,如果在代码中没有指定支持Hive的配置,需要在resources目录下添加hive-site.xml配置文件

配置文件内容如下:

<configuration>
  <!--第一种方式:指定hive的地址,用户名,密码等信息 -->
  <property>
    <name>javax.jdo.option.ConnectionDriverName</name>
    <value>com.mysql.jdbc.Driver</value>
  </property>
  <property>
    <name>javax.jdo.option.ConnectionURL</name>
    <value>jdbc:mysql://127.0.0.1:3306/hive_spark?createDatabaseIfNotExist=true</value>
  </property>
  <property>
    <name>javax.jdo.option.ConnectionUserName</name>
    <value>spark</value>
  </property>
  <property>
    <name>javax.jdo.option.ConnectionPassword</name>
    <value>spark</value>
  </property>
  <!--第二种方式:借助于thriftserver协议 -->
  <!--先在hive服务器中启动该命令hive 两个-service metastore &-->
  <property>
    <name>hive.metastore.uris</name>
    <value>thrift://172.16.0.4:9083</value>
  </property>
</configuration>

3.Spark架构

3.1 通用运行架构

在这里插入图片描述

说明 :

  • ClusterManager

    集群管理者,负责集群整体资源管理和调度

    在Spark Standalone模式中为Master,控制整个集群,监控Worker。

    在Spark on Yarn模式中为资源管理器RecourceManager。

  • Worker

    从节点,负责控制计算的节点
    ​启动Executor或Driver。在Spark on Yarn模式中为NodeManager。

  • Driver

    驱动器,运行Application的main()函数并且创建SparkContext的进程。

    当全部的Executor运行完毕后,Driver负责将SparkContext关闭。

  • SparkContext

    是整个 Spark 应用程序的上下文,控制应用的生命周期,通常用SparkContext代表Driver。

    • 负责与ClusterManager通信
    • 负责资源的申请
    • 负责任务的分配
    • 负责任务的监控
    • 负责创建RDD、累加器、广播变量

    注意,每个JVM里只能存在一个处于激活状态的SparkContext。

  • Executor

    执行器,Application运行在worker节点上的一个进程。

    • 负责将Task包装成线程池(TaskRunner)
    • 负责启动线程池
    • 负责从线程池中抽取出一个空闲线程运行任务(Task)
    • 负责将数据存在内存或磁盘上

    注意,每个执行器能(Executor)并行运行 Task 的数量就取决于分配给它的 CPU 个数。

3.2 Spark On Yarn运行架构

在这里插入图片描述

在这里插入图片描述

在YARN中,每个Application实例都有一个ApplicationMaster进程,它是Application启动的第一个容器。

  • 它负责和ResourceManager打交道并请求资源
  • 获取资源之后告诉NodeManager为其启动Container

从深层次的含义讲YARN-Cluster和YARN-Client模式的区别其实就是ApplicationMaster进程的区别。

  • YARN-Cluster模式

    • Driver运行在AM(Application Master)中,负责向YARN申请资源,并监督作业的运行状况
    • 当用户提交了作业之后,就可以关掉Client,作业会继续在YARN上运行
    • 因而YARN-Cluster模式不适合运行交互类型的作业
  • YARN-Client模式

    • Application Master仅仅向YARN请求Executor
    • Client会和请求的Container通信来调度他们工作
    • 也就是说Client不能离开

3.3 Spark Standalone运行架构

在这里插入图片描述

4.Spark命令

4.1 spark-shell

Spark提供的终端命令,允许在终端中使用Scala、Java编程语言编写Spark程序。适用于学习测试环境。

语法:

Usage: ./bin/spark-shell [options]

帮助命令:

spark-shell —help

4.2 spark-sql

Spark提供的终端命令,允许在终端使用Sql语言操作数据。适用于学习测试环境。

语法:

Usage: ./bin/spark-sql [options] [cli option]

帮助命令:

spark-sql —help

4.3 spark-submit

Spark提供的作业提交命令,允许将打包好的程序提交到集群中运行。适用于生产环境。

语法:

Usage: spark-submit [options] <app jar | python file | R file> [app arguments]

Usage: spark-submit --kill [submission ID] --master [spark://...]

Usage: spark-submit --status [submission ID] --master [spark://...]

Usage: spark-submit run-example [options] example-class [example args]

帮助命令:

spark-submit —help

含义解释:

  • app jar 表示应用入口的jar包,必须传递
  • app arguments 表示传递给主方法的参数,可以省略
  • options 表示可选配置,可以省略
  • submission ID 表示提交作业的ID,可以省略
  • run-example 表示运行官方自带案例
  • example-class 表示官方自带案例所在类的全类名

可选配置(options):

  • --master 运行模式,默认为local[*]
  • --class 主方法所在类名
  • --name 应用名称
  • --deploy-mode Yarn的客户端还是集群模式,默认为client,可以修改为cluster
  • --executor-memory 执行器内存大小,默认为1g
  • --driver-memory 驱动器内存大小,默认为1g
  • --num-executors 执行器的个数,默认为2
  • --executor-cores 每个执行器的CPU个数,默认为1
  • --driver-cores 驱动器的CPU个数,默认为1,只支持cluster模式下修改
  • --jars 额外依赖的第三方jar包
  • --files 需要分发到各节点的数据文件
  • --total-executor-cores 执行器的CPU个数,默认为集群中全部可用CPU个数,只支持Standalone,Mesos,K8s运行模式下生效

5.编程流程

在这里插入图片描述

6.Spark案例

6.1 构建Spark-maven项目

  1. 直接构建一个Maven-Project项目

  2. 将以下内容添加到pom.xml文件标签内之后,在src目录下依次构建main/scala目录,更新maven项目


<properties>
  <scala.version>2.12.8</scala.version>
  <spark.scala.version>2.12</spark.scala.version>
  <spark.version>3.0.1</spark.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.scala-lang</groupId>
    <artifactId>scala-library</artifactId>
    <version>${scala.version}</version>
  </dependency>
  <!--引入Spark-core核心依赖-->
  <dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-core_${spark.scala.version}</artifactId>
    <version>${spark.version}</version>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.scala-tools</groupId>
      <artifactId>maven-scala-plugin</artifactId>
      <version>2.15.0</version>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>testCompile</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>2.6</version>
      <configuration>
        <useFile>false</useFile>
        <disableXmlReport>true</disableXmlReport>
        <includes>
          <include>**/*Test.*</include>
          <include>**/*Suite.*</include>
        </includes>
      </configuration>
    </plugin>
  </plugins>
</build>

6.2 WordCount案例

  1. 新建一个package

base

  1. 在该包下创建一个scala object

WordCount

  1. 在该对象的中构建main方法
package com.briup.base

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object WordCount {
  def main(args: Array[String]): Unit = {
    if(args.length<2){
      println("请输入需要进行词频统计的文件路径以及结果存储路径!!")
      System.exit(0)
    }
    //目的:获取Spark上下文对象
    //1.获取SparkConf对象
    val conf=new SparkConf();
    conf.setMaster("local[*]")
    conf.setAppName("词频统计案例")
    //2.获取SparkContext对象
    val sc=new SparkContext(conf);
    sc.setLogLevel("warn")
    println(sc)
    //3.借助于sc的方法获取RDD对象
    val fileRDD: RDD[String] = sc.textFile(args(0))
    //4.根据业务逻辑调用RDD中一系列方法(转化方法和行动方法)
    //统计words.txt中每个单词出现的频次
    //将字符串分割成一个个单词
    val mapRDD: RDD[String] = fileRDD.flatMap( (x:String)=> x.split(" ") )
    //转化,将每个单词转化为二元元组
    val wordAndNumRDD=mapRDD.map( (word:String) => (word,1) )
    //按照key相同,value进行相加
    val wordCountRDD=wordAndNumRDD.reduceByKey(_+_)
    //将词频统计结果输出到控制台
    wordCountRDD.foreach(println)
    //将计算结果存储到文本文件中
    wordCountRDD.saveAsTextFile(args(1))
    //5.关闭SparkContext对象
    sc.stop()
  }
}

7.作业流程

在这里插入图片描述

说明 :

  • Application

    应用,用户编写的 Spark 应用程序

    • 包含了运行在Driver上的代码
    • 包含了运行在 Executor上的代码
    • 将应用提交后集群之后,集群管理者为该Application分配资源并将程序转换并执行
  • RDD
    弹性分布式数据集(Resillient Distributed Dataset),Spark 的基本计算单元

  • DAG
    有向无环图( DirectedSched Acycle graph),反应 RDD 之间的依赖关系

  • Job

    作业,由RDD Action算子触发,在SparkContext通过runJob方法向Spark提交Job

    • 一个Application中可以包含多个Job
    • 一个Job转化为一个DAG
  • DAG
    有向无环图(DirectedSched Acycle graph),反应 RDD 之间的依赖关系

    • 一个DAG交给一个DAG Scheduler
  • DAG Scheduler

    有向无环图调度器,负责将DAG根据宽依赖关系划分为Stage,并提交 Stage给 TaskScheduler

    • 一个DAG默认为一个Stage
    • 一个宽依赖关系Stage加一
    • 一个Stage交给一个TaskScheduler
  • Stage

    阶段

    • 一个Stage包含一组相同的Task,这一组Task也叫做TaskSet
    • 一个Stage包含的Task个数取决于分区个数
    • 一个分区对应一个Task
  • Task Scheduler
    任务调度器,将Task分发给Executor执行

  • Task

    任务,执行RDD中对应Stage中所包含的算子

  • SparkEnv
    线程级别的上下文,存储运行时的重要组件的引用,可以进行shuffle管理或广播变量等的管理

文字描述:

  1. 用户在Client端通过spark-submit命令将Application提交到ClusterManager;
  2. ClusterManager接收到App之后,会找一个Worker启动Driver;
  3. Driver程序创建SparkContext,将其作为调度的总入口;
  4. SparkContext在初始化过程中分别创建DAGScheduler(进行Stage调度)和TaskScheduler(进行Task调度)两个模块。同时SparkContext会将App拆分成多个RDD DAG;
  5. 每一个DAG会提交给一个DAGScheduler,DAGScheduler会根据它们的依赖关系拆分成不同的Stage(TaskSets),每一个Stage提交给一个TaskScheduler;
  6. TaskScheduler会将这些Task任务不断的分发到Worker节点的中的Executor中执行;
  7. Executor会启动多线程,将每一个任务交给一个线程执行;
  8. SparkEnv会启动一些控制组件,进行shuffle管理或广播变量等的管理;
  9. Driver管理Task状态,Task完成,Stage完成,作业完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值