写在前面
项目的日志模块中的日志数据都存储在 HBase 中,由于 HBase 只有 rowkey 这个一级索引的特性,为了方便对日志数据的个别字段进行查询,就需要额外创建二级索引
之前参考了网络上非常多的二级索引实现方案,各种方案的思路都是大同小异,无非就是把查询字段和 rowkey 做一个关联,存储在 HBase / ES / MySQL 等等地方,最终决定尝试采取用 Elasticsearch 存储二级索引
设计细节
1. 文档 id 的设计
将 rowkey 作为 ES 文档的 id
这么做有以下几点考虑:
id 需要全局唯一性,而 HBase 中数据的 rowkey 已经保证了这一点
rowkey 不需要被搜索
查询时,只返回文档 id 即可,不需要返回文档的内容(即做了二级索引的那些列的值),这样能提高查询效率
rowkey 如果作为文档的一个列进行存储,会带来一些不必要的磁盘空间占用,假设一个 rowkey 有 20 字节大小,存 1 亿条数据至少会有 1.8 TB 的空间占用
当然,有利也有弊,目前考虑到的弊端包括:
使用非 ES 自己生成的 id,会带来一定的效率损失,每条数据存储时,ES 都要对 id 进行一次判断
…
2. 文档字段的设计
HBase 存储的数据中,需要做索引的列(即需要用来查询的列)
3. 字段属性的设计
索引
存储的字段都创建索引,毕竟不创建索引就不能查询
分词
根据实际情况,字段是否需要设置分词
_source / store
上面有说到,查询时仅需要返回文档的 id,不需要文档的内容,所以存储的字段可以禁用存储,大幅度节省磁盘空间
doc_values
如果存储的字段不需要进行排序、聚合、脚本运算,可以禁用 doc_values,也可以节省磁盘空间
norms
如果字段不需要分词,直接指定字段为 keyword,即默认不开启 norms
一个疑问
本来是先设计了日志数据存储到 HBase,然后才开始考虑二级索引的实现方案,按上面说的这么弄了之后,突然一时有点懵:我为什么不直接把数据存 ES 里?
又多学习了一些 ES 和 HBase 二级索引相关的文章后,在《分布式搜索的面试题3》中看到了这样的描述:
比如说你现在有一行数据
id name age ….30 个字段
但是你现在搜索,只需要根据 id name age 三个字段来搜索
如果你傻乎乎的往 es 里写入一行数据所有的字段,就会导致说 70% 的数据是不用来搜索的,结果硬是占据了 es 机器上的 filesystem cache 的空间,单挑数据的数据量越大,就会导致 filesystem cahce 能缓存的数据就越少
仅仅只是写入 es 中要用来检索的少数几个字段就可以了,比如说,就写入 es id name age 三个字段就可以了,然后你可以把其他的字段数据存在 mysql 里面,我们一般是建议用 es + hbase 的这么一个架构。
hbase 的特点是适用于海量数据的在线存储,就是对 hbase 可以写入海量数据,不要做复杂的搜索,就是做很简单的一些根据 id 或者范围进行查询的这么一个操作就可以了
从 es 中根据 name 和 age 去搜索,拿到的结果可能就 20 个 doc id,然后根据 doc id 到 hbase 里去查询每个 doc id 对应的完整的数据,给查出来,再返回给前端。
你最好是写入 es 的数据小于等于,或者是略微大于 es 的 filesystem cache 的内存容量
然后你从 es 检索可能就花费 20ms,然后再根据 es 返回的 id 去 hbase 里查询,查 20 条数据,可能也就耗费个 30ms,可能你原来那么玩儿,1T 数据都放 es,会每次查询都是 5 ~ 10 秒,现在可能性能就会很高,每次查询就是 50ms。
四个字总结的话,我觉得就是“各司其职”,HBase 就用来存储,ES 就用来做索引,况且目前的实际情况跟文章中说的也很像,要查询的字段就几个,而其他的字段又很大又没用,没必要都丢到 ES 中,浪费查询效率