当前位置: 首页 > 科技观察

Elasticsearch是如何实现亿级数据毫秒级查询和返回的?

时间:2023-03-12 20:48:31 科技观察

如果你在面试的时候遇到这样一个面试题:ES如何在数据量大(亿级)的情况下提高查询效率?这个问题说白了,就看你有没有实际用过ES,因为什么?其实ES的性能并没有你想象的那么好。很多时候数据量很大,尤其是上亿级数据的时候,你可能会惊讶的发现跑一个搜索需要5-10秒,这就是作弊。第一次搜索的时候需要5~10s,后面会快一些,可能几百毫秒。你很疑惑,每个用户第一次访问都会比较慢,是不是比较卡?所以如果你没有玩过ES,或者只是自己玩过Demo,被问到这个问题很容易搞糊涂,这说明你真的是ES玩不好?老实说,ES性能优化没有灵丹妙药。你是什??么意思?只是不要指望随便调整一个参数就能完美应对所有性能慢的场景。也许在某些场景下,你可以更改参数或调整语法,但肯定不是所有场景都可以。性能优化的杀手级特性:FilesystemCache你写入ES的数据实际上是写入了磁盘文件。查询时,操作系统会自动将磁盘文件中的数据缓存到FilesystemCache中。ES的搜索引擎严重依赖底层的FilesystemCache。如果给FilesystemCache更多的内存,尽量让内存能够容纳所有的IDXSegmentFile索引数据文件,那么搜索的时候基本都会用到内存。会很高。性能差距有多大?我们之前做了很多测试和压力测试。如果我们去盘的话,一般都是秒级的,搜索性能肯定是秒级的,1秒,5秒,10秒。但是如果你使用FilesystemCache,它使用的是纯内存,那么一般来说,性能要比磁盘高一个数量级,基本上是毫秒级的,几毫秒到几百毫秒不等。这里有一个真实的案例:某公司在ES节点有3台机器,每台机器好像有很多64G的内存,总内存是64*3=192G。每台机器给ESJVMHeap32G,所以每台机器只剩下32G给FilesystemCache,集群中FilesystemCache的总内存为32*3=96G内存。此时整个磁盘上的索引数据文件在三台机器上一共占用了1T的磁盘容量,ES数据量为1T,所以每台机器的数据量为300G。这个性能好吗?FilesystemCache的内存只有100G,内存中可以存储十分之一的数据,其余的存储在磁盘中。然后,当你进行搜索操作时,大部分操作都到磁盘上,性能肯定很差。归根结底,你需要让ES发挥好。在最好的情况下,它是您机器的内存,它至少可以容纳您总数据量的一半。根据我们自己在生产环境的实践经验,最好的情况下,ES中只存储少量数据,也就是你要用来搜索的索引。如果为FilesystemCache预留的内存是100G,那么可以将索引数据控制在100G以内。这种情况下,你的数据几乎都在内存中查找,性能很高,一般在1秒以内。比如你现在有一行数据:id,name,age....30个字段。但是如果现在搜索的话,只需要根据id、name、age这三个字段进行搜索即可。如果傻傻的把一行数据的所有字段都写到ES里,就会导致说90%的数据都没有用来查找。结果,它只是简单地占用了ES机器上FilesystemCache的空间。单条数据的数据量越大,FilesystemCache可以缓存的数据就越少。其实就是在ES中写几个字段用来检索,比如在es的id、name、age这三个字段写。然后可以将其他字段数据存储到MySQL/HBase中。我们一般推荐使用ES+HBase架构。HBase的特点是适用于海量数据的在线存储,即可以将海量数据写入HBase,但不要做复杂的查询,只需要根据id或range做简单的查询即可。从ES去根据name和age进行搜索,得到的结果可能是20个docid,然后去HBase中根据docid查询每个docid对应的完整数据,找出来,然后返回到前面结尾。写入ES的数据必须小于等于或略大于ES的FilesystemCache的内存容量。然后你可能会花20ms去ES里retrieve,然后根据ES返回的id去HBase中查询。检查20条数据可能需要30ms。可能你以前是这样玩的,1T的数据放在ES里,每次查询要5~10s,但是现在性能可能很高,每次查询50ms。数据预热如果说即使按照上面的方案,ES集群中每台机器写入的数据量仍然会超过FilesystemCache的两倍。比如你写了60G的数据到一台机器,得到的FilesystemCache只有30G,磁盘上还剩下30G的数据。其实可以做数据预热。比如以微博为例,你可以用一些平时看人多的大V的数据,提前在后台搭建一个系统。每隔一段时间,我的后端系统就会搜索热数据并将其闪存到文件系统缓存中。后面用户真正看热点数据的时候,直接从内存中搜索,很快。或者电商,可以给一些平时浏览量最大的产品,比如iPhone8,设置一个后台程序,提前有热点数据,每隔1分钟主动访问一次,刷进去文件系统缓存。对于那些你认为比较热,经常被人访问的数据,最好构建一个专门的缓存预热子系统。就是每隔一段时间提前访问热点数据,让数据进入FilesystemCache。这样,下次有人访问时,性能会好很多。冷热分离ES可以做类似MySQL的水平拆分,即为大量很少访问和不常访问的数据写一个单独的索引,然后为经常访问的热数据写一个单独的索引。技巧是将冷数据写入一个索引,然后将热数据写入另一个索引,这样可以保证热数据在预热之后,尽量保留在FilesystemOSCache中,不要让冷数据洗掉。你看,假设你有6台机器,2个索引,一个冷数据,一个热数据,每个索引有3个Shard。3台机器发布热数据Index,另外3台机器发布冷数据Index。在这种情况下,你会花费大量时间访问热点数据Index,而热点数据可能占总数据量的10%。此时数据量很小,几乎全部都保存在FilesystemCache中,可以保证热点数据的访问性能很高。但是对于冷数据来说,是在另外一个索引里,和热数据索引不在同一台机器上,大家之间没有任何联系。如果有人访问冷数据,磁盘上可能有大量数据。这时候性能就差了。10%的人访问冷数据和90%的人访问热数据都没有关系。文档模型设计对于MySQL,我们经常会有一些复杂的关系查询。ES中怎么玩,尽量不要在ES中使用复杂的关联查询,一旦使用,性能一般不会太好。最好的方式是先在Java系统中完成关联,将关联的数据直接写入ES中。搜索时,不需要使用ES的搜索语法来完成Join等关联搜索。文档模型设计非常重要。有很多操作。不要只想着在搜索的时候进行各种繁杂的操作。ES能支持的操作就这么多,不好操作的东西不要考虑用ES来做。如果有这样的操作,尽量在设计和编写Document模型时完成。另外,一些过于复杂的操作,比如join/nested/parent-childsearch,应该尽量避免,性能很差。分页性能优化ES的分页比较棘手,为什么?比如你每页有10条数据,你要查询第100页,其实每个shard上存储的前1000条数据都是在一个协调节点上找的。如果你有5个分片,那么就有5000条数据,然后协调节点对这5000条数据进行合并处理,最后得到第100页的10条数据。分布式的,你要查100页的10条数据,不可能从5个分片中每个分片查2条数据,直到协调节点合并成10条数据吧?你要从每个shard开始,每个shard查1000条数据,然后根据你的需要进行排序、过滤等,然后再次分页,得到第100页的数据。翻页的时候,翻的越深,每个分片返回的数据越多,协调节点处理的时间就越长,非常坑爹。所以在使用ES进行分页的时候,你会发现越往后翻越慢。我们之前也遇到过这个问题。使用ES进行分页,前几页需要几十毫秒。翻到10页或者几十页的时候,翻一页数据基本上需要5-10秒。有什么解决办法吗?不允许深度分页(默认的深度分页性能很差)。告诉产品经理你的系统不允许你翻那么深的页。默认情况下,你转得越深,性能就越差。类似于app中的推荐商品,一页一页不断下拉;类似于微博,下拉滚动微博,一页一页的,可以使用ScrollAPI自行上网搜索使用方法。scroll会一次性为你生成所有数据的快照,然后每次滑动翻页时,都会通过光标scroll_id移动到下一页和下一页。性能会比上面提到的分页性能更高。很多很多,基本上是毫秒。不过唯一的一点就是这个适合那种类似于微博下拉翻页的场景,不能随意跳转到任何页面。也就是说,你不能先到第10页,再到120页,再回到58页,不能随意跳页。所以现在很多产品都不允许你随意翻页。应用程序和某些网站还允许您向下滚动并一页一页地翻页。初始化时必须指定Scroll参数,告诉ES将本次搜索的上下文保存多长时间。您需要确保用户不会持续翻页数小时,否则可能会因超时而失败。除了使用ScrollAPI,您还可以使用search_after来完成。search_after的思路是利用上一页的结果来帮助检索下一页的数据。显然,这种方法是不允许你随意翻页的,只能一页一页地往回翻。初始化时,需要使用一个具有唯一值的字段作为排序字段。