ES 性能优化没有什么银弹,不要期待调一个参数,可以应对所有的性能慢的场景。
ES性能优化的杀手锏——filesystem cache
向 es 里写的数据,写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到 filesystem cache 里面。
ES的搜索引擎依赖于底层的 filesystem cache ,如果给 filesystem cache 更多的内存,尽量让内存可以容纳所有的 idx segment file 索引数据文件,那么搜索的时候是走内存的,性能会非常高。filesystem cache 性能比走磁盘要高一个数量级,基本上是毫秒级的,从几毫秒到几百毫秒不等。要让 es 性能要好,最佳的情况下,机器的内存至少可以容纳总数据量的一半。
最佳的情况下在 es 中就存少量的数据,要用来搜索的那些索引,如果内存留给 filesystem cache 的是 100G,那么将索引数据控制在 100G 以内,数据几乎全部走内存来搜索,性能非常高。仅仅写入 es 中要用来检索的少数几个字段就可以了,比如写入 es id,name,age 三个字段,然后可以把其他的字段数据存在 mysql/hbase 里,建议用 es + hbase 。
hbase 的特点是适用于海量数据的在线存储,对 hbase 可以写入海量数据,不要做复杂的搜索,做很简单的一些如根据 id 或者范围进行查询。从 ES 中根据 name 和 age 去搜索,拿到 doc id ,然后根据 doc id 到 hbase 里去查询每个 doc id 对应的完整的数据,返回给前端。
写入 ES 的数据最好小于等于,或者是略大于 ES 的 filesystem cache 的内存容量。
数据预热
ES 集群中每个机器写入的数据量还是超过了 filesystem cache 一倍,可以做数据预热。
对于经常会有人访问的数据,最好做一个专门的缓存预热子系统,对热数据每隔一段时间,提前访问一下,让数据进入 filesystem cache 里面,下次访问的时候,性能一定会好很多。
冷热分离
ES 可以做类似于 MySQL 的水平拆分,就是说将大量的访问很少、频率很低的数据,单独写一个索引,然后将访问很频繁的热数据单独写一个索引。最好是将冷数据写入一个索引中,然后热数据写入另外一个索引中,这样可以确保热数据在被预热之后,尽量都让他们留在 filesystem os cache 里,别让冷数据给冲刷掉。
Document 模型设计
对于 MySQL经常有一些复杂的关联查询。在 ES 里该怎么玩儿,ES 里面的复杂的关联查询尽量别用,性能一般不太好。
最好先在 Java 系统里就完成关联,将关联好的数据直接写入 ES 中。搜索的时候,就不需要利用 ES 的搜索语法来完成 join 之类的关联搜索了。
document 模型设计是非常重要的,不要考虑用 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 做分页的时候,会发现越翻到后面,就越慢。
有什么解决方案吗?
不允许深度分页(默认深度分页性能很差)
跟产品经理说系统不允许翻页,默认翻的越深,性能就越差。
类似于 app 里的推荐商品不断下拉出来一页一页的
类似于微博中,下拉刷微博,刷出来一页一页的,可以用 scroll api 。
scroll 会一次性生成所有数据的快照,然后每次滑动向后翻页就是通过游标 scroll_id 移动,获取下一页,性能会比上面说的分页性能要高很多,基本上都是毫秒级的。
这个适合于那种类似微博下拉翻页的,不能随意跳到任何一页的场景。
初始化时必须指定 scroll 参数,告诉 ES 要保存此次搜索的上下文多长时间。需要确保用户不会持续不断翻页翻几个小时,否则可能因为超时而失败。
除了用 scroll api ,也可以用 search_after 来做, search_after 的思想是使用前一页的结果来帮助检索下一页的数据,这种方式也不允许随意翻页。初始化时需要使用一个唯一值的字段作为 sort 字段。