[ElasticStack] Elasticsearch学习小结

Elasticsearch是什么

基于lucene,隐藏复杂性,提供简单易用的restful api接口、java api接口(还有其他语言的api接口)

(1)分布式的文档存储引擎

(2)分布式的搜索引擎和分析引擎

(3)分布式,支持PB级数据

Elasticsearch的核心概念

(1)Near Realtime(NRT):近实时,两个意思,从写入数据到数据可以被搜索到有一个小延迟(大概1秒);基于es执行搜索和分析可以达到秒级

(2)Cluster:集群,包含多个节点,每个节点属于哪个集群是通过一个配置(集群名称,默认是elasticsearch)来决定的,对于中小型应用来说,刚开始一个集群就一个节点很正常

(3)Node:节点,集群中的一个节点,节点也有一个名称(默认是随机分配的),节点名称很重要(在执行运维管理操作的时候),默认节点会去加入一个名称为“elasticsearch”的集群,如果直接启动一堆节点,那么它们会自动组成一个elasticsearch集群,当然一个节点也可以组成一个elasticsearch集群

(4)Document&field:文档,es中的最小数据单元,一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示,每个index下的type中,都可以去存储多个document。一个document里面有多个field,每个field就是一个数据字段。

(5)Index:索引,包含一堆有相似结构的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称。一个index包含很多document,一个index就代表了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。

(6)Type:类型,每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。

(7)shard:单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个shard都是一个lucene index。

(8)replica:任何一个服务器随时可能故障或宕机,此时shard可能就会丢失,因此可以为每个shard创建多个replica副本。replica可以在shard故障时提供备用服务,保证数据不丢失,多个replica还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认5个),replica shard(随时修改数量,默认1个),默认每个索引10个shard,5个primary shard,5个replica shard,最小的高可用配置,是2台服务器。

Elasticsearch核心概念 vs. 数据库核心概念 

Elasticsearch                                  数据库

-----------------------------------------

Document                                       行

Type                                                 表

Index                                               库

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的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard

(8)primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失,起不到容错的作用),但是可以和其他primary shard的replica shard放在同一个节点上

单node环境下创建index

PUT /test_index

{

   "settings" : {

      "number_of_shards" : 3,

      "number_of_replicas" : 1

   }

}

(1)单node环境下,创建一个index,有3个primary shard,3个replica shard

(2)集群status是yellow

(3)这个时候,只会将3个primary shard分配到仅有的一个node上去,另外3个replica shard是无法分配的

(4)集群可以正常工作,但是一旦出现节点宕机,数据全部丢失,而且集群不可用,无法承接任何请求

深度图解剖析Elasticsearch并发冲突问题

悲观锁与乐观锁两种并发控制方案

悲观锁:常见关系型数据库,比如mysql,读取时加锁。

乐观锁:不加锁。写的时候判断当前version和ES的version是否一致。

document路由原理

当客户端创建document的时候,ES就需要决定这个doc是放在这个index的哪个shard上,这个过程称为document routing,即数据路由。

primary shard一旦index建立,是不允许修改的,但是replica shard可以随时修改。

路由算法:shard = hash(routing) % number_of_primary_shards

从hash函数中,产出的hash值一定是相同的

无论hash值是几,无论是什么数字,对number_of_primary_shards求余数,结果一定是在0~number_of_primary_shards-1之间这个范围内的。0,1,2。

ES写入一致性原则

consistency,one(primary shard),all(all shard),quorum(default)

one:要求我们这个写操作,只要有一个primary shard是active活跃可用的,就可以执行

all:要求我们这个写操作,必须所有的primary shard和replica shard都是活跃的,才可以执行这个写操作

quorum:默认的值,要求所有的shard中,必须是大部分的shard都是活跃的,可用的,才可以执行这个写操作

quroum = (primary + number_of_replicas) / 2  + 1

deep paging操作的问题

简单说就是搜索特别深导致性能低下的问题。

比如总共60000条数据,3个shard,每个shard分2W条,每页10条,如果要搜到1000页时,取的是第几条到第几条数据?

实际上每个shard都要将内部的2W条数据的第10001~10010条拿出来,不是10条,是10010条取出,3个shard都返回10010条数据给coordinate node,这个协调节点共收到30030条,再进行排序, _score,相关度分数,然后取到排位最高的前10条数据,这就是我们要的第1000页的10条数据。

这个过程耗费网络带宽,内存,CPU,有大量性能问题,所以要避免deep paging操作。

倒排索引

简单说统计每个单词在哪些doc中出现,建立索引,方便后续更加单词快速查到该单词的文档。

什么是分词器

1. 切分词 2. Normalization(提升recall 召回率)

normalization,建立倒排索引的时候,会执行一个操作,也就是说对拆分出的各个单词进行相应的处理,以提升后面搜索的时候能够搜索到相关联的文档的概率。时态的转换,单复数的转换,同义词的转换,大小写的转换。

分词器将一段文本进行各种处理(时态转换,单复数转换等等),最后处理好的结果才会拿去建立倒排索引。

query string分词

query string必须以和index建立时相同的analyzer进行分词

query string对exact value和full text的区别对待。

exact value,在建立倒排索引的时候,分词的时候,是将整个值一起作为一个关键词建立到倒排索引中的;

full text,会经历各种各样的处理,分词,normalization(时态转换,同义词转换,大小写转换),才会建立到倒排索引中。

同时呢,exact value和full text类型的field就决定了,在一个搜索过来的时候,对exact value field或者是full text field进行搜索的行为也是不一样的,会跟建立倒排索引的行为保持一致;比如说exact value搜索的时候,就是直接按照整个值进行匹配,full text query string,也会进行分词和normalization再去倒排索引中去搜索。

什么是mapping

自动或手动为index中type建立的一种数据结构和相关配置,简称mapping。

dynamic mapping,自动为我们建立index,创建type,以及type对应的mapping,mapping中包含了每个field对应的数据类型,以及如何分词等设置,包括自动设置数据类型,也可以提前手动创建index和type的mapping,自己对各field进行设置,包括数据类型,索引行为,包括分词器等等。

1、mapping核心的数据类型

string

byte,short,integer,long

float,double

boolean

date

2、dynamic mapping

true or false       -->         boolean

123                      -->         long

123.45                 -->         double

2017-01-01        -->         date

"hello world"     -->         string/text

3、查看mapping

GET /index/_mapping/type

filter与query对比

filter,仅仅只是按照搜索条件过滤出需要的数据而已,不计算任何相关度分数,对相关度没有任何影响。

query,会去计算每个document相对于搜索条件的相关度,并按照相关度进行排序。

一般来说,如果你是在进行搜索,需要将最匹配搜索条件的数据先返回,那么用query;如果你只是要根据一些条件筛选出一部分数据,不关注其排序,那么用filter。

除非是你的这些搜索条件,你希望越符合这些搜索条件的document越排在前面返回,那么这些搜索条件要放在query中;如果你不希望一些搜索条件来影响你的document排序,那么就放在filter中即可。

filter与query性能:

filter,不需要计算相关度分数,不需要按照相关度分数进行排序,同时还有内置的自动cache最常使用filter的数据。

query,相反,要计算相关度分数,按照分数进行排序,而且无法cache结果。

ES相关度评分算法:

term frequency/inverse document frequency算法,简称为TF/IDF算法。

Term frequency:搜索文本中的各个词条在field文本中出现了多少次,出现次数越多,就越相关。

Inverse document frequency:搜索文本中的各个词条在整个索引的所有文档中出现了多少次,出现的次数越多,就越不相关。

Doc Values

搜索的时候,要依靠倒排索引;排序的时候,需要依靠正排索引,看到每个document的每个field,然后进行排序,所谓的正排索引,其实就是doc values。

在建立索引的时候,一方面会建立倒排索引,以供搜索用;一方面会建立正排索引,也就是doc values,以供排序,聚合,过滤等操作使用。

doc values是被保存在磁盘上的,此时如果内存足够,os会自动将其缓存在内存中,性能还是会很高;如果内存不足够,os会将其写入磁盘上。

倒排索引举例:

doc1: hello world you and me

doc2: hi, world, how are you

word                   doc1                    doc2

hello                    *

world                  *                           *

you                      *                           *

and                      *

me                       *

hi                                                      *

how                                                  *

are                                                    *

hello you --> hello, you

hello --> doc1

you --> doc1,doc2

正排索引 doc values:

sort by age:

doc1: { "name": "jack", "age": 27 }

doc2: { "name": "tom", "age": 30 }

document          name                  age

doc1                    jack                      27

doc2                    tom                     30         

按照age排序,类似表字段排序。

preference

决定了哪些shard会被用来执行搜索操作。

例如:_primary, _primary_first, _local, _only_node:xyz, _prefer_node:xyz, _shards:2,3

bouncing results问题,两个document排序,field值相同;不同的shard上,可能排序不同;每次请求轮询打到不同的replica shard上;每次页面上看到的搜索结果的排序都不一样。这就是bouncing result,也就是跳跃的结果。

解决方案就是将preference设置为一个字符串,比如说user_id,让每个user每次搜索的时候,都使用同一个replica shard去执行,就不会看到bouncing results了。

Scroll滚动搜索

如果一次性要查出来比如10万条数据,那么性能会很差,此时一般会采取用scoll滚动查询,一批一批的查,直到所有数据都查询完处理完。

获得的结果会有一个scoll_id,下一次再发送scoll请求的时候,必须带上这个scoll_id。

scoll,看起来挺像分页的,但是其实使用场景不一样。分页主要是用来一页一页搜索,给用户看的;scoll主要是用来一批一批检索数据,让系统进行处理的。

倒排索引的结构

(1)包含这个关键词的document list

(2)包含这个关键词的所有document的数量:IDF(inverse document frequency)

(3)这个关键词在每个document中出现的次数:TF(term frequency)

(4)这个关键词在这个document中的次序

(5)每个document的长度:length norm

(6)包含这个关键词的所有document的平均长度

倒排索引不可变的好处:

(1)不需要锁,提升并发能力,避免锁的问题

(2)数据不变,一直保存在os cache中,只要cache内存足够

(3)filter cache一直驻留在内存,因为数据不变

(4)可以压缩,节省cpu和io开销

倒排索引不可变的坏处:

每次都要重新构建整个索引

ES的写入流程(buffer, segment, commit

(1)数据写入buffer缓冲和translog日志文件

(2)每隔一秒钟,buffer中的数据被写入新的segment file,并进入os cache,此时segment被打开并供search使用,不立即执行commit。

(数据写入os cache,并被打开供搜索的过程,叫做refresh,默认是每隔1秒refresh一次。也就是说,每隔一秒就会将buffer中的数据写入一个新的index segment file,先写入os cache中。所以,es是近实时的,数据写入到可以被搜索,默认是1秒。)

(3)buffer被清空

(4)重复1~3,新的segment不断添加,buffer不断被清空,而translog中的数据不断累加

(5)当translog长度达到一定程度的时候,commit操作发生

  (5-1)buffer中的所有数据写入一个新的segment,并写入os cache,打开供使用

  (5-2)buffer被清空

  (5-3)一个commit point被写入磁盘,标明了所有的index segment

  (5-4)filesystem cache中的所有index segment file缓存数据,被fsync强行刷到磁盘上

  (5-5)现有的translog被清空,创建一个新的translog

基于translog和commit point,如何进行数据恢复

fsync+清空translog,就是flush,默认每隔30分钟flush一次,或者当translog过大的时候,也会flush

POST /my_index/_flush,一般来说别手动flush,让它自动执行就可以了

translog,每隔5秒被fsync一次到磁盘上。在一次增删改操作之后,当fsync在primary shard和replica shard都成功之后,那次增删改操作才会成功

但是这种在一次增删改时强行fsync translog可能会导致部分操作比较耗时,也可以允许部分数据丢失,设置异步fsync translog

PUT /my_index/_settings

{

    "index.translog.durability": "async",

    "index.translog.sync_interval": "5s"

}

磁盘文件合并(segment merge)

每秒一个segment file,文件过多,而且每次search都要搜索所有的segment,很耗时。

默认会在后台执行segment merge操作,在merge的时候,被标记为deleted的document也会被彻底物理删除。

每次merge操作的执行流程

(1)选择一些有相似大小的segment,merge成一个大的segment

(2)将新的segment flush到磁盘上去

(3)写一个新的commit point,包括了新的segment,并且排除旧的那些segment

(4)将新的segment打开供搜索

(5)将旧的segment删除

partial update (部分更新)

post /index/type/id/_update

{

   "doc": {

      "要修改的少数几个field即可,不需要全量的数据"

   }

}

例子:

PUT /test_index/test_type/10

{

  "test_field1": "test1",

  "test_field2": "test2"

}

POST /test_index/test_type/10/_update

{

  "doc": {

    "test_field2": "updated test2"

  }

}

partial update实现原理:

1. 内部先获取document。

2. 在内存中封装用户提交的新document,发送PUT请求到ES内部。

3. 将老的document标记为deleted。

4. 将新的document存入索引中。

比全量替换的优点:

1. 所有查询、修改和写回操作,都发生在ES的一个shard内部,避免网络开销(减少2次网络请求),提升性能。

bulk

每一个操作要两个json串,语法如下:

{"action": {"metadata"}}

{"data"}

例如:

{"index": {"_index": "test_index", "_type", "test_type", "_id": "1"}}

{"test_field1": "test1", "test_field2": "test2"}

有哪些类型的操作可以执行呢?

(1)delete:删除一个文档,只要1个json串就可以了

(2)create:PUT /index/type/id/_create,强制创建

(3)index:普通的put操作,可以是创建文档,也可以是全量替换文档

(4)update:执行的partial update操作

2、bulk size最佳大小

bulk request会加载到内存里,如果太大的话,性能反而会下降,因此需要反复尝试一个最佳的bulk size。一般从1000~5000条数据开始,尝试逐渐增加。另外,如果看大小的话,最好是在5~15MB之间。

重建索引

一个field的设置是不能被修改的,如果要修改一个Field,那么应该重新按照新的mapping,建立一个index,然后将数据批量查询出来,重新用bulk api写入index中

批量查询的时候,建议采用scroll api,并且采用多线程并发的方式来reindex数据,每次scoll就查询指定日期的一段数据,交给一个线程即可

(1)一开始,依靠dynamic mapping,插入数据,但是不小心有些数据是2017-01-01这种日期格式的,所以title这种field被自动映射为了date类型,实际上它应该是string类型的;(2)当后期向索引中加入string类型的title值的时候,就会报错。(3)如果此时想修改title的类型,是不可能的;(4)唯一的办法,就是进行reindex,也就是说,重新建立一个索引,使用scroll api将旧索引的数据批量查询出来,再导入新索引;(5)采用bulk api将scoll查出来的一批数据,批量写入新索引.

如果说旧索引的名字,是old_index,新索引的名字是new_index,终端java应用,已经在使用old_index在操作了,难道还要去停止java应用,修改使用的index为new_index,才重新启动java应用吗?这个过程中,就会导致java应用停机,可用性降低。

解决方案:

给java应用一个别名,这个别名是指向旧索引的,java应用先用着,java应用先用goods_index alias来操作,此时实际指向的是旧的my_index,重建索引后,将goods_index alias切换到my_index_new上去,java应用会直接通过index别名使用新的索引中的数据,java应用程序不需要停机,零提交,高可用。

学习资料:

-- <<Elasticsearch顶尖高手>> 中华石杉

原文地址:https://www.cnblogs.com/fyql/p/12463563.html