HBase、Pig、Sqoop实战解析:三大组件原理与故障排查
2026/9/24 19:36:22 网站建设 项目流程

这篇内容本来是我整理给自己团队的内部资料,后来想想,里面不少东西是当年一个个坑踩出来的,拿出来分享比烂在笔记里更有价值。

刚接触 Apache Hadoop 生态的人,十有八九会被一堆以"字母 H 开头"的组件搞晕:HDFS、Hive、HBase、Hue,再加上个叫 Pig、Sqoop 的,光看名字完全猜不出各自干什么。我当年就闹过笑话,以为 Pig 跟 Hive 差不多,都是写 SQL 跑 MapReduce 的;以为 Sqoop 是数据库连接池之类的工具;至于 HBase,一度觉得它就是个"带版本的 HDFS"。等到真正上手做项目才发现,这三个组件定位差异极大,用对了地方效率翻倍,用错了能把自己坑到怀疑人生。

这篇文章我会围绕 HBase、Pig、Sqoop 各自的核心职责、底层原理和常见故障展开,所有内容都来自实际跑生产环境的经验。如果你正在学 Hadoop 生态,或者已经入行但还没系统捋过这三者的边界,这篇文章可以帮你省下大把摸索时间。

1. 先搞清楚三兄弟的分工,才不会瞎用工具

业内习惯把 Hadoop 生态比作一套完整的"数据加工厂",HDFS 是原料仓库,MapReduce 是加工车间,YARN 是调度室。而 HBase、Pig、Sqoop 这三兄弟分别干的是:HBase 是"实时存取柜台",Pig 是"流水线作业指导书",Sqoop 是"搬运货车"。这个类比虽然粗糙,但足够帮你建立第一印象。

1.1 HBase:面向海量数据的实时读写数据库

HBase 是一个分布式、可扩展、面向列族存储的 NoSQL 数据库,运行在 HDFS 之上。它解决的问题非常明确:当数据量达到 TB 甚至 PB 级别时,传统关系型数据库的读写性能会断崖式下跌,而 HBase 依然能提供毫秒级的随机读写能力。

它的典型应用场景包括:

  • 用户行为日志实时采集,支撑推荐系统、风控系统实时查询
  • 订单中心、消息中心等需要海量行存储的业务
  • 物联网设备上报数据的时序存储
  • 聊天记录、评论等社交类数据的读写

HBase 不适合做复杂关联查询、事务性强的业务,也不适合做分析型报表。记住这条边界,后面很多选型问题就迎刃而解。

1.2 Pig:数据流脚本语言的代表

Pig 是 Yahoo 贡献给 Apache 的项目,核心组件是 Pig Latin 这门数据流脚本语言。它跟 Hive 一样,底层都会翻译成 MapReduce 作业在 YARN 上执行,但侧重点完全不同。

Hive 的 HQL 是"SQL 风格"的声明式语言,你说"我要什么",优化器想办法帮你查。而 Pig Latin 是"数据流风格"的流程式语言,你写的是"数据从哪来、每一步怎么变换、最后输出什么"。这种差异让 Pig 在处理 ETL(数据清洗)场景时格外顺手,因为 ETL 本身就是一条明确的流水线:加载 → 过滤 → 转换 → 关联 → 存储。

Pig 还自带一个很有用的特性:不用把数据先灌进表里,直接对 HDFS 上的文件做处理,非常适合临时性的数据探查和验证。

1.3 Sqoop:关系型数据库与 Hadoop 之间的搬运工

Sqoop(SQL-to-Hadoop)解决的问题就一个:让数据在关系型数据库(MySQL、Oracle、PostgreSQL 等)和 HDFS、Hive、HBase 之间高效流转。

不需要你手写复杂的 MapReduce 程序去挨个读数据库表,Sqoop 一条命令就能把整张表导入 HDFS,也能将 HDFS 上的处理结果回写到数据库。它内部帮我们做了 JDBC 连接管理、数据分片、类型映射、错误处理一堆脏活累活。

这三者各有领地、又能相互配合。接下来我分别拆解每个组件的底层原理和实际使用心得。

2. HBase 的底层逻辑:从 RowKey 设计到 WAL 机制

HBase 用起来的核心门槛不在 API,而在对它的存储模型理解是否到位。很多报错和性能问题,根子上都是表设计不合理。

2.1 存储模型务必理解透:RowKey、列族、Cell

HBase 的逻辑模型是一张"稀疏的、多维的、带版本号的映射表"。这条定义展开讲,就是所有使用 HBase 的人必须时刻记住的几个核心概念:

**RowKey(行键)**是每行数据的唯一标识,HBase 中的数据会按照 RowKey 的字典序自动排序存储。这个排序特性决定了 RowKey 的设计直接决定读写性能。连续的行会被分到同一个 Region(数据分片)中,所以如果写入的 RowKey 是单调递增的(比如时间戳),那么所有写请求都会打到同一个 Region 上,形成热点,集群的并行能力完全发挥不出来。

**列族(Column Family)**是列的集合,是 HBase 物理存储的基本单位。列族里的列可以动态添加,不需要预先定义,这正是"HBase 适合半结构化数据"的原因。一个表建议最多设置 2-3 个列族,因为不同列族的数据会分开存储,但写入时还是会一起锁行,列族太多会导致写放大严重。

**Cell(单元格)**由 RowKey + 列族 + 列限定符唯一确定,存储的值带时间戳版本。读取时默认返回最新版本,也可以指定读取某个历史版本。

对比 Hive 这种"表结构固定"的分析型存储,HBase 的灵活性一眼可见。但灵活性也意味着你必须自己规划好"哪些信息放 RowKey、哪些放列",设计错了后期改造成本极高。

2.2 预写日志 WAL 的作用与异常处理

WAL(Write-Ahead Log)是 HBase 高可靠性的根基,机制非常朴素:每次数据写入,先顺序追加到 HDFS 上的 WAL 日志文件,再写入内存中的 MemStore。一旦 RegionServer 宕机,内存数据丢失后,可以从 WAL 重新恢复。

这个"先写日志、再写内存"的设计跟 MySQL 的 redo log 思路一致,都是牺牲一点写入延迟换取崩溃恢复能力。实际运维中最容易踩的坑有三个:

第一个坑是 WAL 路径相关报错。默认 WAL 路径在 HDFS 的/hbase/WALs目录下,如果看到Failed to create WAL或者.regioninfo相关的异常,八成是 HDFS 磁盘满了,或者 HBase 对 HDFS 目录的权限不对。先用hdfs dfs -du -h /hbase/WALs看看占用,再检查目录属主,这能解决 90% 的 WAL 路径异常。

第二个坑是 WAL 文件无限增长。RegionServer 正常运行时会定期把 WAL 里的数据刷入 HFile 并清理旧 WAL,如果清理不及时,WAL 目录会越来越大。检查是不是hbase.regionserver.maxlogs配置过小,导致日志滚动频繁但删除跟不上。

第三个坑是 WAL 损坏。一旦出现LeaseExceptionRecoveredEdits相关报错,说明 WAL 文件在写的过程中出现异常。不要急着删除 WAL,先尝试用hbase hbck修复,实在不行的再把损坏日志移到备份目录。

2.3 Master 初始化卡住的排查思路

群里有朋友遇到"HBase 页面一直显示 master initialing",这个问题我排查过一次,回忆下完整的链路很有参考价值:

先看 RegionServer 活了没有。Master 初始化要等所有 RegionServer 完成心跳注册,如果 RegionServer 起不来,Master 会一直等。查 RegionServer 日志,多半是java.net.UnknownHostException或者 zookeeper 连接超时导致的。

再看 Zookeeper 状态。HBase 的 Master 和 RegionServer 都要依赖 ZK 做分布式协调,如果 ZK 集群有问题,Master 也初始化不了。用zkServer.sh status查看各节点状态,确认 ZK 端口 2181 是否可正常通信。

最后检查 HDFS 安全模式。如果 HDFS 处于 safe mode,HBase 无法创建数据目录,Master 也会卡住。执行hdfs dfsadmin -safemode leave前一定先确认 HDFS 的健康状态,别强制退出安全模式掩盖真实故障。

2.4 端口清单与基本配置

配置 HBase 时最烦的就是端口冲突,我常用的端口清单如下:

服务端口说明
HBase Master Web UI16010查看集群状态、表信息
HBase Master RPC16000Master 与客户端通信
RegionServer Web UI16030查看 Region 分布
RegionServer RPC16020RegionServer 与客户端通信
Zookeeper2181协调服务,HBase 强依赖

hbase-site.xml里最重要的几项配置包括hbase.rootdir(数据在 HDFS 上的路径)、hbase.zookeeper.quorum(ZK 地址列表)、hbase.regionserver.handler.count(处理 RPC 请求的线程数)。线上环境 RegionServer 内存建议给到 16GB-32GB,堆内存给到 8GB-16GB,hfile.block.cache.size建议 0.4 左右,memstore.size建议 0.4 左右,两者加起来别超过 0.8,不然 GC 压力会非常大。

3. Sqoop 的运行机制与高频故障排查全记录

Sqoop 是这三个组件里"看起来最简单、用起来坑最多"的一个。我在生产环境大面积跑过 Sqoop 任务,把高频问题整理出来,很多人卡住的地方我来逐个说明。

3.1 Sqoop 1 和 Sqoop 2 怎么选

先说结论:生产环境老老实实用 Sqoop 1。Sqoop 2 引入了集中式的服务架构,虽然解决了 Sqoop 1 需要到处装客户端、命令不统一的问题,但当时生态不成熟,连接器支持不全,周边工具对接困难,用起来反而更麻烦。目前绝大多数公司的生产环境还在跑 Sqoop 1,基于 Hadoop 2.x 的 Sqoop 1.4.7 是最稳妥的组合。

Sqoop 1 的使用方式是命令行提交 MapReduce 作业,核心逻辑是:通过 JDBC 连接数据库读取元数据,利用数据库的分片键把数据切分成多个区间,然后每个区间交给一个 Map Task 去拉取,最终写到 HDFS 上。这个"分片拉取"机制很关键,理解了它,你就能明白为什么导入速度跟分片键选得好不好强相关。

3.2 导入导出的完整操作流程

Sqoop 导入 MySQL 表到 HDFS,最基础的一条命令:

sqoop import \ --connect jdbc:mysql://192.168.1.10:3306/business \ --username reader \ --password secret \ --table orders \ --target-dir /data/warehouse/ods/orders \ --fields-terminated-by '\001' \ --split-by id \ -m 4

--split-by是指定分片键,Sqoop 会根据这个字段的最大最小值把数据分成 4 个区间;-m 4是并行度,即同时启 4 个 Map Task。实测下来,分片键一定选数字类型的、有索引的列,比如主键 id。选字符串类型列会让分片查询走全表扫描,性能会差很多。

导入到 Hive:

sqoop import \ --connect jdbc:mysql://192.168.1.10:3306/business \ --username reader \ --password secret \ --table orders \ --hive-import \ --hive-table ods.orders \ --create-hive-table

Sqoop 会先帮我们建 Hive 表结构(列名、类型自动映射),再把数据落到 HDFS 后执行LOAD DATA INPATH加载到 Hive 表。

导出回 MySQL 用的则是 export:

sqoop export \ --connect jdbc:mysql://192.168.1.10:3306/business \ --username writer \ --password secret \ --table order_stats \ --export-dir /data/result/order_stats \ --input-fields-terminated-by '\001'

导出时有个致命细节:如果目标表存在主键或唯一索引,而导出的数据里有重复值,Map Task 会因为主键冲突直接失败。建议导出前先对数据做去重,或者把导出模式设成--update-key--update-mode allowinsert来做增量更新。

3.3 连接不上 MySQL 的常见原因

"Sqoop 连接不上 MySQL"是出现频率最高的求助帖,我梳理了原因清单:

**MySQL 8+ 认证插件问题。**MySQL 8 默认认证插件是caching_sha2_password,而 Hadoop 生态通常使用 MySQL 5.x 时代的mysql_native_password认证方式。两种插件不兼容,Sqoop 连不上。解决方案是在 MySQL 侧为 Sqoop 账号指定旧认证插件:

ALTER USER 'sqoop'@'%' IDENTIFIED WITH mysql_native_password BY 'password';

**JDBC 驱动缺失或版本不合。**Sqoop 默认路径里没带 MySQL 驱动,需要手动把mysql-connector-java的 jar 包放到$SQOOP_HOME/lib目录下。注意版本:MySQL 5.x 用 5.1.x,MySQL 8 用 8.0.x,版本不对会报No suitable driver found或各种奇怪的连接异常。

**JDBC URL 写法不对。**连接串必须带上useSSL=falsecharacterEncoding=utf8这类参数,否则可能出现 SSL 握手超时或中文乱码。推荐写法:

jdbc:mysql://192.168.1.10:3306/business?useSSL=false&characterEncoding=utf8

不要在这个连接串里加serverTimezone,老版本驱动不认识这个参数反而会报错。

3.4 ClassNotFoundException: org.apache.hadoop.crypto 的处理

运行 Sqoop 命令时遇到java.lang.ClassNotFoundException: org/apache/hadoop/crypto,这个问题本质是 Sqoop 的 classpath 里缺少 Hadoop 的hadoop-common依赖。原因是 Sqoop 脚本自己去遍历$HADOOP_HOME/share/hadoop/common时出了问题,常见于 Hadoop 3.x 和旧版 Sqoop 混用。

我当时的环境是 Hadoop 3.2 + Sqoop 1.4.7,踩了不少坑,最终稳定运行的处理方式是修改$SQOOP_HOME/bin/sqoop脚本里加载路径的逻辑,确保把 Hadoop 的三个目录都加进 classpath:

export HADOOP_CLASSPATH=$HADOOP_HOME/share/hadoop/common/*:$HADOOP_HOME/share/hadoop/common/lib/*:$HADOOP_HOME/share/hadoop/hdfs/*:$HADOOP_HOME/share/hadoop/hdfs/lib/*:$HADOOP_HOME/share/hadoop/mapreduce/*:$HADOOP_HOME/share/hadoop/mapreduce/lib/*:$HADOOP_HOME/share/hadoop/yarn/*:$HADOOP_HOME/share/hadoop/yarn/lib/*

设置完后用sqoop version验证是否能正常打印版本号,如果还是报错,再用sqoop import -D sqoop.mapreduce.classpath=$HADOOP_HOME/share/hadoop/common/*这种方式临时指定。这一步可以解决绝大多数 Sqoop 装完一运行就报缺类的问题。

4. Pig 的用武之地:数据流脚本是如何被翻译成 MapReduce 作业的

Pig 在现在的技术栈里热度不如 Hive,但它的设计思路放在今天依然有学习价值。尤其是处理"路径复杂的多阶段 ETL",Pig 写起来比 Hive 的嵌套子查询直观得多。

4.1 一段 Pig Latin 脚本的完整拆解

下面这段脚本是典型的 ETL 场景:从用户行为日志中筛选出近 7 天活跃用户,与用户维度表关联后输出结果。

-- 加载用户行为日志,字段分隔符为 \t logs = LOAD '/data/raw/user_logs' USING PigStorage('\t') AS (user_id:long, action:chararray, ts:long); -- 加载用户维度表 users = LOAD '/data/raw/users' USING PigStorage('\t') AS (user_id:long, name:chararray, city:chararray); -- 过滤出近期活跃记录 filtered = FILTER logs BY ts > 1667000000000; -- 按用户分组统计行为次数 grouped = GROUP filtered BY user_id; counts = FOREACH grouped GENERATE group AS user_id, COUNT(filtered) AS cnt; -- 与维度表做关联 joined = JOIN counts BY user_id, users BY user_id; -- 输出最终结果到 HDFS STORE joined INTO '/data/result/active_users' USING PigStorage('\t');

Pig Latin 的执行流程是:先做语法解析和逻辑计划优化,再翻译成物理执行计划,最后编译成一系列 MapReduce 作业。每遇到一个"会触发数据混洗"的操作(GROUP、JOIN、DISTINCT、ORDER BY),就会对应一个 MapReduce 阶段。所以你会发现,跑 Pig 脚本经常会看到多个 MapReduce 作业按顺序执行,这跟手工编写多个 MapReduce 程序的效果一样,但开发效率高了一个数量级。

4.2 Pig 与 Hive 的选择:什么时候用哪个

这张对比表能帮你快速做选择:

维度PigHive
语言风格数据流脚本,流程式SQL 类,声明式
适合场景复杂 ETL、多阶段流水线、数据探查即席查询、报表、统计分析
调试手段可直接对 LOAD 的结果逐步查看需要靠子查询和临时表
学习曲线需要理解数据流思想会写 SQL 就能上手
维护成本脚本直观但不如 SQL 普及团队普及率高

很多朋友问"现在学 Pig 还有用吗",我的看法是:如果你的集群里 Hive 已经跑得很好,团队 SQL 水平也不错,那完全没必须为了用 Pig 而用 Pig。但如果遇到那种"数据经过五六步变换、每步都有各种过滤条件和分支逻辑"的清洗任务,Pig 的数据流表达方式比 Hive 的大嵌套 SQL 好维护得多。我至今仍在用 Pig 做数据探查类的临时任务,因为不需要预先建表,LOAD 一个文件就能开跑,在定位数据质量问题上比 Hive 快得多。

4.3 执行 Pig 脚本的实用调试技巧

Pig 提供了三种执行模式,排错时我一般用 local 模式快速验证语法和结果:

pig -x local script.pig

生产上是全量 HDFS 数据,调试阶段先抽样一小份放本地,把-x local跑通,能省下大量集群排队时间。另外 Pig 支持DESCRIBE查看每一步的 Schema,支持ILLUSTRATE抽样执行,这两个命令是定位"字段类型对不上""关联结果为空"的神器。

-- 查看当前步骤的字段结构 DESCRIBE joined;

执行到某步时发现结果不对,可以先STORE到临时目录去查看输出,确认没问题再继续往下写。这种"分步验证"的习惯,是写 Pig 脚本不翻车的关键。

5. 三条协作链路:把 HBase、Pig、Sqoop 串起来干活

组件单独用都容易理解,真正能发挥价值的是把三者组合起来。这套协作模式在我之前做的用户行为分析平台里跑过完整闭环,下面分享最常用的三条链路。

5.1 链路一:Sqoop + HBase,业务数据实时服务化

场景是这样的:MySQL 里的订单表已经积累了上亿条历史数据,业务方希望做一个订单查询页面,按用户 ID 查订单列表,要求毫秒级响应。MySQL 在单表上亿后,即使建了索引,查询链路也容易触碰瓶颈。

方案是先用 Sqoop 把 MySQL 订单历史数据全量导入 HBase,再通过 HBase 的 Java API 或者 Phoenix 对接实时查询。导入命令:

sqoop import \ --connect jdbc:mysql://192.168.1.10:3306/business \ --username reader \ --password secret \ --table orders \ --hbase-table orders_hbase \ --column-family cf \ --hbase-create-table \ --split-by id \ -m 8

核心参数是--hbase-table指定 HBase 表名、--column-family指定列族名、--hbase-create-table让 Sqoop 自动建表。导入时 Sqoop 会默认把 MySQL 表的主键列作为 RowKey,注意这个 RowKey 是原样的字符串值,如果主键是自增数字,导入 HBase 后 RowKey 数字顺序排列,在高并发下可能发生热点写入,建议导入前在 SQL 查询里用反转或加盐的方式处理 RowKey。

5.2 链路二:Sqoop + Hive,离线数仓的日常同步

这是最经典的离线数仓同步链路。MySQL 业务库的表,每日凌晨通过 Sqoop 增量导入 Hive 数仓的 ODS 层:

sqoop import \ --connect jdbc:mysql://192.168.1.10:3306/business \ --username reader \ --password secret \ --table orders \ --where "update_time >= '2025-01-01 00:00:00'" \ --hive-import \ --hive-table ods.orders_delta \ --hive-partition-key dt \ --hive-partition-value 20250101 \ --split-by id \ -m 4

--hive-partition-key--hive-partition-value这对参数可以把增量数据自动落到指定分区。增量模式一般结合 MySQL 的update_time字段去做时间戳截断,但这要求源表必须建了 update_time 索引,否则每次全表扫描会压垮业务库。

5.3 链路三:Pig + HBase,流式清洗后写入

复杂度更高一点:日志数据先落 HDFS,用 Pig 做清洗转换,关联维度表后写入 HBase 提供服务。Pig 本身不直接支持写 HBase,需要借助HBaseStorage这个存储函数:

-- 清洗后的数据 cleaned = FOREACH logs GENERATE user_id AS rowkey, action AS cf:action, city AS cf:city, cnt AS cf:cnt; -- 写入 HBase 表 STORE cleaned INTO 'hbase://behavior' USING org.apache.pig.backend.hadoop.hbase.HBaseStorage( 'cf:action cf:city cf:cnt', '-loadColumn true');

这条链路的优势在于,一条 Pig 脚本就把"清洗、关联、装载"三步做完了,不需要中间落多份临时数据。HBaseStorage的语法比较老,遇到类冲突时,把hbase-server的 jar 包加到 Pig 的 lib 下基本能解决。

5.4 实时和离线之间的数据一致性处理

多链路协同最容易出问题的是数据口径对不上。Sqoop 同步 MySQL 数据到 Hive,跑批时 MySQL 数据还在变;Sqoop 导入 HBase 时,业务库也还在写入。我在实际项目中总结了几条铁律:

**离线同步必须等业务低峰期。**增量抽取时,源库会有一定的读压力,在线业务高峰期跑同步会拖垮主库。建议在凌晨跑批。

**全量导入 HBase 前先清空目标表对应数据。**很多人直接跑第二次 Sqoop import,导致 HBase 里出现脏数据。正确做法是先disabletruncate目标表,或者通过--hbase-bulkload做批量加载。

**Pig 关联时一旦发现一对多关系,要明确去重策略。**比如订单表和支付表关联,一个订单可能有多笔支付,不先去重会导致结果翻倍,下游报表对不上。

6. 从生产环境跌打出来的几条选型建议

聊完了三兄弟的底层逻辑和协作场景,最后分享几条因为吃过亏才总结出来的"土办法"。

6.1 这些情况别用 HBase

HBase 确实强,但很多业务根本用不着它。如果你的数据量还在百万级以内,MySQL 加索引已经绰绰有余,引入 HBase 等于给自己找运维麻烦。另外,需要多表关联的复杂事务业务、需要实时聚合统计的业务,HBase 都不合适。它给的是大并发、海量数据场景下的单行或范围读能力,不是万能数据库。

6.2 Sqoop 性能调优就三板斧

Sqoop 慢,先看 Map 数量够不够:-m参数决定并行度,单表同步建议 4-8 个;再调--fetch-size修改每次从数据库拉取的行数,MySQL 建议 5000-10000;最后看数据库端是否有慢查询日志,--split-by字段没索引就是慢查询的元凶。这三板斧用下去,Sqoop 导入速度基本能翻倍。

6.3 RowKey 设计的黄金法则

HBase 表设计重中之重是 RowKey。我总结三条黄金法则:

**散列优先。**不要用自增 ID、时间戳这种连续值做 RowKey,生产上普遍做法是加盐(salting):在 RowKey 前拼一个随机前缀,让数据均匀分散到不同 Region。比如用户 ID 后取模:0001_user_100010002_user_10001

**长度适中。**RowKey 不是越长越好,建议控制在 20-100 字节。太短会导致数据热点无法分散,太长会浪费大量内存和存储空间。关键是保证唯一性和可辨识度。

**用 RowKey 承载查询字段。**查询场景如果"按用户查订单",RowKey 设计为user_id_reverse + timestamp就很合适,既能按用户前缀扫描,又能按时间排序。很多人上来就设计一个复杂 JSON 做 RowKey,查询时根本用不上,纯属给自己挖坑。

6.4 监控与运维的核心指标

正常跑生产,这些指标建议盯紧:

  • RegionServer 的 JVM Heap 使用率,超过 85% 会频繁 Full GC
  • HBase 的 Region 数量,单 RegionServer 上 Region 数超过 500 就要考虑预分区或拆分
  • HDFS 磁盘使用率,低于 80% 是健康水位,超过 90% 要立刻处理
  • HBase 的写请求队列长度,如果持续积压,检查 MemStore 刷写是否正常
  • Sqoop 作业的 Map 失败率,超过 5% 基本就是源库或网络有隐患

这些指标用 HBase 自带的 Web UI(16010 和 16030 端口)就能看到大部分,配合 Grafana 做可视化告警更好。


这些年我带过不少新人,发现一个规律:凡是肯沉下心把 HBase 的存储原理、Sqoop 的运行机制、Pig 的翻译流程搞明白的,后面做数据架构设计时思路都特别清晰。工具本身并不难,难的是理解每种工具背后的设计哲学。希望这篇文章能给正在这条路上摸索的朋友一些启发,少走几步弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询