我个人对于Elasticsearch快的原因主要总结三点:
- 内存读取
- 多种索引
- 倒排索引
- doc values
- 集群分片
内存读取
Elasticsearch是基于Lucene, 而Lucene被设计为可以利用操作系统底层机制来缓存内存数据结构,换句话说Elasticsearch是依赖于操作系统底层的 Filesystem Cache,查询时,操作系统会将磁盘文件里的数据自动缓存到 Filesystem Cache 里面去,因此要求Elasticsearch性能足够高,那么就需要服务器的提供的足够内存给Filesystem Cache 覆盖存储的数据。
上一段最后一句话什么意思呢?假如:Elasticsearch 节点有 3 台服务器各64G内存,3台总内存就是 64 * 3 = 192G。每台机器给 Elasticsearch jvm heap 是 32G,那么每服务器留给 Filesystem Cache 的就是 32G(50%),而集群里的 Filesystem Cache 的就是 32 * 3 = 96G 内存。此时,在 3 台Elasticsearch服务器共占用了 1T 的磁盘容量,那么每台机器的数据量约等于 341G,意味着每台服务器只有大概10分之1数据是缓存在内存的,其余都得走硬盘。
说到这里大家未必会有一个直观得认识,因此我从《大型网站技术架构:核心原理与案例分析》第36页抠了一张表格下来:
操作 | 响应时间 |
打开一个网站 | 几秒 |
在数据库中查询一条记录(有索引) | 十几毫秒 |
机械磁盘一次寻址定位 | 4毫秒 |
从机械磁盘顺序读取1MB数据 | 2毫秒 |
从SSD磁盘顺序读取1MB数据 | 0.3毫秒 |
从远程分布式缓存Redis读取一个数据 | 0.5毫秒 |
从内存中读取1MB数据 | 十几微秒 |
Java程序本地方法调用 | 几微秒 |
网络传输2KB数据 | 1微秒 |
从上图加粗项看出,内存读取性能是机械磁盘的200倍,是SSD磁盘约等于30倍,假如读一次Elasticsearch走内存场景下耗时20毫秒,那么走机械硬盘就得4秒,走SSD磁盘可能约等于0.6秒。讲到这里我相信大家对是否走内存的性能差异有一个直观的认识。
对于Elasticsearch有很多种索引类型,但是我认为核心主要是倒排索引和doc values
倒排索引
Lucene将写入索引的所有信息组织为倒排索引(inverted index)的结构形式。倒排索引是一种将分词映射到文档的数据结构,可以认为倒排索引是面向分词的而不是面向文档的。
假设在测试环境的Elasticsearch存放了有以下三个文档:
- Elasticsearch Server(文档1)
- Masterring Elasticsearch(文档2)
- Apache Solr 4 Cookbook(文档3)
以上文档索引建好后,简略显示如下:
词项 | 数量 | 文档 |
4 | 1 | <3> |
Apache | 1 | <3> |
Cookbook | 1 | <3> |
Elasticsearch | 2 | <1><2> |
Mastering | 1 | <1> |
Server | 1 | <1> |
Solr | 1 | <3> |
如上表格所示,每个词项指向该词项所出现过的文档位置,这种索引结构允许快速、有效的搜索出数据。
doc values
对于分组、聚合、排序等某些功能来说,倒排索引的方式并不是最佳选择,这类功能操作的是文档而不是词项,这个时候就得把倒排索引逆转过来成正排索引,这么做会有两个缺点:
- 构建时间长
- 内存占用大,易OutOfMemory,且影响垃圾回收
Lucene 4.0之后版本引入了doc values和额外的数据结构来解决上面得问题,目前有五种类型的doc values:NUMERIC、BINARY、SORTED、SORTED_SET、SORTED_NUMERIC,针对每种类型Lucene都有特定的压缩方法。
doc values是列式存储的正排索引,通过docID可以快速读取到该doc的特定字段的值,列式存储存储对于聚合计算有非常高的性能。
集群分片
Elasticsearch可以简单、快速利用多节点服务器形成集群,以此分摊服务器的执行压力。
此外数据可以进行分片存储,搜索时并发到不同服务器上的主分片进行搜索。
这里可以简单讲述下Elasticsearch查询原理,Elasticsearch的查询分两个阶段:分散阶段与合并阶段。
任意一个Elasticsearch节点都可以接受客户端的请求。接受到请求后,就是分散阶段,并行发送子查询给其他节点;
然后是合并阶段,则从众多分片中收集返回结果,然后对他们进行合并、排序、取长等后续操作。最终将结果返回给客户端。
机制如下图:
分页深度陷阱
基于以上查询的原理,扩展一个分页深度的问题。
现需要查页长为10、第100页的数据,实际上是会把每个 Shard 上存储的前 1000(10*100) 条数据都查到一个协调节点上。如果有 5 个 Shard,那么就有 5000 条数据,接着协调节点对这 5000 条数据进行一些合并、处理,再获取到最终第 100 页的 10 条数据。也就是实际上查的数据总量为pageSize*pageIndex*shard,页数越深则查询的越慢。因此ElasticSearch也会有要求,每次查询出来的数据总数不会返回超过10000条。
那么从业务上尽可能跟产品沟通避免分页跳转,使用滚动加载。而Elasticsearch使用的相关技术是search_after、scroll_id。