ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

HBase实战指南:从架构原理、RowKey设计到部署调优

HBase实战指南:从架构原理、RowKey设计到部署调优 先说个判断大数据这块如果只能推荐一个“必读组件”我会把HBase放在第一梯队。不管你是刚入门学习分布式架构还是在生产环境维护几十个节点的集群HBase的架构思路都会反复出现。更重要的是面试里“HBase概述、架构”基本属于必考题而实际操作又涉及安装配置、表设计、数据读写和调优。这篇文章我用自己跑过的经验把HBase从“是什么”到“怎么架构”再到“怎么部署、怎么排坑”一次讲清楚。网上的HBase文章两极分化严重要么只画个总架构图复制过来连Master和ZooKeeper的关系都讲反要么直接丢一堆配置参数让你抄完全没说为什么。我这里尽量用大白话把架构拆开、把读写链路走一遍再附上我做项目时踩过的坑和可复现的配置。文章偏长建议收藏后按段落慢慢看每个部分都可以独立拿去复习或做小抄。1. 先搞清楚HBase到底解决什么问题很多人一上来就背“HBase是一个高可靠、高性能、面向列、可伸缩的分布式存储系统”背完等于没背。我建议换一个角度先看它解决什么痛点架构和设计自然就理解了。1.1 从一次海量订单查询的坑说起我之前做过一个订单明细项目日增量几千万行单表过百亿。放到MySQL里常规查询靠主键索引勉强能跑但只要跨用户查最近记录、按时间范围扫描、或者字段本身并没有固定结构MySQL就开始难受了。分区表能缓解一部分但写热点、磁盘占用、分区数量无法无限扩展而且一旦需要跨分区扫描性能掉得很难看。HBase不是用来替代MySQL的。它更像一个“大号的、支持水平扩展的Map”你用RowKey当KeyRemaining列随便加每个Cell可以带时间戳版本。它有天然的“稀疏表”能力——同一张表里这一行有20列下一行可以只有2列空列物理上不占空间。这种模型非常适合海量明细、行为日志、推荐特征、时序数据这类场景。核心判断标准就三条数据量是不是到了亿级甚至百亿级读写模式是不是以RowKey点查和范围扫描为主表结构是不是会经常变化或者特别稀疏。如果三个都满足HBase就是很顺手的方案。1.2 HBase的定位与设计目标和MySQL/HDFS对比咱们用一张表就能看明白它的位置。场景典型选型为什么不是HBase在线事务、强一致、多表关联MySQL/PostgreSQLHBase不支持跨行事务和SQL join数据模型也偏稀疏KV海量文件、批式全量计算HDFS/HiveHDFS适合顺序扫描不支持单条随机写和毫秒级点查海量Key-Value、高并发随机读写HBase它本来就是为这个场景设计的文档型灵活结构、需要类SQL查询MongoDB如果你想用完整聚合管线和动态查询MongoDB更合适HBase本质上建造在HDFS之上。它有HDFS的高容错和高扩展但又解决了“HDFS不支持真随机读写”的问题。为什么HDFS随机读写不行因为HDFS把文件拆成Block写数据要追加到一个正在写的大文件随机更新一行等于要改写整个Block大到不现实。HBase在应用层把随机写入转化成顺序写入HFile再用LSM树思路组织数据。写入先写日志和内存批量刷成不可变HFile读的时候靠索引、布隆过滤器和内存缓存去补性能。还有一个容易被人忽略的点HBase不再依赖MySQL那一套主从复制它靠ZooKeeper和Master节点协调元数据。所以理解HBase架构核心就是理解“文件层由HDFS负责协调层由ZooKeeper负责数据层由RegionServer负责”这三句话。2. HBase整体架构拆解一张图看懂它怎么跑起来HBase的架构最忌讳死记硬背。我建议脑子里先建一个三层模型协调层、管理控制层、数据服务层。协调层是ZooKeeper管理控制层是HMaster数据服务层是多个RegionServer。2.1 核心角色Client、ZooKeeper、HMaster、RegionServer先给每个角色定位。Client负责发起读写请求和DDL操作。它不直接去找HMaster也不直连数据所在的HDFS而是先找ZooKeeper。ZooKeeper分布式协调服务解决的是“元数据到底在哪个RegionServer上”以及“Master挂了谁来顶上”。HBase在ZooKeeper上写一个节点专门保存meta表的地址。HMaster负责管理Region分配、Region分裂和合并、表的创建删除等元数据操作。注意它不负责数据读写。一句话记“Master管的是‘哪个Region归哪个RegionServer管’不管具体行的读写。”RegionServer真正干活的节点。它管理若干个Region处理Region上的读写请求内部的WAL、MemStore、HFile都在这个层。客户端写流程的第一步是这样Client连接ZooKeeper拿到meta表所在的RegionServer信息再去meta表所在节点查询目标RowKey落在哪个Region、哪个RegionServer然后直接与目标RegionServer通信。这套流程像什么呢好比你去图书馆找一本书先查总台ZooKeeper知道书籍目录meta表放在哪层再翻目录找到书架编号最后直达书架。所以写数据并不是所有请求都压到Master身上Master仅做管理吞吐由RegionServer横向分摊。有个经常被问到的问题Master挂了系统还能读吗答案是能。因为读写路由不需要MasterRegionServer彼此独立数据还在HDFS上。但表创建、Region分裂这种“全局元数据操作”会不可用。ZooKeeper的作用就是保证Master可以快速重新选主避免长时间脑裂。2.2 数据在Region里是怎么组织的HRegion、HStore、HFile、MemStore、WALRegionServer下面还有一层很重要的结构。每个RegionServer会管理多个Region这个“Region”是表按RowKey连续区间切分出来的一个数据分片。表最开始只有一个Region数据量大了自动分裂成多个再被Master均衡到不同RegionServer上。一个Region内部按“列族”继续切分每个列族对应一个Store。Store就是物理存储单元它由两部分组成内存里的MemStore以及HDFS上的HFile文件。所以“面向列的存储”在物理上有个明显含义同一行在不同列族下的数据可能落到不同文件里列族是隔离和存储的边界。写入的时候RegionServer收到写请求会先写一份WALWrite-Ahead Log预写日志在较老版本里叫HLog。然后写MemStore。MemStore就是一个内存中的有序缓存按RowKey排好序。等内存攒到一定阈值再批量落盘成HFile。HFile一旦生成就不允许再改后续合并文件Compaction也是生成新HFile来替换旧文件。这里强调一个容易混淆的概念MemStore不是单纯的“缓存”。它更多是“写入缓冲”。数据先进内存达到阈值后刷盘让随机写变成顺序写这是LSM树一类存储的核心套路。真正给读取做缓存的是BlockCache这个我在后面读写路径部分再细讲。2.3 为什么HBase选“主从共享元数据”而不是P2P有人可能会问现在很多分布式存储走P2P或者去中心化HBase为什么要保留Master和ZooKeeper这套外部依赖我的理解是HBase设计之初就是基于HDFS的强一致模型HDFS本身有NameNode单点仲裁HBase用ZooKeeper再叠加一层主从协调工程上最直接。P2P的元数据协议比如DHT、去中心化复制写起来很优雅但实现复杂度高而且HBase的RowKey有序区间天然适合“用一个集中的路由表来管理”。让每个Region按边界分配Master来协调路由逻辑非常简单。另一个关键原因强一致的读写在“先到meta表查位置再直达RegionServer”这条链路下天然避免了一些复杂的时钟问题。因为写入请求只会落到唯一一个RegionServer不需要多节点投票决议。ZooKeeper在这里只提供高可用的元数据存储和会话监控真正的数据一致性由单一RegionServer负责。这也是HBase生产上架构清晰、问题容易被定位的原因。3. 核心机制与读写路径从一次put和get说起架构是大骨架真正体现设计精髓的是读写路径。你把一次put和get走完HBase一大半知识点就通了。3.1 写入路径客户端→WAL→MemStore→HFile写一条数据“put ‘my_table’, ‘rowkey1’, ‘cf:name’, ‘张三’”完整的路径如下Client根据RowKey查ZooKeeper拿到meta表位置。再查meta表找到哪个Region包含‘rowkey1’返回目标RegionServer。Client直接给目标RegionServer发写请求。RegionServer把写入操作顺序追加到WAL实际上WAL按Region维度拆成HLog文件这一步是为了故障恢复数据在主节点内存还没落盘时不至于丢。RegionServer把数据写入对应列族的MemStoreMemStore里按RowKey排序组织。写入成功后向客户端返回ack。当MemStore大小超过阈值或者满足其他刷写条件时批量生成HFile落盘。为什么WAL那么重要因为MemStore只是内存。如果RegionServer宕机内存数据全丢。WAL写在HDFS上可以保证宕机后回放日志恢复内存状态。我见过有人图省事把WAL同步级别调成异步甚至想关掉结果模拟RegionServer宕机后出现大量数据丢失教训非常深。HBase的“写一条落一条”牺牲了一点点延迟换来了强一致是一笔非常划算买卖。写完MemStore并不代表数据立即可见吗在HBase里MemStore里的数据对读是可见的不需要等HFile生成。但前提是写入请求被成功应用到了MemStore并且WAL已成功写入这时读请求能读到刚写入的值。这也是HBase能够提供实时读写的关键。3.2 读取路径与优化MemStore、BlockCache、HFile与布隆过滤器读路径的起点类似也是先定位Region然后向目标RegionServer发起get或scan。数据可能位于三层MemStore刚写入还没落盘的数据一定先看这里。BlockCache读缓存存放最近读取的HFile数据块。HBase中HFile是按块读取的默认块大小64KB命中BlockCache能避免重复走磁盘。HFile如果MemStore和BlockCache都没有再扫描磁盘上的HFile文件。这里有一个细节HFile文件越来越多直接扫描所有文件很贵。所以HBase给每个HFile保留了“最小RowKey/最大RowKey”范围信息读取时先跳过范围不对的文件。同时可以配置布隆过滤器BloomFilter进一步回答“这个文件里有没有你找的RowKey”这个问题。布隆过滤器判断“没有”是绝对准确的判断“有”可能误判所以它能大幅减少无谓的磁盘扫描。还有一个很多人忽略的点get和scan的行为不太一样。get是单行点查加上家族和列限定符后非常快。scan则是有序范围遍历往往需要扫描多个Region如果没设计好startRow和stopRow全表扫描是常有的事。凡是用了scan条件不放RowKey前缀的情况性能基本没法看。3.3 Region分裂、合并与负载均衡RegionServer管理Region但Region不是一成不变的。当Region中某个Store达到上限通常是单个StoreFile大小过大或总数据量触发策略Region会分裂成两个子Region新Region覆盖原Region一半的行键区间。分裂完成后Master可以再把子Region分配给其他RegionServer实现负载均衡。分裂过程最笨的办法就是“停读写现场切”那当然不可接受。实际上HBase分裂时父Region保持只读子Region并行提供服务等父Region的数据全部被拆到子Region后清理。这个机制在2.x版本已经比较成熟。你自己不一定要去操控分裂但要知道它会在你压测写到某个量级时突然发生而这会带来一些短时IO抖动所以生产上会用预分区去规避高峰期自动分裂。合并是分裂的逆操作通常有两种Minor Compaction合并少数小文件Major Compaction合并所有文件顺便清理过期版本和已删除数据。合并操作对性能影响很大生产上会在低峰期手动执行或者调整参数。4. 表设计要点RowKey定乾坤列族别乱建HBase的架构决定了它的数据模型和表设计跟关系型数据库完全不同。你第一件要接受的事没有主键自增没有联合索引没有NULL空值占位。所有查询都围绕RowKey展开表设计的第一步是设计RowKey。4.1 理解HBase的表模型多维稀疏Map逻辑上一张表可以看成RowKey → (ColumnFamily : ColumnQualifier → (Timestamp → Value))比如用户表里有一行RowKeyuser_1001列族basebase:name→张三时间戳t2值张三base:age→28时间戳t2列族extraextra:level→VIP397行的用户如果没填年龄那base:age这个位置在物理上根本不占空间。这种“稀疏”能力是关系型数据库非常羡慕的因为关系型数据库不管某行是否有值都要为固定列预留存储。HBase里一个Cell就是“行键列族列名时间戳”定位到的一个值。每一列可以保存多个版本版本就是时间戳。读取时默认返回最新版本也可以通过Get设置setMaxVersions或setTimeRange来读取历史版本。版本数在建表时用VERSIONS控制默认一般是1。我建议VERSIONS能小就小因为版本越多Major Compaction清理越麻烦Scan速度越慢。4.2 RowKey设计原则散列、长度、反序、避免热点RowKey是HBase的命根子很多事故根本原因就是RowKey设计失误。我总结几条比较实用的原则。第一散列优先。RowKey尽量设计成让请求均匀分布在多个Region上。最经典的做法是加盐Salting业务ID前面拼上0到N的散列前缀比如用户ID 1001变成00_1001、01_1001分开落到不同Region。日志表更简单直接对RowKey末尾加随机前缀。第二长度适中但别走极端。过长的RowKey会放大HFile里每行Key存储的成本还影响MemStore占用。但为了可读性也不要省到一个字符。业界共识RowKey几十字节内都行别把整段JSON塞进去。第三如果做时间维度的数据经常要“按时间倒序看最近记录”可以把时间戳反序比如用Long.MAX_VALUE - timestamp做前缀。这样最新的记录在最前面Scan起来很快。反之如果正序scan结果永远从最早的数据开始读最近记录就会被迫先扫过大量旧数据。第四避免热点Region。热点不是单纯“数据多”而是所有流量都砸在同一个Region。最典型的案例就是日志表直接用userid_timestamp当RowKey同一个用户所有日志全在一个Region上别的Region空闲这个Region被打爆。所以说均匀分布比局部有序更重要。实在需要范围查询就采用“分区维度_时间维度”的复合键。4.3 列族数量与列标识设计实操列族的设计建议只有一个字少。正常表1到3个列族绝大多数场景1个就够了。为什么因为每个列族对应一个或多组StoreRegion的MemStore上限会被多个列族分摊。列族多了单个列族的MemStore更容易触发刷写产生的HFile文件碎片更多Compaction压力翻倍。真没必要把一个列族拆得特别细。列限定符Qualifier也要保持精简。HBase存数据时RowKey、列族、列名都是Key的一部分每行都要重复存的。列名取得太长比如last_update_user_name存储膨胀非常厉害。用一个编码映射表把长字段名映射成短编码省下的磁盘和缓存成本很可观。表设计还有一个实际经验先想清楚查询模式再设计RowKey。HBase的Get一定带完整RowKey或者至少带RowKey前缀Scan最好用RowKey的起始区间限制。如果你脑子里还残留“我可以根据任意字段建索引”的旧习惯HBase会分分钟教你做人。做在线实训平台题目时比如“HBase表设计和数据操作”那些关卡看起来是命令题实际考的就是RowKey和列簇的设计思路。5. 安装部署与常用操作把环境搭起来再说架构再好也得跑起来。新手最容易卡在“环境装不上”。这里给一套我实测过很多次的部署路径。5.1 单机/伪分布式/完全分布式快速安装单机模式下载hbase-2.x.x-bin.tar.gz解压后改一改环境变量就可以。单机模式下HBase使用本地文件系统不依赖HDFS适合第一次接触的学习。配置文件conf/hbase-site.xml里不需要写太多默认所有内容都在/tmp/hbase-*下。伪分布式模式如果你已经有单节点Hadoop想让HBase跑在HDFS上那么把hbase.rootdir指到HDFS路径再设置hbase.cluster.distributed为true启动时先启动HDFS再start-hbase。很多在线实训平台的“HBase安装与简单操作”就是这种模式。完全分布式一般3个节点起步。把下载好的HBase分发到每台机器编辑同一个hbase-site.xml指定ZooKeeper的quorum然后启动集群。我第一次搭集群踩过的坑是只改了Master节点的配置没有同步到RegionServer节点结果启动后各节点配置不一致RegionServer反复上下线。所以集群模式下“所有节点用同一份配置”是铁律。# 解压 tar -zxvf hbase-2.4.17-bin.tar.gz -C /opt/module/ cd /opt/module/hbase-2.4.17/conf # 单机模式最简单配置 # hbase-env.sh 里至少设置 JAVA_HOME export JAVA_HOME/opt/module/jdk1.8启动后访问http://master节点:16010看看Web UI。如果RegionServer列表能看到节点说明基础环境没问题。5.2 HBase的关键配置项与端口清单这里放一份高频配置项我平时建生产集群就按这些来。配置项默认值说明hbase.rootdirfile:///tmp/hbaseHDFS路径分布式必须改成hdfs://xxxhbase.cluster.distributedfalse分布式要设truehbase.zookeeper.quorumlocalhostZooKeeper节点列表逗号分隔hbase.zookeeper.property.clientPort2181ZooKeeper端口hbase.hregion.memstore.flush.size134217728128MB单个MemStore刷写阈值hbase.regionserver.global.memstore.size0.4所有MemStore占RegionServer堆上限比例hfile.block.cache.size0.4BlockCache占堆比例hbase.regionserver.handler.count30RegionServer处理RPC的线程数端口清单也是面试和生产排查的常客尤其HBase 2.x跟1.x端口不一样很多人还在背旧的60010让人血压飙升。组件RPC端口Web UI端口HMaster1.x6000060010RegionServer1.x6002060030HMaster2.x1600016010RegionServer2.x1602016030ZooKeeper2181-如果出现连接不上第一反应不是去改代码而是用netstat -anp | grep 16020或看logs/hbase-xxx.log确认端口有没有监听。5.3 Shell与Java API实操示例顺便说说题库里的那些操作新手快速上手路径我比较推荐先把HBase shell命令刷一遍再写Java API。很多平台上的“HBase开发使用Java操作HBase”实训关卡本质上就是让你用Java代码重复shell里的put/get/scan。基础Shell实操# 进入HBase shell hbase shell # 建表两个列族 base 和 extra版本数为3 create user_info, {NAME base, VERSIONS 3}, {NAME extra} list describe user_info # 插入数据 put user_info, user_1001, base:name, 张三 put user_info, user_1001, base:age, 28 put user_info, user_1001, extra:level, VIP3 # 读取单行 get user_info, user_1001 # 读取指定列 get user_info, user_1001, {COLUMN base:name} # 扫描全表生产环境慎用 scan user_info # 删除行 deleteall user_info, user_1001 # 下线并删除表 disable user_info drop user_infoJava API的骨架代码HBase 2.x版本类似这样Configuration conf HBaseConfiguration.create(); conf.set(hbase.zookeeper.quorum, hadoop01:2181,hadoop02:2181,hadoop03:2181); try (Connection conn ConnectionFactory.createConnection(conf)) { TableName tableName TableName.valueOf(user_info); try (Table table conn.getTable(tableName)) { // 写入 Put put new Put(Bytes.toBytes(user_1002)); put.addColumn(Bytes.toBytes(base), Bytes.toBytes(name), Bytes.toBytes(李四)); table.put(put); // 读取 Get get new Get(Bytes.toBytes(user_1002)); Result result table.get(get); byte[] name result.getValue(Bytes.toBytes(base), Bytes.toBytes(name)); System.out.println(Bytes.toString(name)); } }用Java操作HBase时有一个坑不要频繁创建Connection。Connection是重量级对象内部维护连接池和ZooKeeper会话。每个线程都去ConnectionFactory.createConnection()很快会把ZooKeeper连接数打满。正确做法是把Connection做成单例包装成一个工具类全局复用。6. 生产环境典型问题与排查记录说句实话架构图在出问题的时候帮不了你排查经验才是真正的护身符。我把自己在项目里遇到过的几类问题和几套排查命令整理一下。6.1 Region热点与数据倾斜现象某个RegionServer的CPU或IO明显高于其他节点请求超时集中出现或者某个Region的StoreFile增长特别快。原因大多数出在RowKey上。如果RowKey前缀是同一个用户ID即使数据被分到多个Region某个用户的所有请求也只能打到一个Region。排查方式看Web UI里RegionServer的请求数和Region数再用hbase hbck或者HBase的RegionSplitter看Region区间边界。解决方案通常就是加盐。有一种常见的盐值策略Math.abs(MurmurHash3.hash(rowkey)) % 分区数把哈希结果作为前缀。代价是牺牲了range scan的连续性所以在做全局线上实时数据的时候我会权衡“点查”和“范围扫描”的优先级。6.2 WAL阻塞与刷写异常现象日志里出现Flush of region ... is slow或者Too many open WAL files。这类问题经常是HDFS写入太慢导致WAL没有及时滚动RegionServer上的日志文件堆积。MemStore如果一直不刷盘全局内存达到上限后HBase会强制阻塞写入表现为客户端RegionTooBusyException或者写延迟飙高。排查命令先看RegionServer GC日志再查看HDFS DataNode磁盘IO确认是不是慢盘导致append延迟然后看MemStore大小是不是一直接近上限。通常做法是调大hbase.hregion.memstore.flush.size同时调低hbase.regionserver.global.memstore.size让刷写更积极或者为HBase单独划分一块高速盘。但任何参数调整都要配合压测别为了“噶血”而把吞吐搞崩。6.3 版本过多或大Value引发的扫描性能问题有人以为版本数越多越安全搞个VERSIONS 100结果Scan某个RowKey时同一个Cell要返回几十上百条记录。还有一个常犯错误往HBase里存十几MB的JSON串。HBase读写单位最小是HFile块默认64KB大Value会跨很多块BlockCache内存被瞬间挤爆GC压力急剧上升。如果确实有“小附件、算法结果”需求更稳妥的做法是把文件放HDFS或对象存储HBase存路径和元信息。这个取舍在容量规划阶段就该确定不然上线后迁移非常痛苦。我记得有个项目线上查询经常Full GC最后定位就是用户头像被Base64后直接存进HBase一条Value将近1MB缓存全被扫描请求冲垮。6.4 故障速查表与常见面试题症状可能原因排查建议客户端连不上ZooKeeper没启、网络不通echo ruokRegionServer反复上下线各节点配置不一致、时钟偏移同步配置检查ntp写操作卡顿严重MemStore内存满、WAL堆积看请求耗时分位数查看RegionServer GC读多写少但读还慢BlockCache太小、布隆过滤器没开调hfile.block.cache.size建表时BLOOMFILTERROW磁盘占用暴涨HFile碎片多、未Major Compaction低峰期手动major_compact tableName顺便给几个“面试高频题”的简短回答思路帮你建立“考官问这个到底想考我什么”的感觉。HBase为什么写快读慢因为LSM设计写走顺序日志和内存读需要合并内存和多个HFile实际靠缓存、布隆过滤器、索引来补偿。RegionServer宕机后数据会丢吗不会WAL在HDFS上Master会把该RS上的region重新分配并回放WAL恢复MemStore数据。列族为什么不宜多多个列族会导致MemStore内存占比被稀释增加刷写和Compaction数量文件数爆炸。RowKey设计中怎么避免热点加盐、加哈希前缀、时间戳反转、预分区。Master没有参与读写为什么还需要它负责Region管理、DDL、负载均衡、宕机恢复只是不处理单行读写而已。7. 生产调优与个人实操心得如果只把HBase部署出来、能写能读那只是入门。下面这部分我结合自己压测和调优的经验说说几个值得动手的地方。7.1 预分区与BulkLoad批量导入建表时不加预分区所有数据写入初始的单个Region等Region到阈值再分裂。一旦触发自动分裂写请求会被短暂的“分裂过程”拖住而且两个子Region的边界不一定符合你的数据分布。所以生产环境必须根据RowKey分布提前把表切分成多个Region。Shell里可以这么建# 提前按散列边界划分成8个Region create access_log, {NAME cf, BLOOMFILTER ROW, VERSIONS 1, COMPRESSION SNAPPY}, SPLITS [1, 2, 3, 4, 5, 6, 7]如果要做历史数据迁移千万不要一条条put。正确姿势是先用MapReduce或者Spark直接生成HFile再通过hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles加载到表里。这个叫BulkLoad把“在线写”变成“离线生成文件”导入亿级数据的速度可以比逐行Put快一个数量级。我用BulkLoad导过20亿条记录提前用同一个RowKey生成规则对HFile做分区导入过程对在线集群的影响非常小。7.2 缓存与线程参数调优每个RegionServer的JVM堆就那么大BlockCache和MemStore是存量博弈。经验值如果读多写少BlockCache给到0.4到0.5写多读少MemStore可以到0.4混合负载各0.3左右比较稳。别把两兄弟加到超出堆的一半以上否则其他小对象都没地方放。hbase.regionserver.handler.count默认30很多博客一刀切说调成100。但RPC线程数增加意味着线程上下文切换和堆压力都增加。我用过的经验是在机械盘时代线程太多反而排队SSD和万兆网卡环境下可以适当调高到60左右。每次改完都要压测看着P99延迟来调别凭感觉往上加代码。还有一个容易漏掉的参数hbase.client.scanner.caching。如果Scan一次取1000行客户端和服务器之间往返次数会少很多但太多也会导致单次RPC返回的数据量过大让RegionServer和客户端GC同时发抖。行宽小、数据多的时候我把caching放在100到500之间行宽大比如带有多个长列时caching降到10甚至更小。7.3 我踩过的坑“别学我”清单最后分享几个我印象深刻的失误。一是把HBase当关系数据库用。我接手过一个模块表设计者为了让查询方便在RowKey里拼接了“用户ID订单ID商品ID时间”结果RowKey膨胀到上百字节每行的Key重复存储相同用户数据无法集中扫描反而要来回去查多个Region。后来改成“用户ID反转订单ID”才缓解。当然这也告诉我一个道理RowKey设计必须清晰地画出“这个表最主要的查询路径”其他查询路径要在上层应用层解决。二是一上来就关闭WAL。某个测试环境为了追求写入速度参照网上某篇“优化”文章把setDurability(SKIP_WAL)写上。恰逢RegionServer因为内存问题被Kill掉测试数据全部消失。那个项目后来花了两天才把流程重跑一遍。写路径上的WAL不是可选项而是强一致的底线。如果实在想降低WAL开销应该用异步批量日志分组而不是关掉它。三是做数据清理时习惯性全表Scan。我们要对一批历史用户做标记本可以直接用Scan加联合条件快速过滤结果我一开始写成了不带任何startRow的Scan跑到一半RegionServer的BlockCache被冲没线上的读流量被严重拖慢。从那以后凡是线上Scan一定设置startRow和stopRow只扫描必要的区间绝不贪图省事。这几个坑写出来是希望你直接绕过。HBase整体并不难难的是对架构的敬畏——它用LSM换来了高写入和扩展性你就必须接受RowKey、列族、Compaction这些设计约束。我现在的习惯是任何表设计都先在纸上画一遍读写路径模拟RowKey分布再动手建环境每次上线前至少做一次RegionServer故障演练确认WAL回放能恢复正常。按这个流程做基本没出过大问题。
返回列表