我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

HBase Shell实战指南:从集群检查到数据操作的核心命令详解

HBase Shell实战指南:从集群检查到数据操作的核心命令详解 1. 开工之前必看连接、退出与集群状态检查先说个我自己的场景。有一次线上HBase集群出现RegionServer频繁心跳超时刚接手的大数据同事跑过来问到底哪台机器出问题了你们平时怎么用HBase Shell查的我一听就明白了他只知道一个hbase shell能进进去之后list一下表然后一脸懵地不知道还能干什么。这其实是很多刚接触HBase的人共同的困惑Shell到底能做什么在什么场景下用它以及那些命令背后真正要解决的问题是什么。HBase Shell本质上是基于JRuby封装的一套交互式命令行工具它把HBase的Java API做了轻量级映射。你在Shell里敲的每一行命令最终都会转化成对HBase Master或RegionServer的RPC调用。所以它并不是什么玩具而是实打实的生产运维工具。进入Shell的方式很简单# 前提是已配置好HBASE_HOME环境变量 hbase shell如果你装的是CDH或者HDP这类发行版可能会存在多个HBase版本共存的情况这时候建议显式指定/usr/bin/hbase shell进入之后第一步先别急着操作表我习惯先执行一轮“体检”。最常用的就是status命令hbase:001:0 status 1 active master, 0 backup masters, 3 servers, 0 dead, 2.0000 average load这行返回信息里包含几个关键指标active master表示当前活跃的Master节点backup masters是备用Master数量servers表示存活的RegionServer数量dead表示宕掉的RegionServer数量average load表示每个RegionServer上承载的Region平均数量。这里有个经验之谈average load这个值并不是越多越好也不是越少越好它和Region大小、集群机器配置强相关。如果集群里Region总数是3000RegionServer只有3台那平均负载就是1000这种分布通常是正常的。但如果看到某个RegionServer的负载远超其他节点比如其他都是5突然有一台是200那基本可以断定Region倾斜了后续需要用move命令做手动均衡。status命令还支持status simple和status detailed两种粒度的查看。生产环境排查问题我强烈建议用后者hbase:002:0 status detailed输出会列出每个RegionServer的IP、端口、堆内存使用率、Region数量、Store文件数、MemStore大小等非常详细的信息。我曾经靠这条命令定位过一次内存泄漏问题有一个RegionServer的MemStore比例一直涨到40%以上而其他节点只有15%左右最终追踪下来发现是有张表的列簇设置了过大的MemStore刷新阈值流量一上来就直接打爆了那个节点。此外还有两个基本命令hbase:003:0 version hbase:004:0 whoamiversion能确认当前客户端连接的是哪个HBase版本这个在排查客户端与服务端版本兼容性问题时特别有用。whoami在启用了Kerberos认证的集群上可用来确认当前登录身份避免权限不足时一头雾水。最后说一个极其容易犯的低级错误忘记退出。直接在终端输入quit或者按CtrlD都能退出Shell。但在脚本化调用时如果不显式退出进程会一直挂住导致后续任务队列阻塞。所以脚本里跑完命令后一定要补上quit。2. 建表和改表操作DDL命令的参数才是真正的重点2.1 命名空间与基础建表语法HBase 0.98版本之后引入了命名空间的概念它类似关系型数据库里的Schema库主要作用是做资源隔离和权限管理。我最开始接触HBase时直接用默认空间建了一堆表后期想按业务线做数据隔离不得不花费大量精力去迁数据。所以现在只要涉及新业务第一件事就是先建好命名空间hbase:005:0 create_namespace ods hbase:006:0 create_namespace dws, {hbase.namespace.quota.maxregion 100}第二个例子中的hbase.namespace.quota.maxregion表示该命名空间下最多允许创建100个Region这是HBase 2.0之后引入的资源限制功能适合在多业务共用一个物理集群的场景下做“软隔离”。查看命名空间可以用list_namespace查看某命名空间下的所有表用list_namespace_tables ods。建表的基础语法长得这样hbase:007:0 create ods:user_action_log, cf这条命令会创建一张名为user_action_log的表属于ods命名空间表内只有一个列簇cf。这里的cf是Column Family的缩写我习惯在建表时用有意义的短英文名比如info、data、meta方便后续写代码时识别。列簇是HBase表结构中最重要的物理存储边界同一个列簇下的所有列会存储在同一个Store文件里不同列簇则是完全独立的存储文件。2.2 参数化建表VERSIONS、TTL和COMPRESSION怎么选只写一个列簇名就建表能跑通但远不够严谨。真实生产环境我几乎从不这样建表因为默认参数在很多场景下并不是最优解。带参数建表的完整语法如下hbase:008:0 create ods:user_action_log, {NAME cf, VERSIONS 3, TTL 604800, COMPRESSION SNAPPY, BLOCKCACHE true}, {NUMREGIONS 6, SPLITALGO HexStringSplit}逐个拆解这里面的参数VERSIONS 3表示保留每个单元格最近3个历史版本。默认值是1也就是旧值会被直接覆盖成最新值无法追溯历史。如果你有“记录变更轨迹”的需求必须显式设置大于1否则后续查历史版本时会发现什么都查不到只能吃哑巴亏。TTL 604800表示数据生命周期单位是秒。604800秒正好是7天表示超过一周的旧数据会被后台线程自动清理。这个参数对于日志类数据非常实用能有效避免数据无限膨胀导致Region过大。COMPRESSION SNAPPY数据压缩算法。SNAPPY在压缩比和解压速度之间是最均衡的选择压缩/解压过程对CPU消耗较小适合绝大多数在线业务场景。如果对压缩比有极高要求但不介意换CPU开销可以选GZ。BLOCKCACHE true是否开启LRU块缓存。对读多写少的场景必须开启对纯粹的写入日志场景建议设置为false避免缓存被没用到的数据污染。NUMREGIONS 6SPLITALGO HexStringSplit预分区设定。这可能是所有参数里对性能影响最直接的一项。默认情况下一张新表只有一个Region换句话说不论写入多少数据都落在同一台RegionServer上热点问题不可避免。预先分6个区配合十六进制字符串拆分算法能让rowkey近似均匀地分布到6个Region上。这里插一个踩坑经历早期有一次给订单表建表存的是纯数字字符串rowkey但我没有设定SPLITALGO直接用了默认的StringSplit。结果预分区的边界和实际rowkey完全不在同一个起始字符集上大部分数据全挤到一个Region里去了。所以预分区方案的选型必须结合你实际的rowkey字符分布来定数字型rowkey和ASCII字符串rowkey的切分策略完全不同。2.3 查看表结构和修改表结构的实战补充用describe查看表结构是所有后续操作的基础hbase:009:0 describe ods:user_action_log输出会分为几个部分表名、列簇列表、每个列簇的参数配置。重点看{ NAME cf, VERSIONS 3, ... }这段。比如列簇顺序HBase中列簇的排列顺序会影响Region内数据文件的物理布局实际影响不大但看着舒服也便于统一管理。修改表结构用alter命令。这里有一条铁律HBase中不能在线修改已经创建好的列簇的存储策略其实可以改但修改参数后并不会立刻生效需要等到下一次major compaction之后才会真正落地底层文件。# 给已存在的表增加一个列簇 hbase:010:0 alter ods:user_action_log, {NAME stat, VERSIONS 1} # 修改已有列簇的TTL hbase:011:0 alter ods:user_action_log, {NAME cf, TTL 86400} # 删除一个列簇 hbase:012:0 alter ods:user_action_log, {NAME stat, METHOD delete}注意删除列簇会导致整个列簇的所有数据被直接清掉这个操作没有任何恢复手段。在线上执行前我一般会先describe确认当前表结构再用disable把表停掉然后修改最后enable恢复。虽然HBase的alter在2.x版本支持在线执行但从风险控制的角度生产环境还是尽量在低峰期操作。有一个细节需要特别提醒ALTER命令返回的提示信息可能与实际生效之间存在几秒钟的延迟。修改完立刻执行describe有时候看到的还是旧值这不是命令执行失败了而是Master的更新还未同步到RegionServer上等一会再查即可。我被这个“假象”坑过好几次以为命令没生效就重复执行反而引发了一连串无意义的变更。3. 增删改查实操DML命令里最容易踩的五个坑3.1 put写入列簇、列名、值三者缺一不可HBase Shell中写入一行数据标准语法是hbase:013:0 put ods:user_action_log, rowkey_0001, cf:hostname, web-01 hbase:014:0 put ods:user_action_log, rowkey_0001, cf:ip, 10.0.0.12 hbase:015:0 put ods:user_action_log, rowkey_0001, cf:timestamp, 1589875200每次put只能写入一个单元格也就是Rowkey 列簇:列名对应的一个值。如果你有多个列要写需要执行多次put或者在代码中用BufferedMutator批量提交。Shell层面没有提供一条命令同时写多列的能力。这里有几个坑需要点破。第一个坑列簇必须显式带列名。写put t1, row1, cf, value是不行的系统会认为你要把value写入到名字为cf这个列但实际上HBase的存储单元必须落在一个具体的列名下所以必须写成cf:col_name。当然cf和col_name之间用什么分隔符默认是冒号且不能修改。如果你传入的值本身包含冒号比如IP地址10.0.0.12:8080那也完全没有问题因为冒号只会切割列簇:列名的前半部分。第二个坑Shell中字符串必须用引号括起来。不带引号的裸写法会导致JRuby把它当作变量解析直接报错。尤其是数字如果写成put t1,row1,cf:cnt, 123Shell会尝试把整数123转成bytes看似成功了但实际存储的字节可能不是你想的字符串123。数字和字符串在HBase底层的字节表示完全不同这会给后期Scan造成极大的困惑。写命令时除非确定要存一个数值类型否则一律加引号。第三个坑中文编码问题。Shell写入中文值默认采用UTF-8编码读取时如果终端字符集不匹配显示出来的是一串乱码。这并不代表数据已经损坏只是终端显示问题。千万不要因为显示乱码就反复重写用get命令加上十六进制转义查看的方式辅助确认即可。hbase:016:0 get ods:user_action_log, rowkey_0001, {COLUMN cf:hostname} COLUMN CELL cf:hostname timestamp2024-01-20T10:00:00, valueweb-01补充说明一下时间戳每次put未指定版本时HBase会自动取当前系统时间作为该单元格的版本号。如果你想要让两个不同时间写入但内容相同的值呈现出不同的版本可以用{TIMESTAMP 指定时间戳}。但在生产环境我从不手动指定时间戳——一旦集群中不同机器的时间不同步会导致数据版本错乱老数据反而变成新版本极难排查。3.2 get读取按RowKey精确查找get是最简单高效的读取方式因为HBase的查询性能强依赖RowKey走的是LSM树和BlockCache的快速路径hbase:017:0 get ods:user_action_log, rowkey_0001 hbase:018:0 get ods:user_action_log, rowkey_0001, {COLUMN cf:hostname} hbase:019:0 get ods:user_action_log, rowkey_0001, {COLUMN [cf:hostname, cf:ip], VERSIONS 3}第三个例子能够看出VERSIONS参数对查询历史版本非常重要。还是拿上面的表举例三次put写入同一个RowKey的同一个单元格不同值如果表结构里VERSIONS还是默认1那么不论怎么加VERSIONS 3参数查询返回的永远只有最后一次写入的值。因为版本数在表结构层面已经被限制死了查询侧再加参数也不会有历史版本存活。表结构同查询参数是“取交集”的关系。另外还需要留意的是get只能查单个RowKey如果你要查一个RowKey范围Scan才是正确的选择。很多新手在这个地方纠结半天想用get查前缀其实是思维惯性——HBase只提供了精确点查和范围扫描两种读取模式不存在“按前缀点查”这种折中模式。3.3 scan读取范围遍历的基础姿势scan在Shell中的基础用法是hbase:020:0 scan ods:user_action_log这条命令会全表扫描数据量大时会非常慢且极大消耗集群的IO资源。我在生产环境几乎从不执行无条件的全表scan除非表里就几十条记录。更安全的姿势是加上STARTROW和LIMIThbase:021:0 scan ods:user_action_log, {STARTROW rowkey_0001, ENDROW rowkey_0010, LIMIT 10}请注意STARTROW是闭区间会包含这一行的数据ENDROW是开区间不会包含这一行的数据。这个细节无数次让人怀疑人生。比如你想查rowkey_0001到rowkey_0010之间的数据ENDROW写rowkey_0010结果发现rowkey_0010没查出来——一定不要觉得是数据丢了去确认一下是不是开闭区间搞混了。3.4 delete和deleteall到底删了什么这是DML里最容易制造“事故”的一对命令。先看语法hbase:022:0 delete ods:user_action_log, rowkey_0001, cf:hostname hbase:023:0 deleteall ods:user_action_log, rowkey_0001第一条delete删除的是rowkey_0001这一行中cf:hostname这一个单元格的全部版本。第二条deleteall删除的是rowkey_0001这一行的所有列簇下的所有列。理解它们区别的关键在于HBase的删除并不是物理清除而是写入一个删除标记Tombstone。查询时看到的是这些数据“不存在”了但底层文件依然占用磁盘空间。只有等到下一次Major Compaction时被标记删除的数据才会真正被物理清理掉。这解释了为什么有时候删除了大量数据表占用的HDFS磁盘空间却没有立即下降。新人遇到这个问题总是以为删除失败了其实是Compaction还没触发。另外用deleteall删除整行后重新put同一条RowKey的数据与直接更新一个已存在单元格的版本号是两种完全不同的底层状态。前者会产生一个新的版本序列后者在StoreFile里会追加一条带新时间戳的数据。3.5 增量操作incr和append的独特价值incr和append算是Shell里的两个隐藏技能点很多人写了很久的HBase都没碰过它们。incr是原子自增操作适用于计数器场景比如网站的PV/UV统计。标准的put是“读-改-写”三步在并发场景下会丢更新。而incr直接下推到RegionServer在服务器端完成累加不存在并发覆盖问题。hbase:024:0 incr ods:user_action_log, rowkey_0002, cf:pageview, 1执行一次后这个单元格的值会变成1再执行一次变2。要注意的是如果你的表创建时没有显式开启IN_MEMORY和BLOOMFILTER操作也能正常执行。但有一个限制incr要求单元格原本存储的是可解析的64位长整型数据。如果之前用put写入了一个非数字字符串再执行incr会直接报错。append则是向单元格原有值末尾追加字符串同样具备原子性hbase:025:0 append ods:user_action_log, rowkey_0002, cf:log, a hbase:026:0 append ods:user_action_log, rowkey_0002, cf:log, b最终这个单元格的值是ab。这种能力在需要低成本记录操作日志片段时非常有效比如一个商品的浏览轨迹。4. Scan进阶与Filter组合使用用最少资源捞到最准的数据4.1 多版本查询与计时器范围过滤很多系统设计者在初始阶段把HBase当成了“只有最新版本”的KV存储。但开篇建表时已经提到VERSIONS允许保留同一单元格的多个历史值。那么查询时怎么拿到这些历史值还是用scan或get的参数hbase:027:0 scan ods:user_action_log, {COLUMN cf:status, VERSIONS 5}这个操作会返回同一RowKey下同一列簇:列名的最近5个版本并按时间戳倒序展示。我做过一个简单的订单状态流转追踪系统每个单元格写入订单状态时都保留最近10个版本。这样的话业务上只需要用单条get命令就能完整还原一个订单从下单、支付、出库、签收到售后的所有状态时间线完全不需要额外的流水表非常省事。TIMERANGE参数则允许指定一个时间窗口hbase:028:0 scan ods:user_action_log, {TIMERANGE [1589875200000, 1589961600000]}注意单位是毫秒且左闭右开。如果要查某一天的数据你得把当天的起始毫秒作为左边界第二天的起始毫秒作为右边界。这个参数在定位某段时间写入的异常数据时很好用。4.2 过滤器语法不用JRuby代码也能精准匹配HBase Shell中scan支持直接内联FILTER表达式最常见的两个是PrefixFilter和ValueFilter。PrefixFilter主要用于按RowKey前缀过滤例如查所有rowkey_000开头的行hbase:029:0 scan ods:user_action_log, {FILTER PrefixFilter(rowkey_000), LIMIT 100}它的底层实现其实等价于STARTROW加一个临时STOPROW。不过细节对用户透明直接写PrefixFilter更直观。这里需要注意在过滤器中写的条件字符串值必须用英文单引号包裹。如果值里本身包含单引号就会很麻烦建议在业务上对rowkey的字符集做约束。ValueFilter则是按单元格的值来过滤数据。比如排查某台机器名出现哪些日志hbase:030:0 scan ods:user_action_log, {FILTER ValueFilter(, substring:web-01), LIMIT 20}这里substring:是比较操作符中的一种表示包含匹配。除它之外还有binary:、binaryprefix:等。我实测下来binary:属于精确匹配性能相对较好因为可以走底层的BloomFilter加速。而substring:由于必须扫描整个单元格内容在大表上执行代价很高生产环境务必加上LIMIT限额。4.3 组合过滤案例多条件AND与性能取舍真正线上用得更多的情况是多条件组合。HBase Shell支持用AND连接多个过滤器hbase:031:0 scan ods:user_action_log, { FILTER PrefixFilter(rowkey_0001) AND ValueFilter(, substring:web-01), LIMIT 100 }看到这条命令你应该意识到一个问题过滤器是在RegionServer端对每条存储记录做谓词判断的它“看似”绕过了客户端网络传输但如果过滤条件不能下推到HBase底层的StoreFile级别的命中优化它依然需要读取并序列化大量的底层数据块实际开销远超你的直觉。所以给一个我的经验法则能通过RowKey范围搞定的查询绝不依赖Filter。比如上述需求RowKey已经能界定rowkey_0001到rowkey_0002改写成STARTROW rowkey_0001, ENDROW rowkey_0002扫描数据量直接从全表缩减到一个小分片性能提升可能是几十倍。Filter适合的是前缀完全不同、无法归并到连续范围的前缀查询这是它的应用边界。4.4 count命令的两个视角count命令是scan的一种聚合场景它会逐条扫描并统计满足条件的行数。如果没有任何条件生产环境的千万级大表建议直接放弃因为count的代价与全表扫描无异。hbase:032:0 count ods:user_action_log hbase:033:0 count ods:user_action_log, {INTERVAL 1000, CACHE 1000}INTERVAL表示每统计1000行显示一次进度CACHE表示每次RPC批量Scan多少行。在数据量达到百万级别时默认的CACHE值可能太小导致Scan请求满天飞响应极慢。适当调大CACHE到1000以上能显著提升速度但也不是越大越好过大的CACHE会占用RegionServer较多的堆内存。另外count还支持联合FILTER计数比如统计某个业务前缀的数据量hbase:034:0 count ods:user_action_log, {FILTER PrefixFilter(rowkey_), CACHE 5000}由于PrefixFilter能够下推到StartRow/StopRow进行优化这个操作的效率比普通Filter要高不少。我曾经用类似命令配合定时任务做线上小时级数据量对账效果非常稳定。5. 运维管理命令与自动化Shell不只是数据开发工具5.1 Region运维命令前面status看到Region倾斜时就需要用move或者assign来调整。手动移动一个Region需要先知道Region名。可以用locate_region查hbase:035:0 locate_region ods:user_action_log, rowkey_0001输出会返回Region名、RegionServer地址、起始RowKey和结束RowKey。拿到Region名之后就可以手动迁移hbase:036:0 move encodedRegionName, serverNameencodeRegionName就是在HBase UI页面上看到的Region列表里的哈希值serverName是目标RegionServer的Hostname,端口,时间戳格式。这个操作在生产环境务必谨慎它会在RegionServer间搬运数据期间涉及Region的重新上线过程对读写有一定影响。低峰期操作 逐台均衡不要一次性批量执行大量move。5.2 常用管理命令集合另一类运维需求是清理无用表。标准的删除流程有两步第一步停表第二步删表hbase:037:0 disable ods:old_log hbase:038:0 drop ods:old_log如果你跳过disable直接执行dropHBase会直接拒绝执行并给出提示“Table old_log is enabled, so it cannot be dropped”。这条限制是为了避免你误删正在被写入的表。如果只想快速清空表数据而不删除表结构可以用truncatehbase:039:0 truncate ods:user_action_logtruncate的执行逻辑是先disable表、再drop表、然后立即用原表结构重新create一张空表。所以它也能达到“把数据全部清空但保留表定义”的目的。但这里有一个重要提醒在新版本HBase中执行truncate会重置所有预分区设置也就是说你之前精心设计好的预分区边界全部丢失重建的表会回到单Region初始状态。如果表流量很大一顿truncate之后线上立刻出现热点写是常有的事。保存原来的预分区信息正确的做法是在truncate之前先用describe记录每个列簇的参数和分区数truncate之后用create按同样参数重建。有些经验丰富的运维同事也会直接建一张新表通过copy_table或批处理脚本把数据迁移过去避免在线上直接drop带来的风险。5.3 用Ruby脚本和echo管道实现自动化HBase Shell本身是JRuby环境支持批量执行脚本。最简单的自动化方式是管道输入echo list_namespace | hbase shell如果有多条命令用分号分隔echo status simple; list_namespace_tables ods | hbase shell但这种方式有个缺点命令写长了以后可读性很差且转义字符极其痛苦。更推荐的方式是把命令写进一个Ruby脚本文件然后用hbase shell xxx.rb执行。比如下面这个检查表是否可用的脚本# check_table.rb tables [ods:user_action_log, dws:user_daily_report, ads:dim_sku] tables.each do |tbl| begin is_enabled enabled?(tbl) puts #{tbl} enabled#{is_enabled} rescue e puts #{tbl} error#{e.message} end end quit执行方式hbase shell /opt/scripts/check_table.rb把类似的命令串进Crontab或者Azkaban调度里就能实现表状态监控的定时巡检。我在生产环境就部署过一套巡检脚本每小时检查一次近10张核心表的enable状态、Region数、平均Load等指标如果发现连续3次检查异常就自动发告警确实帮团队提前规避过几次风险。5.4 批量数据工具表复制与数据迁移日常开发中偶尔需要在测试环境复刻一套生产表结构或者把数据从一张表迁移到另一张表。HBase原生提供了一个copy_table命令可以低成本实现hbase:040:0 copy_table ods:user_action_log, ods:user_action_log_bak这个命令本质上是执行一次远程或本地MapReduce任务来读取源表并写入目标表。如果目标表不存在系统会先按源表结构自动建表如果已存在会追加写入。需要注意copy_table对RegionServer的数据节点IO有较大压力尽量在业务低峰期执行。如果是跨集群的复制参数也不复杂hbase:041:0 copy_table ods:user_action_log, ods:user_action_log_bak, {ZK_QUORUM zk1:2181,zk2:2181,zk3:2181}用ZK_QUORUM指定目标集群的ZooKeeper地址就能跨集群复制。当然跨集群复制更推荐采用官方推荐的Replication或者Snapshot ExportSnapshot方式但那几条链路属于更高阶的运维专题Shell命令里能做的相对有限。5.5 其他高频命令速查整理几个实际运维中经常使用、但容易想不起来具体拼写的命令命令作用关键注意点list列出所有用户表不包含命名空间系统表list_namespace列出所有命名空间返回default和hbase系统空间get_table返回Table对象返回值通常用于JRuby脚本操作balancer触发Region重新均衡balancer_switch true开启自愈均衡flush 表名手动刷新MemStore到StoreFile常用于低峰期主动落盘减少RegionServer压力major_compact 表名触发Major Compaction开销较大避免频繁执行zk_dump展示ZooKeeper中的HBase元数据排查集群故障时常用有一件事值得单独拿出来说major_compact。它合并所有StoreFile并物理清除Tombstone标记对压缩存储、空间释放和读性能优化很有帮助但在生产集群上频繁执行会引发“雪崩”效应——所有RegionServer同时进行大合并磁盘IO猛增、CPU飙升直接压垮集群。正确姿势是选定业务低峰期分组逐台执行或者干脆用hbase.majorcompaction配置错开执行时间。6. 最后分享两条个人经验关于HBase Shell操作层面的东西就这些但真正拉开水平差距的是使用习惯和风险意识。第一任何影响范围较大的操作执行之前先确认自己的身份。Shell没有撤销概念。truncate和drop的破坏力不亚于生产环境的rm -rf务必确认操作的确实是你想清理的目标表。开个命名空间前缀是好的习惯ods和dws这些环境隔离能极大降低误操作的波及面。第二Shell更适合诊断和临时调整而不是形成核心业务流程的强依赖。真正高并发的数据读写还是应该走Java API或者支持协处理器的高阶客户端。Shell脚本化可以用于监控巡检、元数据检查和数据修复但别把每天的ETL主链路做成一堆hbase shell拼接毕竟JRuby脚本的出错排查成本远高于开发语言。最后再提一个小技巧。每次进入Shell时先执行一下list批量刷一遍表名能快速发现是否有新老大表变化。我用这个习惯已经避开了多次因表结构误判导致的运维事故。HBase Shell看着简单但每一行命令背后都藏着底层存储和分布式协调的细节遇到问题多想想它真正做了什么比死记硬背一堆命令有用得多。
返回列表