图解ElasticSearch 搜索原理

摘要

先自上而下,后自底向上的介绍ElasticSearch的底层工作原理,试图回答以下问题:

  • 为什么我的搜索 *foo-bar* 无法匹配 foo-bar ?

  • 为什么增加更多的文件会压缩索引(Index)?

  • 为什么ElasticSearch占用很多内存?


版本

elasticsearch版本: elasticsearch-2.2.0

内容

图解ElasticSearch

云上的集群

图解ElasticSearch 搜索原理

集群里的盒子

云里面的每个白色正方形的盒子代表一个节点——Node。

图解ElasticSearch 搜索原理

节点之间

在一个或者多个节点直接,多个绿色小方块组合在一起形成一个ElasticSearch的索引。

图解ElasticSearch 搜索原理

索引里的小方块

在一个索引下,分布在多个节点里的绿色小方块称为分片——Shard。

图解ElasticSearch 搜索原理

Shard=Lucene Index

一个ElasticSearch的Shard本质上是一个Lucene Index。

图解ElasticSearch 搜索原理

Lucene是一个Full Text 搜索库(也有很多其他形式的搜索库),ElasticSearch是建立在Lucene之上的。接下来的故事要说的大部分内容实际上是ElasticSearch如何基于Lucene工作的。

图解Lucene

Mini索引——segment

在Lucene里面有很多小的segment,我们可以把它们看成Lucene内部的mini-index。

图解ElasticSearch 搜索原理

Segment内部

有着许多数据结构

  • Inverted Index
  • Stored Fields
  • Document Values
  • Cache

图解ElasticSearch 搜索原理

最最重要的Inverted Index

图解ElasticSearch 搜索原理

Inverted Index主要包括两部分:

  1. 一个有序的数据字典Dictionary(包括单词Term和它出现的频率)。
  2. 与单词Term对应的Postings(即存在这个单词的文件)。

当我们搜索的时候,首先将搜索的内容分解,然后在字典里找到对应Term,从而查找到与搜索相关的文件内容。

图解ElasticSearch 搜索原理

查询“the fury”

图解ElasticSearch 搜索原理

自动补全(AutoCompletion-Prefix)

如果想要查找以字母“c”开头的字母,可以简单的通过二分查找(Binary Search)在Inverted Index表中找到例如“choice”、“coming”这样的词(Term)。

图解ElasticSearch 搜索原理

昂贵的查找

如果想要查找所有包含“our”字母的单词,那么系统会扫描整个Inverted Index,这是非常昂贵的。

图解ElasticSearch 搜索原理

在此种情况下,如果想要做优化,那么我们面对的问题是如何生成合适的Term。

问题的转化

图解ElasticSearch 搜索原理

对于以上诸如此类的问题,我们可能会有几种可行的解决方案:

  • * suffix -> xiffus *

    如果我们想以后缀作为搜索条件,可以为Term做反向处理。

  • (60.6384, 6.5017) -> u4u8gyykk

    对于GEO位置信息,可以将它转换为GEO Hash。

  • 123 -> {1-hundreds, 12-tens, 123}

    对于简单的数字,可以为它生成多重形式的Term。

解决拼写错误

一个Python库 为单词生成了一个包含错误拼写信息的树形状态机,解决拼写错误的问题。

图解ElasticSearch 搜索原理

Stored Field字段查找

当我们想要查找包含某个特定标题内容的文件时,Inverted Index就不能很好的解决这个问题,所以Lucene提供了另外一种数据结构Stored Fields来解决这个问题。本质上,Stored Fields是一个简单的键值对key-value。默认情况下,ElasticSearch会存储整个文件的JSON source。

图解ElasticSearch 搜索原理

Document Values为了排序,聚合

即使这样,我们发现以上结构仍然无法解决诸如:排序、聚合、facet,因为我们可能会要读取大量不需要的信息。

所以,另一种数据结构解决了此种问题:Document Values。这种结构本质上就是一个列式的存储,它高度优化了具有相同类型的数据的存储结构。

图解ElasticSearch 搜索原理

为了提高效率,ElasticSearch可以将索引下某一个Document Value全部读取到内存中进行操作,这大大提升访问速度,但是也同时会消耗掉大量的内存空间。

总之,这些数据结构Inverted Index、Stored Fields、Document Values及其缓存,都在segment内部。

搜索发生时

搜索时,Lucene会搜索所有的segment然后将每个segment的搜索结果返回,最后合并呈现给客户。

Lucene的一些特性使得这个过程非常重要:

  • Segments是不可变的(immutable)

    • Delete? 当删除发生时,Lucene做的只是将其标志位置为删除,但是文件还是会在它原来的地方,不会发生改变

    • Update? 所以对于更新来说,本质上它做的工作是:先删除,然后重新索引(Re-index)

  • 随处可见的压缩

    Lucene非常擅长压缩数据,基本上所有教科书上的压缩方式,都能在Lucene中找到。

  • 缓存所有的所有

    Lucene也会将所有的信息做缓存,这大大提高了它的查询效率。

缓存的故事

当ElasticSearch索引一个文件的时候,会为文件建立相应的缓存,并且会定期(每秒)刷新这些数据,然后这些文件就可以被搜索到。

图解ElasticSearch 搜索原理

随着时间的增加,我们会有很多segments,

图解ElasticSearch 搜索原理

所以ElasticSearch会将这些segment合并,在这个过程中,segment会最终被删除掉

图解ElasticSearch 搜索原理

这就是为什么增加文件可能会使索引所占空间变小,它会引起merge,从而可能会有更多的压缩。

举个栗子

有两个segment将会merge

图解ElasticSearch 搜索原理

这两个segment最终会被删除,然后合并成一个新的segment

图解ElasticSearch 搜索原理

这时这个新的segment在缓存中处于cold状态,但是大多数segment仍然保持不变,处于warm状态。

以上场景经常在Lucene Index内部发生的。

图解ElasticSearch 搜索原理

在Shard中搜索

ElasticSearch从Shard中搜索的过程与Lucene Segment中搜索的过程类似。

图解ElasticSearch 搜索原理

与在Lucene Segment中搜索不同的是,Shard可能是分布在不同Node上的,所以在搜索与返回结果时,所有的信息都会通过网络传输。

需要注意的是:

1次搜索查找2个shard = 2次分别搜索shard

图解ElasticSearch 搜索原理

对于日志文件的处理

当我们想搜索特定日期产生的日志时,通过根据时间戳对日志文件进行分块与索引,会极大提高搜索效率。

当我们想要删除旧的数据时也非常方便,只需删除老的索引即可。

图解ElasticSearch 搜索原理

在上种情况下,每个index有两个shards

如何Scale

图解ElasticSearch 搜索原理

shard不会进行更进一步的拆分,但是shard可能会被转移到不同节点上

图解ElasticSearch 搜索原理

所以,如果当集群节点压力增长到一定的程度,我们可能会考虑增加新的节点,这就会要求我们对所有数据进行重新索引,这是我们不太希望看到的,所以我们需要在规划的时候就考虑清楚,如何去平衡足够多的节点与不足节点之间的关系。

节点分配与Shard优化

  • 为更重要的数据索引节点,分配性能更好的机器
  • 确保每个shard都有副本信息replica

图解ElasticSearch 搜索原理

路由Routing

每个节点,每个都存留一份路由表,所以当请求到任何一个节点时,ElasticSearch都有能力将请求转发到期望节点的shard进一步处理。

图解ElasticSearch 搜索原理

一个真实的请求

图解ElasticSearch 搜索原理

Query

图解ElasticSearch 搜索原理

Query有一个类型filtered,以及一个multi_match的查询

Aggregation

图解ElasticSearch 搜索原理

根据作者进行聚合,得到top10的hits的top10作者的信息

请求分发

这个请求可能被分发到集群里的任意一个节点

图解ElasticSearch 搜索原理

上帝节点

图解ElasticSearch 搜索原理

这时这个节点就成为当前请求的协调者(Coordinator),它决定:

  • 根据索引信息,判断请求会被路由到哪个核心节点
  • 以及哪个副本是可用的
  • 等等

路由

图解ElasticSearch 搜索原理

在真实搜索之前

ElasticSearch 会将Query转换成Lucene Query

图解ElasticSearch 搜索原理

然后在所有的segment中执行计算

图解ElasticSearch 搜索原理

对于Filter条件本身也会有缓存

图解ElasticSearch 搜索原理

但queries不会被缓存,所以如果相同的Query重复执行,应用程序自己需要做缓存

图解ElasticSearch 搜索原理

所以,

  • filters可以在任何时候使用
  • query只有在需要score的时候才使用

返回

搜索结束之后,结果会沿着下行的路径向上逐层返回。

图解ElasticSearch 搜索原理

图解ElasticSearch 搜索原理

图解ElasticSearch 搜索原理

图解ElasticSearch 搜索原理

图解ElasticSearch 搜索原理

参考

参考来源:

SlideShare: Elasticsearch From the Bottom Up

Youtube: Elasticsearch from the bottom up

Wiki: Document-term matrix

Wiki: Search engine indexing

Skip list

Standford Edu: Faster postings list intersection via skip pointers

StackOverflow: how an search index works when querying many words?

StackOverflow: how does lucene calculate intersection of documents so fast?

Lucene and its magical indexes

misspellings 2.0c: A tool to detect misspellings

结束

·END·


作者:Richaaaard

来源:www.cnblogs.com/richaaaard/p/5226334.html

Elasticsearch原理剖析

Elasticsearch原理剖析
近期Elasticsearch的问题不少,特别是前些日子的108亿和50w数据泄露的安全问题。

Elasticsearch原理剖析

Master/Slave架构 VS P2P 环形结构
  • Master-Slave
Master 节点负责管理整个系统,监视 slave 节点的运行状态,同时为其下的每一个 slave 节点分配存储的范围,是查询和写入的入口.master 节点一般全局只有 1个,该节点的状态将严重影响整个系统的性能,当 master 节点宕机时,会引起整个系统的瘫痪.实践中,经常设置多个副本 master 节点,通过联机热备的方式提高系统的容错性.
  • P2P 环形结构
P2P结构中没有master节点
Master-Slave结构的系统设计简单,可控性好,但 master中心节点易成为瓶颈(bigtable为master节点不成为瓶颈做了很多措施);P2P环形结构的系统无中心节点,自协调性好,扩展方便,但可控性较差,且系统设计比master-slave 结构的系统要复杂.
Hbase,bigtable,es和HDFS一样采用master/slave架构。
hbase存多读少,不适合高并发查询,适合存数据;es是全文检索,适合日志分析日志统计之类。
ES概述
ES 在 Master 被选举之前是一个 P2P 的系统,但是当 Master 被选取后,它的管理本质上是 Master 和 slave的模式。
Elasticsearch 看名字就能大概了解下它是一个弹性的搜索引擎。首先弹性隐含的意思是分布式,单机系统是没法弹起来的,然后加上灵活的伸缩机制,就是这里的 Elastic 包含的意思。它的搜索存储功能主要是 Lucene 提供的,Lucene 相当于其存储引擎,它在之上封装了索引,查询,以及分布式相关的接口。
Elasticsearch是一个实时(索引数据到能被搜索大概1s左右)的分布式搜索和分析引擎,主要用于全文搜索,结构化搜索以及分析。Elasticsearch使用Lucene作为内部引擎,但是在使用它做全文搜索时,只需要使用统一开发好的API即可,而不需要了解其背后复杂的Lucene的运行原理,可以说是一个开箱即用的分布式实现,其内部定义了大量的默认值。Elasticsearch并不仅仅是Lucene这么简单,它不但包括了全文搜索功能,还可以进行以下工作:
  • 分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
  • 实时分析的分布式搜索引擎。
  • 可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。
ES提供了两套API(内部调用都指向同一个地方)分别是基于curl的rest API和Java API,通过API不仅能对数据的CURD进行处理,还能对索引及es集群进行关联
ES 的基本概念
Elasticsearch的底层搜索是以lucene来实现的。es其主要是提供了一个分布式的框架来扩展了lucene,从而实现大数据量的,分布式搜索功能。其实现思想很简单,将大数据量分而治之,哈希分成多份,然后对每一份进行“lucene处理”——用lucene索引、检索,最后将每份结果合并返回。
Elasticsearch原理剖析

Lucene中包含了四种基本数据类型,分别是:

  • Index:索引,由很多的Document组成。

  • Document:由很多的Field组成,是Index和Search的最小单位。

  • Field:由很多的Term组成,包括Field Name和Field Value。

  • Term:由很多的字节组成。一般将Text类型的Field Value分词之后的每个最小单元叫做Term。

Elasticsearch原理剖析
ES 中的几个核心概念
  • 集群(Cluster)一组拥有共同的 cluster name 的节点。

  • 节点(Node) 集群中的一个 Elasticearch 实例。

  • 索引(Index) 相当于关系数据库中的database概念,一个集群中可以包含多个索引。这个是个逻辑概念。

  • 主分片(Primary shard) 索引的子集,索引可以切分成多个分片,分布到不同的集群节点上。分片对应的是 Lucene 中的索引。

  • 副本分片(Replica shard)每个主分片可以有一个或者多个副本。

  • 类型(Type)相当于数据库中的table概念,mapping是针对 Type 的。同一个索引里可以包含多个 Type。

  • Mapping 相当于数据库中的schema,用来约束字段的类型,不过 Elasticsearch 的 mapping 可以自动根据数据创建。

  • 文档(Document) 相当于数据库中的row。

  • 字段(Field)相当于数据库中的column。

  • 分配(Allocation) 将分片分配给某个节点的过程,包括分配主分片或者副本。如果是副本,还包含从主分片复制数据的过程。

  • gateway: 代表es索引快照的存储方式,es默认是先把索引存放到内存中,当内存满了时再持久化到本地硬盘。gateway对索引快照进行存储,当这个es集群关闭再重新启动时就会从gateway中读取索引备份数据。es支持多种类型的gateway,有本地文件系统(默认),分布式文件系统,Hadoop的HDFS和amazon的s3云存储服务。

ES的索引数据结构
传统数据库为特定列增加一个索引,例如B-Tree索引来加速检索。Elasticsearch和Lucene使用倒排索引(inverted index)来达到相同目的,倒排索引中用到的数据结构是FST树
ES优点
Elasticsearch主要优势是:速度快,使用方便,分布式的,检索,功能强大。
ES官方的想做的是ELK结合起来做日志分析等工作。估计这也是它最多的应用场景。
Elasticsearch 现在的主要目标市场已经从站内搜索转移到了监控与日志数据的收集存储和分析,也就是大家常谈论的ELK。
Elasticsearch 现在主要的应用场景有三块。站内搜索,主要和 Solr 竞争,属于后起之秀。NoSQL json文档数据库,主要抢占 Mongo 的市场,它在读写性能上优于 Mongo,同时也支持地理位置查询,还方便地理位置和文本混合查询,属于歪打正着。监控,统计以及日志类时间序的数据的存储和分析以及可视化,这方面是引领者。
ES架构
Elasticsearch原理剖析
  • Gateway是ES用来存储索引的文件系统,支持多种类型。
  • Gateway的上层是一个分布式的lucene框架。
  • Lucene之上是ES的模块,包括:
    索引模块、搜索模块、映射解析模块等
  • ES模块之上是 Discovery、Scripting和第三方插件。
    Discovery是ES的节点发现模块,不同机器上的ES节点要组成集群需要进行消息通信,集群内部需要选举master节点,这些工作都是由Discovery模块完成。
    支持多种发现机制,如 Zen 、EC2、gce、Azure。
    Scripting用来支持在查询语句中插入javascript、python等脚本语言,scripting模块负责解析这些脚本,使用脚本语句性能稍低。
  • ES也支持多种第三方插件。
  • 再上层是ES的传输模块和JMX.传输模块支持多种传输协议,如 Thrift、memecached、http,默认使用http。
    JMX是java的管理框架,用来管理ES应用。
  • 最上层是ES提供给用户的接口,可以通过RESTful接口或java api和ES集群进行交互。
服务发现以及选主 ZenDiscovery
  1. 节点启动后先ping(这里的ping是 Elasticsearch 的一个RPC命令。如果 discovery.zen.ping.unicast.hosts 有设置,则ping设置中的host,否则尝试ping localhost 的几个端口, Elasticsearch 支持同一个主机启动多个节点)Ping的response会包含该节点的基本信息以及该节点认为的master节点。

  2. 选举开始,先从各节点认为的master中选,规则很简单,按照id的字典序排序,取第一个。

  3. 如果各节点都没有认为的master,则从所有节点中选择,规则同上。这里有个限制条件就是 discovery.zen.minimum_master_nodes,如果节点数达不到最小值的限制,则循环上述过程,直到节点数足够可以开始选举。

  4. 最后选举结果是肯定能选举出一个master,如果只有一个local节点那就选出的是自己。

  5. 如果当前节点是master,则开始等待节点数达到 minimum_master_nodes(最小候选节点数),然后提供服务。

  6. 如果当前节点不是master,则尝试加入master。

Elasticsearch 将以上服务发现以及选主的流程叫做 ZenDiscovery 。由于它支持任意数目的集群(1-N),所以不能像 Zookeeper/Etcd那样限制节点必须是奇数,也就无法用投票的机制来选主,而是通过一个规则,只要所有的节点都遵循同样的规则,得到的信息都是对等的,选出来的主节点肯定是一致的。但分布式系统的问题就出在信息不对等的情况,这时候很容易出现脑裂(Split-Brain)的问题,大多数解决方案就是设置一个quorum值,要求可用节点必须大于quorum(一般是超过半数节点),才能对外提供服务。而 Elasticsearch 中,这个quorum的配置就是 discovery.zen.minimum_master_nodes 。

ES是如何实现Master选举的

Elasticsearch的选举是ZenDiscovery模块负责的,通过多播或单播技术来发现同一个集群中的其他节点并与它们连接。

一个节点如何选取它自己认为的master节点?

它会对所有可以成为master的节点(node.master: true)根据nodeId字典排序,,然后选出第一个(第0位)节点,暂且认为它是master节点。

如果对某个节点的投票数达到一定的值(可以成为master节点数n/2+1)并且该节点自己也选举自己,那这个节点就是master。否则重新选举一直到满足上述条件。

多播和单播

基于以下假设:集群由cluster.name设置项相同的节点自动连接而成,同一个网段中存在多个独立的集群.Zen 发现机制是ElasticSearch中默认的用来发现新节点的功能模块,而且集群启动后默认生效。Zen发现机制默认配置是用多播来寻找其它的节点。如果各个模块工作正常,该节点就会自动添加到与节点中集群名字(cluster.name)一样的集群,同时其它的节点都能感知到新节点的加入。在比较大的集群中,多播发现机制可能会产生太多不必要的流量开销,Zen发现机制引入了第二种发现节点的方法:单播模式。关于名词多播和单播(链接:https://www.zhihu.com/question/29360024/answ):

  • 多播:当节点并非集群的一部分时(比如节点只是刚刚启动或者重启 ),它会发送一个多播的ping请求到网段中,该请求只是用来通知所有能连接到节点和集群它已经准备好加入到集群中。

  • 单播: 关闭多播,就可以安全地使用单播。当节点不是集群的一部分时(比如节点重启,启动或者由于某些错误从集群中断开),节点会发送一个ping请求到事先设置好的地址中,来通知集群它已经准备好加入到集群中了。

为了安全考虑,阿里一般用单播模式。

ElasticSearch运行时会启动两个探测进程。一个进程用于从主节点向集群中其它节点发送ping请求来检测节点是否正常可用。另一个进程的工作反过来了,其它的节点向主节点发送ping请求来验证主节点是否正常且忠于职守
ES是如何避免脑裂现象的
可以通过discovery.zen.minimum_master_nodes
这个参数的设置来避免脑裂,设置为(N/2)+1。
ES的集群只有一个节点的话可以有副本吗?
Elasticsearch 禁止同一个分片的主分片和副本分片在同一个节点上,所以如果是一个节点的集群是不能有副本的。
集群如何恢复以及容灾

分布式系统的一个要求就是要保证高可用。如果是故障导致节点挂掉,Elasticsearch 就会主动allocation。但如果节点丢失后立刻allocation,稍后节点恢复又立刻加入,会造成浪费。Elasticsearch的恢复流程大致如下:

  • 集群中的某个非master节点丢失网络连接
  • 如果该节点上的分片有副本,那么master提升该节点上的所有主分片的在其他节点上的副本为主分片。
    cluster集群状态变为 yellow ,因为副本数不够等待一个超时设置的时间,如果丢失节点回来就可以立即恢复(默认为1分钟,通过 index.unassigned.node_left.delayed_timeout 设置)。
    如果该分片已经有写入,则通过translog进行增量同步数据。
否则将副本分配给其他节点,开始同步数据。
  • 但如果该节点上的分片没有副本,则无法恢复,集群状态会变为red,表示可能要丢失该分片的数据了。
如果是主节点master挂掉怎么办呢?当从节点们发现和主节点连接不上了,那么他们会自己决定再选举出一个节点为主节点。但是这里有个脑裂的问题,假设有5台机器,3台在一个机房,2台在另一个机房,当两个机房之间的联系断了之后,每个机房的节点会自己聚会,推举出一个主节点。
这个时候就有两个主节点存在了,当机房之间的联系恢复了之后,这个时候就会出现数据冲突了。解决的办法就是设置参数: discovery.zen.minimum_master_nodes为3(超过一半的节点数),那么当两个机房的连接断了之后,就会以大于等于3的机房的master为主,另外一个机房的节点就停止服务了。
es搜索的过程描述(默认搜索方式Query Then Fetch)
Query Then Fetch:如果你搜索时,没有指定搜索方式,就是使用的这种搜索方式。这种搜索方式,大概分两个步骤,第一步,先向所有的shard发出请求,各分片只返回排序和排名相关的信息(注意,不包括文档document),然后按照各分片返回的分数进行重新排序和排名,取前size个文档。然后进行第二步,去相关的shard取document。这种方式返回的document与用户要求的size是相等的。
搜索被执行成一个两阶段过程,我们称之为 Query Then Fetch
       在初始查询阶段时,查询会广播到索引中每一个分片拷贝(主分片或者副本分片)。每个分片在本地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列。PS:在搜索的时候是会查询Filesystem Cache的,但是有部分数据还在Memory Buffer,所以搜索是近实时的。

每个分片返回各自优先队列中 所有文档的 ID 和排序值 给协调节点,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。

接下来就是 取回阶段,协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。每个分片加载并丰富文档,如果有需要的话,接着返回文档给协调节点。一旦所有的文档都被取回了,协调节点返回结果给客户端。

ES 集群分片的读写操作流程
1、路由计算(routing)和副本一致性(replica)
  • routing
Elasticsearch针对路由计算选择了一个很简单的方法,计算如下:
routing = hash(routing) % number_of_primary_shards
每个数据都有一个routing参数,默认情况下,就使用其_id值,将其_id值计算hash后,对索引的主分片数取余,就是数据实际应该存储到的分片ID
由于取余这个计算,完全依赖于分母,所以导致Elasticsearch索引有一个限制,索引的主分片数,不可以随意修改。因为一旦主分片数不一样,索引数据不可读。
  • 副本一致性(replica)
作为分布式系统,数据副本可算是一个标配。Elasticsearch数据写入流程。自然涉及副本,在有副本配置的情况下,数据从发向Elasticsearch节点,到接到Elasticsearch节点响应返回,流向如下
Elasticsearch原理剖析
1)客户端请求发送给master Node1节点,这里也可以发送给其他节点
2)Node1节点用数据的_id计算出数据应该存储在shard0上,通过cluster state信息发现shard0的主分片在Node3节点上,Node1转发请求数据给Node3,Node3完成数据的索引,索引过程在上篇博客中详细介绍了。
3)Node3并行转发数据给分配有shard0的副本分片Node1和Node2上。当收到任一节点汇报副本分片数据写入成功以后,Node3即返回给初始的接受节点Node1,宣布数据写入成功。Node1成功返回给客户端。
2、shard的allocate配置
上文介绍了分片的索引过程,通过路由计算可以确定文本所在的分片id,那么分片在集群中的分配策略是如何确定的?
一般来说,某个shard分配在哪个节点上,是由Elasticsearch自动决定的。以下几种情况会触发分配动作:
  • 新索引生成
  • 索引的删除
  • 新增副本分片
  • 节点增减引发的数据均衡
ES 中的shard(分片)
Shard 实际上是一个 Lucene 的一个实例(Lucene Index),但往往一个 Elastic Index 都是由多个 Shards (primary & replica)构成的。
特别注意,在单个 Lucene 实例里最多包含2,147,483,519 (= Integer.MAX_VALUE – 128) 个 Documents。
Lucene Index结构
一个 Lucene Index 在文件系统的表现上来看就是存储了一系列文件的一个目录。一个 Lucene Index 由许多独立的 segments 组成,而 segments 包含了文档中的词汇字典、词汇字典的倒排索引以及 Document 的字段数据(设置为Stored.YES的字段),所有的 segments 数据存储于 _.cfs的文件中。
Segment
Elasticsearch原理剖析

Segment 直接提供了搜索功能的,ES 的一个 Shard (Lucene Index)中是由大量的 Segment 文件组成的,且每一次 fresh 都会产生一个新的 Segment 文件,这样一来 Segment 文件有大有小,相当碎片化。ES 内部则会开启一个线程将小的 Segment 合并(Merge)成大的 Segment,减少碎片化,降低文件打开数,提升 I/O 性能。

Segment 文件是不可变更的。当一个 Document 更新的时候,实际上是将旧的文档标记为删除,然后索引一个新的文档。在 Merge 的过程中会将旧的 Document 删除掉。具体到文件系统来说,文档 A 是写入到 .cfs 文件里的,删除文档 A 实际上是在.del文件里标记某个 document 已被删除,那么下次查询的时候则会跳过这个文档,是为逻辑删除。当归并(Merge)的时候,老的 segment 文件将会被删除,合并成新的 segment 文件,这个时候也就是物理删除了。

Elasticsearch原理剖析
Elasticsearch原理剖析
新建index,但是还未插入数据时的目录结构:
Elasticsearch原理剖析
插入数据之后会多很多文件:
  • 存储原文_source的文件.fdt .fdm .fdx;
  • 存储倒排索引的文件.tim .tip .doc;
  • 用于聚合排序的列存文件.dvd .dvm;
  • 全文检索文件.pos .pay .nvd .nvm等。
  • 加载到内存中的文件有.fdx .tip .dvm,
  • 其中.tip占用内存最大,而.fdt . tim .dvd文-件占用磁盘最大
  • 另外segment较小时文件内容是保存在.cfs文件中,.cfe文件保存Lucene各文件在.cfs文件的位置信息,这是为了减少Lucene打开的文件句柄数。

存储文件类型比较可见:https://www.itcodemonkey.com/article/8954.html

参考链接
https://zhuanlan.zhihu.com/p/33671444
https://yq.aliyun.com/articles/581877
https://www.cnblogs.com/LBSer/p/4119841.html
https://my.oschina.net/u/2935389/blog/754674
https://blog.csdn.net/yangwenbo214/article/details/77802331
https://www.jianshu.com/p/2cac077e05cf
————————————————

作者:lhj_sjtu

原文:https://blog.csdn.net/weixin_36564655/java/article/details/82736327

·END·

学习Elasticsearch中的analyzer

analyzer执行将输入字符流分解为token的过程,它一般发生在两个场合:

在indexing的时候,也即在建立索引的时候
在searching的时候,也即在搜索时,分析需要搜索的词语

analysis?

分析是Elasticsearch在文档发送之前对文档正文执行的过程,以添加到反向索引中(inverted index)。 在将文档添加到索引之前,Elasticsearch会为每个分析的字段执行许多步骤:

Character filtering (字符过滤器): 使用字符过滤器转换字符

Breaking text into tokens (把文字转化为标记): 将文本分成一组一个或多个标记

Token filtering:使用标记过滤器转换每个标记

Token indexing:把这些标记存于index中

standard analyzer是Elasticsearch的缺省分析器:

没有 Char Filter
使用standard tokonizer
把字符串变为小写,同时有选择地删除一些stop words等。默认的情况下stop words为_none_,也即不过滤任何stop words。

总体说来一个analyzer可以分为如下的几个部分:

  • 0个或1个以上的character filter
  • 1个tokenizer
  • 0个或1个以上的token filter

Analyze API

GET /_analyze
POST /_analyze
GET /<index>/_analyze
POST /<index>/_analyze

安装IK中文分词器

elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.2/elasticsearch-analysis-ik-7.6.2.zip

查看安装的插件列表
elasticsearch-plugin list

安装完成需要 重新启动一下我们的Elasticsearch,以便这个plugin能装被加载

安装icu
elasticsearch-plugin install analysis-icu

#创建索引
PUT chinese

#指定analyzer
PUT /chinese/_mapping
{
  "properties": {
    "content": {
      "type": "text",
      "analyzer": "ik_max_word",
      "search_analyzer": "ik_smart"
    }
  }
}

#测试分词
GET /chinese/_analyze
{
  "text": "我爱北京天安门",
  "analyzer": "ik_max_word"
}

    PUT /chinese/_doc/1
    {
      "content":"我爱北京天安门"
    }
     
    PUT  /chinese/_doc/2
    {
      "content": "北京,你好"
    }
    
    GET /chinese/_search
    {
      "query": {
        "match": {
          "content": "北京"
        }
      }
    }
    
#测试搜索
GET /chinese/_search
{
  "query": {
    "match": {
      "content": "天安门"
    }
  }
}

    GET /chinese/_search
    {
      "query": {
        "match": {
          "content": "北京天安门"
        }
      }
    }

elasticsearch入门实战

1:安装:先去官网下载下载地址: https://www.elastic.co/downloads/elasticsearch 。我去,这也太慢了,200多MB要下到什么时候?赶紧找一个国内的镜像站,换到华为镜像,目前版本是7.6.2,所以访问 https://mirrors.huaweicloud.com/elasticsearch/7.6.2/ 我下载的是zip包,下载完成后直接解压。 同样访问 https://mirrors.huaweicloud.com/kibana/7.6.2/ 下载kibana

2:启动:双击elasticsearch.bat启动服务,命令行窗口会显示相关的信息,默认端口为9200。确认 elasticsearch 正确启动 $ curl -X GET 'http://localhost:9200/' -H 'Content-Type: application/json'。curl需要安装cygwin,安装时注意填入国内镜像站地址,参见 http://www.zhushiyao.com/?p=23392

设置用户名密码(暂时不用)$ curl -X GET -u "elastic:changeme" 'http://localhost:9200/' -H 'Content-Type: application/json

双击 kibana-7.6.2-windows-x86_64\bin\kibana.bat 启动kibana,默认地址是 http://localhost:5601 ,配置文件在: $KIBANA_HOME/config/kibana.yml

3:建立索引:

$curl -XPUT ‘http://localhost:9200/twitter/_doc/3?pretty’ -H ‘Content-Type: application/json’ -d ‘
{
“user”: “elastic”,
“post_date”: “2010-01-15T01:46:38”,
“message”: “Building the site, should be kewl”
}’

通过上面方法写入到Elasticsearch的文档,在默认的情况下并不马上可以进行搜索。这是因为在Elasticsearch的设计中,有一个叫做refresh的操作。它可以使更改可见以进行搜索的操作。通常会有一个refresh timer来定时完成这个操作。这个周期为1秒。

也可以在kibana的dev tool里执行,

Console UI 分成两个窗格:编辑器窗格(左)和响应窗格(右)。使用编辑器键入请求并将它们提交到 Elasticsearch。结果将显示在右侧的响应窗格中。

4:检查索引存在。

$ curl -X GET 'http://localhost:9200/twitter/_doc/3?pretty=true'

5:搜索。

$ curl -XGET 'http://localhost:9200/twitter/_search?q=user:kimchy&pretty=true' #指定索引里搜索

$curl -XGET ‘http://localhost:9200/twitter/_search?pretty=true’ -H ‘Content-Type: application/json’ -d ‘
{
“query” : {
“match” : { “user”: “kimchy” }
}
}’

以下命令使用的是 kibana dev tool

查看Elasticsearch信息

GET /

创建

PUT twitter/_doc/1
{
“user”: “GB”,
“uid”: 1,
“city”: “Beijing”,
“province”: “Beijing”,
“country”: “China”
}

修改

POST twitter/_doc/1
{
“user”: “GB”,
“uid”: 1,
“city”: “Shenzhen”,
“province”: “Guangdong”,
“country”: “China”
}

防止覆盖,如果文档已经存在的话,我们会收到一个错误的信息

PUT twitter/_create/1
{
“user”: “GB”,
“uid”: 1,
“city”: “Shenzhen”,
“province”: “Guangdong”,
“country”: “China”
}

查看文档

GET twitter/_doc/1

只查看source的内容

GET twitter/_doc/1/_source

只查看部分字段

GET twitter/_doc/1?_source=city,country

查看多个文档

GET _mget
{
“docs”: [
{
“_index”: “twitter”,
“_id”: 1
},
{
“_index”: “twitter”,
“_id”: 2
}
]
}

查看多个文档

GET twitter/_doc/_mget
{
“ids”: [“1”, “2”]
}

更新文档

POST twitter/_update/1
{
“doc”: {
“city”: “成都”,
“province”: “四川”
}
}

先查询,后修改

POST twitter/_update_by_query
{
“query”: {
“match”: {
“user”: “GB”
}
},
“script”: {
“source”: “ctx._source.city = params.city;ctx._source.province = params.province;ctx._source.country = params.country”,
“lang”: “painless”,
“params”: {
“city”: “上海”,
“province”: “上海”,
“country”: “中国”
}
}
}

处理名字是中文字段的文档

POST twitter/_update_by_query
{
“query”: {
“match”: {
“姓名”: “张彬”
}
},
“script”: {
“source”: “ctx._source[\”签到状态\”] = params[\”签到状态\”]”,
“lang”: “painless”,
“params” : {
“签到状态”:”已签到”
}
}
}

直接根据id来更新

POST twitter/_update/1
{
“script” : {
“source”: “ctx._source.city=params.city”,
“lang”: “painless”,
“params”: {
“city”: “长沙”
}
}
}

更新或插入

POST twitter/_update/3
{
“doc”: {
“user”: “GB”,
“uid”: 3,
“city”: “常德”,
“province”: “湖南”,
“country”: “China”
},
“doc_as_upsert”: true
}
GET twitter/_doc/3

检查一个文档是否存在

HEAD twitter/_doc/1

删除一个文档

DELETE twitter/_doc/1

搜索后进行删除

POST twitter/_delete_by_query
{
“query”: {
“match”: {
“city”: “上海”
}
}
}

批量处理

POST _bulk
{ “index” : { “_index” : “twitter”, “_id”: 1} }
{“user”:”双榆树-张三”,”message”:”今儿天气不错啊,出去转转去”,”uid”:2,”age”:20,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市海淀区”,”location”:{“lat”:”39.970718″,”lon”:”116.325747″}}
{ “index” : { “_index” : “twitter”, “_id”: 2 }}
{“user”:”东城区-老刘”,”message”:”出发,下一站云南!”,”uid”:3,”age”:30,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市东城区台基厂三条3号”,”location”:{“lat”:”39.904313″,”lon”:”116.412754″}}
{ “index” : { “_index” : “twitter”, “_id”: 3} }
{“user”:”东城区-李四”,”message”:”happy birthday!”,”uid”:4,”age”:30,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市东城区”,”location”:{“lat”:”39.893801″,”lon”:”116.408986″}}
{ “index” : { “_index” : “twitter”, “_id”: 4} }
{“user”:”朝阳区-老贾”,”message”:”123,gogogo”,”uid”:5,”age”:35,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市朝阳区建国门”,”location”:{“lat”:”39.718256″,”lon”:”116.367910″}}
{ “index” : { “_index” : “twitter”, “_id”: 5} }
{“user”:”朝阳区-老王”,”message”:”Happy BirthDay My Friend!”,”uid”:6,”age”:50,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市朝阳区国贸”,”location”:{“lat”:”39.918256″,”lon”:”116.467910″}}
{ “index” : { “_index” : “twitter”, “_id”: 6} }
{“user”:”虹桥-老吴”,”message”:”好友来了都今天我生日,好友来了,什么 birthday happy 就成!”,”uid”:7,”age”:90,”city”:”上海”,”province”:”上海”,”country”:”中国”,”address”:”中国上海市闵行区”,”location”:{“lat”:”31.175927″,”lon”:”121.383328″}}

查询到所有的输入的文档

POST twitter/_search

查询有多少条数据

GET twitter/_count

批量操作,index和create的区别。index总是可以成功,它可以覆盖之前的已经创建文档,但是create则不行,如果已经有以那个id为名义的文档,就不会成功

POST _bulk
{ “create” : { “_index” : “twitter”, “_id”: 1} }
{“user”:”双榆树-张三”,”message”:”今儿天气不错啊,出去转转去”,”uid”:2,”age”:20,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市海淀区”,”location”:{“lat”:”39.970718″,”lon”:”116.325747″}}
{ “index” : { “_index” : “twitter”, “_id”: 2 }}
{“user”:”东城区-老刘”,”message”:”出发,下一站云南!”,”uid”:3,”age”:30,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市东城区台基厂三条3号”,”location”:{“lat”:”39.904313″,”lon”:”116.412754″}}
{ “index” : { “_index” : “twitter”, “_id”: 3} }
{“user”:”东城区-李四”,”message”:”happy birthday!”,”uid”:4,”age”:30,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市东城区”,”location”:{“lat”:”39.893801″,”lon”:”116.408986″}}
{ “index” : { “_index” : “twitter”, “_id”: 4} }
{“user”:”朝阳区-老贾”,”message”:”123,gogogo”,”uid”:5,”age”:35,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市朝阳区建国门”,”location”:{“lat”:”39.718256″,”lon”:”116.367910″}}
{ “index” : { “_index” : “twitter”, “_id”: 5} }
{“user”:”朝阳区-老王”,”message”:”Happy BirthDay My Friend!”,”uid”:6,”age”:50,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市朝阳区国贸”,”location”:{“lat”:”39.918256″,”lon”:”116.467910″}}
{ “index” : { “_index” : “twitter”, “_id”: 6} }
{“user”:”虹桥-老吴”,”message”:”好友来了都今天我生日,好友来了,什么 birthday happy 就成!”,”uid”:7,”age”:90,”city”:”上海”,”province”:”上海”,”country”:”中国”,”address”:”中国上海市闵行区”,”location”:{“lat”:”31.175927″,”lon”:”121.383328″}}

用delete来删除一个已经创建好的文档

POST _bulk
{ “delete” : { “_index” : “twitter”, “_id”: 1 }}

用update来进行更新一个文档

POST _bulk
{ “update” : { “_index” : “twitter”, “_id”: 2 }}
{“doc”: { “city”: “长沙”}}

关闭一个index

POST twitter/_close

打开一个index

POST twitter/_open

冻结索引

POST twitter/_freeze

包含冻结索引做搜索

POST twitter/_search?ignore_throttled=false

解冻索引

POST twitter/_unfreeze

删除一个index

DELETE twitter

对于那些名字是中文字段的文档来说,在painless语言中,直接打入中文字段名字,并不能被认可

搜索所有的文档

GET /_search
GET /_all/_search

设定返回数(默认10个)

GET /_search?size=20

对多个index进行搜索

POST /index1,index2,index3/_search

对所有以index为开头的索引来进行搜索,排除index3

POST /index*,-index3/_search

搜索特定的index

GET twitter/_search

分页

GET twitter/_search?size=2&from=2

GET twitter/_search
{
“size”: 2,
“from”: 2,
“query”: {
“match_all”: {}
}
}

通过filter_path来控制输出的较少的字段

GET twitter/_search?filter_path=hits.total

通过_source来定义返回想要的字段

GET twitter/_search
{
“_source”: [“user”, “city”],
“query”: {
“match_all”: {
}
}
}

设置_source为false,这样不返回任何的_source信息

GET twitter/_search
{
“_source”: false,
“query”: {
“match”: {
“user”: “张三”
}
}
}

用script field来生成在_source里没有的field

GET twitter/_search
{
“query”: {
“match_all”: {}
},
“script_fields”: {
“years_to_100”: {
“script”: {
“lang”: “painless”,
“source”: “100-doc[‘age’].value”
}
},
“year_of_birth”:{
“script”: “2019 – doc[‘age’].value”
}
}
}

查询我们的索引里到底有多少文档

GET twitter/_count

满足条件的文档的数量

GET twitter/_count
{
“query”: {
“match”: {
“city”: “北京”
}
}
}

获得一个index的settings

GET twitter/_settings

设置

PUT twitter
{
“settings”: {
“number_of_shards”: 1,
“number_of_replicas”: 1
}
}

查询目前的index的mapping

GET twitter/_mapping

正确地创建我们的mapping,我们必须先把之前的twitter索引删除掉,并同时使用settings来创建这个index

删除索引

DELETE twitter

PUT twitter
{
“settings”: {
“number_of_shards”: 1,
“number_of_replicas”: 1
}
}

创建mapping

PUT twitter/_mapping
{
“properties”: {
“address”: {
“type”: “text”,
“fields”: {
“keyword”: {
“type”: “keyword”,
“ignore_above”: 256
}
}
},
“age”: {
“type”: “long”
},
“city”: {
“type”: “text”,
“fields”: {
“keyword”: {
“type”: “keyword”,
“ignore_above”: 256
}
}
},
“country”: {
“type”: “text”,
“fields”: {
“keyword”: {
“type”: “keyword”,
“ignore_above”: 256
}
}
},
“location”: {
“type”: “geo_point”
},
“message”: {
“type”: “text”,
“fields”: {
“keyword”: {
“type”: “keyword”,
“ignore_above”: 256
}
}
},
“province”: {
“type”: “text”,
“fields”: {
“keyword”: {
“type”: “keyword”,
“ignore_above”: 256
}
}
},
“uid”: {
“type”: “long”
},
“user”: {
“type”: “text”,
“fields”: {
“keyword”: {
“type”: “keyword”,
“ignore_above”: 256
}
}
}
}
}

查看 mapping

GET twitter/_mapping

批量导入

POST _bulk
{ “index” : { “_index” : “twitter”, “_id”: 1} }
{“user”:”双榆树-张三”,”message”:”今儿天气不错啊,出去转转去”,”uid”:2,”age”:20,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市海淀区”,”location”:{“lat”:”39.970718″,”lon”:”116.325747″}}
{ “index” : { “_index” : “twitter”, “_id”: 2 }}
{“user”:”东城区-老刘”,”message”:”出发,下一站云南!”,”uid”:3,”age”:30,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市东城区台基厂三条3号”,”location”:{“lat”:”39.904313″,”lon”:”116.412754″}}
{ “index” : { “_index” : “twitter”, “_id”: 3} }
{“user”:”东城区-李四”,”message”:”happy birthday!”,”uid”:4,”age”:30,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市东城区”,”location”:{“lat”:”39.893801″,”lon”:”116.408986″}}
{ “index” : { “_index” : “twitter”, “_id”: 4} }
{“user”:”朝阳区-老贾”,”message”:”123,gogogo”,”uid”:5,”age”:35,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市朝阳区建国门”,”location”:{“lat”:”39.718256″,”lon”:”116.367910″}}
{ “index” : { “_index” : “twitter”, “_id”: 5} }
{“user”:”朝阳区-老王”,”message”:”Happy BirthDay My Friend!”,”uid”:6,”age”:50,”city”:”北京”,”province”:”北京”,”country”:”中国”,”address”:”中国北京市朝阳区国贸”,”location”:{“lat”:”39.918256″,”lon”:”116.467910″}}
{ “index” : { “_index” : “twitter”, “_id”: 6} }
{“user”:”虹桥-老吴”,”message”:”好友来了都今天我生日,好友来了,什么 birthday happy 就成!”,”uid”:7,”age”:90,”city”:”上海”,”province”:”上海”,”country”:”中国”,”address”:”中国上海市闵行区”,”location”:{“lat”:”31.175927″,”lon”:”121.383328″}}

查询数据 match query

GET twitter/_search
{
“query”: {
“match”: {
“city”: “北京”
}
}
}

script search 比较低效

GET twitter/_search
{
“query”: {
“script”: {
“script”: {
“source”: “doc[‘city’].contains(params.name)”,
“lang”: “painless”,
“params”: {
“name”: “北京”
}
}
}
}
}

URI search

GET twitter/_search?q=city:”北京”

用filter来过滤

GET twitter/_search
{
“query”: {
“bool”: {
“filter”: {
“term”: {
“city.keyword”: “北京”
}
}
}
}
}

term 查询

GET twitter/_search
{
“query”: {
“constant_score”: {
“filter”: {
“term”: {
“city”: {
“value”: “北京”
}
}
}
}
}
}

指明一个专有的field来进行搜索

GET twitter/_search
{
“query”: {
“match”: {
“user”: {
“query”: “朝阳区-老贾”,
“operator”: “or”
}
}
}
}
GET twitter/_search
{
“query”: {
“match”: {
“user”: “朝阳区-老贾”
}
}
}

至少匹配3个字

GET twitter/_search
{
“query”: {
“match”: {
“user”: {
“query”: “朝阳区-老贾”,
“operator”: “or”,
“minimum_should_match”: 3
}
}
}
}

“and“操作

GET twitter/_search
{
“query”: {
“match”: {
“user”: {
“query”: “朝阳区-老贾”,
“operator”: “and”
}
}
}
}

multi_match搜索

GET twitter/_search
{
“query”: {
“multi_match”: {
“query”: “朝阳”,
“fields”: [
“user”,
“address^3”, //对address含有 “朝阳” 的文档的分数进行3倍的加权
“message”
],
“type”: “best_fields”
}
}
}

包含特定前缀的文档 Prefix query

GET twitter/_search
{
“query”: {
“prefix”: {
“user”: {
“value”: “朝”
}
}
}
}

精确的字词匹配 Term query

GET twitter/_search
{
“query”: {
“term”: {
“user.keyword”: {
“value”: “朝阳区-老贾”
}
}
}
}

多个terms进行查询

GET twitter/_search
{
“query”: {
“terms”: {
“user.keyword”: [
“双榆树-张三”,
“东城区-老刘”
]
}
}
}

复合查询(compound query)

GET twitter/_search
{
“query”: {
“bool”: {
“must”: [
{
“match”: {
“city”: “北京”
}
},
{
“match”: {
“age”: “30”
}
}
]
}
}
}

把一些满足条件的排出在外,使用must_not

GET twitter/_search
{
“query”: {
“bool”: {
“must_not”: [
{
“match”: {
“city”: “北京”
}
}
]
}
}
}

should。它表述“或”的意思,也就是有就更好,没有就算了

GET twitter/_search
{
“query”: {
“bool”: {
“must”: [
{
“match”: {
“age”: “30”
}
}
],
“should”: [
{
“match_phrase”: {
“message”: “Happy birthday”
}
}
]
}
}
}

位置查询

GET twitter/_search
{
“query”: {
“bool”: {
“must”: [
{
“match”: {
“address”: “北京”
}
}
]
}
},
“post_filter”: {
“geo_distance”: {
“distance”: “3km”,
“location”: {
“lat”: 39.920086,
“lon”: 116.454182
}
}
}
}

在5公里以内的所有位置信息,并按照远近大小进行排序 sort

GET twitter/_search
{
“query”: {
“bool”: {
“must”: [
{
“match”: {
“address”: “北京”
}
}
]
}
},
“post_filter”: {
“geo_distance”: {
“distance”: “5km”,
“location”: {
“lat”: 39.920086,
“lon”: 116.454182
}
}
},
“sort”: [
{
“_geo_distance”: {
“location”: “39.920086,116.454182”,
“order”: “asc”,
“unit”: “km”
}
}
]
}

范围查询

GET twitter/_search
{
“query”: {
“range”: {
“age”: {
“gte”: 30,
“lte”: 40
}
}
}
}

exists来查询一个字段是否存在

GET twitter/_search
{
“query”: {
“exists”: {
“field”: “city”
}
}
}

//准备的数据
PUT twitter/_doc/20
{
“user” : “王二”,
“message” : “今儿天气不错啊,出去转转去”,
“uid” : 20,
“age” : 40,
“province” : “北京”,
“country” : “中国”,
“address” : “中国北京市海淀区”,
“location” : {
“lat” : “39.970718”,
“lon” : “116.325747”
}
}

匹配短语

GET twitter/_search
{
“query”: {
“match”: {
“message”: “happy birthday”
}
}
}

match_phrase。它要求Happy必须是在birthday的前面

GET twitter/_search
{
“query”: {
“match_phrase”: {
“message”: “Happy birthday”
}
},
“highlight”: {
“fields”: {
“message”: {}
}
}
}

POST _bulk
{“index”:{“_index”:”twitter1″,”_id”:1}}
{“user”:”张庆”,”message”:”今儿天气不错啊,出去转转去”,”uid”:2,”age”:20,”city”:”重庆”,”province”:”重庆”,”country”:”中国”,”address”:”中国重庆地区”,”location”:{“lat”:”39.970718″,”lon”:”116.325747″}}

Multi Search API,使用单个API请求执行几次搜索

GET twitter/_msearch
{“index”:”twitter”}
{“query”:{“match_all”:{}},”from”:0,”size”:1}
{“index”:”twitter”}
{“query”:{“bool”:{“filter”:{“term”:{“city.keyword”:”北京”}}}}, “size”:1}
{“index”:”twitter1″}
{“query”:{“match_all”:{}}}

指定多个索引操作

GET twitter*/_search

elasticsearch入门知识整理

Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎。

但是,Lucene只是一个库。Lucene本身并不提供高可用性及分布式部署。想要发挥其强大的作用,你需使用Java并要将其集成到你的应用中。L

Elasticsearch也是使用Java编写并使用Lucene来建立索引并实现搜索功能,但是它的目的是通过简单连贯的RESTful API让全文搜索变得简单并隐藏Lucene的复杂性。

Elasticsearch不仅仅是Lucene和全文搜索引擎,它还提供:

  • 分布式的实时文件存储,每个字段都被索引并可被搜索
  • 实时分析的分布式搜索引擎
  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据

Elasticsearch 是一个搜索和分析引擎。Logstash 是服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到诸如 Elasticsearch 等“存储库”中。Kibana 则可以让用户在 Elasticsearch 中使用图形和图表对数据进行可视化。

一、基础概念-快速入门

节点 Node、集群 Cluster 和分片 Shards

ElasticSearch 是分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个实例。单个实例称为一个节点(node),一组节点构成一个集群(cluster)。分片是底层的工作单元,文档保存在分片内,分片又被分配到集群内的各个节点里,每个分片仅保存全部数据的一部分。

Cluster也就是集群的意思。Elasticsearch集群由一个或多个节点组成,可通过其集群名称进行标识。 在config/elasticsearch.yml里定制我们的集群的名字 。

一个集群由一个或多个node组成 。

根据node的作用,可以分为如下的几种:

master-eligible:可以作为主node。一旦成为主node,它可以管理整个cluster的设置及变化:创建,更新,删除index;添加或删除node;为node分配shard

data:数据node

ingest: 数据接入(比如 pipepline)

machine learning (Gold/Platinum License)

Shard: Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片(shard)。当你创建一个索引的时候,你可以指定你想要的分片(shard)的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上 ,

两种类型的分片:primary shard和replica shard。 主副本和副本分片之间的主要区别在于只有主分片可以接受索引请求。副本和主分片都可以提供查询请求。

Primary shard: 每个文档都存储在一个Primary shard。 索引文档时,它首先在Primary shard上编制索引,然后在此分片的所有副本上(replica)编制索引。索引可以包含一个或多个主分片。 此数字确定索引相对于索引数据大小的可伸缩性。 创建索引后,无法更改索引中的主分片数。
Replica shard: 每个主分片可以具有零个或多个副本。 副本是主分片的副本,有两个目的:

增加故障转移:如果主要故障,可以将副本分片提升为主分片

提高性能:get和search请求可以由主shard或副本shard处理。

#为每个index设置相应的shard数值
curl -XPUT http://localhost:9200/another_user?pretty -H 'Content-Type: application/json' -d '
{
    "settings" : {
        "index.number_of_shards" : 2,
        "index.number_of_replicas" : 1
    }
}'

索引 Index、类型 Type 和文档 Document

对比我们比较熟悉的 MySQL 数据库:

index → db
type → table
document → row

如果我们要访问一个文档元数据应该包括囊括 index/type/id 这三种类型

index 是单个数据库的同义词。每个 Index (即数据库)的名字必须是小写。 Elastic 会索引所有字段,经过处理后写入一个反向索引(Inverted Index)。查找数据的时候,直接查找该索引。 索引是文档的集合, 每个Index一个或许多的documents组成,并且这些document可以分布于不同的shard之中。 在Elasticsearch中的文档可以有object及nested结构。一个index是一个逻辑命名空间,它映射到一个或多个主分片,并且可以具有零个或多个副本分片。

$ curl -X GET 'http://localhost:9200/_cat/indices?v'

type: 类型是文档的逻辑容器,类似于表是行的容器。 您将具有不同结构(模式)的文档放在不同类型中。在Elasticsearch 6.0以后,一个Index只能含有一个type。这其中的原因是:相同index的不同映射type中具有相同名称的字段是相同; 在Elasticsearch索引中,不同映射type中具有相同名称的字段在Lucene中被同一个字段支持。在默认的情况下是_doc。在未来8.0的版本中,type将被彻底删除。

Document : Index 里面单条的记录, 多条 Document 构成了一个 Index , Document 使用 JSON 格式表示 。Elasticsearch是面向文档的,这意味着您索引或搜索的最小数据单元是文档。文档在Elasticsearch中有一些重要的属性:

它是独立的。文档包含字段(名称)及其值。
它可以是分层的。可以将其视为文档中的文档。字段的值可以很简单,就像位置字段的值可以是字符串一样。它还可以包含其他字段和值。例如,位置字段可能包含城市和街道地址。
结构灵活。您的文档不依赖于预定义的架构。例如,并非所有事件都需要描述值,因此可以完全省略该字段。但它可能需要新的字段,例如位置的纬度和经度。

文档通常是数据的JSON表示形式
Type,它是虚拟的逻辑分组,用来过滤 Document 。

$ curl 'localhost:9200/_mapping?pretty=true'   #列出每个 Index 所包含的 Type

二、使用 RESTful API 与 Elasticsearch 进行交互

可以使用 RESTful API 通过端口 9200 和 Elasticsearch 进行通信

curl -X <VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'

部件名作用VERB适当的 HTTP 方法 或 谓词 : GETPOSTPUTHEAD 或者 DELETEPROTOCOL http 或者 https(如果你在 Elasticsearch 前面有一个 https 代理)HOSTElasticsearch 集群中任意节点的主机名,或者用 localhost 代表本地机器上的节点。PORT运行 Elasticsearch HTTP 服务的端口号,默认是 9200PATHAPI 的终端路径(例如 _count 将返回集群中文档数量)。Path 可能包含多个组件,例如:_cluster/stats_nodes/stats/jvmQUERY_STRING任意可选的查询字符串参数 (例如 ?pretty 将格式化地输出 JSON 返回值,使其更容易阅读)BODY一个 JSON 格式的请求体 (如果请求需要的话)

Elastic 默认一次返回10条结果,可以通过size字段改变这个设置。

#新建 Index
$ curl -X PUT 'localhost:9200/weather'                  
#删除Index
$ curl -X DELETE 'localhost:9200/weather'               
#新增数据 
$ curl -X PUT 'localhost:9200/accounts/person/1' -d '
{
  "user": "张三",
  "title": "工程师",
  "desc": "数据库管理"
}' 
$ curl -X POST 'localhost:9200/accounts/person' -d '
{
  "user": "李四",
  "title": "工程师",
  "desc": "系统管理"
}'
#更新记录
$ curl -X PUT 'localhost:9200/accounts/person/1' -d '
{
    "user" : "张三",
    "title" : "工程师",
    "desc" : "数据库管理,软件开发"
}' 
#删除记录
$ curl -X DELETE 'localhost:9200/accounts/person/1'
#查询记录
$ curl -X GET 'localhost:9200//db/user/2'
#计算集群中文档的数量
curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
    "query": {
        "match_all": {}
    }
}
'
#查询记录
$ curl 'localhost:9200/accounts/person/_search'  -d '
{
  "query" : { "match" : { "desc" : "软件" }}
}'

参考:
https://blog.csdn.net/UbuntuTouch/article/details/99443042
https://zhuanlan.zhihu.com/p/54384152
http://www.ruanyifeng.com/blog/2017/08/elasticsearch.html

批量处理 bulk

批量处理命令。在一次的REST请求中,可以完成很多的操作。 在输入命令时,我们需要特别的注意:千万不要添加除了换行以外的空格,否则会导致错误 。 一个好的起点是批量处理1000到5,000个文档,总有效负载在5MB到15MB之间

通过bulk API为数据编制索引时,您不应在集群上进行任何查询/搜索。 这样做可能会导致严重的性能问题

Open/close Index

lasticsearch支持索引的在线/离线模式。 使用脱机模式时,在群集上几乎没有任何开销地维护数据。 关闭索引后,将阻止读/写操作。 当您希望索引重新联机时,只需打开它即可。 但是,关闭索引会占用大量磁盘空间。 您可以通过将cluster.indices.close.enable的默认值从true更改为false来禁用关闭索引功能,以避免发生意外。

Freeze/unfreeze index

冻结索引(freeze index)在群集上几乎没有开销(除了将其元数据保留在内存中),并且是只读的。 只读索引被阻止进行写操作。冻结索引受到限制,以限制每个节点的内存消耗。 每个节点的并发加载的冻结索引数受search_throttled线程池中的线程数限制,默认情况下为1。 默认情况下,即使已明确命名冻结索引,也不会针对冻结索引执行搜索请求。 这是为了防止由于误将冻结的索引作为目标而导致的意外减速。 如果要包含冻结索引做搜索,必须使用查询参数ignore_throttled = false来执行搜索请求

两类搜索:

  • queries 进行全文搜索
  • aggregations 对数据进行统计及分析

搜索结果中 hits数组里,可以看到所有的结果。_score的项。它表示搜索结果的相关度。这个分数值越高,表明搜索匹配的相关度越高 。

index的mapping

Elasticsearch号称是schemaless,在实际所得应用中,每一个index都有一个相应的mapping。这个mapping在我们生产第一个文档时已经生产。它是对每个输入的字段进行自动的识别从而判断它们的数据类型。我们可以这么理解schemaless:

不需要事先定义一个相应的mapping才可以生产文档。字段类型是动态进行识别的。这和传统的数据库是不一样的
如果有动态加入新的字段,mapping也可以自动进行调整并识别新加入的字段

自动识别字段有一个问题,那就是有的字段可能识别并不精确,

注意:我们不能为已经建立好的index动态修改mapping。这是因为一旦修改,那么之前建立的索引就变成不能搜索的了。一种办法是reindex从而重新建立我们的索引。如果在之前的mapping加入的字段,那么我们可以不用重新建立索引

对于一个keyword类型的项来说,这个项里面的所有字符都被当做一个字符串。它们在建立文档时,不需要进行index。keyword字段用于精确搜索,aggregation和排序(sorting)

使用match query时,默认的操作是OR

bool请求通常是must,must_not, should及filter的一个或其中的几个一起组合形成的。should只有在特殊的情况下才会影响hits。在正常的情况下它不会影响搜索文档的个数。那么在哪些情况下会影响搜索的结果呢?这种情况就是针对只有should的搜索情况,也就是如果你在bool query里,不含有must, must_not及filter的情况下,一个或更多的should必须有一个匹配才会有结果

Elasticsearch有位置查询

match查询时时不用分先后顺序的

把 所感兴趣的数据导入到Elasticsearch , 可以通过:

Beats:我们可以通过beats把数据导入到Elasticsearch中
Logstash:我们可以Logstash把数据导入。Logstash的数据来源也可以是Beats
REST API:我们可以通过Elastic所提供的丰富的API来把数据导入到Elasticsearch中。我们可以通过Java, Python, Go, Nodejs等各种Elasticsearch API来完成我们的数据导入。

Elasticsearch可以实现秒级的搜索速度,其中很重要的一个原因就当一个文档被存储的时候,同时它也对文档的数据进行了索引(indexing)

Analyzer分为三个部分:Char Filters, Tokenizer及 Token Filter。它们的作用分别如下:

Char Filter: 字符过滤器的工作是执行清除任务,例如剥离HTML标记。
Tokenizer: 下一步是将文本拆分为称为标记的术语。 这是由tokenizer完成的。 可以基于任何规则(例如空格)来完成拆分。 有关tokennizer的更多详细信息,请访问以下URL:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html。
Token filter: 一旦创建了token,它们就会被传递给token filter,这些过滤器会对token进行规范化。 Token filter可以更改token,删除术语或向token添加术语。

在默认的情况下,standard analyzer是Elasticsearch的缺省分析器:

没有 Char Filter
使用standard tokonizer
把字符串变为小写,同时有选择地删除一些stop words等。默认的情况下stop words为_none_,也即不过滤任何stop words。