
1. 环境准备与架构理解1.1 为什么用Sqoop导数据到HBase先说结论Sqoop从关系型数据库往HBase导数据这件事在大数据链路里属于“脏活累活”但也是绕不开的一环。很多团队的实际场景是——业务库在MySQL/Oracle里数仓底表在Hive里而实时查询或者特征服务需要毫秒级点查这时候HBase作为在线存储层就顶上来了。而数据怎么从业务库搬进HBaseSqoop是最朴素、最不容易翻车的方案。可能有人会问为什么不直接用Java API写个程序查出MySQL的数据再put到HBase当然可以但在以下场景里Sqoop有明显优势数据量在百万到千万级别逻辑简单不值得为一次性导入写一堆代码。需要周期性全量或增量导入Sqoop的命令化脚本比代码更容易维护。团队以SQL工程师为主不熟悉Java或Scala生态。但我不推荐Sqoop的场景也明确说一下如果你的数据量在亿级以上且写入压力大或需要复杂的ETL转换比如多表join、清洗规则特别多或者要实时同步binlog增量那请直接考虑HBase原生BulkLoad或Canal、DataX这类方案。Sqoop适合的是“中等体量、批量、周期性”的导入诉求定位清晰别拿它当全能工具。再说说Sqoop与HBase协作的本质Sqoop任务本质是一个MapReduce作业。导入HBase时Mapper从关系型数据库读取数据通过HBase的客户端API直接写入目标表。关键的认知是——Sqoop不直接操作HDFS而是通过HBase的写入接口落地数据。这个过程绕开了MapReduce写HFile的环节走的是标准的Put路径这意味着它会写入WAL会触发MemStore刷写和Region拆分。这也是为什么后面要讲预分区和Rowkey设计——不是锦上添花而是避免导入任务把HBase集群搞得不稳定。1.2 版本选择与端口清单Sqoop有两个大版本1.4.x是经典版本2.x是几乎没人用的服务端模式。我推荐直接用Sqoop 1.4.7配Apache Hadoop 2.x或3.x都能稳定工作。HBase这边建议用HBase 1.2.x或2.x主要注意和Hadoop版本的兼容性。需要特别注意的是Sqoop本身不自带HBase相关依赖。你想让Sqoop往HBase写数据必须手动把HBase的jar包放到Sqoop的lib目录下。这一步不做好后面必然报ClassNotFoundException之类的错。我踩过好多次这个坑后面会专门写一节讲解决方式。HBase集群端口这块很多新手容易搞混。整理一份常用端口清单组件端口号说明ZooKeeper2181客户端连HBase必过ZooKeeperHBase Master RPC16000HMaster服务端口HBase Master Web UI16010查看Master状态和Region分布RegionServer RPC16020数据读写服务端口RegionServer Web UI16030查看RegionServer负载和Region详情HBase REST8080REST API服务可选如果你的集群是CDH或HDP发行版端口配置一般是套模板的我上面列出的是Apache原生默认值。排查Sqoop能否连上HBase时第一件事就是telnet这几个端口。如果直接从Sqoop机器上访问2181和16020必须通。别一上来就怀疑代码有问题八成是网络或者端口没放行。1.3 从整体链路理解数据流向建议在执行任何命令之前先在纸上把整条数据链路画出来想清楚每一步的输入输出。从MySQL的一张表到HBase的一张表数据经过了这样几步Sqoop任务启动MapReduce ApplicationMaster向YARN申请资源。每个Mapper通过JDBC从MySQL读取数据默认按主键拆分查询范围。Mapper解析出每一行数据按指定的映射规则生成Put对象。HBase客户端将Put写入目标RegionServer同时先写WAL预写日志。MemStore累积到阈值后刷写为HFile文件最终由HBase后台合并。理解这条链路的价值在于你能知道瓶颈可能会出在哪儿。比如Mapper数量配置不合理可能导致MySQL被打爆慢查询RegionServer数量少而写入量大可能导致MemStore刷写频繁出现阻塞表没有预分区所有写入都怼到同一个Region那个Region所在的RegionServer必然成为热点。这些隐患在几百条数据的测试阶段根本看不出来一旦上生产就会以各种异常的形式暴露出来。2. 导入前的必知参数与会话设计2.1 Sqoop四种导入模式怎么选Sqoop 1.4.7支持import、import-all-tables、codegen、eval四种常用操作模式实操中用得最多的是import。但你得知道另外几个模式的存在因为调试依赖它们import核心模式把一张表从关系型数据库导入HDFS、Hive或HBase。import-all-tables把数据库里所有表都导入一遍。适合一次性迁移但大项目中容易因为外键关系和数据差异翻车我建议拆分单表来导。codegen生成Java类让你能自定义Sqoop生成的反序列化逻辑。一般用不到除非你要对读取的数据做很特殊的处理。eval直接在关系型数据库上执行SQL并打印结果。调试JDBC连接、验证账号权限时好用。导入HBase时import模式的语法结构大概是这样的sqoop import \ --connect jdbc:mysql://hadoop102:3306/testdb \ --username root \ --password 123456 \ --table test_table \ --hbase-table target_table \ --column-family info \ --hbase-row-key id \ --hbase-create-table \ --split-by id \ -m 4这里面每个参数都有深意后面逐个讲。这里先提醒一个容易犯的错误--hbase-table指定的表名和--table指定的MySQL表名不是一回事前者是HBase里的目标表后者是数据源表。不少新手以为两个必须同名导致导完发现数据不在预期位置。2.2 核心参数逐个拆解连接参数--connect指定JDBC连接串MySQL的写法是jdbc:mysql://ip:port/database注意在后面加上?useSSLfalsecharacterEncodingutf-8否则可能出现SSL握手报错或中文乱码。--username和--password不多说生产环境建议把密码放到--password-file指定的HDFS文件里或直接用-P交互式输入别直接把密码明文写在命令里——你永远不知道这个命令行的记录会被谁看到。导入范围参数--table和--query二选一。--table指定单表配合--where可以过滤行--query让你写自定义SQL比如多表join后的结果。需要注意如果用了--querySQL语句里必须包含$CONDITIONS这个占位符Sqoop会用这个变量做数据拆分不写会直接报错。例如SELECT a.id, a.name, b.score FROM user a JOIN order b ON a.id b.user_id WHERE $CONDITIONSHBase映射参数--hbase-tableHBase目标表名。--column-family写入的列族名必须提前存在或用--hbase-create-table自动创建。--hbase-row-key指定MySQL的哪一列作为HBase的Rowkey。如果不指定默认用表主键。这个参数直接影响数据的分布策略后面专门讲Rowkey设计时展开。--hbase-create-table告诉Sqoop如果目标HBase表不存在自动创建。但自动创建的表只有一个Region没有任何预分区策略数据量大时必成热点。并行度参数-m指定Mapper数量也就是并行度。--split-by指定拆分列Sqoop会根据这个列的取值范围把数据均匀分给各个Mapper。常见的错误是拆行列不是主键、列上有大量重复值或空值导致数据倾斜——某个Mapper分到的数据量远超其他Mapper表现为导入任务整体跑得慢但只有一个task在慢慢磨。关于数据库压力也要诚实提醒-m不是越大越好。我曾遇到过设-m 8直接把业务MySQL的CPU打到100%的事故因为那台MySQL本来就在扛线上业务。如果你导的是业务库先确认低峰期再控制在-m 2到-m 4之间比较稳妥。数据仓库备用库可以适当调高。2.3 Rowkey设计与预分区Rowkey设计是HBase里最核心的话题Sqoop导入同样绕不开。很多人觉得Sqoop导入就是一条命令的事Rowkey设计是后面写代码时才考虑的。这个认知害人不浅——Sqoop导入时HBase表的Rowkey决定了未来所有查询的性能和数据的分布形态一旦设计错了后面再改就是迁移全表数据代价极大。Sqoop导入时Rowkey由--hbase-row-key指定的列的值直接决定。最笨的用法是把自增主键直接作为Rowkey也就是region1: 1-1000 region2: 1001-2000 region3: 2001-3000看起来均匀但你要想一个问题业务查询大概率不是按自增主键范围查的更多是用用户ID、订单号这类业务主键。如果Rowkey不带业务语义那HBase的随机读优势就发挥不出来——每次查询都要全表扫描等于用错了工具。更严重的是自增主键作为Rowkey会导致新数据全部写入最后一个Region形成写热点。因为HBase的Rowkey是按字典序存储的自增主键不断增大永远落在末尾Region。我的建议是导入时先把业务查询的维度想清楚把最高频的查询条件设计成Rowkey前缀。比如订单表经常按user_id create_time查那Rowkey就设计成user_id_reversed timestamp反转user_id是为了让新旧用户的数据分布均匀。同样时间戳可以考虑倒序让最新数据排在前面。Sqoop导入时可以通过一个MySQL查询列来构造这个复合Rowkey比如在SQL里用CONCAT拼接出需要的字符串。这种写法在--query模式下很容易实现算是我实际项目里的常用招数。再配合预分区。Sqoop的--hbase-create-table创建的表只有一个Region写入量一大必然热点和频繁拆分。更合理的做法是先在HBase Shell里手动建好预分区表然后Sqoop直接导入不用--hbase-create-table。比如hbase shellcreate target_table, info, {SPLITS [001, 101, 201, 301, 401, 501]}这里SPLITS数组里的字符串是Rowkey的分界点HBase会按照字典序在分界点处切开多个Region。如果Rowkey常量确定可以直接指定位数或枚举值比如按日期前缀2024-01、2024-02或按用户ID段。这块内容我在第四章结合Shell操作再细讲。2.4 数据类型映射要点Sqoop导入HBase时MySQL的数据类型会经过一层转换。这个转换不一定是你想要的所以必须搞清楚。Sqoop读取MySQL数据后会先转换成Java类型然后通过Bytes.toBytes()把Java类型序列化成二进制字节数组存入HBase。映射规则大致如下MySQL类型Java类型HBase存储INT / BIGINTInteger / Long二进制大端字节序VARCHAR / CHARStringUTF-8字节数组DATE / DATETIMEjava.sql.Date / Timestamp取决于具体保存格式DECIMAL / FLOAT / DOUBLEBigDecimal / Float / DoubleHBase统一存字节数组BLOB / TEXTbyte[]原始字节数组问题在于HBase里存的都是字节数组Sqoop不会额外记录类型信息。你把INT存进去读取的时候你需要知道它是以大端字节序存储的用Bytes.toInt()或Bytes.toLong()才能正确解析。如果你用HBase Shell的get命令看看到的一串十六进制就是二进制序列化后的内容不是可读的文本。我实际工作中最常用的做法是导入前在MySQL端把数据格式尽量转换成通用字符串比如日期统一格式化为yyyy-MM-dd HH:mm:ss数字统一转成VARCHAR。代价是存储空间稍微变大但换来的好处是读写两侧解析逻辑都简单透明不会因为类型映射的隐蔽问题导致数据解读错误。当然这是业务可接受范围内的妥协如果你的下游是Spark或者Phoenix去读它们各自有类型解析机制全程用字节数组也没问题。这一点根据团队技术栈自己权衡即可。3. 实操过程与核心环节实现3.1 数据源准备与目标表设计以我一个实际做过的电商订单场景为例。业务MySQL里有一张订单表t_order字段包括order_id、user_id、shop_id、order_amount、order_status、create_time。初始有200万条历史订单数据需要导入HBase供实时查询服务使用。查询场景主要是按user_id查这个用户的所有订单偶尔按order_id回查详情。目标表设计时我先确定的几个原则列族不需要多一个cf足够。Rowkey设计为user_id反转 倒序时间戳因为查询主要按用户维度。预分区按照用户ID分布来切分比如根据用户量均匀切16个Region。create order_hbase, cf, {SPLITS [10000,20000,30000,40000,50000,60000,70000,80000,90000,100000,110000,120000,130000,140000,150000]}这里SPLITS值的意思是Rowkey中user_id反转后的数值落在10000以内的进第一个Region10000到20000之间的进第二个Region以此类推。前提是你知道业务里user_id的取值范围。3.2 拼接Sqoop导入命令目标表建好后执行导入命令。因为需要把user_id和时间戳拼接成复合Rowkey所以用--query模式sqoop import \ --connect jdbc:mysql://mysql-host:3306/business_db?useSSLfalsecharacterEncodingutf-8 \ --username sqoop_user \ --password-file /user/sqoop/mysql.pwd \ --query SELECT CONCAT(REVERSE(LPAD(user_id, 10, 0)), _, REPLACE(REVERSE(create_time), -, )) AS rowkey, order_id, user_id, shop_id, order_amount, order_status, create_time FROM t_order WHERE \$CONDITIONS \ --hbase-table order_hbase \ --column-family cf \ --hbase-row-key rowkey \ --split-by user_id \ -m 4逐行解释关键点REVERSE(LPAD(user_id, 10, 0))把user_id先补齐10位保证字典序正确再反转。因为原始user_id位数不一直接反转会导致9排在10后面补齐位数后反转就避免了这个问题。REPLACE(REVERSE(create_time), -, )create_time形如2024-06-01 12:00:00反转后变成00:00:21 10-60-4202再去掉横线目的是让时间倒序且字典序和实际时间倒序一致。这样同一个用户的最新订单排在靠前位置适合“最近订单优先展示”的业务逻辑。\$CONDITIONS在bash命令行里$CONDITIONS会做变量替换需要用\$转义让这个字符串按原样传给Sqoop。--split-by user_id并行拆分列用user_id。注意如果user_id重复值特别多可以考虑用order_id做拆分列否则某些Mapper可能扫到一大把重复数据。执行命令后Sqoop会提交MapReduce作业。你会在控制台看到每个Mapper的进度条和日志。正常跑完后最后一行会出现类似Map-Reduce Framework的统计信息包括map完成的记录数。这里要注意看一个关键数字每个Mapper处理的记录数。如果某个Mapper处理了80%的数据其他Mapper只有零星记录说明拆分布均匀需要检查拆分列或并行度设置。3.3 验证导入结果导入完成后去HBase Shell验证数据。注意第一件事是看Region分布是否均匀status detailed这个命令会列出每个RegionServer上有多少个Region。如果16个Region全挤在同一个RegionServer上说明预分区没生效或者建表时SPLITS没写对。如果Region分布正常但数据偏斜某个Region很大其他很小说明Rowkey设计还不够均匀。然后随机抽查几条数据scan order_hbase, {LIMIT 2}ROW COLUMNCELL rowkey值1 columncf:order_id, timestamp..., value1023001 rowkey值1 columncf:user_id, timestamp..., value1000001通过get命令按Rowkey精确查询get order_hbase, rowkey值检查几个点列族名是否匹配、qualifier是否就是MySQL列名、字符串类型是否出现了乱码、时间戳字段是否变成了一堆不可读的字节。如果乱码极大可能是编码问题回去检查MySQL连接串里的characterEncodingutf-8以及MapReduce任务里mapreduce.map.java.opts的编码参数。验证数据总量是否和MySQL一致这也是我养成的习惯。先记录MySQL的行数SELECT COUNT(*) FROM t_order;再用HBase Shell执行全表计数的count命令或者用HBase自带的org.apache.hadoop.hbase.mapreduce.RowCounter工具hbase org.apache.hadoop.hbase.mapreduce.RowCounter order_hbase该任务会跑一个MapReduce来做计数准确且不阻塞线上读写。数量对不上时优先排查是否用--query模式漏掉了数据比如拆分的条件重叠或遗漏、或Mapper拉取数据时MySQL端出现超时导致部分数据丢失。3.4 增量导入与全量导入的策略选择历史数据导入完成后日常新增数据怎么持续进入HBaseSqoop有三种常见策略策略一增量导入append模式适合新数据主键或时间戳不断增大的场景。命令里加上--incremental append \ --check-column order_id \ --last-value 1023001这个模式下Sqoop会只导order_id大于1023001的数据。注意这个last-value得自己保存和更新没有自动状态管理。你可以把每次导入完的最大值写到文件或数据库表里下次导入前读出来。这个策略在HBase场景下并不常用因为如果数据源不是单调递增的比如修改了历史数据append模式会漏数据。策略二时间戳增量导入lastmodified模式--incremental lastmodified \ --check-column update_time \ --last-value 2024-06-01 00:00:00适合表中存在update_time字段且更新会刷新该字段的场景。Sqoop会导入所有update_time大于上次记录值的数据同时把mergeKey指定的列相同的旧数据合并。典型用法是同步“有更新但主键不变”的变动数据。但缺陷是依赖业务系统真正维护了update_time很多历史表其实不维护或维护得不准确用了这个模式反而会丢数据。策略三全量覆盖重建如果你导的是维度表、配置表这类变化不大但需要全量最新的表最省事的做法是定期全量导入到一个独立HBase表比如order_hbase_tmp然后通过HBase的快照或表重命名切换读写流量。这种方式的优点是逻辑简单、不会因为增量策略的边界条件丢数据缺点是每次导入占用资源较大。对于千万级以内的表资源消耗完全可接受。3.5 批量导入与性能优化默认情况下Sqoop导入HBase时使用标准写入API每条Put都要经过WAL。当数据量大时WAL写入会成为性能瓶颈。优化的首选方式是Sqoop 1.4.7提供的--bulkLoad参数sqoop import \ --bulkLoad \ --hbase-table order_hbase \ --column-family cf \ --hbase-row-key rowkey \ ...其他参数...--bulkLoad的原理是Sqoop生成的MapReduce作业不再直接写HBase集群而是先把数据生成HFile格式文件最后通过HBase的LoadIncrementalHFiles工具把HFile加载进封装的Region。这样做绕过了写WAL、避免了MemStore刷写和Split风暴导入速度能提升数倍而且对集群写入压力也小很多。但注意两个实际痛点一是--bulkLoad模式下Rowkey的设计更关键因为生成的HFile是按Rowkey排序的如果Rowkey分布不连续或严重偏斜生成的HFile在load阶段可能要做额外拆分合并反而拖慢速度。二是--bulkLoad要求HBase表的列族和Qualifier设计跟生成HFile的格式完全一致中途改动列名或列族名会直接导致load失败。对刚接触Sqoop导入HBase的朋友我的建议是先用默认的非bulkLoad方式跑通流程确认数据和Rowkey设计没问题后再切换到bulkLoad模式优化导入速度。别一上来就追求性能先把正确性验证通过。另外可以在HBase端做两项调整把hbase.client.write.buffer调到2MB以上减少刷写次数将目标表的hbase.regionserver.wal.enable保持默认开启不要为了性能盲目关闭WAL。关闭WAL确实能加速写入但当RegionServer宕机时会丢失所有尚未刷写的数据这种丢数据的风险在Sqoop导入场景下尤其危险——因为你可能无法轻易重导。我见过有团队为了榨性能关掉WAL结果一次宕机把几天增量数据全丢了后面恢复数据折腾了两周。这个教训值得记住。4. 常见问题与排查技巧实录4.1 高频报错速查表把我在实践中遇到的高频报错和处理方式整理成一张速查表报错关键字原因解决方法ClassNotFoundException: com.mysql.jdbc.DriverMySQL驱动jar没放到Sqoop的lib目录下载mysql-connector-java-5.1.49.jar放到$SQOOP_HOME/libClassNotFoundException: org.apache.hadoop.hbase.HBaseConfigurationHBase相关jar没添加到Sqoop把hbase-client、hbase-common、hbase-server、hbase-protocol等jar复制到Sqoop lib目录Could not load db driver classJDBC连接串或驱动类名错误MySQL用com.mysql.jdbc.Driver确认连接串格式Connection refused: connect网络不通或端口未开放或MySQL端口写错检查MySQL、ZK、RegionServer的端口连通性Region is not online / RegionServer is shutting down目标表正在被拆分或RegionServer负载过高检查RegionServer状态等拆分完成后再重试No such column: rowkeyMySQL结果集中没有--hbase-row-key指定的列确认--query的SELECT字段包含了该列Wrong FS: hdfs://...HBase配置中的HDFS地址和Hadoop不一致统一core-site.xml中的fs.defaultFS配置TableExistsException: target_table目标表已存在但Sqoop仍尝试建表去掉--hbase-create-table参数4.2 Sqoop连接不上MySQL的排查思路“Sqoop连接不上MySQL”是热词榜上的常客也是我刚接触时被折磨最久的问题。这里我按照排查链路梳理一遍第一步确认基本网络连通telnet mysql_host 3306如果telnet不通查防火墙、安全组、MySQL的bind-address配置。MySQL默认可能只监听127.0.0.1需要改配置文件里的bind-address0.0.0.0并重启服务。这一步很多人忽略导致从集群外访问永远连不上。第二步确认驱动和类加载用eval模式快速验证连接sqoop eval \ --connect jdbc:mysql://mysql_host:3306/business_db?useSSLfalse \ --username sqoop_user \ --password-file /user/sqoop/mysql.pwd \ --query SELECT 1如果这条能跑通说明连接和驱动都没问题接下来排查import命令本身基本是参数问题。如果这条就报ClassNotFoundException或Communications link failure先确认mysql connector jar版本。第三步检查账号权限和网络白名单Sqoop导入大量数据时MySQL需要允许远程连接。你能在MySQL客户端里连上不代表Sqoop所在的节点能连上——可能是账号的host限制问题。用GRANT ALL ON business_db.* TO sqoop_user%;授权后再试。生产库最好限制到指定IP段安全这块自己把握。排查到这里基本能覆盖90%的连接失败场景。剩下的是比较冷门的情况比如MySQL的SSL配置冲突在连接串中显式加?useSSLfalse能绕过或者MySQL高版本8.0默认的caching_sha2_password认证插件和旧版驱动不兼容需要更换com.mysql.cj.jdbc.Driver并升级驱动jar到8.0.x。4.3 ClassNotFoundExceptionSqoop缺HBase依赖的终极解Sqoop连接HBase时报ClassNotFoundException或者NoClassDefFoundError几乎都是因为Sqoop的lib目录下缺少HBase相关jar包。这个问题在Sqoop 2.x时代尤其严重因为官方发行版默认不带HBase集成。标准的解决步骤找到HBase安装目录下的lib目录。把这几个jar复制到Sqoop的lib目录hbase-client-*.jarhbase-common-*.jarhbase-server-*.jarhbase-protocol-*.jarhbase-hadoop-compat-*.jarhbase-hadoop2-compat-*.jarhbase-metrics-api-*.jarhbase-shaded-protobuf-*.jarHBase 2.x需要另外确保Hadoop的hadoop-common、hadoop-hdfs等高版本jar也在Sqoop lib里HBase 2.x依赖的Hadoop版本如果比Sqoop自带的高还会出现HDFS RPC版本不一致的问题。有个取巧的办法如果复制jar太麻烦直接把Sqoop的lib目录配到HBASE_CLASSPATH也不行因为Sqoop启动脚本只加载自己的lib路径。所以最靠谱的还是老老实实复制一份反正是单机上的配置不占多少空间。处理完jar问题后还是要验证一下配置是否正确——直接跑一个最小导入测试指定一个小表导入试试。已验证能跑通后再跑大数据量任务。4.4 WAL预写日志异常与写入失败热词榜里有“hbase wal预写日志异常”这个在Sqoop大量写入时确实容易冒出来。典型表现是导入任务卡在某个阶段RegionServer日志出现Blocked waiting for space in WAL queue或者HBaseCommon-1-RS_LOG_REPLAYER相关的异常。WAL是HBase写入的第一道关口所有Put先写WAL再进MemStore。当WAL对应的HDFS文件写入慢比如HDFS磁盘满、NameNode压力大、网络抖动或者RegionServer的handler线程全部阻塞在WAL写入上就会出现这类异常。Sqoop批量导入时写入速率远高于日常业务写入更容易暴露WAL瓶颈。排查和处理的建议检查HDFS剩余空间。hdfs dfsadmin -report看每个DataNode的剩余空间。WAL是HDFS上的文件磁盘满了必然写不进去。确认WAL路径配置。看HBase的hbase-site.xml里hbase.wal.dir默认在HDFS的/hbase/WALs下。如果这个目录的DFS Used接近100%想办法清理或扩容。查看RegionServer日志确认是WAL单点问题还是整体IO问题。如果JournalNode或DataNode丢包频繁那不只是HBase的问题整个HDFS集群都要检查。作为短期缓解手段可以减少-m并行度或加上--batch参数降低瞬时写入速率。但这只是治标长期方案是让HDFS集群恢复稳定或调整HBase刷写参数。这里特别提一句热词“hbase wals路径”——很多团队在排障时找不到WAL文件在哪。用这个命令查看hdfs dfs -ls /hbase/WALs或者根据RegionServer主机名进入对应子目录hdfs dfs -ls /hbase/WALs/hostname,port,startcode/wal文件名理解了路径规则排障时才能精准定位是哪个RegionServer的WAL出了问题。4.5 HBase Shell操作自动拆分与预分区实战最后专门补充HBase Shell里涉及的表管理和Rowkey分布操作因为Sqoop导入后几乎必然要跟这些功能打交道。关于自动拆分HBase有自动Region拆分机制默认开启。触发的条件是Region大小超过hbase.hregion.max.filesize默认10GB或达到hbase.hregion.split.constant的阈值。自动拆分是HBase“自己觉得”需要拆就拆策略包括IncreasingToUpperBoundRegionSplitPolicy和SteppingSplitPolicy等。在生产环境里如果你导入数据量很大自动拆分的过程会对集群产生额外压力。Sqoop导入期间频繁拆分不仅拖慢导入速度还可能让读写出现间歇性抖动。我实践中的做法是导入前先把自动拆分关掉导入完成后手动根据数据量触发拆分或者直接在建表时用预分区规划好Region数导入完成后如果个别Region偏大再开启自动拆分做均衡。关闭某个表的自动拆分alter order_hbase, CONFIG {SPLIT_POLICY org.apache.hadoop.hbase.regionserver.DisabledRegionSplitPolicy}某个Region Server的Region大小确实偏大时手动拆分split order_hbase, 分界Rowkey关于预分区在前面实战部分提到的SPLITS数组方式是预分区的基础用法。如果你的Rowkey是相对固定的枚举值或区间建议直接用这种方式。如果你的Rowkey设计里有时间戳尾巴那使用SPLITS_FILE方式更灵活把分界点写在本地文件里每行一个值create order_hbase, cf, {SPLITS_FILE /tmp/splits.txt}文件内容示例10000 20000 30000预分区数量的规划建议Region数量不要少于RegionServer数量理想的Region数为RegionServer数 × 每个RegionServer的负载容量 × 2左右。太少了会热点太多了会增加调度开销。比如3个RegionServer的集群预分区15-30个Region是比较健康的范围。关于Region均衡导入完成后如果发现Region在RegionServer之间分布不均衡手动触发balancerbalance_switch true balancer()注意生产中balancer是否开启要跟运维协商因为region移动会带来短暂的读写抖动。最后分享一个我自己的实操习惯每次跑Sqoop导入任务前我都会写一个简单的环境检查脚本依次检查MySQL连通性、Sqoop依赖jar、HBase表是否存在及预分区设置、HDFS剩余空间、ZooKeeper状态。五分钟的检查能避免绝大多数导入过程中的突发问题。导入完成后我会把每个Mapper处理的记录数、总耗时、目标表region分布情况记录到一个固定文件里久而久之就能形成一套稳定的基线下次导入变慢或异常时对比基线马上能定位大概原因。这个习惯帮我省了很多排障时间希望你也能用上。