HBase二级索引 #
一、二级索引概述 #
HBase只支持RowKey索引,二级索引用于加速非RowKey列的查询。
1.1 为什么需要二级索引 #
text
HBase查询限制
├── 只支持RowKey查询
│ └── get/scan基于RowKey
│
├── 非RowKey列查询
│ └── 需要全表扫描
│
└── 二级索引解决
└── 加速非RowKey列查询
1.2 二级索引方案 #
text
二级索引方案
├── Phoenix索引
│ └── 全局索引、本地索引
│
├── Coprocessor索引
│ └── 使用协处理器维护索引
│
├── 外部索引
│ ├── Elasticsearch
│ └── Lucene
│
└── 双写方案
└── 应用层维护索引表
二、Phoenix二级索引 #
2.1 全局索引 #
text
全局索引特点
├── 独立的索引表
├── 索引数据分布在所有Region
├── 适合读多写少场景
└── 写入需要跨Region更新索引
sql
-- 创建全局索引
CREATE INDEX idx_user_email ON user (email);
-- 创建覆盖索引
CREATE INDEX idx_user_email_name ON user (email) INCLUDE (name, age);
-- 使用索引查询
SELECT email, name, age FROM user WHERE email = 'test@example.com';
2.2 本地索引 #
text
本地索引特点
├── 索引数据与原数据在同一Region
├── 写入性能好
├── 适合写多读少场景
└── 查询需要访问所有Region
sql
-- 创建本地索引
CREATE LOCAL INDEX idx_local_email ON user (email);
-- 使用索引查询
SELECT * FROM user WHERE email = 'test@example.com';
2.3 函数索引 #
sql
-- 创建函数索引
CREATE INDEX idx_lower_email ON user (LOWER(email));
-- 使用函数索引
SELECT * FROM user WHERE LOWER(email) = 'test@example.com';
2.4 覆盖索引 #
sql
-- 创建覆盖索引
CREATE INDEX idx_cover ON user (email) INCLUDE (name, age);
-- 覆盖索引查询(不需要回表)
SELECT email, name, age FROM user WHERE email = 'test@example.com';
2.5 索引管理 #
sql
-- 查看索引
!indexes user
-- 禁用索引
ALTER INDEX idx_user_email ON user DISABLE;
-- 启用索引
ALTER INDEX idx_user_email ON user ENABLE;
-- 重建索引
ALTER INDEX idx_user_email ON user REBUILD;
-- 删除索引
DROP INDEX idx_user_email ON user;
三、Coprocessor二级索引 #
3.1 实现原理 #
text
Coprocessor索引原理
├── RegionObserver监听数据变更
│ ├── prePut/postPut
│ └── preDelete/postDelete
│
├── 同步更新索引表
│ └── 写入索引数据
│
└── 查询时先查索引表
└── 获取RowKey后再查原表
3.2 实现示例 #
java
import org.apache.hadoop.hbase.coprocessor.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
public class IndexObserver implements RegionObserver {
private static final byte[] INDEX_TABLE = Bytes.toBytes("user_email_index");
private static final byte[] CF = Bytes.toBytes("info");
private static final byte[] EMAIL_COL = Bytes.toBytes("email");
private static final byte[] IDX_CF = Bytes.toBytes("idx");
private static final byte[] ROWKEY_COL = Bytes.toBytes("rowkey");
@Override
public void postPut(ObserverContext<RegionCoprocessorEnvironment> c,
Put put, WALEdit edit, Durability durability)
throws IOException {
// 获取email值
byte[] emailValue = getValue(put, CF, EMAIL_COL);
if (emailValue == null) return;
// 获取RowKey
byte[] rowKey = put.getRow();
// 写入索引表
Connection conn = c.getEnvironment().getConnection();
Table indexTable = conn.getTable(TableName.valueOf(INDEX_TABLE));
Put indexPut = new Put(emailValue);
indexPut.addColumn(IDX_CF, ROWKEY_COL, rowKey);
indexTable.put(indexPut);
indexTable.close();
}
@Override
public void postDelete(ObserverContext<RegionCoprocessorEnvironment> c,
Delete delete, WALEdit edit, Durability durability)
throws IOException {
// 删除索引数据
// 实现删除逻辑
}
private byte[] getValue(Put put, byte[] cf, byte[] qualifier) {
List<Cell> cells = put.get(cf, qualifier);
return cells.isEmpty() ? null : CellUtil.cloneValue(cells.get(0));
}
}
3.3 查询索引 #
java
public byte[] queryByEmail(Connection conn, String email) throws IOException {
// 查询索引表
Table indexTable = conn.getTable(TableName.valueOf("user_email_index"));
Get get = new Get(Bytes.toBytes(email));
Result result = indexTable.get(get);
if (result.isEmpty()) {
return null;
}
// 获取RowKey
byte[] rowKey = result.getValue(Bytes.toBytes("idx"), Bytes.toBytes("rowkey"));
indexTable.close();
return rowKey;
}
public Result queryUserByEmail(Connection conn, String email) throws IOException {
byte[] rowKey = queryByEmail(conn, email);
if (rowKey == null) {
return null;
}
// 查询原表
Table userTable = conn.getTable(TableName.valueOf("user"));
Get get = new Get(rowKey);
Result result = userTable.get(get);
userTable.close();
return result;
}
四、Elasticsearch索引 #
4.1 架构设计 #
text
Elasticsearch索引架构
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Application │ ──────► │ HBase │ │
│ └─────────────┘ └─────────────┘ │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │Elasticsearch│ ◄────── │ Sync │ │
│ │ (索引) │ │ (同步) │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
4.2 同步方案 #
java
import org.apache.hadoop.hbase.client.*;
import org.elasticsearch.client.*;
import org.elasticsearch.action.index.IndexRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
public class HBaseESSync {
private RestHighLevelClient esClient;
private ObjectMapper mapper = new ObjectMapper();
public void syncToES(Connection hbaseConn, String tableName) throws Exception {
Table table = hbaseConn.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
ResultScanner scanner = table.getScanner(scan);
for (Result result : scanner) {
// 构建文档
Map<String, Object> doc = new HashMap<>();
String rowKey = Bytes.toString(result.getRow());
doc.put("id", rowKey);
// 添加其他字段
byte[] nameValue = result.getValue(Bytes.toBytes("info"), Bytes.toBytes("name"));
if (nameValue != null) {
doc.put("name", Bytes.toString(nameValue));
}
byte[] emailValue = result.getValue(Bytes.toBytes("info"), Bytes.toBytes("email"));
if (emailValue != null) {
doc.put("email", Bytes.toString(emailValue));
}
// 索引到ES
IndexRequest request = new IndexRequest("user_index")
.id(rowKey)
.source(mapper.writeValueAsString(doc), XContentType.JSON);
esClient.index(request, RequestOptions.DEFAULT);
}
scanner.close();
table.close();
}
}
4.3 查询流程 #
java
public List<String> searchUserIds(String query) throws Exception {
// 1. 查询ES获取RowKey列表
SearchRequest request = new SearchRequest("user_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("name", query));
sourceBuilder.fetchSource(false); // 只返回ID
request.source(sourceBuilder);
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
List<String> rowKeys = new ArrayList<>();
for (SearchHit hit : response.getHits()) {
rowKeys.add(hit.getId());
}
return rowKeys;
}
public List<Result> queryUsersBySearch(Connection conn, String query) throws Exception {
List<String> rowKeys = searchUserIds(query);
// 2. 根据RowKey批量查询HBase
List<Get> gets = new ArrayList<>();
for (String rowKey : rowKeys) {
gets.add(new Get(Bytes.toBytes(rowKey)));
}
Table table = conn.getTable(TableName.valueOf("user"));
Result[] results = table.get(gets);
table.close();
return Arrays.asList(results);
}
五、索引方案对比 #
5.1 方案对比 #
| 方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| Phoenix索引 | 简单易用、SQL支持 | 写入性能影响 | 读多写少 |
| Coprocessor索引 | 灵活可控 | 开发复杂 | 自定义需求 |
| Elasticsearch索引 | 强大搜索能力 | 需要同步 | 全文搜索 |
| 双写方案 | 简单直接 | 一致性难保证 | 简单场景 |
5.2 选择建议 #
text
索引方案选择
├── 简单SQL查询
│ └── Phoenix索引
│
├── 自定义索引逻辑
│ └── Coprocessor索引
│
├── 全文搜索需求
│ └── Elasticsearch索引
│
└── 简单场景
└── 双写方案
六、索引设计原则 #
6.1 索引列选择 #
text
索引列选择原则
├── 高选择性列
│ └── 值分布均匀
│
├── 高频查询列
│ └── 经常作为查询条件
│
├── 组合索引
│ └── 多列组合查询
│
└── 避免过度索引
└── 影响写入性能
6.2 索引维护 #
text
索引维护建议
├── 定期重建索引
│ └── 修复不一致
│
├── 监控索引性能
│ └── 关注查询效率
│
├── 索引数据备份
│ └── 防止数据丢失
│
└── 索引空间管理
└── 控制索引数量
七、索引最佳实践 #
7.1 Phoenix索引最佳实践 #
sql
-- 1. 选择合适的索引类型
-- 读多写少:全局索引
CREATE INDEX idx_global ON user (email);
-- 写多读少:本地索引
CREATE LOCAL INDEX idx_local ON user (email);
-- 2. 使用覆盖索引避免回表
CREATE INDEX idx_cover ON user (email) INCLUDE (name, age);
-- 3. 异步构建大表索引
CREATE INDEX idx_async ON user (email) ASYNC;
7.2 索引一致性保证 #
text
索引一致性保证
├── 使用事务
│ └── Phoenix事务支持
│
├── 定期校验
│ └── 对比原表和索引表
│
├── 重建索引
│ └── 发现不一致时重建
│
└── 监控告警
└── 监控索引状态
7.3 性能优化 #
text
索引性能优化
├── 批量写入
│ └── 减少索引更新次数
│
├── 异步索引
│ └── 异步构建索引
│
├── 索引压缩
│ └── 减少索引存储空间
│
└── 合理分区
└── 索引表预分区
八、常见问题 #
8.1 索引不一致 #
sql
-- 问题:索引数据与原数据不一致
-- 解决:重建索引
ALTER INDEX idx_user_email ON user REBUILD;
8.2 索引写入慢 #
sql
-- 问题:写入性能下降
-- 解决:使用本地索引或异步索引
-- 使用本地索引
CREATE LOCAL INDEX idx_local ON user (email);
-- 使用异步索引
CREATE INDEX idx_async ON user (email) ASYNC;
8.3 索引查询不生效 #
sql
-- 问题:查询未使用索引
-- 解决:使用Hint或检查查询条件
-- 使用Hint
SELECT /*+ INDEX(user idx_email) */ * FROM user WHERE email = 'test@example.com';
-- 检查索引
!indexes user
九、总结 #
本节介绍了HBase二级索引:
| 方案 | 特点 |
|---|---|
| Phoenix索引 | 简单易用,SQL支持 |
| Coprocessor索引 | 灵活可控,自定义逻辑 |
| Elasticsearch索引 | 强大搜索能力 |
| 双写方案 | 简单直接 |
下一步,让我们学习管理与运维!
最后更新:2026-03-27