1、lucene基本概念
目前以 Lucene 为基础建立的开源可用全文搜索引擎主要是 Solr 和 Elasticsearch。
Solr 和 Elasticsearch 都是比较成熟的全文搜索引擎,能完成的功能和性能也基本一样。
但是 ES 本身就具有分布式的特性和易安装使用的特点,而 Solr 的分布式需要借助第三方来实现,例如通过使用 ZooKeeper 来达到分布式协调管理。
不管是 Solr 还是 Elasticsearch 底层都是依赖于 Lucene,而 Lucene 能实现全文搜索主要是因为它实现了倒排索引的查询结构。
-
segment : lucene内部的数据是由一个个segment组成的,写入lucene的数据并不直接落盘,而是先写在内存中,经过了refresh间隔,lucene才将该时间段写入的全部数据refresh成一个segment,segment多了之后会进行merge成更大的segment。lucene查询时会遍历每个segment完成。由于lucene* 写入的数据是在内存中完成,所以写入效率非常高。但是也存在丢失数据的风险,所以Elasticsearch基于此现象实现了translog,只有在segment数据落盘后,Elasticsearch才会删除对应的translog。
-
doc : doc表示lucene中的一条记录
-
field :field表示记录中的字段概念,一个doc由若干个field组成。
-
term :term是lucene中索引的最小单位,某个field对应的内容如果是全文检索类型,会将内容进行分词,分词的结果就是由term组成的。如果是不分词的字段,那么该字段的内容就是一个term。
-
倒排索引(inverted index): lucene索引的通用叫法,即实现了term到doc list的映射。
-
正排数据:搜索引擎的通用叫法,即原始数据,可以理解为一个doc list。
-
docvalues :Elasticsearch中的列式存储的名称,Elasticsearch除了存储原始存储、倒排索引,还存储了一份docvalues,用作分析和排序。
其中主要有如下几个核心术语需要理解:
-
词条(Term): 索引里面最小的存储和查询单元,对于英文来说是一个单词,对于中文来说一般指分词后的一个词。
-
词典(Term Dictionary): 或字典,是词条 Term 的集合。搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
-
倒排表(Post list): 一个文档通常由多个词组成,倒排表记录的是某个词在哪些文档里出现过以及出现的位置。每条记录称为一个倒排项(Posting)。倒排表记录的不单是文档编号,还存储了词频等信息。
-
倒排文件(Inverted File): 所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件被称之为倒排文件,倒排文件是存储倒排索引的物理文件。
2、ES 核心概念
集群(Cluster)
ES 的集群搭建很简单,不需要依赖第三方协调管理组件,自身内部就实现了集群的管理功能。
ES 集群由一个或多个 Elasticsearch 节点组成,每个节点配置相同的 cluster.name 即可加入集群,默认值为 “elasticsearch”。
确保不同的环境中使用不同的集群名称,否则最终会导致节点加入错误的集群。
一个 Elasticsearch 服务启动实例就是一个节点(Node)。节点通过 node.name 来设置节点名称,如果不设置则在启动时给节点分配一个随机通用唯一标识符作为名称。
①发现机制
那么有一个问题,ES 内部是如何通过一个相同的设置 cluster.name 就能将不同的节点连接到同一个集群的?答案是 Zen Discovery。
Zen Discovery 是 Elasticsearch 的内置默认发现模块(发现模块的职责是发现集群中的节点以及选举 Master 节点)。
它提供单播和基于文件的发现,并且可以扩展为通过插件支持云环境和其他形式的发现。
②节点的角色
每个节点既可以是候选主节点也可以是数据节点,通过在配置文件 ../config/elasticsearch.yml 中设置即可,默认都为 true。
node.master: true //是否候选主节点
node.data: true //是否数据节点
数据节点负责数据的存储和相关的操作,例如对数据进行增、删、改、查和聚合等操作,所以数据节点(Data 节点)对机器配置要求比较高,对 CPU、内存和 I/O 的消耗很大。
主节点负责创建索引、删除索引、跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点、追踪集群中节点的状态等,稳定的主节点对集群的健康是非常重要的。
③脑裂现象
同时如果由于网络或其他原因导致集群中选举出多个 Master 节点,使得数据更新时出现不一致,这种现象称之为脑裂,即集群中不同的节点对于 Master 的选择出现了分歧,出现了多个 Master 竞争。
“脑裂”问题可能有以下几个原因造成:
-
网络问题: 集群间的网络延迟导致一些节点访问不到 Master,认为 Master 挂掉了从而选举出新的 Master,并对 Master 上的分片和副本标红,分配新的主分片。
-
节点负载: 主节点的角色既为 Master 又为 Data,访问量较大时可能会导致 ES 停止响应(假死状态)造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。
-
内存回收: 主节点的角色既为 Master 又为 Data,当 Data 节点上的 ES 进程占用的内存较大,引发 JVM 的大规模内存回收,造成 ES 进程失去响应。
为了避免脑裂现象的发生,做出优化措施:
-
适当调大响应时间,减少误判。 通过参数 discovery.zen.ping_timeout 设置节点状态的响应时间,默认为 3s,可以适当调大。
如果 Master 在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。调大参数(如 6s,discovery.zen.ping_timeout:6
),可适当减少误判。
-
选举触发。 我们需要在候选集群中的节点的配置文件中设置参数
discovery.zen.munimum_master_nodes
的值。
这个参数表示在选举主节点时需要参与选举的候选主节点的节点数,默认值是 1,官方建议取值(master_eligibel_nodes2)+1
,其中 master_eligibel_nodes
为候选主节点的个数。
这样做既能防止脑裂现象的发生,也能最大限度地提升集群的高可用性,因为只要不少于 discovery.zen.munimum_master_nodes
个候选节点存活,选举工作就能正常进行。
当小于这个值的时候,无法触发选举行为,集群无法使用,不会造成分片混乱的情况。
-
角色分离。 即是上面我们提到的候选主节点和数据节点进行角色分离,这样可以减轻主节点的负担,防止主节点的假死状态发生,减少对主节点“已死”的误判。
节点(Node)
a)节点是一个ElasticSearch的实例,其本质就是一个Java进程;
b)一台机器上可以运行多个ElasticSearch实例,但是建议在生产环境中一台机器上只运行一个ElasticSearch实例;
Node 是组成集群的一个单独的服务器,用于存储数据并提供集群的搜索和索引功能。与集群一样,节点也有一个唯一名字,默认在节点启动时会生成一个uuid作为节点名,
分片(Shards)
ES 支持 PB 级全文搜索,当索引上的数据量太大的时候,ES 通过水平拆分的方式将一个索引上的数据拆分出来分配到不同的数据块上,拆分出来的数据库块称之为一个分片。
这类似于 MySQL 的分库分表,只不过 MySQL 分库分表需要借助第三方组件而 ES 内部自身实现了此功能。
在一个多分片的索引中写入数据时,通过路由来确定具体写入哪一个分片中,所以在创建索引的时候需要指定分片的数量,并且分片的数量一旦确定就不能修改。
副本(Replicas)
副本就是对分片的 Copy,每个主分片都有一个或多个副本分片,当主分片异常时,副本可以提供数据的查询等操作。
主分片和对应的副本分片是不会在同一个节点上的,所以副本分片数的最大值是 N-1(其中 N 为节点数)。
对文档的新建、索引和删除请求都是写操作,必须在主分片上面完成之后才能被复制到相关的副本分片。
ES 为了提高写入的能力这个过程是并发写的,同时为了解决并发写的过程中数据冲突的问题,ES 通过乐观锁的方式控制,每个文档都有一个 _version
(版本)号,当文档被修改时版本号递增。
一旦所有的副本分片都报告写成功才会向协调节点报告成功,协调节点向客户端报告成功。
映射(Mapping)
映射是用于定义 ES 对索引中字段的存储类型、分词方式和是否存储等信息,就像数据库中的 Schema ,描述了文档可能具有的字段或属性、每个字段的数据类型。
只不过关系型数据库建表时必须指定字段类型,而 ES 对于字段类型可以不指定然后动态对字段类型猜测,也可以在创建索引时具体指定字段的类型。
对字段类型根据数据格式自动识别的映射称之为动态映射(Dynamic Mapping),我们创建索引时具体定义字段类型的映射称之为静态映射或显示映射(Explicit Mapping)。
3、安装使用
①下载和解压 Elasticsearch,无需安装解压后即可用,解压后目录如上图:
-
bin
:二进制系统指令目录,包含启动命令和安装插件命令等。 -
config
:配置文件目录。 -
data
:数据存储目录。 -
lib
:依赖包目录。 -
logs
:日志文件目录。 -
modules
:模块库,例如 x-pack 的模块。 -
plugins
:插件目录。
②安装目录下运行 bin/elasticsearch
来启动 ES。
③默认在 9200 端口运行,请求 curl http://localhost:9200/ 或者浏览器输入 http://localhost:9200,得到一个 JSON 对象,其中包含当前节点、集群、版本等信息。
4、集群健康状态
要检查群集运行状况,我们可以在 Kibana 控制台中运行以下命令 GET /_cluster/health,得到如下信息:
{ "cluster_name" : "wujiajian", "status" : "yellow", "timed_out" : false, "number_of_nodes" : 1, "number_of_data_nodes" : 1, "active_primary_shards" : 9, "active_shards" : 9, "relocating_shards" : 0, "initializing_shards" : 0, "unassigned_shards" : 5, "delayed_unassigned_shards" : 0, "number_of_pending_tasks" : 0, "number_of_in_flight_fetch" : 0, "task_max_waiting_in_queue_millis" : 0, "active_shards_percent_as_number" : 64.28571428571429 }
集群状态通过 绿,黄,红 来标识:
-
绿色:集群健康完好,一切功能齐全正常,所有分片和副本都可以正常工作。
-
黄色:预警状态,所有主分片功能正常,但至少有一个副本是不能正常工作的。此时集群是可以正常工作的,但是高可用性在某种程度上会受影响。
-
红色:集群不可正常使用。某个或某些分片及其副本异常不可用,这时集群的查询操作还能执行,但是返回的结果会不准确。对于分配到这个分片的写入请求将会报错,最终会导致数据的丢失。
5、写索引原理
一个文档,最终会落在主分片的一个分片上,到底应该在哪一个分片?这就是数据路由。
路由算法
shard = hash(routing) % number_of_primary_shards
Routing 是一个可变值,默认是文档的 _id
,也可以设置成一个自定义的值。
Routing 通过 Hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards
(主分片的数量)后得到余数。
这个在 0 到 number_of_primary_shards-1
之间的余数,就是我们所寻求的文档所在分片的位置。
这就解释了为什么我们要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。
主分片数量不可变
涉及到以往数据的查询搜索,所以一旦建立索引,主分片数不可变。
手动指定 routing number
PUT /test_index/_doc/15?routing=num { "num": 0, "tags": [] }
6、存储原理
上面介绍了在 ES 内部索引的写处理流程,这个流程是在 ES 的内存中执行的,数据被分配到特定的分片和副本上之后,最终是存储到磁盘上的,这样在断电的时候就不会丢失数据。
具体的存储路径可在配置文件 ../config/elasticsearch.yml
中进行设置,默认存储在安装目录的 Data 文件夹下。
建议不要使用默认值,因为若 ES 进行了升级,则有可能导致数据全部丢失:
path.data: /path/to/data //索引数据
path.logs: /path/to/logs //日志记录
①分段存储
索引文档以段的形式存储在磁盘上,何为段?索引文件被拆分为多个子文件,则每个子文件叫作段,每一个段本身都是一个倒排索引,并且段具有不变性,一旦索引的数据被写入硬盘,就不可再修改。
在底层采用了分段的存储模式,使它在读写时几乎完全避免了锁的出现,大大提升了读写性能。
段被写入到磁盘后会生成一个提交点,提交点是一个用来记录所有提交后段信息的文件。
一个段一旦拥有了提交点,就说明这个段只有读的权限,失去了写的权限。相反,当段在内存中时,就只有写的权限,而不具备读数据的权限,意味着不能被检索。
②延迟写策略
介绍完了存储的形式,那么索引写入到磁盘的过程是怎样的?是否是直接调 Fsync 物理性地写入磁盘?
答案是显而易见的,如果是直接写入到磁盘上,磁盘的 I/O 消耗上会严重影响性能。
那么当写数据量大的时候会造成 ES 停顿卡死,查询也无法做到快速响应。如果真是这样 ES 也就不会称之为近实时全文搜索引擎了。
为了提升写的性能,ES 并没有每新增一条数据就增加一个段到磁盘上,而是采用延迟写的策略。
每当有新增的数据时,就将其先写入到内存中,在内存和磁盘之间是文件系统缓存。
当达到默认的时间(1 秒钟)或者内存的数据达到一定量时,会触发一次刷新(Refresh),将内存中的数据生成到一个新的段上并缓存到文件缓存系统 上,稍后再被刷新到磁盘中并生成提交点。
我们也可以手动触发 Refresh,POST /_refresh
刷新所有索引,POST /nba/_refresh
刷新指定的索引。
这时可以在创建索引时在 Settings 中通过调大 refresh_interval = "30s"
的值 , 降低每个索引的刷新频率,设值时需要注意后面带上时间单位,否则默认是毫秒。当 refresh_interval=-1
时表示关闭索引的自动刷新。
③段合并
由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。
每一个段都会消耗文件句柄、内存和 CPU 运行周期。更重要的是,每个搜索请求都必须轮流检查每个段然后合并查询结果,所以段越多,搜索也就越慢。
Elasticsearch 通过在后台定期进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。
7、ES 的性能优化
存储设备
磁盘在现代服务器上通常都是瓶颈。Elasticsearch 重度使用磁盘,你的磁盘能处理的吞吐量越大,你的节点就越稳定。
这里有一些优化磁盘 I/O 的技巧:
-
使用 SSD。就像其他地方提过的, 他们比机械磁盘优秀多了。
-
使用 RAID 0。条带化 RAID 会提高磁盘 I/O,代价显然就是当一块硬盘故障时整个就故障了。不要使用镜像或者奇偶校验 RAID 因为副本已经提供了这个功能。
-
另外,使用多块硬盘,并允许 Elasticsearch 通过多个 path.data 目录配置把数据条带化分配到它们上面。
-
不要使用远程挂载的存储,比如 NFS 或者 SMB/CIFS。这个引入的延迟对性能来说完全是背道而驰的。
-
如果你用的是 EC2,当心 EBS。即便是基于 SSD 的 EBS,通常也比本地实例的存储要慢。
内部索引优化
Elasticsearch 为了能快速找到某个 Term,先将所有的 Term 排个序,然后根据二分法查找 Term,时间复杂度为 logN,就像通过字典查找一样,这就是 Term Dictionary。
现在再看起来,似乎和传统数据库通过 B-Tree 的方式类似。但是如果 Term 太多,Term Dictionary 也会很大,放内存不现实,于是有了 Term Index。
就像字典里的索引页一样,A 开头的有哪些 Term,分别在哪页,可以理解 Term Index是一棵树。
这棵树不会包含所有的 Term,它包含的是 Term 的一些前缀。通过 Term Index 可以快速地定位到 Term Dictionary 的某个 Offset,然后从这个位置再往后顺序查找。
在内存中用 FST 方式压缩 Term Index,FST 以字节的方式存储所有的 Term,这种压缩方式可以有效的缩减存储空间,使得 Term Index 足以放进内存,但这种方式也会导致查找时需要更多的 CPU 资源。
对于存储在磁盘上的倒排表同样也采用了压缩技术减少存储所占用的空间。
调整配置参数
-
给每个文档指定有序的具有压缩良好的序列模式 ID,避免随机的 UUID-4 这样的 ID,这样的 ID 压缩比很低,会明显拖慢 Lucene。
-
对于那些不需要聚合和排序的索引字段禁用 Doc values。Doc Values 是有序的基于
document=>field value
的映射列表。 -
不需要做模糊检索的字段使用 Keyword 类型代替 Text 类型,这样可以避免在建立索引前对这些文本进行分词。
-
如果你的搜索结果不需要近实时的准确度,考虑把每个索引的
index.refresh_interval
改到 30s 。如果你是在做大批量导入,导入期间你可以通过设置这个值为 -1 关掉刷新,还可以通过设置
index.number_of_replicas: 0
关闭副本。别忘记在完工的时候重新开启它。 -
避免深度分页查询建议使用 Scroll 进行分页查询。普通分页查询时,会创建一个
from+size
的空优先队列,每个分片会返回from+size
条数据,默认只包含文档 ID 和得分 Score 给协调节点。如果有 N 个分片,则协调节点再对(from+size)×n 条数据进行二次排序,然后选择需要被取回的文档。当 from 很大时,排序过程会变得很沉重,占用 CPU 资源严重。
-
减少映射字段,只提供需要检索,聚合或排序的字段。其他字段可存在其他存储设备上,例如 Hbase,在 ES 中得到结果后再去 Hbase 查询这些字段。
-
创建索引和查询时指定路由 Routing 值,这样可以精确到具体的分片查询,提升查询效率。路由的选择需要注意数据的分布均衡。
JVM 调优
-
确保堆内存最小值( Xms )与最大值( Xmx )的大小是相同的,防止程序在运行时改变堆内存大小。Elasticsearch 默认安装后设置的堆内存是 1GB。可通过
../config/jvm.option
文件进行配置,但是最好不要超过物理内存的50%和超过 32GB。 -
GC 默认采用 CMS 的方式,并发但是有 STW 的问题,可以考虑使用 G1 收集器。
-
ES 非常依赖文件系统缓存(Filesystem Cache),快速搜索。一般来说,应该至少确保物理上有一半的可用内存分配到文件系统缓存。
8、图解es分布式基础
透明隐藏特性
-
分布式机制:分布式数据存储及共享。
-
分片机制:数据存储到哪个分片,副本数据写入。
-
集群发现机制:cluster discovery。新启动es实例,自动加入集群。
-
shard负载均衡:大量数据写入及查询,es会将数据平均分配。
-
shard副本:新增副本数,分片重分配。
Elasticsearch的垂直扩容与水平扩容
垂直扩容:使用更加强大的服务器替代老服务器。但单机存储及运算能力有上线。且成本直线上升。如10t服务器1万。单个10T服务器可能20万。
水平扩容:采购更多服务器,加入集群。大数据。
增减或减少节点时的数据rebalance
新增或减少es实例时,es集群会将数据重新分配。
master节点
功能:
-
创建删除节点
-
创建删除索引
节点对等的分布式架构
-
节点对等,每个节点都能接收所有的请求
-
自动请求路由
-
响应收集
shard&replica机制
(1)每个index包含一个或多个shard
(2)每个shard都是一个最小工作单元,承载部分数据,lucene实例,完整的建立索引和处理请求的能力
(3)增减节点时,shard会自动在nodes中负载均衡
(4)primary shard和replica shard,每个document肯定只存在于某一个primary shard以及其对应的replica shard中,不可能存在于多个primary shard
(5)replica shard是primary shard的副本,负责容错,以及承担读请求负载
(6)primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改
(7)primary shard的默认数量是1,replica默认是1,默认共有2个shard,1个primary shard,1个replica shard
注意:es7以前primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard
(8)primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失,起不到容错的作用),但是可以和其他primary shard的replica shard放在同一个节点上
9、Index、Type、Document基本概念
1、Index
index
:索引是文档(Document)的容器,是一类文档的集合。
索引这个词在 ElasticSearch 会有三种意思:
1)、索引(名词)
类比传统的关系型数据库领域来说,索引相当于SQL中的一个数据库(Database)
。索引由其名称(必须为全小写字符)进行标识。
2)、索引(动词)
保存一个文档到索引(名词)的过程
。这非常类似于SQL语句中的 INSERT关键词。如果该文档已存在时那就相当于数据库的UPDATE。
3)、倒排索引
关系型数据库通过增加一个B+树索引到指定的列上,以便提升数据检索速度。索引ElasticSearch 使用了一个叫做 倒排索引
的结构来达到相同的目的。
2、Type
Type
可以理解成关系数据库中Table。
之前的版本中,索引和文档中间还有个类型的概念,每个索引下可以建立多个类型,文档存储时需要指定index和type。从6.0.0开始单个索引中只能有一个类型
3、Document
Document
Index 里面单条的记录称为Document(文档)。等同于关系型数据库表中的行。
_index
文档所属索引名称。
_type
文档所属类型名。
_id
Doc的主键。在写入的时候,可以指定该Doc的ID值,如果不指定,则系统自动生成一个唯一的UUID值。
_version
文档的版本信息。Elasticsearch通过使用version来保证对文档的变更能以正确的顺序执行,避免乱序造成的数据丢失。
_seq_no
严格递增的顺序号,每个文档一个,Shard级别严格递增,保证后写入的Doc的_seq_no
大于先写入的Doc的_seq_no。
primary_term
primary_term也和_seq_no
一样是一个整数,每当Primary Shard发生重新分配时,比如重启,Primary选举等,_primary_term会递增1
found
查询的ID正确那么ture, 如果 Id 不正确,就查不到数据,found字段就是false。
_source
文档的原始JSON数据。
10、ES集群相关命令
_cat命令
_cat
系列提供了一系列查询Elasticsearch集群状态的接口。
/_cat/allocation #查看单节点的shard分配整体情况 /_cat/shards #查看各shard的详细情况 /_cat/shards/{index} #查看指定分片的详细情况 /_cat/master #查看master节点信息 /_cat/nodes #查看所有节点信息 /_cat/indices #查看集群中所有index的详细信息 /_cat/indices/{index} #查看集群中指定index的详细信息 /_cat/segments #查看各index的segment详细信息,包括segment名, 所属shard, 内存(磁盘)占用大小, 是否刷盘 /_cat/segments/{index}#查看指定index的segment详细信息 /_cat/count #查看当前集群的doc数量 /_cat/count/{index} #查看指定索引的doc数量 /_cat/recovery #查看集群内每个shard的recovery过程.调整replica。 /_cat/recovery/{index}#查看指定索引shard的recovery过程 /_cat/health #查看集群当前状态:红、黄、绿 /_cat/pending_tasks #查看当前集群的pending task /_cat/aliases #查看集群中所有alias信息,路由配置等 /_cat/aliases/{alias} #查看指定索引的alias信息 /_cat/thread_pool #查看集群各节点内部不同类型的threadpool的统计信息, /_cat/plugins #查看集群各个节点上的plugin信息 /_cat/fielddata #查看当前集群各个节点的fielddata内存使用情况 /_cat/fielddata/{fields} #查看指定field的内存使用情况,里面传field属性对应的值 /_cat/nodeattrs #查看单节点的自定义属性 /_cat/repositories #输出集群中注册快照存储库 /_cat/templates #输出当前正在存在的模板信息
11、索引CRUD命令
1、查询索引
查询索引命令上面已经展示过了,这里再补充一些
条件过滤
_cat/indices?v&health=yellow #查询健康状态为yellow的索引
排序
_cat/indices?v&health=yellow&s=docs.count:desc #根据文档数量进行索引排序
索引详细信息
curl -X GET "localhost:9200/my_index/_stats?pretty" #索引详细信息
2、创建索引
PUT /student{ "settings": { "number_of_shards": 3, "number_of_replicas": 1 }, "mappings": { "properties": { "name": { "type":"text" }, "country": { "type":"keyword" }, "age": { "type":"integer" }, "date": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" } } } }
12、文档CRUD命令
POST和PUT的区别
1)在ES中,如果不确定文档的ID,那么就需要用POST,它可以自己生成唯一的文档ID。如果确定文档的ID,那么就可以用PUT,当然也可以用POST,它们都可以创建或修改文档(如果是修改,那么_version版本号提高1)
2)PUT、GET、DELETE是幂等的,而POST并不一定是幂等。如果你对POST也指定了文档ID,那它其实和PUT没啥区别,那它就是幂等。如果你没有指定文档ID那么就不是幂等操作了,因为同一数据,你执行多次POST,那么生成多个UUID的文档,
1、创建文档
1)PUT方式创建
PUT /student/_doc/1 { "name": "徐小小", "country": "杭州", "age": "3", "date": "2019-09-04" }
2)POST方式创建
POST不指定主键
POST /student/_doc{ "name": "徐小小", "country": "杭州", "age": "3", "date": "2019-09-04" }
文档查看
GET /student/_doc/1
13、Query查询&Filter
Query context 查询上下文
这种语句在执行时既要计算文档是否匹配,还要计算文档相对于其他文档的匹配度有多高,匹配度越高,_score
分数就越高
Filter context 过滤上下文
过滤上下文中的语句在执行时只关心文档是否和查询匹配,不会计算匹配度,也就是得分。
filter与query对比大解密
filter,仅仅只是按照搜索条件过滤出需要的数据而已,不计算任何相关度分数,对相关度没有任何影响
query,会去计算每个document相对于搜索条件的相关度,并按照相关度进行排序
一般来说,如果你是在进行搜索,需要将最匹配搜索条件的数据先返回,那么用query;如果你只是要根据一些条件筛选出一部分数据,不关注其排序,那么用filter除非是你的这些搜索条件,你希望越符合这些搜索条件的document越排在前面返回,那么这些搜索条件要放在query中;如果你不希望一些搜索条件来影响你的document排序,那么就放在filter中即可
filter与query性能
-
filter,不需要计算相关度分数,不需要按照相关度分数进行排序,同时还有内置的自动cache最常使用filter的数据
-
query,相反,要计算相关度分数,按照分数进行排序,而且无法cache结果
Query查询
1、match查询
match query
: 知道分词器的存在,会对filed进行分词操作,然后再查询match_all
: 查询所有文档multi_match
: 可以指定多个字段match_phrase
: 短语匹配查询,ElasticSearch引擎首先分析(analyze)查询字符串,从分析后的文本中构建短语查询,这意味着必须匹配短语中的所有分词,
2、term查询和terms查询
term query
: 会去倒排索引中寻找确切的term,它并不知道分词器的存在。这种查询适合keyword 、numeric、date。
term
:查询某个字段为该关键词的文档(它是相等关系而不是包含关系)
terms
:查询某个字段里含有多个关键词的文档
#1、查询地址等于'香港'的文档 (命中:ID = 2,5) GET student/_search { "query":{ "term":{ "address":"香港"} } } #如果仅检索'香'那是无法命中的,因为keyword需要完全匹配才能命中 #2、查询地址等于"香港"或"北京"的 (命中: ID =2,3,5) GET student/_search { "query":{ "terms":{ "address":["香港","北京"] } } } 3、控制查询返回的数量 #返回前两条数据 (命中: ID = 2,5) GET student/_search { "from":0, "size":2, "query":{ "match":{"interests": "演戏"} } } 4、指定返回的字段 GET student/_search { "_source":["name","age"], "query":{ "match":{"interests": "演戏"} } } 5、显示要的字段、去除不需要的字段、可以使用通配符* GET student/_search { "query":{ "match_all": {} }, "_source":{ "includes": "addr*", "excludes": ["name","bir*"] } } 6、排序 GET student/_search { "query":{ "match_all": {} }, "sort":[{ "age":{"order": "desc"} }] }
7、 范围查询
range
: 实现范围查询
include_lower
: 是否包含范围的左边界,默认是true
include_upper
: 是否包含范围的右边界,默认是true
#1、查询生日的范围 (命中 ID = 2,4,5) GET student/_search { "query": { "range": { "birthday": { "from": "1950-01-11", "to": "1990-01-11", "include_lower": true, "include_upper": false } } } }
8、wildcard查询
允许使用通配符* 和 ?来进行查询*
代表0个或多个字符?
代表任意一个字符
#1、查询姓名'徐'开头的 (命中 ID = 1) GET student/_search { "query": { "wildcard": { "name": "徐*" } } }
9、fuzzy实现模糊查询
模糊查询可以在Match和 Multi-Match查询中使用以便解决拼写的错误,模糊度是基于Levenshteindistance计算与原单词的距离。使用如下:
(命中: ID = 2,5,4) GET student/_search { "query": { "fuzzy": { "interests": { "value": "演" } } } } #疑惑 :如果我把'演'改成'演员'就查不到数据了
10、高亮搜索结果
{ "query":{ "match":{ "interests": "演戏" } }, "highlight": { "fields": { "interests": {} } } }
Filter查询
filter是不计算相关性的,同时可以cache。因此,filter速度要快于query
。
#1、获取年龄为3的 (命中 ID = 1) GET student/_search { "post_filter":{ "term":{"age": 3} } } #2、查询年纪为3或者63的 (命中 ID = 1,4) GET student/_search { "post_filter":{ "terms":{"age":[3,63]} } }
14、复合查询
bool query
(布尔查询)、boosting query
(提高查询)、constant_score
(固定分数查询)、dis_max
(最佳匹配查询)、function_score
(函数查询)
bool查询包含四种操作符,分别是must,should,must_not,filter。他们均是一种数组,数组里面是对应的判断条件。
must: 必须匹配。贡献算分 must_not:过滤子句,必须不能匹配,但不贡献算分 should: 选择性匹配,至少满足一条。贡献算分filter: 过滤子句,必须匹配,但不贡献算分
boosting query
说明
boosting需要搭配三个关键字 positive
, negative
, negative_boost
场景举例
我们通过去索引中搜索 '苹果公司' 相关的信息,然后我们在查询中的信息为 '苹果'。
1)那么我们查询的条件是:must = '苹果'。也就是文档中必须包含'苹果'
但是我们需要的结果是苹果公司相关信息,如果你的文档是 '苹果树','苹果水果',那么其实此苹果非彼苹果如果匹配到其实没有任何意义。
2)那么我们修改查询条件为: must = '苹果' AND must_not = '树 or 水果'
就是说就算文档包含了苹果,但因为包含了树或者水果那么我们也会过滤这条文档信息,因为我们要查的苹果公司相关信息,如果你是苹果树那对我来讲确实是不匹配,
constant_score
(固定分数查询)
定义
常量分值查询,目的就是返回指定的score
,一般都结合filter
使用,因为filter context忽略score。
dis_max
:
只是取分数最高的那个query的分数而已。
function_score是处理分值计算过程的终极工具。它让你能够对所有匹配了主查询的每份文档调用一个函数来调整甚至是完全替换原来的_score。
注意
要使用function_score,用户必须定义一个查询和一个或多个函数,这些函数计算查询返回的每个文档的新分数。
它拥有几种预先定义好了的函数:
weight
对每份文档适用一个简单的提升,且该提升不会被归约:当weight为2时,结果为2 * _score。
field_value_factor
使用文档中某个字段的值来改变_score,比如将受欢迎程度或者投票数量考虑在内。
random_score
使用一致性随机分值计算来对每个用户采用不同的结果排序方式,对相同用户仍然使用相同的排序方式。
衰减函数(Decay Function) - linear,exp,gauss
15、聚合查询(Metric聚合)
四个关键字: Metric(指标)
、Bucketing(桶)
、Matrix(矩阵)
、Pipeline(管道)
ES聚合分析是什么?
概念
Elasticsearch除全文检索功能外提供的针对Elasticsearch数据做统计分析的功能。它的实时性高,所有的计算结果都是即时返回。
Elasticsearch将聚合分析主要分为如下4类:
Metric(指标): 指标分析类型,如计算最大值、最小值、平均值等等 (对桶内的文档进行聚合分析的操作)Bucket(桶): 分桶类型,类似SQL中的GROUP BY语法 (满足特定条件的文档的集合)Pipeline(管道): 管道分析类型,基于上一级的聚合分析结果进行在分析Matrix(矩阵): 矩阵分析类型(聚合是一种面向数值型的聚合,用于计算一组文档字段中的统计信息)
指标(Metric)详解
Metric聚合分析分为单值分析和多值分析两类:
#1、单值分析,只输出一个分析结果 min,max,avg,sum,cardinality#2、多值分析,输出多个分析结果 stats,extended_stats,percentile,percentile_rank,top hits
Bucketing(桶)
Bucket 可以理解为一个桶,它会遍历文档中的内容,凡是符合某一要求的就放入一个桶中,分桶相当与 SQL 中的 group by。
关键字有:Terms Aggregation
、Filter Aggregation
、Histogram Aggregation
、Range Aggregation
、Date Aggregation
16、ElasticSearch的深度分页
常见深度分页方式 from+size
分页方式 scroll
search_after
三种分页方式比较
分页方式 | 性能 | 优点 | 缺点 | 场景 |
from + size | 低 | 灵活性好,实现简单 | 深度分页问题 | 数据量比较小,能容忍深度分页问题 |
scroll | 中 | 解决了深度分页问题 |
无法反应数据的实时性(快照版本) 维护成本高,需要维护一个 scroll_id |
海量数据的导出(比如笔者刚遇到的将es中20w的数据导入到excel) 需要查询海量结果集的数据 |
search_after | 高 |
性能最好 不存在深度分页问题 能够反映数据的实时变更 |
实现复杂,需要有一个全局唯一的字段 连续分页的实现会比较复杂,因为每一次查询都需要上次查询的结果 |
海量数据的分页 |
默认情况下ElasticSearch索引的refresh_interval
为1秒
,这意味着数据写1秒才就可以被搜索到。
枚举org.elasticsearch.action.support.WriteRequest.RefreshPolicy
定义了三种策略:
NONE, IMMEDIATE, WAIT_UNTIL;
可知有以下三种刷新策略:
-
RefreshPolicy#IMMEDIATE:
请求向ElasticSearch提交了数据,立即进行数据刷新,然后再结束请求。
优点:实时性高、操作延时短。
缺点:资源消耗高。 -
RefreshPolicy#WAIT_UNTIL:
请求向ElasticSearch提交了数据,等待数据完成刷新,然后再结束请求。
优点:实时性高、操作延时长。
缺点:资源消耗低。 -
RefreshPolicy#NONE:
默认策略。
请求向ElasticSearch提交了数据,不关系数据是否已经完成刷新,直接结束请求。
优点:操作延时短、资源消耗低。
缺点:实时性低。
17、ES的分词
1、Analysis 和 Analyzer
Analysis
: 文本分析是把全文本转换一系列单词(term/token)的过程,也叫分词。Analysis是通过Analyzer来实现的。
当一个文档被索引时,每个Field都可能会创建一个倒排索引(Mapping可以设置不索引该Field)。
倒排索引的过程就是将文档通过Analyzer分成一个一个的Term,每一个Term都指向包含这个Term的文档集合。
当查询query时,Elasticsearch会根据搜索类型决定是否对query进行analyze,然后和倒排索引中的term进行相关性查询,匹配相应的文档。
2 、Analyzer组成
分析器(analyzer)都由三种构件块组成的:character filters
, tokenizers
, token filters
。
1) character filter 字符过滤器
在一段文本进行分词之前,先进行预处理,比如说最常见的就是,过滤html标签(<span>hello<span> --> hello),& --> and(I&you --> I and you)
2) tokenizers 分词器
英文分词可以根据空格将单词分开,中文分词比较复杂,可以采用机器学习算法来分词。
3) Token filters Token过滤器
将切分的单词进行加工。大小写转换(例将“Quick”转为小写),去掉词(例如停用词像“a”、“and”、“the”等等),或者增加词(例如同义词像“jump”和“leap”)。
三者顺序
:Character Filters—>Tokenizer—>Token Filter
三者个数
:analyzer = CharFilters(0个或多个) + Tokenizer(恰好一个) + TokenFilters(0个或多个)
字符过滤:使用字符串过滤器转变字符串。
文本切分为分词:将文本切分为单个或多个分词。
分词过滤:使用分词过滤器转变每个分词。
1、character filter:在一段文本进行分词之前,先进行预处理,比如说最常见的就是,过滤html标签(hello –> hello),& –> and(I&you –> I and you)
2、tokenizer:分词,hello you and me –> hello, you, and, me
3、token filter:lowercase,stop word,synonymom,dogs –> dog,liked –> like,Tom –> tom,a/the/an –> 干掉,mother –> mom,small –> little
3、Elasticsearch的内置分词器
-
Standard Analyzer – 默认分词器,按词切分,小写处理
-
Simple Analyzer – 按照非字母切分(符号被过滤), 小写处理
-
Stop Analyzer – 小写处理,停用词过滤(the,a,is)
-
Whitespace Analyzer – 按照空格切分,不转小写
-
Keyword Analyzer – 不分词,直接将输入当作输出
-
Patter Analyzer – 正则表达式,默认\W+(非字符分割)
-
Language – 提供了30多种常见语言的分词器
-
Customer Analyzer 自定义分词器
中文分词
1、IK分词器
18、搜索参数小总结
1、preference
决定了哪些shard会被用来执行搜索操作
_primary, _primary_first, _local, _only_node:xyz, _prefer_node:xyz, _shards:2,3
bouncing results问题,两个document排序,field值相同;不同的shard上,可能排序不同;每次请求轮询打到不同的replica shard上;每次页面上看到的搜索结果的排序都不一样。这就是bouncing result,也就是跳跃的结果。
搜索的时候,是轮询将搜索请求发送到每一个replica shard(primary shard),但是在不同的shard上,可能document的排序不同
解决方案就是将preference设置为一个字符串,比如说user_id,让每个user每次搜索的时候,都使用同一个replica shard去执行,就不会看到bouncing results了
2、timeout
主要就是限定在一定时间内,将部分获取到的数据直接返回,避免查询耗时过长
3、routing
document文档路由,_id路由,routing=user_id,这样的话可以让同一个user对应的数据到一个shard上去
4、search_type
default:query_then_fetch
dfs_query_then_fetch,可以提升revelance sort精准度
19、索引动态配置
index.refresh_interval 执行刷新操作的频率,这使得索引的最近更改可以被搜索。默认为 1s。可以设置为 -1 以禁用刷新。
index.max_result_window 用于索引搜索的 from+size 的最大值。默认为 10000。
index.max_docvalue_fields_search 一次查询最多包含开启doc_values字段的个数,默认为100。
index.number_of_replicas 每个主分片的副本数,默认为 1,该值必须大于等于0
index.auto_expand_replicas 基于可用节点的数量自动分配副本数量,默认为 false(即禁用此功能)
index.blocks.read_only 设置为 true 使索引和索引元数据为只读,false 为允许写入和元数据更改。
index.max_rescore_window 在搜索此索引中 rescore 的 window_size 最大值
index.blocks.read_only_allow_delete 与index.blocks.read_only基本类似,唯一的区别是允许删除动作。
index.blocks.write 设置为 true 可禁用对索引的写入操作。
index.blocks.read 设置为 true 可禁用对索引的读取操作
index.blocks.metadata 设置为 true 可禁用索引元数据的读取和写入。
index.max_refresh_listeners 索引的每个分片上可用的最大刷新侦听器数。
对于已存在的索引,我们想要修改它的动态配置,可以使用_settings方法。
mapping配置
通常索引的 Mapping 结构可以在创建索引时由 ElasticSearch 帮我们自动构建,字段类型由 ElasticSearch 自动推断,但这样做有一些问题,比如字段类型推断不准确,默认所有字段都会构建倒排索引等,自定义Mapping就可以解决上述这些问题。
我们创建了索引,在创建索引的时候,我们指定了mapping属性,mapping属性中规定索引中有哪些字段,字段的类型是什么。在mapping中,我们可以定义如下内容:
-
类型为String的字段,将会被全文索引;
-
其他的字段类型包括:数字、日期和geo(地理坐标);
-
日期类型的格式;
-
动态添加字段的映射规则;
字段的可用类型如下:
-
简单的类型,比如:text(分词),keyword(不分词),date,long,double,boolean,ip。我们可以看到,类型当中没有String,字符串的类型是text,所有text类型的字段都会被全文索引。数字类型有两个,long(长整型)和double(浮点型)。
-
JSON的层级类型:Object(对象)和Nested(数组对象)。Object类型时,该字段可以存储一个JSON对象;Nested类型时,该字段可以存储一个数组对象。
-
复杂的类型:包括 geo_point、geo_shape和completion。
常用数据类型
text、keyword、number、array、range、boolean、date、geo_point、ip、nested、object。
-
text:默认会进行分词,支持模糊查询(5.x之后版本string类型已废弃,请大家使用text)。
-
keyword:不进行分词,默认开启doc_values来加速聚合排序操作,占用了大量磁盘io 如非必须可以禁用doc_values。
-
number:如果只有过滤场景 用不到range查询的话,使用keyword性能更佳,另外数字类型的doc_values比字符串更容易压缩。
-
range:对数据的范围进行索引,目前支持 number range、date range 、ip range。
-
array:es不需要显示定义数组类型,只需要在插入数据时用’[]‘表示即可。[]中的元素类型需保持一致。
-
boolean: 只接受true、false,也可以是字符串类型的“true”、“false”。
-
date:支持毫秒、根据指定的format解析对应的日期格式,内部以long类型存储。
-
geo_point:存储经纬度数据对。
-
ip:将ip数据存储在这种数据类型中,方便后期对ip字段的模糊与范围查询。
-
ested:嵌套类型,一种特殊的object类型,存储object数组,可检索内部子项。
-
object:嵌套类型,不支持数组。
断路器(Circuit Breaker)控制内存的使用量
断路器用于阻止产生OutOfMemoryError的操作,每一个断路器设置一个内存使用的上限,一旦操作达到该上限,ElasticSearch将阻止该操作继续使用内存。设置较多,一般不需要修改,保持默认值:
-
indices.breaker.total.limit: defaults to 70% of JVM heap
-
indices.breaker.request.limit: defaults to 60% of JVM heap
-
indices.breaker.request.overhead: defaults to 1
-
network.breaker.inflight_requests.limit: defaults to 100% of JVM heap
-
network.breaker.inflight_requests.overhead: defaults to 1
-
script.max_compilations_per_minute: defaults to 15
20、elasticsearch为什么比mysql快
Mysql 只有 term dictionary 这一层,是以 b-tree 排序的方式存储在磁盘上的。检索一个 term 需要若干次的 random access 的磁盘操作。
而 Lucene 在 term dictionary 的基础上添加了 term index 来加速检索,term index 以树的形式缓存在内存中。从 term index 查到对应的 term dictionary 的 block 位置之后,再去磁盘上找 term,大大减少了磁盘的 random access 次数。
额外值得一提的两点是:term index 在内存中是以 FST(finite state transducers)的形式保存的,其特点是非常节省内存。Term dictionary 在磁盘上是以分 block 的方式保存的,一个 block 内部利用公共前缀压缩,比如都是 Ab 开头的单词就可以把 Ab 省去。这样 term dictionary 可以比 b-tree 更节约磁盘空间。
ElasticSearch为了提高检索性能,无所不用的压缩数据,减少磁盘的随机读,以及及其苛刻的使用内存,使用各种快速的搜索算法等手段
追求更快的检索算法 — 倒排,二分
更小的数据传输— 压缩数据(FST,ROF,公共子前缀压缩)
更少的磁盘读取— 顺序读(DocValue顺序读)
21、Elasticsearch中数据是如何存储的
lucene文件内容
lucene包的文件是由很多segment文件组成的,segments_xxx文件记录了lucene包下面的segment文件数量。每个segment会包含如下的文件。
Name | Extension | Brief Description |
---|---|---|
Segment Info | .si | segment的元数据文件 |
Compound File | .cfs, .cfe | 一个segment包含了如下表的各个文件,为减少打开文件的数量,在segment小的时候,segment的所有文件内容都保存在cfs文件中,cfe文件保存了lucene各文件在cfs文件的位置信息 |
Fields | .fnm | 保存了fields的相关信息 |
Field Index | .fdx | 正排存储文件的元数据信息 |
Field Data | .fdt | 存储了正排存储数据,写入的原文存储在这 |
Term Dictionary | .tim | 倒排索引的元数据信息 |
Term Index | .tip | 倒排索引文件,存储了所有的倒排索引数据 |
Frequencies | .doc | 保存了每个term的doc id列表和term在doc中的词频 |
Positions | .pos | Stores position information about where a term occurs in the index 全文索引的字段,会有该文件,保存了term在doc中的位置 |
Payloads | .pay | Stores additional per-position metadata information such as character offsets and user payloads 全文索引的字段,使用了一些像payloads的高级特性会有该文件,保存了term在doc中的一些高级特性 |
Norms | .nvd, .nvm | 文件保存索引字段加权数据 |
Per-Document Values | .dvd, .dvm | lucene的docvalues文件,即数据的列式存储,用作聚合和排序 |
Term Vector Data | .tvx, .tvd, .tvf | Stores offset into the document data file 保存索引字段的矢量信息,用在对term进行高亮,计算文本相关性中使用 |
Live Documents | .liv | 记录了segment中删除的doc |
22、MySQL、HBase、ES的特点和区别
MySQL:关系型数据库,主要面向OLTP,支持事务,支持二级索引,支持sql,支持主从、Group Replication架构模型(本文全部以Innodb为例,不涉及别的存储引擎)。
HBase:基于HDFS,支持海量数据读写(尤其是写),支持上亿行、上百万列的,面向列的分布式NoSql数据库。天然分布式,主从架构,不支持事务,不支持二级索引,不支持sql。
ElasticSearch:ES是一款分布式的全文检索框架,底层基于Lucene实现,虽然ES也提供存储,检索功能,但我一直不认为ES是一款数据库,但是随着ES功能越来越强大,与数据库的界限也越来越模糊。天然分布式,p2p架构,不支持事务,采用倒排索引提供全文检索。
总结下这些数据库的适用场景:
如果你对数据的读写要求极高,并且你的数据规模不大,也不需要长期存储,选redis;
如果你的数据规模较大,对数据的读性能要求很高,数据表的结构需要经常变,有时还需要做一些聚合查询,选MongoDB;
如果你需要构造一个搜索引擎或者你想搞一个看着高大上的数据可视化平台,并且你的数据有一定的分析价值或者你的老板是土豪,选ElasticSearch;
如果你需要存储海量数据,连你自己都不知道你的数据规模将来会增长多么大,那么选HBase。
转载自:https://zhuanlan.zhihu.com/p/37964096
转自:
https://www.cnblogs.com/hanease/p/16400722.html