[TOC]
提问
面对着这么一个庞然大物,我们除了代码API写的六、知道一些ElasticSearch入门介绍外,有时候我这个人挺会怀疑我自己用到别人的代码或者框架,但是自己又写不出来,没办法只好啃源码。
说白了,任何程序运行要如何达到你期待的效果,这是一个漫长的路;
- ES的查询原理是怎么流转的
- ES 为什么会在线上变慢
- ES 到底要放些什么数据或者字段
- ES到底需要配置多少内存
- ES为什么分页越深越慢
- ES什么时候需要单独建索引
ES查询原理
你在调用es写数据的api的时候,这里到底是写道缓存中,还是磁盘文件中?
是否曾知道,ElasticSearch 的缓存长的什么样?
[这里是图片001]
在客户端进程与es机器进程交互主要是api通信,但es进程内部又做了些什么?
es会走具体的分片,再走内存中的文件系统缓存(filesystem cache),·filesystem cache·这是es 搜索引擎的核心,最底层的;
缓存没面中后,才会去读取磁盘文件,最终才决定是返回什么数据。
所以
es 的搜索引擎严重依赖于底层的 filesystem cache
,你如果给 filesystem cache
更多的内存,尽量让内存可以容纳所有的 idx segment file
索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。
ElasticSearch 会变慢
有时候,测试环境、线上环境的性能差是不一样的,随着时间的变化,性能差也变明显了,这究竟是什么导致的?
比如:我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能绝对是秒级别的,1秒、5秒、10秒。
但如果是走 filesystem cache
,是走纯内存的,那么一般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。
身边有个真实的案例。
某个公司 es 节点有 3 台机器,每台机器看起来内存很多,64G,总内存就是
64 * 3 = 192G
。每台机器给 es jvm heap 是32G
,那么剩下来留给filesystem cache
的就是每台机器才32G
,总共集群里给filesystem cache
的就是32 * 3 = 96G
内存。而此时,整个磁盘上索引数据文件,在 3 台机器上一共占用了
1T
的磁盘容量,es 数据量是1T
,那么每台机器的数据量是300G
。这样性能好吗?
filesystem cache
的内存才 100G,十分之一的数据可以放内存,其他的都在磁盘,然后你执行搜索操作,大部分操作都是走磁盘,性能肯定差。
归根结底,你要让 es 性能要好,最佳的情况下,就是你的机器的内存,至少可以容纳你的总数据量的一半。
我身边也有一个非常成功的生产环境实践经验:
最佳的情况下,是仅仅在 es 中就存少量的数据,就是你要用来搜索的那些索引,如果内存留给
filesystem cache
的是 100G,那么你就将索引数据控制在100G
以内,这样的话,你的数据几乎全部走内存来搜索,性能非常之高,一般可以在 1 秒以内。
ElasticSearch 合理塞字段
但是,内存这种资源,要合理的放数据,放的多了,索引文件就变多了,内存就这么耗去了。
可能在开发的时候,估计把整个数据硬塞到ElasticSearch ,但是真正的使用查询了用了几个字段;
id,name,age ....
30 个字段。但是你现在搜索,只需要根据id,name,age
三个字段来搜索。如果你傻乎乎往 es 里写入一行数据所有的字段,就会导致说
90%
的数据是不用来搜索的,结果硬是占据了 es 机器上的filesystem cache
的空间,单条数据的数据量越大,就会导致filesystem cahce
能缓存的数据就越少。其实,仅仅写入 es 中要用来检索的少数几个字段就可以了
这时候你可能会想,剩下的数据字段要查询怎么办?
我们可以这么进行操作,先查询ElasticSearch,得到一个Id,再在传到其他DB(如mysql/hbase)进行查询,
比如我曾经的公司就用es + hbase
这么一个架构,主要用于保存日志,这个日志一般通过kafka进行收集,但落地是有两个部分的,关键数据会过一下es,再统一保存到hbase。
hbase 的特点是适用于海量数据的在线存储,就是对 hbase 可以写入海量数据,
但是不要做复杂的搜索,做很简单的一些根据 id 或者范围进行查询的这么一个操作就可以了。
比如:
从 es 中根据 name 和 age 去搜索,拿到的结果可能就 20 个 doc id
,然后根据 doc id
到 hbase 里去查询每个 doc id
对应的完整的数据,给查出来,再返回给前端。
总结一下,怎么在有限的es配置的内存塞数据:
写入 es 的数据最好小于等于,或者是略微大于 es 的 filesystem cache 的内存容量。
然后你从 es 检索可能就花费 20ms,
然后再根据 es 返回的 id 去 hbase 里查询,查 20 条数据,可能也就耗费个 30ms,
1T 数据都放es,会每次查询都是 5~10秒,现在可能性能就会很高,每次查询就是 50ms。
所以,塞数据的多少与你配置的内存资源有关,有钱的话,塞什么字段都没关系!!!自嘲下
如何保证es的查询不落到磁盘
数据预热
假如说,es 集群中每个机器写入的数据量还是超过了 filesystem cache
一倍,比如说你写入一台机器 60G 数据,结果 filesystem cache
就 30G,还是有 30G 数据留在了磁盘上。
其实可以做数据预热。专门为那些高访问的数据加热到es中
举个例子,拿微博来说,你可以把一些大V,平时看的人很多的数据,你自己提前后台搞个系统,每隔一会儿,自己的后台系统去搜索一下热数据,刷到 filesystem cache
里去,后面用户实际上来看这个热数据的时候,他们就是直接从内存里搜索了,很快。
或者是电商,你可以将平时查看最多的一些商品,比如说 iphone 8,热数据提前后台搞个程序,每隔 1 分钟自己主动访问一次,刷到 filesystem cache
里去。
对于那些你觉得比较热的,经常会有人访问的数据,最好做一个专门的缓存预热子系统,就是对热数据每隔一段时间,就提前访问一下,让数据进入 filesystem cache
里面去。这样下次别人访问的时候,一定性能会好一些。
冷热分离
我们在进行项目开发,往往不可能想到比较周到,有可能半路的时候加个中间件 来做个各种优化;
我们上面只是说了个大概塞什么数据到ES,然后数据量与ES内存量的比列;我们在上片文章知道查询es其实是通过走索引的;
现在我们将大量访问的数据放到这个中间件(有点像缓存)中,es 可以做类似于 mysql 的水平拆分,就是说将大量的访问很少、频率很低的数据,单独写一个索引,然后将访问很频繁的热数据单独写一个索引。最好是将冷数据写入一个索引中,然后热数据写入另外一个索引中,这样可以确保热数据在被预热之后,尽量都让他们留在 filesystem os cache
里,别让冷数据给冲刷掉。
你看,假设你有 6 台机器,2 个索引,一个放冷数据,一个放热数据,每个索引 3 个shard。
3 台机器放热数据 index;另外 3 台机器放冷数据 index。然后这样的话,你大量的时候是在访问热数据 index,热数据可能就占总数据量的 10%,此时数据量很少,几乎全都保留在 filesystem cache
里面了,就可以确保热数据的访问性能是很高的。
但是对于冷数据而言,是在别的 index 里的,跟热数据 index 不在相同的机器上,大家互相之间都没什么联系了。如果有人访问冷数据,可能大量数据是在磁盘上的,此时性能差点,就 10% 的人去访问冷数据,90% 的人在访问热数据,也无所谓了。
减少复杂的关联查询
我们经常使用Mysql,也不可避免看到es有这功能,就经常使用一些复杂的关联查询。
es 能支持的操作就是那么多,不要考虑用 es 做一些它不好操作的事情。如果真的有那种操作,尽量在 document 模型设计的时候,写入的时候就完成。
另外对于一些太复杂的操作,比如 join/nested/parent-child 搜索都要尽量避免,性能都很差的。
分页性能优化
es 的分页是较坑的,为啥呢?假如你每页是 10 条数据,你现在要查询第 100 页,实际上是会把每个 shard 上存储的前 1000
条数据都查到一个协调节点上,如果你有个 5 个shard,那么就有 5000 条数据,接着协调节点对这 5000 条数据进行一些合并、处理,再获取到最终第 100 页的 10 条数据。
分布式的,你要查第100页的10条数据,不可能说从5个 shard,每个 shard 就查 2 条数据?
最后到协调节点合并成 10 条数据?你必须得从每个 shard 都查 1000 条数据过来,然后根据你的需求进行排序、筛选等等操作,最后再次分页,拿到里面第 100 页的数据。你翻页的时候,翻的越深,每个 shard 返回的数据就越多,而且协调节点处理的时间越长。非常坑爹。所以用 es 做分页的时候,你会发现越翻到后面,就越是慢。
我们之前也是遇到过这个问题,用 es 作分页,前几页就几十毫秒,翻到 10 页 or 几十页的时候,基本上就要 5~10秒 才能查出来一页数据了。
有什么解决方案吗?
不允许深度分页/默认深度分页性能很惨
你系统不允许翻那么深的页,跟产品经理说,默认翻的越深,性能就越差。
类似于 app 里的推荐商品不断下拉出来一页一页的
类似于微博中,下拉刷微博,刷出来一页一页的,你可以用 scroll api
,关于如何使用,自行上网搜索。
scroll 会一次性给你生成所有数据的一个快照,然后每次翻页就是通过游标移动,获取下一页下一页这样子,性能会比上面说的那种分页性能也高很多很多,基本上都是毫秒级的。
但是 唯一的一点就是,这个适合于那种类似微博下拉翻页的,不能随意跳到任何一页的场景。也就是说,你不能先进入第 10 页,然后去 120 页,然后又回到 58 页,不能随意乱跳页。所以现在很多产品,都是不允许你随意翻页的,app,也有一些网站,做的就是你只能往下拉,一页一页的翻。
另外,这个 scroll 是要保留一段时间内的数据快照的,你需要确保用户不会持续不断翻页翻几个小时。