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升级磁盘

ElasticSearch分析——扩容处理

之前介绍了一个分片即一个Lucene 索引 ,一个Elasticsearch索引即一系列Lucene分片的集合。 你的应用程序与索引进行交互Elasticsearch 帮助你将请求路由至相应的分片。为啥需要扩容呢当我们设置某个索引的分片数量的时候使用这样的api来设定,代表有一个主分片,0套副本分片。也就是说当这一个分片宕机,意味着检索的不可用。PUT /index{ "settings": { "number_of_shards": 1, "number_of_replicas": 0 }}当每天的数据量随着日志文件的堆积,es中的数据变得越来越庞大,一个节点再也承受不了我们的流量。 我们需要添加一个节点,水平扩展来提升我们的负载。然而只有一个主分片的话,就算是新加入节点也不会自动平移到第二个节点上,解决方案只有重建索引;这就是事后应对方案了。所以我们在事前就应该作出预分配,这样来应对未来的调整,虽然在单节点一个索引多个分片 代理了存储的消耗还有检索的消耗,但是对于扩容有着天然的优势,如果我们在建立索引的时候预分配了2个主分片,这样呢当新加入节点的时候,就会把其中一个分片自动平移到另一个节点上。在 Elasticsearch 中新添加的索引默认被指定了五个主分片。 这意味着我们最多可以将那个索引分散到五个节点上,每个节点一个分片。 它具有很高的处理能力,还未等你去思考这一切就已经做到了!不过这里就提出了一个脑裂的问题,可能会出现多个小集群。这样数据完整性就无法得到保证。所以ES规定了当一个集群能够对外提供访问的前提是,可以提供访问的节点的数目超过集群总数的半数+1;discovery.zen.minium_master_nodes=6对于集群而言,是否是分片越多越好呢,这一点肯定不是越多越好,为什么讲呢,一个分片并不是没有代价的。首先一个分片的底层即为一个 Lucene 索引,会消耗一定文件句柄、内存、以及 CPU 运转。还有就是多分片来讲对于检索也是限制,需要扫描所有的分片。如何选择容量呢一个太少10000个有太多,需要多少分片呢? 一般情况下这是一个无法回答的问题。因为实在有太多相关的因素了:你使用的硬件、文档的大小和复杂度、文档的索引分析方式、运行的查询类型、执行的聚合以及你的数据模型等等,说特么一堆废话,还得是根据实际的使用场景来进行计算基于你准备用于生产环境的硬件创建一个拥有单个节点的集群。创建一个和你准备用于生产环境相同配置和分析器的索引,只有一个主分片无副本分片。索引实际的文档(或者尽可能接近实际)。运行实际的查询和聚合(或者尽可能接近实际)这样就得到了单个分片的数据容量,用需要索引的数据总数加上一部分预期的增长,除以单个分片的容量,结果就是你需要的主分片个数,这是在所有节点机器配置以及网络环境一致的理想估算值。副本什么是副本,在分布式系统中,副本就是为了提神系统吞吐量和数据完整性上提出的一种产物,而且在es当中,这个副本策略是通过API动态设置的,然后集群内部平滑伸缩。如下图所示的集群中有3个节点一个索引具有3个主节点的和一套副本。PUT /blogs/_settings{ "number_of_replicas" : 2}这样集群对外提供检索能力的节点就伸缩到9个,在光主分片的效果上提神了3倍。在索引写入时,副本分片做着与主分片相同的工作。新文档首先被索引进主分片然后再同步到其它所有的副本分片。增加副本数并不会增加索引容量。在索引检索时,如果你的索引也如常见的那样是偏向查询使用的,那你可以通过增加副本的数目来提升查询性能,但也要为此 增加额外的硬件资源。主分片和副本分片如何交互写操作新建、索引和删除 请求都是 写 操作, 必须在主分片上面完成之后才能被复制到相关的副本分片。客户端向 Node 1 发送新建、索引或者删除请求。节点使用文档的 _id 确定文档属于分片 0 。请求会被转发到 Node 3,因为分片 0 的主分片目前被分配在 Node 3 上。Node 3 在主分片上面执行请求。如果成功了,它将请求并行转发到 Node 1 和 Node 2 的副本分片上。一旦所有的副本分片都报告成功, Node 3 将向Master节点报告成功,Master节点向客户端报告成功。在客户端收到成功响应时,文档变更已经在主分片和所有副本分片执行完成,变更是安全的。检索操作可以从主分片或者从其它任意副本分片检索文档,在处理读取请求时,Master在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。1、客户端向 Node 1 发送获取请求。2、节点使用文档的 _id 来确定文档属于分片 0 。分片 0 的副本分片存在于所有的三个节点上。 在这种情况下,它将请求转发到 Node 23、Node 2 将文档返回给 Node 1 ,然后将文档返回给客户端。

ElasticSearch分析——索引底层原理

存储实现使用的是Lucene做索引的ElasticSearch,和磁盘的交互就是通过Lucene的store模块来进行的,ElasticSearch的Store使用的是Lucene的Directory类对应。Lucene的Directory类声明了 对目标源的写入删除,读取,同步,重命名等功能。Ba

ElasticSearch分析——基础知识

索引ElasticSearch把数据存放在一个或者多个索引中,索引可以理解为关系型数据库中的数据库,可以向索引中读取数据 或者写入数据,ElasticSearch索引的底层实现还是依赖于Lucene把数据写入索引或者检索数据。对于ElasticSearch的一个索引是由一个或者多个Lucene索引组成;在Lucene中使用的是 按段搜索 的。 每一 segments本身都是一个倒排索引;一个Lucene索引在Elasticsearch称作一个分片。一个Elasticsearch索引 是分片的集合。 当Elasticsearch在索引中检索的时候,会发送查询到每一个属于索引的分片(Lucene 索引),然后进行评分之后获取到文档对应的score,根据是否过滤然后返回对应的文档。文档文档类型值全文检索类库的中的关键人物,索引的过程是为了给文档建库,映射是为了给文档建模,检索索引是读取存储的文档数据。文档定义为多个字段,每个字段的值的类型不是固定的 可以有一个或者多个值,支持嵌套文档,可以理解为一个JSON结构存储的数据。类型可以理解为关系数据库中的数据表,每个文档都是有一个对应的类型;映射在文档会被定义为多个字段。每个字段都有对应的映射关系,如字符串类型 Long,时间类型。等等 Elasticsearch会提供自动类型映射,也提供用户字节设定的_mapping查询来更改字段的映射关系节点每个Elasticsearch实例就是一个节点,单机满足可以常用的文档检索功能,不过在生产环境还是更加倾向于多节点构成的集群来处理需求,Elasticsearch对外提供的节点主要是3类节点。主节点 :负责协调集群内部其他节点的工作,数据节点 :用来存放数据对外提供检索功能,部落节点 : 该节点主要用于链接不同的集群实现检索多个集群的额数据。集群多个节点Elasticsearch实例来构成的集群,分布式的部署可以提升整个Elasticsearch的负载能力和吞吐量,成为集群可以无间断的对外提供检索能力,倘若发生了宕机,也会根据剩余的节点来进行恢复,由于分片副本的存在,所以数据的容灾可以做到保证。只要不是善于集群节点半数一下的,就可以对外提供服务;集群的节点的加入依赖于多播机制局域网对等发现;集群如果主节点发生宕机之后,剩余的节点会发送给其余的节点ping信号,当收到恢复之后,会根据返回得所属实例的pid的值从小到大排序先选第一个当做准master节点,然后再进行投票,最后确认出leader。然后播报给集群内的其余节点,对外恢复访问;分片其实这个分片就是一个Lucene索引,在Elasticsearch索引创建的时候回指定这个索引的主分片有几个,实际就是指定会创建多少个Lucene索引。更少的Lucene索引,检索会更高,但是更多的Lucene索引会有更快的索引速度和更慢的检索速度;而且这个主分片的数量在创建的时候被指定完毕后是无法更改的。除非创建一个新的索引然后所以重新索引全部的数据;每个分片也就是每个Lucene索引是由多个段组成的;每个段写入一次检索多次所以是创建了就是无法修改的。就算是文档被删除了,对应的段也不会立即释放;删除的信息保存在.del文件中,段本身不会进行修改,有点像mysql中我们吧数据删除了 但是真实的表空间却没有缩小,只要执行 Optmize Table tableName 才会回收空洞。然而这里的段合并的操作就是在新建新的索引的时候把根据.del文件中无用信息,不进行索引,只重新索引那些留存的数据;副本在分布式中间件中,一般来讲副本的存在,一方面,是提供数据的完备性。另一方面也提供了请求负载。在ES中的副本就是干这几件事情。这个副本节点可以动态更新设置,对完提供查询的能力,你可以把这些副本分片当做主分片一样。另一方面根据节点数等于分片数的设定,同时增加一杯副本。我们让主分片和其副本分片存放在不同的节点上,这样就算是主分片宕机,那么副本同样也是可以工作的;

ELasticsearch笔记

title: ELasticsearch笔记author: Moodtags:Elasticsearchcategories:Elasticsearchdate: 2018-11-12 23:15:00Elasticsearch 可以横向扩展至数百(甚至数千)的服务器节点,同时可以处理PB级数据。我