ElasticSearch分析——性能优化

优化检索-使用缓存在文档评分那一篇中,我简短的叙述了针对过滤器缓存的一点知识,我们知道在使用filtered查询会比查询后置过滤,在性能上有较为可观的提神,会减少非必要的文档打分。字段级别的缓存Doc Values字段级别的缓存,难以绕过的ES的一座山Doc Values当你对一个字段进行排序时,Elasticsearch 需要访问每个匹配到的文档得到相关的值。倒排索引的检索性能是非常快的,但是在字段值排序时却不是理想的结构。在搜索的时候,我们能通过搜索关键词快速得到结果集。当排序的时候,我们需要倒排索引里面某个字段值的集合。换句话说,我们需要 转置 倒排索引。默认情况下每个字段的 Doc Values 都是激活的,Doc Values 是在索引时创建的,当字段索引时,Elasticsearch 为了能够快速检索,会把字段的值加入倒排索引中,同时它也会存储该字段的 Doc Values也就是双写.例如存在如下倒排索引对于以下倒排索引:Term Doc_1 Doc_2 Doc_3------------------------------------brown | X | X |dog | X | | Xdogs | | X | Xfox | X | | Xfoxes | | X |in | | X |jumped | X | | Xlazy | X | X |leap | | X |over | X | X | Xquick | X | X | Xsummer | | X |the | X | | X------------------------------------如果我们想要获得所有包含 brown 的文档的词的完整列表GET /my_index/_search{ "query" : { "match" : { "body" : "brown" } }, "aggs" : { "popular_terms": { "terms" : { "field" : "body" } } }}倒排索引是根据项来排序的,所以我们首先在词项列表中找到 brown ,然后扫描所有列,找到包含 brown 的文档。我们可以快速看到 Doc_1 和 Doc_2包含 brown 这个 token。然而对于聚合部分,我们需要找到 Doc_1 和 Doc_2 里所有唯一的词项。 用倒排索引做这件事情代价很高: 我们会迭代索引里的每个词项并收集 Doc_1 和 Doc_2 列里面 token。这很慢而且难以扩展:随着词项和文档的数量增加,执行时间也会增加。Doc values 通过转置两者间的关系来解决这个问题。倒排索引将词项映射到包含它们的文档,doc values 将文档映射到它们包含的词项:Doc Terms-----------------------------------------------------------------Doc_1 | brown, dog, fox, jumped, lazy, over, quick, theDoc_2 | brown, dogs, foxes, in, lazy, leap, over, quick, summerDoc_3 | dog, dogs, fox, jumped, over, quick, the-----------------------------------------------------------------当数据被转置之后,想要收集到 Doc_1 和 Doc_2 的唯一 token 会非常容易。获得每个文档行,获取所有的词项,然后求两个集合的并集。因此,搜索和聚合是相互紧密缠绕的。搜索使用倒排索引查找文档,聚合操作收集和聚合 doc values 里的数据。为什么说Doc Values会很快,那是因为不占用ES的堆内存。Doc Values通过序列化把数据结构持久化到磁盘,我们可以充分利用操作系统的内存,而不是 JVM 的Heap 如果你的物理机器的内存十分的庞大,那么你可以适当的预留堆外内存给Doc Values,同时禁止使用swap交换内存。分片级别的缓存分片缓存默认是禁用的,可以在索引的配置项index.cache.query.enable=true来开启,或者通过_settings接口来设置,在检索阶段使用设置query_cache=true来开启分片缓存。比较好的一点是分片缓存会自动失效和更新,当分片内容发生改变,ES会自动更新缓存内容,默认情况每个节点最多使用1%的堆内存来作为分片缓存,当然你也可以通过参数来更改indices.cache.query.size来调整百分比,也可以手动设置过期时间indices.cache.query.expire断路器哦断路器,一个陌生又熟悉的字眼,在Hystrix中提到过,用于保护服务的一种机制,在ES中针对查询的预估内存特别大,那么断路器就会拒绝这次查询。如果某个查询的内存占用高过了预估值。这个断路器的配置值为indices.breaker.fielddata.limit=60%,这就意味着JVM堆的最多60的空间用于字段数据的缓存。Elasticsearch 包括一个字段数据断熔器 ,这个设计就是为了处理上述情况。 断熔器通过内部检查(字段的类型、基数、大小等等)来估算一个查询需要的内存。它然后检查要求加载的字段数据是否会导致 fielddata 的总量超过堆的配置比例。如果估算查询的大小超出限制,就会 触发 断路器,查询会被中止并返回异常。这都发生在数据加载 之前 ,也就意味着不会引起 OutOfMemoryException优化ES-JVM优化对es使用的JVM参数进行调优,在超大堆毫无意外直接使用G1垃圾回收器优化ES-热点线程GET /_nodes/hot_threads::: {g1-sys-es-15}{bscbTjq_S4e0lxIdmUbKHg}{wIOUk45xSwebMOtx2UmMCQ}{192.168.2.1}{192.168.2.1:9300}{ml.machine_memory=67450658816, ml.max_open_jobs=20, xpack.installed=true, box_type=hot, ml.enabled=true} Hot threads at 2021-01-30T08:27:26.988, interval=500ms, busiestThreads=3, ignoreIdleThreads=true: 94.7% (473.4ms out of 500ms) cpu usage by thread 'elasticsearch[g1-sys-es-15][write][T#7]' 4/10 snapshots sharing following 2 elements java.base@10.0.2/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) java.base@10.0.2/java.lang.Thread.run(Thread.java:844) 94.7% (473.2ms out of 500ms) cpu usage by thread 'elasticsearch[g1-sys-es-15][write][T#12]' 2/10 snapshots sharing following 16 elements org.joni.ByteCodeMachine.matchAt(ByteCodeMachine.java:254) org.joni.Matcher.matchCheck(Matcher.java:304) org.joni.Matcher.searchInterruptible(Matcher.java:480) org.joni.Matcher.search(Matcher.java:318) org.elasticsearch.grok.Grok.captures(Grok.java:247) org.elasticsearch.ingest.common.GrokProcessor.execute(GrokProcessor.java:66) app//org.elasticsearch.ingest.CompoundProcessor.execute(CompoundProcessor.java:100) app//org.elasticsearch.ingest.Pipeline.execute(Pipeline.java:58) app//org.elasticsearch.ingest.PipelineExecutionService.innerExecute(PipelineExecutionService.java:155) app//org.elasticsearch.ingest.PipelineExecutionService.access$100(PipelineExecutionService.java:43) app//org.elasticsearch.ingest.PipelineExecutionService$1.doRun(PipelineExecutionService.java:78) app//org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingAbstractRunnable.doRun(ThreadContext.java:723) app//org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37) java.base@10.0.2/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135) java.base@10.0.2/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) java.base@10.0.2/java.lang.Thread.run(Thread.java:844) 3/10 snapshots sharing following 17 elements org.joni.ByteCodeMachine.opAnyCharStar(ByteCodeMachine.java:883) org.joni.ByteCodeMachine.matchAt(ByteCodeMachine.java:230) org.joni.Matcher.matchCheck(Matcher.java:304) org.joni.Matcher.searchInterruptible(Matcher.java:480) org.joni.Matcher.search(Matcher.java:318) org.elasticsearch.grok.Grok.captures(Grok.java:247) org.elasticsearch.ingest.common.GrokProcessor.execute(GrokProcessor.java:66) app//org.elasticsearch.ingest.CompoundProcessor.execute(CompoundProcessor.java:100) app//org.elasticsearch.ingest.Pipeline.execute(Pipeline.java:58) app//org.elasticsearch.ingest.PipelineExecutionService.innerExecute(PipelineExecutionService.java:155) app//org.elasticsearch.ingest.PipelineExecutionService.access$100(PipelineExecutionService.java:43) app//org.elasticsearch.ingest.PipelineExecutionService$1.doRun(PipelineExecutionService.java:78) app//org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingAbstractRunnable.doRun(ThreadContext.java:723) app//org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37) java.base@10.0.2/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135) java.base@10.0.2/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) java.base@10.0.2/java.lang.Thread.run(Thread.java:844) 4/10 snapshots sharing following 19 elements org.joni.StackMachine.pop(StackMachine.java:324) org.joni.ByteCodeMachine.opFail(ByteCodeMachine.java:1749) org.joni.ByteCodeMachine.opExact1(ByteCodeMachine.java:428) org.joni.ByteCodeMachine.matchAt(ByteCodeMachine.java:203) org.joni.Matcher.matchCheck(Matcher.java:304) org.joni.Matcher.searchInterruptible(Matcher.java:480) org.joni.Matcher.search(Matcher.java:318) org.elasticsearch.grok.Grok.captures(Grok.java:247) org.elasticsearch.ingest.common.GrokProcessor.execute(GrokProcessor.java:66) app//org.elasticsearch.ingest.CompoundProcessor.execute(CompoundProcessor.java:100) app//org.elasticsearch.ingest.Pipeline.execute(Pipeline.java:58) app//org.elasticsearch.ingest.PipelineExecutionService.innerExecute(PipelineExecutionService.java:155) app//org.elasticsearch.ingest.PipelineExecutionService.access$100(PipelineExecutionService.java:43) app//org.elasticsearch.ingest.PipelineExecutionService$1.doRun(PipelineExecutionService.java:78) app//org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingAbstractRunnable.doRun(ThreadContext.java:723) app//org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37) java.base@10.0.2/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135) java.base@10.0.2/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) java.base@10.0.2/java.lang.Thread.run(Thread.java:844) unique snapshot org.joni.StackMachine.pop(StackMachine.java:324) org.joni.ByteCodeMachine.opFail(ByteCodeMachine.java:1749) org.joni.ByteCodeMachine.opCClass(ByteCodeMachine.java:720) org.joni.ByteCodeMachine.matchAt(ByteCodeMachine.java:220) org.joni.Matcher.matchCheck(Matcher.java:304) org.joni.Matcher.searchInterruptible(Matcher.java:480) org.joni.Matcher.search(Matcher.java:318) org.elasticsearch.grok.Grok.captures(Grok.java:247) org.elasticsearch.ingest.common.GrokProcessor.execute(GrokProcessor.java:66) app//org.elasticsearch.ingest.CompoundProcessor.execute(CompoundProcessor.java:100) app//org.elasticsearch.ingest.Pipeline.execute(Pipeline.java:58) app//org.elasticsearch.ingest.PipelineExecutionService.innerExecute(PipelineExecutionService.java:155) app//org.elasticsearch.ingest.PipelineExecutionService.access$100(PipelineExecutionService.java:43) app//org.elasticsearch.ingest.PipelineExecutionService$1.doRun(PipelineExecutionService.java:78) app//org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingAbstractRunnable.doRun(ThreadContext.java:723) app//org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37) java.base@10.0.2/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135) java.base@10.0.2/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) java.base@10.0.2/java.lang.Thread.run(Thread.java:844) 可以通过热点现场来定位当前es阻塞在那个阶段,用于我们判断es的瓶颈,查询参数通过type来制定 如cpu,wait当前阻塞线程,最后通过interval来控制时间间隔。常用的读写调优a、调优思路写入调优:增加refresh和flush时间间隔以及index buffer区和translog区空间,来减少segment生成,segment merge 以及刷盘的频率;查询调优:一定程度上增加副本个数来增大查询吞吐量,定期对冷索引执行force meger操作,是其合并segment。b、搜索调优实战reindex:针对分片分配不均,我们可以通过重建索引的方式使分配均匀分配cluster.routing.use_adaptive_replica_selection = true:开启自适应副本分片开关,使节点的路由更为智能cluster.routing.allocation.same_shard.host=true :单机多节点部署,打开此开关,可以保证同一分片的主副本不落在同一台物理机上number_of_replicas = 2 :一定程度上增大副本数目可以增大查询的吞吐量垂直拓展和水平拓展简单的理解想象关系学数据库的分表分库,垂直扩展就相当于专库专用,水平拓展就是分表,大表化作小表。在ES当中呢垂直扩展就是机器加内存呗,加机器,水平拓展呢就是使用副本分片,加机器,让主分片和副本分片均匀的分布在集群内的每个节点,避免同一个索引的主分片和副本分片共处于一个节点。还有就是河里分配集群内节点的职责,如制定主节点,数据节点 已经专门用来做数据聚合查询的聚合节点。在高负载场景使用ES基于高负载场景 第一选择合适的存储,如在Linux上使用NioFSDirector,在linux上使用64位的JVM也可以使用mmap的方式,直接使用虚拟内存映射。速度大大的。控制索引刷新的频率,在前面的章节我们看到,一个文档被索引到lucene段中到可以被Search实例检索到 有个1S的延时,在实时性要求不是很高的场景,可以调整这个刷新的时间,用来减轻压力线程池调优,在高并发查询的场景,es默认的搜索线程配比完全不够,默认search线程大小为((cpu个数 * 3) / 2) + 1;可以使用下列的配置来更改#queue_size允许控制没有线程执行它们的挂起请求队列的初始大小。thread_pool.search.size: 200#size参数控制线程数,默认为核心数乘以5。thread_pool.search.min_queue_size:10#min_queue_size设置控制queue_size可以调整到的最小量。thread_pool.search.max_queue_size: 1000#max_queue_size设置控制queue_size可以调整到的最大量。thread_pool.search.auto_queue_frame_size: 2000#auto_queue_frame_size设置控制在调整队列之前进行测量的操作数。它应该足够大,以便单个操作不会过度偏向计算。thread_pool.search.target_response_time: 6s#target_response_time是时间值设置,指示线程池队列中任务的目标平均响应时间。如果任务通常超过此时间,则将调低线程池队列以拒绝任务。调整段合并的配置更换SSD 升级磁盘的能力。增加副本的数量在高检索场景使用ES 多读使用缓存 过滤器缓存 或者是分片缓存查询使用到过滤器,避免后置过滤使用路由,在索引期间把对于的数据放到某个节点,在查询时候根据路由参数,可以直接定位到对应的节点,减少fetch的分片数目。并行查询,也就是为你的每个索引的主分片在创建时预分配。使用Doc Vlaue 来进行聚合和排序对于聚合查询,控制不反悔size在高索引场景使用ES 多写批量索引判断是否启用doc value 你肯定知道开启doc value 会作双写,如果你的索引压根就不会进行聚合查询 和排序,你可以选择禁用doc vlaue。还有就是你要控制你的文档的字段你需要权衡索引所需非最合理的副本和速度上的区别,你要控制可用性和可靠性的天平针对多写入的场景 你要把事务日志的默认时间开启延长,或者把事务文件的大小调整,避免flush动作。更换SSD升级磁盘