使用 Spark 进行时间序列分析: 10 深入 Apache Spark 应用

在上一章节中,我们利用开源组件将时间序列分析投入生产环境。 这需要大量精力来搭建和管理平台。 本章将通过使用 Databricks 这一基于云的托管型平台即服务 PaaS)解决方案,应对这些挑战,进一步发挥 Apache Spark 的潜力。 我们将通过一个端到端的时间序列分析案例,展示如何利用 Databricks 的高级功能,包括 Delta Live Tables 流式管道、AutoML、Unity Catalog 以及 AI/BI 仪表板。

本章将涵盖以下主要内容:

  • Databricks 组件与设置
  • 工作流
  • 监控、安全与治理
  • 用户界面

Databricks 组件与配置

我们将如第 8 章所述,使用 Databricks 环境作为平台基础设施。 请按照技术需求章节中关于设置第 8 章所述Databricks 环境的说明进行操作。

工作区、文件夹和笔记本

环境设置完成后,请按照此处提供的链接中的说明导入笔记本:

  1. 浏览 Databricks 工作区:Workspace UI | Databricks Documentation
  2. 创建一个名为 ts_spark 的文件夹及其子文件夹 ch10Organize workspace objects into folders | Databricks Documentation
  3. 将本示例的笔记本导入到 ch10 文件夹:Import and export Databricks notebooks | Databricks Documentation

    总共有八个笔记本,可以从以下 URL 导入:

在笔记本导入后,我们接下来可以设置集群。

20年工作经验,承接微信小程序,App,网站,网站后端开发。有意向私聊我哈。微信:akluse

集群

我们可以使用 Databricks 的机器学习运行时 MLR)或使用无服务器计算来配置集群。

MLR 集群预装了用于机器学习 ML)的常用库。 它会在您的云服务提供商账户中实例化虚拟机。 云服务提供商将向您收取虚拟机使用费用。 创建集群时,请选择 CPU 和内存配置最低的小型实例以控制成本。 这对于本章的示例来说已经足够。 有关 Databricks 集群的设置,请参阅第 8 章的技术要求部分。

MLR 集群包含了 AutoML 示例所需的库,我们将在后续章节详细介绍。 如果您不想承担与 MLR 相关的云服务商虚拟机费用,可以跳过此示例的代码执行。 我们将提供另一种不使用 AutoML 的工作流程。

在撰写本文时,这些虚拟机的云服务商费用已超出免费试用账户的额度。 这意味着您需要升级到付费云服务账户,费用将计入您创建账户时指定的信用卡。 这些虚拟机和云基础设施费用并非来自 Databricks 的免费试用账户。

无服务器集群包含在您的 Databricks 成本中,因为底层虚拟机完全由 Databricks 托管。 这意味着它不会产生额外的云服务商费用。 但无服务器集群目前需要安装 ML 库, 如您将在代码示例中看到的那样。 未来 Databricks 可能会提供预装 ML 库的无服务器集群。

在撰写本文时,Databricks 已开始在免费试用账户中提供无服务器功能。 这意味着只要在 Databricks 免费试用账户的时间和成本限制范围内,您使用无服务器集群执行本章代码将是免费的。 这一点可能会在未来发生变化。

您可以在以下资源中找到关于 MLR 和无服务器集群的更多信息:

完成集群配置后,接下来我们将使用 Delta Live Tables 来配置数据管道。

 

使用 Delta Live Tables 进行流式处理

Databricks Delta Live TablesDLT)是一种低代码声明式的数据管道构建方案。 在本示例中,我们将使用 DLT 构建特征工程管道,从源文件获取数据、检查数据质量,并将其转换为可用于训练时间序列模型的特征。 您可以通过以下链接获取更多关于 DLT 的信息:

Getting Started with DLT

我们将在实现工作流章节中详细讲解 DLT 配置。

工作流

Databricks 工作流相当于我们在第 4 章和第 9 章中使用的 Airflow DAG。 您可以在作业 (也称为工作流)的以下链接中找到更多信息:

Lakeflow Jobs | Databricks Documentation

现在我们将深入探讨作业配置的细节。

工作流实现

本章中的代码示例包括四个工作流程。这些工作流程在 Databricks 中被实现为作业。可视化这些作业的最佳方式是从 Databricks 中的“工作流程 > 作业 > 任务”视图中查看,如图 10.1、10.2、10.3 和 10.4 所示。

工作如下:  

  • ts-spark_ch10_1a_ingest_and_train – 该作业用于数据摄取、特征工程和模型训练,如图 10.1 所示 ,包含以下任务:
    • 重置
    • 特征提取
    • 模型训练

图10.1:数据摄取、特征工程和模型训练作业

  • ts-spark_ch10_1b_ingest_and_train_automl – 第二个作业如图 10.2 所示,是第一个作业的另一个版本,区别在于使用了 AutoML 技术,这将在使用 AutoML 训练章节中详细说明。

 

图 10.2:数据摄取、特征工程和模型训练(AutoML)作业

  • ts-spark_ch10_2b_ingest_and_forecast – 该作业负责摄取新数据、重新训练模型并生成和评估预测结果,如图 10.3 所示。 包含以下任务:
    • dlt_features
    • update_model
    • generate_forecast
    • update_data
    • 评估预测

 

图10.3:数据摄取、模型重训练及预测生成任务

  • ts-spark_ch10_2a_update_iteration – 该作业如图 10.4 所示,会多次调用前一个作业来摄取新数据。 它模拟了现实场景中定期(例如每天或每周)使用新数据启动前述端到端工作流的过程。

 

图10.4:多次调用以摄取和处理新数据作业

模块化与任务分离

如第 9 章所述,我们将作业拆分为多个任务以展示模块化的最佳实践。 这有利于独立进行代码变更、扩展和任务重试。 不同团队可以分别拥有这些任务的所有权。 根据您单独启动任务的需求 ,本示例中的作业在您自己的实现中可以进一步拆分。

我们将在接下来的章节中详细解释这些作业及相关任务,首先从数据摄入和训练作业开始。

要设置本章所需的作业,请按照以下链接中关于创建作业和配置任务的说明操作:

创建作业及相关任务时,请参考后续章节中的配置表格,将 <USER_LOGIN> 替换为您自己的 Databricks 用户登录名。

数据摄取与训练

图 10.1 所示,ts-spark_ch10_1a_ingest_and_train 作业中的任务部分将在本节详细说明。

表 10.1 展示了在按照前述 URL 中的说明操作时,ts_spark_ch10_1a_ingest_and_train 作业需要使用的配置。 请注意,为简化起见,我们为每个任务赋予了与其运行的代码笔记本或管道相同的名称。

作业

ts_spark_ch10_1a_ingest_and_train

任务1

任务名称

ts_spark_ch10_reset

类型

Notebook

工作区

路径(笔记本)

/Workspace/Users/<USER_LOGIN>/ts-spark/ch10/ts_spark_ch10_reset

计算

无服务器

任务2

任务名称

ts_spark_ch10_dlt_features

类型

管道

管道

ts_spark_ch10_dlt_features

触发流水线的完全刷新

R

取决于

ts_spark_ch10_reset

任务3

任务名称

ts_spark_ch10_model_training

类型

笔记本

工作区

路径(笔记本)

/ 工作区/用户/< 用户登录名>/ts-spark/ch10/ts_spark_ch10_模型训练

计算

无服务器

依赖项

ts_spark_ch10_dlt_特征

表 10.1:作业配置 - ts_spark_ch10_1a_数据摄取与训练

重置

重置任务将执行以下操作:

  • 重置 Databricks 目录 ts_spark,该目录用于本示例
  • 从本章节的 GitHub 位置下载数据文件到 ts_spark 目录中创建的卷

本任务的代码位于 ts_spark_ch10_reset 笔记本中。

目录与卷

Databricks 的 Unity Catalog 提供了数据治理和管理功能。 它将数据组织成三级层次结构:目录、模式(相当于数据库)以及表、视图或卷。 表格数据存储在表和视图中,而文件则存储在卷中。 在我们的代码示例中,我们使用了一个单独的目录 ts_spark,以及卷来存储数据文件。

 

dlt_features

此任务用于数据摄取和特征工程。 其实现为 ts_spark_ch10_dlt_features DLT 管道,如图 10.5 所示.

 

图10.5:特征工程流水线

您可在此处查找并放大查看图 10.5 的数字版本:https://packt.link/D9OXb

要设置本章所需的 DLT 管道,请按照以下链接中的管道创建说明操作:

Configure Lakeflow Declarative Pipelines | Databricks Documentation

请注意,在设置 DLT 管道前需先创建 ts_spark 目录。 请参照以下说明通过 Catalog Explorer 创建 ts_spark 目录:Create catalogs | Databricks Documentation

请参考表 10.2 中的配置创建流水线,并将 <USER_LOGIN> 替换为您自己的 Databricks 用户登录名。

管道

ts_spark_ch10_dlt_features

常规

流水线名称

《10》ts_spark_ch10_dlt 特性

无服务器

R

流水线模式

已触发

源代码

路径(笔记本)

/ 工作区/用户/< 用户登录名>/ts-spark/ch10/ts_spark_ch10_dlt 特性

目标位置

存储选项

Unity Catalog

默认目录 / 默认 架构

ts_spark / ch10

表 10.2:DLT 配置 – ts_spark_ch10_dlt_features

该管道任务的代码位于 ts_spark_ch10_dlt_features 笔记本中。 它包含以下步骤:

  1. 使用 Auto Loader 从 vol01_hist 卷中的文件读取历史数据,检查数据,并将数据存储到 raw_hist_power_consumption 流表中。

Auto Loader

Databricks Auto Loader(在代码中也被称为 cloudfiles)能够高效地增量摄取云存储位置中新到达的数据文件。 有关 Auto Loader 的更多信息,请访问以下链接:What is Auto Loader? | Databricks Documentation.

数据质量检查

Databricks DLT 可包含数据质量检查,基于质量约束确保数据管道内的数据完整性。 有关 DLT 中数据质量检查的更多信息,请访问以下链接:Manage data quality with pipeline expectations | Databricks Documentation.

  1. 使用 Auto Loader 从 vol01_upd 卷中读取更新数据,检查数据后将其存储到 raw_upd_power_consumption 流式表中。
  2. 从 raw_hist_power_consumption 流式表读取原始历史数据,转换后存储到 curated_hist_power_consumption 流式表中。
  3. 从 raw_upd_power_consumption 流表中读取原始更新数据,进行数据转换后,将结果存储到 curated_upd_power_consumption 流表中。
  4. 将 curated_hist_power_consumption 和 curated_upd_power_consumption 流表的数据进行追加合并,最终结果存储到 curated_all_power_consumption 流表中。
  5. 从 curated_all_power_consumption 流式表中读取精选数据,使用 Tempo 计算 5 分钟窗口内的指数移动平均 EMA),并将数据重采样为每小时均值。 然后将聚合数据存储到 features_aggr_power_consumption 物化视图中。

Tempo

Databricks Tempo 是一个开源项目,旨在简化 Apache Spark 中的时间序列数据处理。 您可以通过以下链接获取有关 Tempo 的更多信息:Tempo 0.1.29 documentation.

  1. 从 features_aggr_power_consumption 物化视图中读取聚合数据,并使用 Tempo 与 curated_all_power_consumption 流表进行 AsOf 连接。 然后将结果存储到 features_gnlr_power_consumption 物化视图中。

这些步骤对应奖牌方法的数据转换阶段,该方法已在第 4 章的数据处理与存储部分讨论过。.

模型训练

此任务用于利用先前 dlt_features 任务中计算出的特征来训练 Prophet 模型。model_training 的代码位于 ts_spark_ch10_model_training 笔记本中。 具体步骤如下:

  1. 读取特征 自 features_aggr_power_consumption.
  2. 将 Date 列重命名为 ds,并将 hourly_Global_active_power 重命名为 y。 这些列名是 Prophet 所要求的。
  3. 启动一个 MLflow 运行以跟踪 MLflow 中的训练过程。
  4. 将 Prophet 模型拟合到 数据集。
  5. 将模型注册到 Unity Catalog,并将别名设为 Champion.

请注意,本笔记本展示的是简化版的模型训练过程,这已足够说明本章示例中的训练步骤。 其中不包含完整的模型实验过程和超参数调优,这些内容我们已在第 7 章中介绍过。.

使用 AutoML 进行训练

另一种模型训练方法是使用 Databricks AutoML 来为给定数据集寻找最佳模型。

AutoML 是 Databricks 中一项自动化机器学习模型开发流程的功能, 能够自动完成数据画像、特征工程、模型选择和超参数调优等任务。 用户可借此快速生成针对回归、分类和预测问题的基准模型。 采用"玻璃盒"方法的 AutoML 会提供每个模型的底层代码,这与不展示代码细节的"黑盒"方法形成鲜明对比。 如图 10.6 所示,可通过 UI 使用 AutoML; 也可如本章示例所示通过编程方式调用。

 

图 10.6:Databricks AutoML

您可以在此处找到有关 AutoML 的更多信息:

Databricks AutoML - Automated Machine Learning | Databricks.

该 ts-spark_ch10_1b_ingest_and_train_automl 作业展示了如何以编程方式将 AutoML 包含在训练任务中。 此任务的代码位于 ts_spark_ch10_model_training_automl 笔记本中。 具体步骤如下:

  1. 读取特征 自 features_aggr_power_consumption.
  2. 调用 databricks.automl.forecast 函数,该函数负责重命名列、启动 MLflow 运行以跟踪训练过程,并根据指定的 primary_metric(示例中使用的是 mdape)找到最佳预测模型。
  3. 将模型注册到 Unity Catalog,并将别名设为 Champion.

作业 ts_spark_ch10_1b_ingest_and_train_automl 的配置如表 10.3 所示.

作业(可选)

ts_spark_ch10_1b_ingest_and_train_automl

任务1

任务名称

ts_spark_ch10_reset

类型

笔记本

工作区

路径(笔记本)

/ 工作区/用户/< 用户登录名>/ts-spark/ch10/ts_spark_ch10_reset

计算

无服务器

任务2

任务名称

ts_spark_ch10_dlt_features

类型

管道

管道

ts_spark_ch10_dlt_features

触发对管道的完全刷新

R

取决于

ts_spark_ch10_reset

任务3

任务名称

ts_spark_ch10_model_training_automl

类型

笔记本

工作区

路径(笔记本)

/ 工作区/用户/< 用户登录名>/ts-spark/ch10/ts_spark_ch10_模型训练_自动机器学习

计算

请参考前文关于集群的部分来选择计算资源

取决于

ts_spark_ch10_dlt_features

表 10.3:作业配置 - ts_spark_ch10_1b_ingest_and_train_automl

需要注意的是, 与之前不使用 AutoML 的训练方法相比, 除了简化步骤外, 我们还能找到最佳模型。

数据摄取与预测

任务部分属于 ts-spark_ch10_2b_ingest_and_forecast 作业,如图 10.3 所示,将在本节详细说明。

作业 ts_spark_ch10_2b_ingest_and_forecast 的配置如表 10.4 所示.

作业

ts_spark_ch10_2b_ingest_and_forecast

作业参数

键:upd_iter

值:1

任务1

任务名称

ts_spark_ch10_dlt_features

类型

管道

管道

ts_spark_ch10_dlt_features

触发对管道的完全刷新

R

任务2

任务 名称

ts_spark_ch10_更新模型

类型

笔记本

工作区

路径(笔记本)

/ 工作区/用户/< 用户登录名>/ts-spark/ch10/ts_spark_ch10_更新模型

计算

无服务器

取决于

ts_spark_ch10_dlt_features

任务3

任务名称

ts_spark_ch10_生成预测

类型

笔记本

工作区

路径(笔记本)

/ 工作区/用户/< 用户登录名>/ts-spark/ch10/ts_spark_ch10_生成预测

计算

无服务器

取决于

ts_spark_ch10_更新模型

任务4

任务名称

ts_spark_ch10_update_data

类型

笔记本

工作区

路径(笔记本)

/ 工作区/用户/< 用户登录名>/ts-spark/ch10/ts_spark_ch10_update_data

计算

无服务器

取决于

ts_spark_ch10_生成预测

任务5

任务 名称

ts_spark_ch10_评估预测

类型

笔记本

工作区

路径(笔记本)

/ 工作区/用户/< 用户登录>/ts-spark/ch10/ts_spark_ch10_评估预测

计算

无服务器

取决于

ts_spark_ch10_更新数据

表 10.4:作业配置 – ts_spark_ch10_2b_ingest_and_forecast

dlt_features

此任务与 ts_spark_ch10_dlt_features DLT 管道相同,如图 10.5 所示,在之前的数据摄取与训练部分中,它被用于处理历史数据。 不同之处在于,这里我们将调用此管道来处理来自 vol01_upd 卷的新数据文件。

update_model

本任务用于通过先前 dlt_features 任务中计算的特征来训练 Prophet 模型。update_model 的代码位于 ts_spark_ch10_update_model 笔记本中。 该任务与 model_training 章节讨论的任务类似,不同之处在于我们现在有新的数据需要纳入训练。 具体步骤如下:

  1. 读取特征 自 features_aggr_power_consumption.
  2. 将 Date 列重命名为 ds,并将 hourly_Global_active_power 重命名为 y。 这些列名是 Prophet 所要求的。
  3. 将 Prophet 模型拟合到 数据集。
  4. 将模型注册到 Unity Catalog,并将别名设为 Champion.

随着最新模型更新完成,我们可以用它来进行下一步预测。

generate_forecast

此任务使用先前训练好的模型生成并存储预测结果。 相关代码 generate_forecast 位于 ts_spark_ch10_generate_forecast 笔记本中。 具体步骤如下:

  1. 从 Champion 模型中加载 Unity Catalog。
  2. 生成未来 24 小时的预测。
  3. 将预测结果连同模型名称和版本存储到 forecast 表中。

生成预测后,我们可以将预测时间段与实际数据进行比较,接下来我们将获取实际数据。

update_data

此任务只需将新时间段的数据文件从 vol01_upd_src 卷复制到 vol01_upd 卷。 用于 update_data 的代码位于 ts_spark_ch10_update_data 笔记本中。

evaluate_forecast

此任务计算并存储预测准确度指标。 相关代码 evaluate_forecast 位于 ts_spark_ch10_evaluate_forecast 笔记本中。 具体步骤如下:

  1. 将 features_aggr_power_consumption 实际值表与先前创建的 forecast 表进行关联。
  2. 计算 mdape 指标。
  3. 将计算得出的指标与模型名称及版本一同存储至 forecast_metrics 表中。
  4. 将数据质量检查结果存储到 dq_results 表中。

完成预测评估后,我们可以报告结果和相关指标。 我们将在用户界面部分对此进行说明。 在此之前,让我们详细阐述如何协调新数据多次迭代到达与相应处理流程

更新迭代

如 ts-spark_ch10_2a_update_iteration 作业所示(见图 10.4),它模拟了现实世界中定期(例如每日或每周)有新数据需要处理的情况。 该作业会调用 ts-spark_ch10_2b_ingest_and_forecast 作业七次,对应一周的每日新数据。 每次调用都会完成一个新数据文件的端到端处理流程,如前述数据摄取与预测章节所述。

ts_spark_ch10_2a_update_iterations 作业的配置如表 10.5 所示.

作业

ts_spark_ch10_2a_更新迭代

任务1

任务名称

ts_spark_ch10_更新迭代

类型

对于每一个

输入

[1,2,3,4,5,6,7]

任务 2(添加一个任务到循环中)

任务名称

ts_spark_ch10_更新迭代_迭代

类型

运行作业

工作

ts_spark_ch10_2b_ingest_and_forecast

作业参数

键:upd_iter

值:{{input}}

表 10.5:作业配置 - ts_spark_ch10_2a_update_iterations

启动作业

随着作业配置和说明的完成,我们现在将启动这些作业,它们将执行本章的代码。 您可以在此处找到有关运行作业的更多信息:

Trigger a single job run | Databricks Documentation

按以下顺序进行:

  1. 点击立即运行执行 ts-spark_ch10_1a_ingest_and_train。 等待作业完成。
  2. 点击立即运行执行 ts-spark_ch10_2a_update_iteration.

任务启动并执行后,我们可以查看其状态,具体将在下一节中说明。

监控、安全与治理

正如我们在第 4 章的从 DataOps 到 ModelOps 再到 DevOps 章节以及第 9 章的治理与安全章节中所讨论的,对于生产环境中处理敏感数据的工作负载而言,关键要求是建立适当的监控、安全与治理机制。 利用 Databricks 与 Unity Catalog 等托管平台的固有功能,可大幅简化这一过程。 若选择自主开发测试定制化平台,则需要投入大量时间精力才能稳健满足这些要求。

监控

作业监控可通过工作流 作业 运行页面进行,如图 10.7 所示 ts-spark_ch10_2b_ingest_and_forecast 作业的监控界面。 我们可以查看不同运行实例的参数、持续时间、状态等监控所需的关键信息。

 

图 10.7:Databricks 工作流 - 作业 - 运行

监控 ts_spark_ch10_dlt_features DLT 管道可以通过工作流 管道页面进行,如图 10.8 所示。 我们可以看到不同的阶段、数据检查、持续时间和状态等对监控有用的信息。

 

图 10.8:Databricks DLT 管道

您可以在此处获取有关可观测性、监控和告警的更多信息:

安全

图 10.9 所示 ,使用 Unity Catalog 时,只需点击几下即可设置表和其他对象的访问权限。

 

图 10.9:Databricks Unity Catalog - 权限设置

还可以根据以下资源: 在表内更细粒度的行或列级别定义精细的访问控制

Access Controls with Unity Catalog | Databricks

您可在此处查看更多安全相关信息:

Security and compliance | Databricks Documentation

治理

治理中的一个重要考量是能够追踪数据资产的沿袭,如图 10.10 所示 。这里我们可以看到数据来源、多个中间阶段以及最终存储数据的表格。Unity Catalog 在 Databricks 中自动追踪这些信息,为我们提供数据流的实时可视性。

 

图 10.10:Databricks Unity Catalog - 血缘关系视图

您可以在此处查找并放大图 10.10 的数字版本:

https://packt.link/D6DyC

我们仅简要介绍了 Databricks Unity Catalog 的治理与安全功能。 您可以在此处获取更多信息:

Unity Catalog | Databricks

在了解如何利用 Databricks 等平台进行监控、安全和治理后,接下来我们将探讨如何展示时间序列分析的结果。

Databricks UI — AI/BI 仪表板

在展示我们目前完成的时间序列分析结果时,Databricks 提供了几种用户界面选项,包括 AI/BI 仪表板、Genie 空间、基于 AI 的聊天机器人和 Lakehouse 应用。 本节我们将重点介绍 AI/BI 仪表板,其他选项将在下一章中讨论。

我们在本书中广泛使用了各种图表来呈现数据和分析结果。 这需要我们通过在笔记本中执行代码来生成这些图表。 当我们能够编写代码并拥有执行环境时,这种方式效果很好。 当不具备这些条件时,展示数据和分析结果的常见方式是使用报告仪表板。 通过 Databricks AI/BI 仪表板可以实现这一功能,如图 10.11 所示。.

Databricks AI/BI 仪表板是集成在 Databricks 平台中的解决方案,用于创建报告和仪表板。 它具有 AI 驱动的功能,可协助创建查询和数据可视化。 这些仪表板可以发布和共享以供使用。

 

图 10.11:Databricks AI/BI 仪表盘

要在您自己的环境中安装此仪表板,请先从以下位置下载:

https://github.com/PacktPublishing/Time-Series-Analysis-with-Spark/blob/main/ch10/ts_spark_ch10.lvdash.json

然后按照此处说明将仪表板文件导入您自己的环境

Dashboards | Databricks Documentation

运行该仪表板需要 SQL 仓库。 请参考以下说明创建 SQL 仓库:

Create a SQL warehouse | Databricks Documentation.

本仪表板以组合视图形式整合了以下内容:

  • 实际值与预测值的图表
  • 数据质量检查通过和失败的记录数
  • 不同模型版本的指标

您可在以下链接中获取更多关于 AI/BI 仪表板的信息:

概述

通过这个在托管 Spark 平台上进行时间序列分析的端到端示例,本章展示了如何利用 Databricks 的开箱即用功能来进一步发挥 Apache Spark 的潜力。 我们从流式数据管道的摄入开始,历经特征工程和模型训练,再到推理和报告生成,同时确保了监控、安全与治理措施的到位。 通过将 Databricks 的预置功能与我们自定义代码相结合,我们实现了一个可扩展到更多应用场景的解决方案。

这将我们引向最后一章,我们将在此探讨时间序列分析领域的一些最新进展。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

akluse

失业老程序员求打赏,求买包子钱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值