HBase 的二级索引(Secondary Index)是如何实现的?
在 HBase 中,原生架构只支持通过 RowKey 进行快速的字典检索。如果你需要根据其他列(非 RowKey 列)来查询数据,原生 HBase 只能进行全表扫描(Full Table Scan),这在海量数据下是不可接受的。
为了解决这个问题,就需要引入二级索引(Secondary Index)。
HBase 二级索引的核心思想非常简单:建立一个映射关系,将“需要查询的列值”映射到“原始数据的 RowKey”上。 当查询时,先查索引获取 RowKey,再用 RowKey 去查主表获取完整数据。
目前 HBase 实现二级索引主要有以下几种主流方案:
1. 基于 HBase 协处理器(Coprocessor)自定义实现
HBase 提供了协处理器机制,类似于关系型数据库中的触发器(Trigger)。
- 实现原理:
- 使用协处理器中的
Observer(观察者)组件。 - 重写
prePut或postPut方法。当客户端向主表写入一条数据时,协处理器会拦截这个动作。 - 在拦截的方法内,提取需要建立索引的列值,组装成新的 RowKey(例如:
列值_原RowKey),然后将这条索引数据自动写入到一个专门的 HBase 索引表中。 - 同理,重写
preDelete等方法来同步删除索引。
- 使用协处理器中的
- 查询过程:客户端先查索引表,获取主表的 RowKey,然后再去主表 Get 数据。
- 优点:逻辑在服务端执行,对客户端透明;实时性较高。
- 缺点:
- 开发和维护成本高(需编写 Java 代码并部署到所有 RegionServer)。
- 增加服务器的 CPU 和内存开销。
- 分布式事务问题:主表和索引表属于不同的 Region,写入操作无法保证强一致性(主表写成功,索引表可能写失败)。
2. 使用 Apache Phoenix(目前最主流的方案)
Apache Phoenix 是一个构建在 HBase 之上的关系型数据库引擎,它提供了标准的 SQL 接口,并且内置了完善的二级索引支持。Phoenix 的底层索引实现其实也是基于 HBase 的协处理器。
Phoenix 提供了几种不同类型的索引:
A. 全局索引 (Global Index)
- 适用场景:读多写少的场景。
- 原理:索引数据存放在独立于主表的 HBase 表中。当写入主表时,Phoenix 会自动更新索引表。
- 特点:因为索引表和主表分布在不同的 RegionServer 上,写入时会有跨节点的网络开销;但查询时可以直接定位到索引数据。
B. 本地索引 (Local Index)
- 适用场景:写多读少,或者存储空间有限的场景。
- 原理:索引数据和主表数据存放在同一个 Region 中(通过共享同一个表,使用特殊的 Column Family 区分)。
- 特点:写入性能好(无需跨网络同步);但在查询时,因为不知道数据具体在哪个 Region,需要向所有 Region 发起并发查询,对读性能有一定影响。
C. 覆盖索引 (Covered Index)
- 原理:在建立索引时,把业务经常需要查询的非索引列也冗余存入索引表中。
- 特点:查询时,直接从索引表中就能拿到所需的所有字段,不需要再回表(去主表查原数据),极大地提升了查询性能。
3. HBase + 外部搜索引擎(Elasticsearch / Solr)
对于复杂的查询(如全文检索、多条件组合查询、模糊查询、地理位置查询),HBase 本身并不擅长。业界非常流行“HBase + 搜索引擎”的异构双写方案。
- 实现原理:
- HBase:作为最终的数据存储(Source of Truth),存储完整的海量数据。
- Elasticsearch (ES):作为索引引擎,只存储需要查询的字段 + HBase 的 RowKey。
- 数据同步方式:
- 客户端双写:代码里同时向 HBase 和 ES 写入(容易出现不一致)。
- MQ 异步解耦:数据发往 Kafka,消费者分别写入 HBase 和 ES。
- 基于日志抓取:使用如 Lily HBase Indexer 工具监听 HBase 的 WAL(预写日志),或者通过 HBase 协处理器,将数据变更异步同步到 ES。
- 查询过程:客户端先拼接复杂查询条件去 ES 中查,ES 返回一批符合条件的 HBase RowKey,客户端再拿着这批 RowKey 去 HBase 中进行批量
Get操作获取详情。 - 优点:查询能力极其强大,完美解决多维查询问题。
- 缺点:架构复杂度高,维护两套存储系统;数据存在短暂的延迟(最终一致性)。
4. 客户端双写(应用层代码实现)
- 原理:在业务系统的代码逻辑中,由开发者自己控制写入。每次保存数据时,先写一张主表,再写一张专门设计的索引表。
- 优点:架构简单,不需要引入额外的组件或复杂的服务端部署。
- 缺点:代码侵入性极强;极易出现数据不一致(如网络抖动导致写了主表没写索引表);后续如果想增加索引列,需要跑跑批任务重构数据,扩展性极差。
总结与选型建议
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Apache Phoenix | 首选。需要 SQL 接口,结构化数据,字段查询场景明确。 | 官方推荐,支持丰富,开发效率高,支持覆盖索引。 | 引入新框架,对高并发写入有轻微性能损耗。 |
| HBase + ES | 复杂查询(多条件组合、全文检索、模糊匹配等)。 | 查询能力无敌,读性能极高。 | 架构复杂,维护成本高,数据同步存在延迟(最终一致性)。 |
| 原生协处理器 | 公司技术栈受限,不能引入 Phoenix/ES,且对性能要求极高。 | 服务端执行,不用维护外部系统。 | 开发极其困难,调试复杂,容易导致 RegionServer 崩溃。 |
| 客户端双写 | 极小规模的简单项目。 | 不需要任何额外技术。 | 容易数据不一致,代码耦合度高。 |
在实际的企业级开发中,目前几乎不会有人纯手工写协处理器来实现二级索引。绝大多数团队都会选择 Apache Phoenix(解决常规的关系型查询)或者 HBase + Elasticsearch(解决复杂的多维检索)。
右滑查看面试常问