在 ES 7.0 之前存储结构是 Index -> Type -> Document,按 MySQL 对比就是 database - table - id(实际这种对比不那么合理)。7.0 之后 Type 被废弃了,就暂把 index 当做 table 吧。在 Dev Tools 的 Console 可以通过以下命令查看一些基本信息。也可以替换为 crul 命令。
GET /_cat/health?v&pretty:查看集群健康状态
GET /_cat/shards?v :查看分片状态
GET yourindex/_mapping :index mapping结构
GET yourindex/_settings :index setting结构
GET /_cat/indices?v :查看当前节点所有索引信息
重点是 mapping 和 setting ,mapping 可以理解为 MySQL 中表的结构定义,setting 负责控制如分片数量、副本数量。以下是截取了某日志 index 下的部分 mapping 结构,ES 对字符串类型会默认定义成 text ,同时为它定义一个叫做 keyword 的子字段。这两的区别是:text 类型会进行分词, keyword 类型不会进行分词。
分词是什么意思?看完 ES 的索引原理你就 get 了。ES 基于倒排索引。嘛意思?传统索引一般是以文档 ID 作索引,以内容作为记录。倒排索引相反,根据已有属性值,去找到相应的行所在的位置,也就是将单词或内容作为索引,将文档 ID 作为记录。下图是 ES 倒排索引的示意图,由 Term index,Team Dictionary 和 Posting List 组成。图中的 Ada、Sara 被称作 term,其实就是分词后的词了。如果把图中的 Term Index 去掉,是不是有点像 MySQL 了?Term Dictionary 就像二级索引,但 MySQL 是保存在磁盘上的,检索一个 term 需要若干次的 random access 磁盘操作。而 ES 在 Term Dictionary 基础上多了层 Term Index ,它以 FST 形式保存在内存中,保存着 term 的前缀,借此可以快速的定位到 Term dictionary 的本 term 的 offset 。而且 FST 形式和 Term dictionary 的 block 存储方式都很节省内存和磁盘空间。到这就知道为啥快了,就是因为有了内存中的 Term Index , 它为 term 的索引 Term Dictionary 又做了一层索引。不过,也不是说 ES 什么查询都比 MySQL 快。检索大致分为两类。
2.3.1 分词后检索
ES 的索引存储的就是分词排序后的结果。比如图中的 Ada,在 MySQL 中 %da% 就扫全表了,但对 ES 来说可以快速定位2.3.2 精确检索该情况其实相差是不大的,因为 Term Index 的优势没了,却还要借此找到在 term dictionary 中的位置。也许由于 MySQL 覆盖索引无需回表会更快一点。2.4 什么时候用 ES如前所述,对于业务中的查询场景什么时候适合使用 ES ?我觉得有两种。2.4.1 全文检索在 MySQL 中字符串类型根据关键字模糊查询就是一场灾难,对 ES 来说却是小菜一碟。具体场景,比如消息表对消息内容的模糊查询,即聊天记录查询。但要注意,如果需要的是类似广大搜索引擎的关键字查询而非日志的短语匹配查询,就需要对中文进行分词处理,最广泛使用的是 ik 。Ik 分词器的安装这里不再细说。什么意思呢?分词开头对日志的查询,键入 “我可真是个机灵鬼” 时,只会得到完全匹配的信息。而倘若去掉 “”,又会得到按照 “我”、“可”,“真”….分词匹配到的所有信息,这明显会返回很多信息,也是不符合中文语义的。实际期望的分词效果大概是“我”、“可”、“真是”,“机灵鬼”,之后再按照这种分词结果去匹配查询。这是 ES 默认的分词策略对中文的支持不友善导致的,按照英语单词字母来了,可英语单词间是带有空格的。这也是不少国外软件中文搜索效果不 nice 的原因之一。对于该问题,你可以在 console 使用下方命令,测试当前 index 的分词效果。
POST yourindex/_analyze { "field":"yourfield", "text":"我可真是个机灵鬼" }
2.4.2 组合查询
如果数据量够大,表字段又够多。把所有字段信息丢到 ES 里创建索引是不合理的。使用 MySQL 的话那就只能按前文提到的分库分表、读写分离来了。何不组合下。
1. ES + MySQL
将要参与查询的字段信息加上 id,放入 ES,做好分词。将全量信息放入 MySQL,通过 id 快速检索。
2. ES + HBASE
如果要省去分库分表什么的,或许可以抛弃 MySQL ,选择分布式数据库,比如 HBASE , 对于这种 NOSQL 来说,存储能力海量,扩容 easy ,根据 rowkey 查询也很快。以上思路都是经典的索引与数据存储隔离的方案了。当然,摊子越大越容易出事,也会面临更多的问题。使用 ES 作索引层,数据同步、时序性、mapping 设计、高可用等都需要考虑。毕竟和单纯做日志系统对比,日志可以等待,用户不能。2.5 小结本节简单介绍了 ES 为啥快,和这个快能用在哪。现在你可以打开 Kibana 的控制台试一试了。如果想在 Java 项目中接入的话,有 SpringBoot 加持,在 ES 环境 OK 的前提下,完全是开箱即用,就差一个依赖了。基本的 CRUD 支持都是完全 OK 的。
3. HBASE
前面有提到 HBASE , 什么是 HBASE ,鉴于篇幅这里简单说说。3.1 存储结构关系型数据库如 MySQL 是按行来的。HBASE 是按列的(实际是列族)。列式存储上表就会变成:
下图是一个 HBASE 实际的表模型结构。
Row key 是主键,按照字典序排序。TimeStamp 是版本号。info 和 area 都是列簇(column Family),列簇将表进行横向切割。name、age 叫做列,属于某一个列簇,可进行动态添加。Cell 是具体的 Value 。
个人觉得软件开发是循序渐进的,技术服务于项目,合适比新颖复杂更重要。如何完成一次快速的查询?最该做的还是先找找自己的 Bug,解决了当前问题再创造新问题。本文列举到的部分方案对于具体实现大多一笔带过,实际无论是 MySQL 的分表还是 ES 的业务融合都会面临很多细节和困难的问题,搞工程的总要绝知此事要躬行。