版权声明:本文为CSDN博主「@TangXin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Happy_Sunshine_Boy/article/details/112007602
文章目录
1.PrestoSQL更名Trino
2.PrestoSQL-345 与 Hive-3.1.0 的操作
2.1 Presto连接Hive配置
2.2 基础配置介绍
2.2.1 在 Ambari 集群中操作 Hive 组件
2.2.2 在 Presto 集群客户端操作
2.2.3 Presto支持Hive的分区表操作
2.3 优化,提高查询性能
2.4 分区操作
2.5 特殊列
2.6 特殊表
2.7 操作示例 Examples
2.7.1 create schema(schema相当于MySQL中的数据库名称)
2.7.2 show schemas
2.7.3 create table
2.7.4 delete
2.7.5 call
2.7.6 call*
2.7.7 select
2.7.8 create table
2.7.9 analyze
2.7.10 drop table
2.7.11 drop schema
2.8 Presto连接Hive的局限性
2.9 Hive3 版本局限性
3.Presto与SparkSQL测试对比
4.测试内容及环境
4.1 生成数据
4.2 Presto CLI启动
4.3 SparkSQL CLI启动
5.测试结果
5.1 100G数据量测试结果对比
5.2 500G数据量测试结果对比
6.测试局限性
7.测试中遇到的问题
7.1 Too many open files
7.2 节点OOM宕机
7.3 目前产品化存在的问题
8.参数调优建议
==========================================================================
1、PrestoSQL更名Trino
2020年12月27日,PrestoSQL 项目名称更新为 Trino。
GitHub:https://github.com/trinodb/trino
仅仅更改了名字 ,社区和软件都没有改变。
Martin: it was inspired from neutrinos(subatomic particles), which are light and travel very fast. And then we made the name smaller by shortening it to trino
Trino的灵感来自于中微子,neutrinos(中微子)是一种光速很快的粒子,然后大佬们把这个名字缩短为trino。
更名的原因参考:https://mp.weixin.qq.com/s/ZOimAKDyeaZMLyKgXxH52A
2.PrestoSQL-345 与 Hive-3.1.0 的操作
2.1 Presto连接Hive配置
需要在presto集群的所有worker节点进行配置
cd /opt/presto-server-345
vim etc/catalog/hive.properties
注意:如果连接多个Hive集群,只保证hive.properties名称不能重复即可实现(待验证。。。)
名称 hive-hadoop2 是指定的,不能改变
connector.name=hive-hadoop2
hive.metastore.uri=thrift://example.net:9083
在worker节点重启presto服务
bin/launcher restart
2.2 基础配置介绍
Presto 只使用的 Hive 中的 Data files 和 Metadata 信息,没有使用 HiveQL;
Presto Hive connector 连接 Ambari2.7.3 集群操作 Hive3.1.0 组件,支持的 文件格式 包括:
ORC
Parquet
Avro
RCText (RCFile using ColumnarSerDe)
RCBinary (RCFile using LazyBinaryColumnarSerDe)
SequenceFile
JSON (using org.apache.hive.hcatalog.data.JsonSerDe)
CSV (using org.apache.hadoop.hive.serde2.OpenCSVSerde)
TextFile
为了在使用 Hive3.x 时对 Avro 表提供一流的支持。需要将以下属性定义添加到 Hive metastore 配置文件(hive-site.xml)中(并重新启动 metastore 服务)::
<property>
<name>metastore.storage.schema.reader.impl</name>
<value>org.apache.hadoop.hive.metastore.SerDeStorageSchemaReader</value>
</property>
注意:avro 格式不支持:
CREATE TABLE AS
,
CREATE TABLE
时不支持:partitioning(partitioned_by)
,bucketing(bucketed_by)
不支持修改列的:ALTER TABLE
登录 Ambari 平台进行设置,添加完成配置参数之后,在 Ambari 平台页面进行重启 Hive 组件操作:
image.png支持 表的类型:事务表(只支持对插入的读取和写入;完全支持分区和分桶操作)和 ACID表(ACID表只支持行级删除);
Presto支持读取 Hive 物化视图(materialized views),这些视图显示为常规的只读表;
Presto支持连接 多个 Hive 集群(保证配置文件名称 hive.properties 不相同即可);
基础配置必须在 Presto 所有的 worker 节点:hive.config.resources=/etc/hadoop/conf/core-site.xml,/etc/hadoop/conf/hdfs-site.xml
当 HDFS 不使用 Kerberos 时,Presto 使用 Presto 进程的 OS 用户访问 HDFS。例如:如果 Presto 以 nobody 的身份运行,它就以 nobody 的身份访问 HDFS。可以通过在 Presto JVM 配置中设置 HADOOP_USER_NAME 系统属性来覆盖这个用户名,用适当的用户名替换hdfs_user:-DHADOOP_USER_NAME=hdfs_user
注意: 当更改 Presto 用户访问 HDFS 时,先在 HDFS 上删除 /tmp/Presto-*,因为新用户可能无法访问现有的临时目录。
2.2.1 在 Ambari 集群中操作 Hive 组件
切换hive用户
su - hive
生成测试数据
cat user.txt
1,abc,25,13188888888888
2,test,30,13888888888888
3,adsfsadf,34,899314121
上传hdfs
hadoop fs -put /hive/user.txt /tmp
启动hive客户端
hive
创建数据库
create database hivetest;
使用数据库
use hivetest;
创建数据表
create table tgm_test
(id int,
name string,
age string,
tel string)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
STORED AS TEXTFILE;
导入数据
load data inpath 'user.txt' into table tgm_test;
查询数据
select * from tgm_test;
+--------------+----------------+---------------+-----------------+
| tgm_test.id | tgm_test.name | tgm_test.age | tgm_test.tel |
+--------------+----------------+---------------+-----------------+
| 1 | abc | 25 | 13188888888888 |
| 2 | test | 30 | 13888888888888 |
| 3 | adsfsadf | 34 | 899314121 |
+--------------+----------------+---------------+-----------------+
2.2.2 在 Presto 集群客户端操作
客户端连接
./presto-cli --server manager.presto:8080 --catalog hive
查询schemas,相当于查询数据库
presto> show schemas from hive;
Schema
default
hivetest
information_schema
(3 rows)
Query 20201117_011102_00000_b3d8m, FINISHED, 3 nodes
Splits: 53 total, 53 done (100.00%)
1.22 [3 rows, 48B] [2 rows/s, 39B/s]
查询表
presto> show tables from hive.hivetest;
Table
tgm_test
(1 row)
Query 20201117_011151_00002_b3d8m, FINISHED, 3 nodes
Splits: 53 total, 53 done (100.00%)
0.49 [1 rows, 26B] [2 rows/s, 53B/s]
查看表接口
presto> desc hive.hivetest.tgm_test;
Column | Type | Extra | Comment
--------+---------+-------+---------
id | integer | |
name | varchar | |
age | varchar | |
tel | varchar | |
(4 rows)
Query 20201117_011709_00004_b3d8m, FINISHED, 3 nodes
Splits: 53 total, 53 done (100.00%)
0.24 [4 rows, 260B] [16 rows/s, 1.05KB/s]
查询数据
presto> select * from hive.hivetest.tgm_test;
Query 20201117_011207_00003_b3d8m failed: Failed to list directory: hdfs://manager93.bigdata:8020/warehouse/tablespace/managed/hive/hivetest.db/tgm_test
注意:该问题是由于远程 Ambari 集群,HDFS 路径文件没有访问权限,设置为:777权限,可以执行查询。
设想远程 Ambari 集群,开启 Ranger 权限控制也可以授权 presto 用户进行操作(Ambari集群节点需要有presto相应的用户)。
查询数据
presto> select * from hive.hivetest.tgm_test;
id | name | age | tel
----+----------+-----+----------------
1 | abc | 25 | 13188888888888
2 | test | 30 | 13888888888888
3 | adsfsadf | 34 | 899314121
(3 rows)
Query 20201117_012244_00005_b3d8m, FINISHED, 2 nodes
Splits: 17 total, 17 done (100.00%)
0.36 [3 rows, 73B] [8 rows/s, 203B/s]
2.2.3 Presto支持Hive的分区表操作
注意:Presto支持Hive的分区表操作,已根据以下步骤验证过
1.创建分区表
create table test_emp
(name string,
salary bigint)
partitioned by (dt string)
row format delimited fields terminated by ','
lines terminated by '\n'
stored as textfile;
2.示例数据data.txt
zhangsan,200
lisi,400
wangwu,350
zhaoliu,409
xiaoming,500
3.把data.txt上传到hdfs /tmp目录
hadoop fs -put data.txt /tmp
4.把data.txt导入Hive
load data inpath '/tmp/data.txt' into table test_tmp partition(dt='2020-12-27')
5.查询Hive分区表test_tmp
select * from test_tmp;
select * from test_tmp where dt='2020-12-29';
2.3 优化,提高查询性能
如果查询语句很复杂,并且包含大型数据集的连接查询,应该在 tables/partitions 运行 ANALYZE,通过收集数据的统计信息来提高查询性能;
在分析分区表时,可以通过可选的 partitions 属性指定要分析的分区(可以是分区的全集也可以是子集),这个属性是一个数组,包含分区键的值,按照在表模式中声明的顺序:
ANALYZE table_name WITH (
partitions = ARRAY[
ARRAY['p1_value1', 'p1_value2'],
ARRAY['p2_value1', 'p2_value2']])
该查询将收集键值为 p1_value1、p1_value2 和 p2_value1、p2_value2 的两个分区的统计信息
收集所有列的统计信息并不是必要的, 只需对特定的列的统计信息进行收集即可:
ANALYZE table_name WITH (
partitions = ARRAY[ARRAY['p2_value1', 'p2_value2']],
columns = ARRAY['col_1', 'col_2'])
该查询收集键值为p2_value1、p2_value2的分区的列col_1和col_2的统计信息。
注意: 如果该列的统计信息已被收集,应该先删除之前收集的统计信息,再进行重新收集:
可以删除所有的统计信息
CALL system.drop_stats(schema_name, table_name)
也可以只删除特定分区和列的统计信息
CALL system.drop_stats(schema_name, table_name, ARRAY[ARRAY['p2_value1', 'p2_value2']])
Presto支持 动态过滤 优化,任何文件格式存储的分区表都支持动态分区修剪,动态桶修剪仅支持用于广播的任何形式文件格式存储的表。【动态过滤操作是延时的,减少扫描数据量,节省查询和CPU时间】
2.4 分区操作
在指定的表中创建一个空分区
system.create_empty_partition(schema_name, table_name, partition_columns, partition_values)
在 metastore 检查和更新分区列表,有三种模式可供选择:
system.sync_partition_metadata(schema_name, table_name, mode, case_sensitive)
ADD: 添加文件系统上存在、但不包括在metastore中的任何分区。
DROP: 删除在metastore中存在、但不在文件系统上的任何分区。
FULL: 同时执行 ADD 和 DROP 操作。
case_sensitive参数是可选的,默认值为true表示与Hive的MSCK修复表行为的兼容性,即期望文件系统路径中的分区列名使用小写(例如col_x=SomeValue)。文件系统上不符合此约定的分区将被忽略,除非将参数设置为false。
删除分区子集或整个表的统计信息。分区被指定为数组,其元素是分区值的数组(类似于create_empty_partition中的partition_values参数)。如果省略了partition_values参数,则删除整个表的统计信息。
system.drop_stats(schema_name, table_name, partition_values)
将现有位置注册为指定表的元存储中的新分区。当省略location参数时,将使用partition_columns和partition_values构造分区位置。出于安全原因,仅当hive.allow-register-partition-procedure设置为true时,才启用该过程。
system.register_partition(schema_name, table_name, partition_columns, partition_values, location)
在元存储中为指定表注销给定的现有分区。 分区数据不会被删除。
system.unregister_partition(schema_name, table_name, partition_columns, partition_values)
2.5 特殊列
除了定义的列之外,Hive connector 还会自动在每个表的许多隐藏列中公开元数据。 可以像其他任何列一样在SQL语句中使用这些列,例如,可以直接选择它们或在条件语句中使用它们。
image.png2.6 特殊表
原始 Hive 表属性可以作为隐藏表使用,每个表属性包含单独的列,单独的一行包含属性值。属性表名与附加$properties的表名相同。可以检查属性名称和值与一个简单的查询:
SELECT * FROM hive.web."page_views$properties";
2.7 操作示例 Examples
2.7.1 create schema(schema相当于MySQL中的数据库名称)
创建一个名为 web 的新 Hive schema:
CREATE SCHEMA hive.web
image.png
2.7.2 show schemas
查看新创建的 schema web:
show schemas from hive
image.png
2.7.3 create table
在 web schema 中创建一个名为 page_views 的新 Hive 表,它使用 ORC 文件格式存储,按日期和国家进行 分区,并按用户分组到50个 桶 中。注意,Hive 要求分区列是表中的最后一列:
CREATE TABLE hive.web.page_views (
view_time timestamp,
user_id bigint,
page_url varchar,
ds date,
country varchar
)
WITH (
format = 'ORC',
partitioned_by = ARRAY['ds', 'country'],
bucketed_by = ARRAY['user_id'],
bucket_count = 50
)
image.png image.png
2.7.4 delete
从 page_views 表中删除一个分区。
DELETE FROM hive.web.page_views
WHERE ds = DATE '2016-08-09'
AND country = 'US'
2.7.5 call
向 page_views 表添加一个空分区。
CALL system.create_empty_partition(
schema_name => 'web',
table_name => 'page_views',
partition_columns => ARRAY['ds', 'country'],
partition_values => ARRAY['2016-08-09', 'US'])
image.png
image.png
image.png
2.7.6 call*
删除 page_view s表的一个分区的统计信息。
CALL system.drop_stats(
schema_name => 'web',
table_name => 'page_views',
partition_columns => ARRAY['ds', 'country'],
partition_values => ARRAY['2016-08-09', 'US'])
报错:io.prestosql.spi.PrestoException: line 4:5: Unknown argument name: partition_columns
使用如下命令进行分区删除不报错:
DELETE FROM hive.web.page_views
WHERE ds = DATE '2016-08-09'
image.png
2.7.7 select
查询page_views表:
SELECT * FROM hive.web.page_views
image.png列出page_views表的分区:
SELECT * FROM hive.web."page_views$partitions"
image.png
2.7.8 create table
创建一个名为 request_logs 的 外部 Hive 表,它指向 HDFS 中:
CREATE TABLE hive.web.request_logs (
request_time timestamp,
url varchar,
ip varchar,
user_agent varchar
)
WITH (
format = 'TEXTFILE',
external_location = 'hdfs://manager93.bigdata:8020/tmp/presto'
)
-- 后台使用hive用户(不是说启动Presto的OS用户吗?)去写 hdfs 目录 /tmp/presto,需要赋权 hive 用户对 hdfs 目录 /tmp/presto 有写的权限
image.png2.7.9 analyze
收集 request_logs 表的统计信息:
ANALYZE hive.web.request_logs
-- 由于没有数据,所以暂时收集不到统计信息
image.png2.7.10 drop table
删除外部表 request_logs。只会删除该表的元数据。引用的数据目录不被删除:
DROP TABLE hive.web.request_logs
-- 报错:io.prestosql.spi.security.AccessDeniedException: Access Denied: Cannot drop table web.request_logs
-- 缺省的 hive 安全设置不允许删除表。在配置单元目录文件中设置此配置以启用 drop 表:hive.allow-drop-table=true
-- 切换 presto 用户,在 presto 的所有 worker 节点,/opt/presto-server-345/etc/catalog/hive.properties文件中添加配置:hive.allow-drop-table=true
-- 重启presto:bin/launcher restart
image.png2.7.11 drop schema
删除 schema:
DROP SCHEMA hive.web
-- 删除schema之前,先要删除schema下面的所有tables,保证schema是空的,否则报错:Schema not empty: web
-- 强制删除
image.png2.8 Presto连接Hive的局限性
仅当 Where 子句匹配整个分区时,才支持 Delete 删除操作;
Alter Schema 使用失败,因为 Hive Metastore 不支持 Schema 重命名;
2.9 Hive3 版本局限性
出于安全原因,无法访问 sys 系统目录;
image.png不支持 Hive 带有 timestamp with local zone(本地区域数据类型的时间戳)。 可以从具有这种类型列的表中读取,但无法访问列数据。 不支持写入这样的表。
由于 Hive 问题 hive21002 和 hive22167,Presto 不能正确地从由 Hive 3.1 或更高版本创建的 Parquet、RCBinary 或 Avro 文件格式中读取时间戳值。当读取这些文件格式时,Presto 返回的结果与 Hive 不同。
可以使用 ORC 格式创建事务性表,如下所示:
CREATE TABLE <name>
WITH (
format='ORC',
transactional=true,
)
AS <query>
Presto 不支持收集 Hive 事务表的表统计信息。在表创建之后,需要使用 Hive 来收集表统计信息和分析表计算统计信息。
3.Presto与SparkSQL测试对比
本次测试是使用 TPC-DS 工具进行 100G、500G 数据量生成并导入 Hive 仓库,作为 Presto 和 SparkSQL 性能测试对比的数据集。通过本次对比测试的结果,粗略浅析 Presto 作为 SQL On Hadoop 解决方案的优劣。
4.测试内容及环境
数据集及测试:
image.png
前20条SQL:SQL1、SQL2、SQL3、SQL4、SQL5、SQL6、SQL7、SQL8、SQL9、SQL10、SQL11、SQL12、SQL13、SQL15、SQL16、SQL17、SQL18、SQL19、SQL20、SQL21
100G数据量参数配置:
image.png
注意:虽然100G数据量下测试配置内存不同,但是由于数据量小,都能 全缓存 本次100G数据。
500G数据量参数配置:
image.png注意:500G数据量下,Presto与SparkSQL的参数配置。在本次测试中,保证Presto和SparkSQL执行内存、CPU核数(168)等资源相等。但是,以上配置并非是Presto和SparkSQL相同资源下的最优配置。
4.1 生成数据
cd /root/tpcds/generator
100G数据量生成
hadoop jar target/tpcds-gen-1.1.jar -d /tpcds100Presto/ -p 60 -s 100
500G数据量生成
hadoop jar target/tpcds-gen-1.1.jar -d /tpcds5100Presto/ -p 60 -s 500
修改权限
hadoop fs -chmod -R 777 /tpcds100Presto
hadoop fs -chmod -R 777 /tpcds500Presto
导入Hive仓库
注意:
本次100G数据量测试没有使用Hive的分区表,因为Presto在查询Hive分区表时报错,暂时把分区删除了。
本次500G数据量有Hive分区表,Presto支持对Hive的分区表操作。
hive -f hive-mr-tpcds-external.sql
hive -f hive-mr-tpcds-drop-create.sql
hive -f hive-mr-tpcds-insert.sql
授权
hadoop fs -chmod -R 777 hdfs://manager:8020/warehouse/tablespace/managed/hive/tpcds100.db
hadoop fs -chmod -R 777 hdfs://manager:8020/warehouse/tablespace/managed/hive/tpcds500.db
4.2 Presto CLI启动
cd /usr/hdp/3.0.1.0-187/presto/presto-server-345
100G数据量测试,PResto CLI启动
./presto-cli --server manager:30088 --catalog hive --schema tpcds100
500G数据量测试,PResto CLI启动
./presto-cli --server manager:30088 --catalog hive --schema tpcds500
在CLI中粘贴SQL进行查询,特别不方便,最好在页面支持SQL语句的执行及监控。
执行HDP20-Presto.sql中的SQL语句,该文件中有20条SQL语句,从TPC-DS的HDP.sql 99条SQL中选择的前20条SQL(不知道挑选哪些,默认选择了前20条)
HDP20-Presto.sql中的SQL语句,语法需要修改,不然在Presto中执行报错,修改规则如下:
-
关键词 "AS DATE" 修改为 "AS date";
-
"+ INTERVAL 30 days" 修改为 "+ INTERVAL '30' day"
-
Presto不支持反引号 `` 修改为双引号 "";
-
Presto不支持 "CAST('2002-02-01' AS DATE)" 修改为 "DATE '2002-02-01'"
......待发现
4.3 SparkSQL CLI启动
100G数据量SparkSQL启动
/usr/hdp/3.0.1.0-187/spark2/bin/spark-sql --driver-memory 20G --executor-memory 30G --num-executors 12 --executor-cores 10 --master yarn --deploy-mode client --database tpcds100 --conf spark.sql.shuffle.partitions=360 --conf spark.speculation=true --conf spark.sql.auto.repartition=true
500G数据量SparkSQL启动
/usr/hdp/3.0.1.0-187/spark2/bin/spark-sql --driver-memory 20G --executor-memory 20G --num-executors 6 --executor-cores 28 --master yarn --deploy-mode client --database tpcds500 --conf spark.sql.shuffle.partitions=360(***) --conf spark.speculation=true --conf spark.sql.auto.repartition=true
5.测试结果
5.1 100G数据量测试结果对比
使用Presto执行20条SQL执行5个轮次,取第1轮次与2~5轮次的平均值进行对比,结果如下:
image.png
可以看出,100G数据量下,第1轮此比2~5轮次平均值 提升 19%,Presto也是利用了内存缓存机制,但是效果不如SparkSQL明显,可能是数据量较小,差别不明显。
使用Presto与SparkSQL,取2~5轮次的平均值进行对比,结果如下:
image.png可以看出,100G数据量下,Presto的性能略好与SparkSQL,平均性能 提升 49%;但是也存在一些SQL性能略低于SparkSQL;
5.2 500G数据量测试结果对比
使用Presto执行20条SQL执行5个轮次,取Presto on Hive第1轮次SparkSQL on Hive第1轮次结果进行对比,结果如下:
image.png由上面的对比可知,在相同资源下,Presto on Hive第一次执行SQL的性能是SparkSQL on Hive第一次执行SQL性能的 3.7倍。但是也存在一些SQL(SQL1、SQL9)使Presto的性能不如SparkSQL。
使用Presto与SparkSQL,取2~5轮次的平均值进行对比,结果如下:
image.png由上面的对比可知,在相同资源下,Presto on Hive执行SQL的平均性能是SparkSQL on Hive执行SQL平均性能的 4.1倍。但是也是存在一些SQL(SQL9)使Presto性能不如SparkSQL。
6.测试局限性
本次测试存在以下局限性:
本次100G、500G数据量下测试的局限性:
a. (100G)Presto集群内存资源设置600G
,SparkSQL提交到yarn集群,总Executor内存设置360G
,内存配置应该在相同的情况下进行测试;
b. (100G、500G)SQL选择,前20条,不具有代表性,Presto和SparkSQL主要是基于内存,应该选择一些大表,分区表进行测试;
c. (100G)测试数据量100G太小;
d. (100G、500G)Presto与SparkSQL的SQL查询语句略有不同,支持语法有别(差别不大),执行TPC-DS中SQL时Presto测试需要修改,暂时使用前20条SQL进行测试;
e. (100G、500G)Ambari平台集成的Presto集群稳定性还需要再做提升,测试时遇到节点不稳定的问题;
f. (100G、500G)Presto和SparkSQL的配置,并非其相同资源下的最优配置。
7.测试中遇到的问题
7.1 Too many open files
在Presto CLI执行SQL时,数据量在500G、2T,大数据量下时,会遇到以下问题:
Query 20201223_205832_00013_bczwm failed: Error opening Hive split hdfs://manager:8020/warehouse/tablespace/managed/hive/tpcds500.db/store_sales/000025_0 (offset=33554432, length=33554432): DestHost:destPort manager:8020 , LocalHost:localPort manager/192.168.10.1:0. Failed on local exception: java.io.IOException: Too many open files
解决:
问题原因,是集群节点打开文件数限制了,做如下设置:
1.设置ulimit
ulimit -n 1000000
2.设置最大文件打开数,最大进程打开数
vim /etc/security/limits.conf
-
soft noproc 20000
-
hard noproc 20000
-
soft nofile 131072
-
hard nofile 131072
7.2 节点OOM宕机
在使用Presto执行大数据量(500G、2T)的测试时,在SQL执行的过程中会遇到worker节点挂掉的现象,导致Presto集群不稳定,查看日志,报错 OutOfMemory,还有一些其他错误。
以上错误通过参数调优可以解决。
7.3 目前产品化存在的问题
Presto集群 参数调优 很复杂,暂时没有完全弄清楚所有配置参数的含义及设置大小(可以着手常见最优配置);
Coordinator存在 单点故障(nginx代理);
查询大数据量时容易造成OOM,导致节点宕机(参数调优,溢写到磁盘);
Presto没有容错能力,无重试机制(MPP架构通病);
Presto查询时占用内存较大,如果和其他服务公用节点,易影响其他组件性能(考虑单独集群部署);
8.参数调优建议
image.png image.png
网友评论