数据分析工具:Apache Drill:Drill的插件机制与自定义数据源_第1页
数据分析工具:Apache Drill:Drill的插件机制与自定义数据源_第2页
数据分析工具:Apache Drill:Drill的插件机制与自定义数据源_第3页
数据分析工具:Apache Drill:Drill的插件机制与自定义数据源_第4页
数据分析工具:Apache Drill:Drill的插件机制与自定义数据源_第5页
已阅读5页,还剩10页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

数据分析工具:ApacheDrill:Drill的插件机制与自定义数据源1数据分析工具:ApacheDrill:Drill的插件机制与自定义数据源1.1ApacheDrill简介1.1.1Drill的核心特性ApacheDrill是一个分布式SQL查询引擎,专为大规模复杂数据集设计,支持动态数据探索。其核心特性包括:Schema-free查询:Drill能够查询没有预定义模式的复杂数据,如JSON、Avro、Parquet等。动态数据发现:Drill可以自动推断数据的结构,无需用户预先定义表结构。分布式处理:Drill可以跨多个节点并行处理数据,提高查询性能。多数据源支持:Drill不仅支持HDFS,还支持S3、AzureBlobStorage、MongoDB等多种数据存储系统。实时查询:Drill支持实时数据流查询,适用于需要快速响应的场景。1.1.2Drill的架构解析ApacheDrill的架构主要由以下几个组件构成:QueryCoordinator:负责接收用户查询,解析SQL语句,生成执行计划,并分发给执行节点。DataExecutionNodes:执行查询计划,处理数据,返回结果。这些节点可以是集群中的任何机器。MetadataStore:存储数据源的元数据信息,帮助QueryCoordinator快速定位数据。StoragePlugins:连接到不同的数据源,如HDFS、S3等,提供数据读取和写入的能力。示例:使用ApacheDrill查询HDFS上的JSON文件假设我们有以下JSON文件存储在HDFS中:{

"name":"JohnDoe",

"age":30,

"city":"NewYork"

}

{

"name":"JaneDoe",

"age":25,

"city":"SanFrancisco"

}我们可以使用Drill的SQL查询来读取这些数据:--连接到HDFS数据源

USEhdfs;

--查询JSON文件

SELECT*FROM`path/to/jsonfile`;在Drill中,我们不需要预先定义表结构,直接使用SELECT语句即可查询JSON文件中的数据。Drill会自动推断数据的结构,并返回查询结果。示例:自定义数据源插件ApacheDrill允许用户通过编写自定义插件来支持新的数据源。以下是一个简单的自定义插件示例,用于连接到一个假想的数据库系统://自定义插件类

publicclassMyCustomPluginimplementsDrillPlugin{

@Override

publicStoragePluginConfiggetPluginConfig(PluginConfigpluginConfig){

//实现获取插件配置的方法

returnnewMyCustomStoragePluginConfig(pluginConfig);

}

@Override

publicStoragePluginWrappergetStoragePlugin(PluginContextcontext,StoragePluginConfigpluginConfig){

//实现获取存储插件的方法

returnnewMyCustomStoragePlugin(context,pluginConfig);

}

//其他必要的方法实现

}在这个示例中,我们定义了一个MyCustomPlugin类,实现了DrillPlugin接口。通过实现getPluginConfig和getStoragePlugin方法,我们可以配置和创建自定义的存储插件。这使得Drill能够连接到我们定义的数据库系统,从而查询其中的数据。示例:使用自定义插件查询数据一旦自定义插件被正确配置和加载,我们就可以使用Drill的SQL查询来访问自定义数据源中的数据:--使用自定义插件连接到数据源

USEmy_custom_plugin;

--查询数据

SELECT*FROM`my_custom_table`;在这个示例中,my_custom_plugin是我们自定义插件的名称,my_custom_table是我们想要查询的数据表。通过使用自定义插件,Drill能够理解并处理这个数据源中的数据,返回查询结果。通过这些示例,我们可以看到ApacheDrill的灵活性和强大功能,它不仅能够处理各种复杂数据,还允许用户通过自定义插件扩展其数据源支持。这使得Drill成为大数据分析领域的一个重要工具。2数据分析工具:ApacheDrill插件机制与自定义数据源2.1插件机制详解2.1.1理解Drill的插件体系ApacheDrill是一个分布式SQL查询引擎,它支持对各种数据源进行查询,包括HDFS、S3、NoSQL数据库、关系型数据库等。Drill的灵活性和可扩展性主要归功于其插件体系,允许用户自定义数据源和存储插件,以适应不同的数据存储格式和位置。Drill的插件体系基于Java,主要分为两大类:存储插件和函数插件。存储插件负责与特定的数据源进行交互,而函数插件则用于扩展Drill的SQL函数库。存储插件架构存储插件通过实现org.apache.drill.exec.store包下的接口来与Drill集成。核心接口包括:DrillbitEndpoint:提供Drillbit的信息,用于网络通信。StoragePlugin:存储插件的主接口,定义了插件的基本行为。StoragePluginConfig:配置存储插件的参数。TableProvider:提供数据表的元数据信息。RecordReader:读取数据记录。RecordWriter:写入数据记录。函数插件架构函数插件通过实现org.apache.drill.exec.expr.fn包下的接口来扩展Drill的SQL函数库。主要接口包括:DrillFuncHolder:持有函数的实例。DrillSimpleFunc:实现简单的函数逻辑。DrillAggFuncHolder:持有聚合函数的实例。DrillAggFunc:实现聚合函数的逻辑。2.1.2插件开发流程开发Drill插件的流程主要包括以下几个步骤:定义插件配置:创建一个StoragePluginConfig的子类,用于配置插件的参数。实现存储插件接口:根据需要实现StoragePlugin接口,以及相关的TableProvider、RecordReader和RecordWriter接口。注册插件:在Drill的配置文件中注册插件,或者通过Drill的API动态注册。测试插件:使用Drill的测试框架验证插件的功能和性能。部署插件:将插件部署到Drill集群中,确保所有节点都能访问到插件。2.1.3插件注册与管理插件的注册和管理主要通过Drill的配置文件或API进行。在配置文件中,可以使用以下格式注册存储插件:storage.myplugin.type=myplugin

storage.myplugin.enabled=true

storage.myplugin.config={

"param1":"value1",

"param2":"value2"

}通过API注册插件则需要使用Drill的PluginRegistry类,示例如下:importorg.apache.drill.exec.store.StoragePlugin;

importorg.apache.drill.exec.store.StoragePluginConfig;

importorg.apache.drill.exec.store.dfs.FileSystemConfig;

importorg.apache.drill.exec.store.dfs.FileSystemPlugin;

//创建插件配置实例

StoragePluginConfigconfig=newFileSystemConfig()

.with("param1","value1")

.with("param2","value2");

//注册插件

StoragePluginplugin=newFileSystemPlugin(config);

pluginRegistry.register(plugin);插件的管理包括插件的启用、禁用、更新和卸载。这些操作可以通过修改配置文件或使用Drill的管理API进行。2.2自定义数据源自定义数据源是Drill插件机制的一个重要应用,允许用户查询任何可以访问的数据源。自定义数据源的开发流程与存储插件类似,但需要更深入地理解数据源的特性和Drill的数据模型。2.2.1数据源接口实现实现自定义数据源的关键是实现TableProvider接口,该接口定义了如何获取数据表的元数据信息。以下是一个简单的TableProvider实现示例:importorg.apache.drill.exec.store.TableProvider;

importorg.apache.drill.exec.store.dfs.FileSystemConfig;

importorg.apache.drill.exec.store.dfs.FileSystemPlugin;

importorg.apache.drill.exec.store.dfs.PathFactory;

importorg.apache.drill.exec.store.dfs.PathSegment;

importorg.apache.drill.exec.store.dfs.ReadDefinition;

importorg.apache.drill.exec.store.dfs.Readers;

importorg.apache.drill.exec.store.dfs.SchemaNegotiator;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorConfig;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorFactory;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProvider;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderFactory;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPlugin;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginFactory;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactory;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImpl;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginImplFactoryImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImplImpl;

publicclassMyTableProviderimplementsTableProvider{

@Override

publicSchemaNegotiatorgetSchemaNegotiator(SchemaNegotiatorConfigconfig){

//实现获取数据表的元数据信息的逻辑

returnnull;

}

@Override

publicReadersgetReaders(ReadDefinitionreadDef){

//实现获取数据记录读取器的逻辑

returnnull;

}

}2.2.2数据源注册自定义数据源的注册与存储插件的注册类似,可以通过配置文件或API进行。以下是一个通过API注册自定义数据源的示例:importorg.apache.drill.exec.store.StoragePlugin;

importorg.apache.drill.exec.store.StoragePluginConfig;

importorg.apache.drill.exec.store.dfs.FileSystemConfig;

importorg.apache.drill.exec.store.dfs.FileSystemPlugin;

importorg.apache.drill.exec.store.dfs.PathFactory;

importorg.apache.drill.exec.store.dfs.PathSegment;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPlugin;

importorg.apache.drill.exec.store.dfs.SchemaNegotiatorProviderPluginFactory;

//创建插件配置实例

StoragePluginConfigconfig=newFileSystemConfig()

.with("param1","value1")

.with("param2","value2");

//创建自定义数据源实例

StoragePluginplugin=newSchemaNegotiatorProviderPluginFactory().create(config);

//注册数据源

pluginRegistry.register(plugin);2.2.3数据源管理数据源的管理包括启用、禁用、更新和卸载。这些操作可以通过修改配置文件或使用Drill的管理API进行。例如,禁用一个数据源可以通过修改配置文件中的enabled参数为false来实现。2.3总结ApacheDrill的插件机制和自定义数据源功能为数据分析师和开发者提供了极大的灵活性和可扩展性。通过理解和掌握这些机制,可以轻松地将Drill集成到各种数据环境中,实现对复杂数据源的高效查询和分析。请注意,上述代码示例仅为框架展示,实际开发中需要根据具体的数据源和需求进行详细实现。3自定义数据源开发3.1数据源插件的必要性在大数据分析领域,ApacheDrill以其强大的查询引擎和对多种数据源的支持而闻名。然而,随着数据存储技术的不断发展,新的数据格式和存储系统层出不穷。为了保持Drill的灵活性和适应性,自定义数据源插件的开发变得至关重要。数据源插件允许用户将Drill连接到任何数据存储,无论是文件系统、数据库还是云存储服务,从而扩展Drill的数据处理能力。3.1.1为什么需要自定义数据源插件?支持新数据格式:随着数据科学的发展,新的数据格式如Parquet、ORC、JSON等不断出现,自定义插件可以确保Drill能够读取和查询这些格式的数据。集成特定数据存储:企业可能使用特定的数据库或存储系统,如MongoDB、Cassandra或自定义的云存储服务,通过开发插件,Drill可以直接查询这些数据源,无需额外的数据迁移或转换。优化性能:对于特定的数据源,自定义插件可以优化数据读取和查询性能,提供更高效的数据处理能力。增强功能:插件可以添加特定于数据源的功能,如支持特定的查询优化策略或数据加密。3.2开发自定义数据源步骤开发自定义数据源插件涉及以下几个关键步骤:理解Drill的插件架构:Drill的插件架构基于Java,主要通过实现特定的接口来定义数据源的读取和查询逻辑。定义数据源插件类:创建一个类来实现org.apache.drill.exec.store包下的接口,如DrillbitStoragePlugin和StoragePluginConfig。实现数据读取和元数据扫描:通过实现ScanFramework和RecordReader接口,定义数据源的读取逻辑。同时,实现MetadataProvider接口来提供元数据扫描功能。注册插件:在Drill的配置文件中注册自定义插件,通常是在drill-override.conf文件中添加相应的配置。测试和调试:使用Drill的测试框架来验证插件的功能和性能,确保其能够正确地读取数据并响应查询。3.2.1代码示例:自定义数据源插件下面是一个简单的自定义数据源插件的代码示例,该插件用于读取存储在HDFS中的CSV文件://自定义数据源插件类

publicclassCSVStoragePluginimplementsDrillbitStoragePlugin{

privateStoragePluginConfigconfig;

privateFileSystemfs;

@Override

publicvoidinit(StoragePluginConfigconfig,PluginContextcontext)throwsException{

this.config=config;

this.fs=context.getFs(config);

}

@Override

publicMetadataProvidergetMetadataProvider(){

returnnewCSVMetadataProvider();

}

@Override

publicFragmentExecutorgetExecutor(){

returnnewCSVFragmentExecutor();

}

//其他方法实现...

}

//CSV元数据提供者

publicclassCSVMetadataProviderimplementsMetadataProvider{

@Override

publicTableMetadatagetTableMetadata(Tabletable)throwsException{

//实现获取CSV文件的元数据逻辑

//返回TableMetadata对象

}

//其他方法实现...

}

//CSV数据片段执行器

publicclassCSVFragmentExecutorimplementsFragmentExecutor{

@Override

publicRecordReadergetRecordReader(ScanSpecscanSpec,FragmentContextcontext)throwsException{

//实现CSV文件的记录读取逻辑

//返回RecordReader对象

}

//其他方法实现...

}3.2.2数据样例假设我们有以下CSV文件数据:id,name,age

1,John,30

2,Alice,25

3,Bob,35在自定义插件中,我们需要定义如何读取这些数据,并将其转换为Drill可以理解的格式。3.3数据源插件示例分析3.3.1分析CSVStoragePlugin在CSVStoragePlugin类中,我们首先初始化配置和文件系统,然后提供元数据提供者和数据片段执行器。元数据提供者负责获取CSV文件的元数据,如列名和数据类型,而数据片段执行器则负责实际的数据读取。3.3.2分析CSVMetadataProviderCSVMetadataProvider类中的getTableMetadata方法需要读取CSV文件的前几行,解析列名和数据类型,然后返回一个TableMetadata对象,该对象描述了CSV文件的结构。3.3.3分析CSVFragmentExecutorCSVFragmentExecutor类中的getRecordReader方法负责创建一个RecordReader对象,该对象可以逐行读取CSV文件,并将数据转换为Drill的记录格式。这通常涉及到解析CSV文件的每一行,将字符串数据转换为相应的数据类型,并构建Drill的记录。3.3.4总结通过上述步骤和代码示例,我们可以看到开发自定义数据源插件需要深入理解Drill的插件架构和数据读取逻辑。自定义插件不仅可以扩展Drill的数据源支持,还可以针对特定数据源优化性能,提供更丰富的功能。在实际开发中,还需要考虑错误处理、性能优化和安全性等方面,以确保插件的稳定性和可靠性。请注意,上述代码示例仅为简化版,实际开发中需要处理更多细节,如配置解析、错误处理和性能优化等。4集成自定义数据源4.1配置Drill以使用自定义数据源在ApacheDrill中,集成自定义数据源是通过扩展插件机制实现的。Drill支持多种数据源,包括HDFS、S3、MongoDB等,但有时用户可能需要处理特定格式的数据或访问特定的存储系统,这就需要自定义数据源插件。4.1.1步骤1:创建自定义插件首先,你需要创建一个自定义插件。这通常涉及到编写Java代码来实现org.apache.drill.exec.store包下的接口。例如,如果你要创建一个自定义的CSV数据源插件,你可能需要实现org.apache.drill.exec.store.dfs.AbstractFileFormatPlugin接口。importorg.apache.drill.exec.store.dfs.AbstractFileFormatPlugin;

importorg.apache.drill.exec.store.dfs.DrillFileSystem;

importorg.apache.drill.exec.store.dfs.ReadDefinitionBuilder;

importorg.apache.drill.exec.store.dfs.RecordReader;

importorg.apache.drill.exec.store.dfs.RecordReaderBuilder;

importorg.apache.hadoop.conf.Configuration;

importorg.apache.hadoop.fs.Path;

publicclassCustomCSVPluginextendsAbstractFileFormatPlugin{

publicCustomCSVPlugin(Configurationconfig){

super(config);

}

@Override

publicRecordReaderBuildergetReaderBuilder(ReadDefinitionBuilderreadDefBuilder,

DrillFileSystemdfs,

Pathpath){

//实现自定义的RecordReaderBuilder

returnnewCustomCSVRecordReaderBuilder(readDefBuilder,dfs,path);

}

//其他必要的方法实现...

}4.1.2步骤2:注册插件创建了自定义插件后,你需要在Drill集群中注册这个插件。这通常是在Drill的配置文件中完成的,例如drill-override.conf。#在drill-override.conf中添加自定义插件

drill.exec.plugins=[CustomCSVPlugin]

drill.exec.plugin.classpath=[/path/to/your/plugin/jar]4.1.3步骤3:配置数据源最后,你需要在Drill中配置你的自定义数据源。这通常涉及到在Drill的Web界面或通过SQL语句来创建一个新的存储插件。--创建自定义CSV数据源

CREATESTORAGEcsv_storage

WITH(type='CustomCSVPlugin',plugin='CustomCSVPlugin',

location='/path/to/csv/files',

format='CSV',

delimiter=',',

header=true);4.2测试自定义数据源连接一旦自定义数据源配置完成,下一步是测试连接是否成功。这可以通过查询自定义数据源中的数据来完成。--查询自定义CSV数据源中的数据

SELECT*FROMcsv_storage.csv_tableLIMIT10;如果查询成功返回数据,那么自定义数据源的连接和配置就是正确的。4.3优化数据源性能优化自定义数据源的性能是确保Drill能够高效处理数据的关键。以下是一些优化策略:4.3.1数据分区如果数据源支持分区,那么在创建存储插件时应该配置分区策略。这可以帮助Drill更快地定位数据,避免不必要的扫描。--创建分区的自定义CSV数据源

CREATESTORAGEcsv_storage

WITH(type='CustomCSVPlugin',plugin='CustomCSVPlugin',

location='/path/to/csv/files',

format='CSV',

delimiter=',',

header=true,

partition=(year,month,day));4.3.2使用索引对于大型数据集,创建索引可以显著提高查询性能。Drill支持创建列级索引,这可以加速对特定列的查询。--创建索引

CREATEINDEXidxONcsv_storage.csv_table(column_name);4.3.3调整查询计划Drill的查询优化器会自动选择最佳的查询计划,但有时手动调整查询计划可以进一步优化性能。例如,使用FORCEINDEX可以强制查询使用特定的索引。--强制使用索引

SELECT/*+FORCE_INDEX(idx)*/*FROMcsv_storage.csv_tableWHEREcolumn_name='value';4.3.4监控和调整资源最后,监控Drill集群的资源使用情况,并根据需要调整资源分配,可以确保自定义数据源的查询性能。这可能涉及到调整Drill的配置参数,如drill.exec.memory.limit,以控制内存使用。通过以上步骤,你可以成功地在ApacheDrill中集成自定义数据源,并优化其性能,以满足大数据分析的需求。5高级主题与最佳实践5.1Drill的动态数据发现在ApacheDrill中,动态数据发现是一个核心特性,它允许用户查询位于不同存储系统中的数据,而无需事先定义数据的结构。这一特性极大地简化了数据探索过程,使得Drill成为处理半结构化和非结构化数据的理想工具。5.1.1原理Drill通过其独特的“schema-less”查询引擎实现动态数据发现。当Drill接收到查询请求时,它会动态地分析数据的结构,生成相应的schema,然后执行查询。这一过程在查询执行时进行,而不是在数据加载时,这使

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论