1. 导读
Elastic search对于大家并不陌生,作为实时搜索引擎中的佼佼者我们可以在各种应用和业务中看到它的身影。初学es时给人的感觉就像它的名字一样 elastic 灵活的,容易伸缩的。简易上手的的配置,只需要输入一句PUT /index就可以生成一个索引并且导入数据到es中进行使用了。但使用es并不是仅仅一个put语句就够了。es迭代频繁,各种指标和配置参数众多,创建索引作为使用es的第一步定义了文档数据结构,很大程度影响了es在使用时的性能,要创建出高效合理的索引则需要了解es在进行索引操作的过程及机制。
借这个契机来分享一下es中索引的创建,本篇分享文章基于elastic search6.7.0版本并围绕下面内容展开。
2. Elastic Search概述
要想建立合理且高效的索引结构,除了要知道当前业务场景下数据的特点,还要了解elastic search的基本概念。
2.1. es基础概念
与MySql中概念的类比关系:
Elastic search | index(索引) | type(类型) | doc(文档) | field(属性) |
---|---|---|---|---|
MySql | database(库) | table(表) | row(行) | column(列) |
需要注意的是es在6版本后只支持一个type,7版本会默认生成一个_doc类型,官网表示会在8版本取消type的概念。
2.2. es整体架构
Elastic Search设计其遵循高扩展和高可用的特性。其高扩展特性体现在对分布式的强支持,在同一网段的es实例节点通过相同的cluster.name组成集群,新节点通过集群名的配置自动发现并加入相同集群名的集群,集群中的数据会自动的负载均衡且不用任何干预。
es中节点有三种不同类型:
- 主节点master:主节点主要干和集群相关的操作,创建删除索引(维护元数据),跟踪监控集群内的节点,有节点丢失或者有节点新加入时会进行集群内相关的rebalance操作
- 协调节点coordinating:协调节点则是处理路由请求,处理搜索,分发索引操作等,就相当于负责负载均衡的东西
- 数据节点data:数据节点主要是存储数据的节点,主要对文档进行增删改查和聚合操作,对服务器的性能要求较高
通过配置参数在本地搭建一个es集群,包括一个主节点,一个协调节点,三个数据节点
node.master决定是否是主节点,node.data决定数据节节点,node.ingest决定协调节点
2.2.1. 分片
分片上存储的是索引数据,一个分片是最小级别的工作单元,索引上所有主分片的数据合起来的数据就是一个索引的所有数据。分片分为主分片和副分片两种,副分片相当于主分片的副本,上面保存着主分片的全部信息,防止主分片节点故障而丧失了数据。主分片和副分片是索引在建立时设置数量,索引一旦建立其主分片数量就不能更改了。
在上面本地配置的集群中,已将主节点和协调节点的node.data属性设置为false,只有三个数据节点可以存放数据,在本地集群环境为了测试为每个索引设置三个主分片,一份副分片,其在节点中的分布如下:
当为这种分布时,如果node-1节点挂掉后,位于node-2节点上的副分片1此时会提升为主分片并响应查询请求,此时集群的可用性没有受到影响。因此主分片的数量最好设置为数据节点的整数倍,使其均匀的分布在数据节点上,分片数量过多也会影响集群的性能。
2.3. es索引操作
es其全文搜索的特点得益于它的倒排索引的数据结构,倒排索引通过对词典的匹配快速定位到文档列表。同时会生成正排索引的存储结构,类似于传统的关系型数据库,用于对数据的聚合与排序操作。
2.3.1. 倒排索引
有以下三份文档
a. i study elasticsearch
b. i study MySql
c. i study Redis
这三份文档在存入es中时,es的底层lucene会生成倒排索引如下
词典 | 包含词典的文档列表 |
---|---|
i | a,b,c |
study | a,b,c |
elasticsearch | a |
MySql | b |
Redis | c |
当输入一条语句进行查询时,分词器将语句进行分词,如果所得的分词与词典中的单词匹配上的话,根据倒排索引就能够非常容易的得到包含该词的文档列表并快速的定位到具体的文档。
2.3.2. phrase短语查询
match_phrase查询可以很直观的让我们理解以倒排索引为基础的全文搜索流程
test索引中有一份文档,文档中只有一个text类型的content字段,内容为this is elastic search。
在没有指定分词器的情况下,es默认使用standard分词器对text类型字段内容进行分词作为倒排索引的词典,可以使用_termvectors命令来查看分词的token信息
通过上面命令行的操作结果可以看到分词器将字段内容分成四个单词,并且还返回了三个参数position,start_offset和end_offset,分别代表的是单词在原始字符串的顺序位置,单词的起始位置以及结束位置。
我们使用match_phrase短语查询来查询文档,在此之前可以用_analyze命令来查看在查询前es中分词器是如何对查询内容进行分词的
默认分词器将查询参数“this is search”分成三个单词,同时分词的token信息中也返回了position,start_offset和end_offset信息。
此时进行match_phrase查询发现查询结果为空
原因在于进行phrase短语查询时,es先对查询内容进行分词,然后对分词后的内容构建短语查询,也就是说必须匹配短语中的所有分词,并保持分词的相对位置不变。
2.3.3. 正排索引
正排索引的结构就类似于传统的关系型数据库doc_value的对应关系,会给每一个参与正排索引的字段将其所属的文档对应起来进行列式存储。
2.4. es索引流程
es本质上是运行的java程序,在es集群中每个节点对应着一个运行的es实例,es在进行索引时会用到jvm内存,文件系统内存(物理机内存),以及磁盘内存
其大致流程如下:
- 当es收到索引请求时,索引数据首先被写入到JVM内存中的buffer区域,此时索引数据无法被搜索到,同时会生成一个translog文件来记录操作,并且es默认每次request都会将生成的translog进行flush操作持久化到磁盘中防止es出现问题产生数据丢失。
- es默认每隔一秒后将存放在buffer区域的数据进行refresh操作提交到文件系统缓存中(操作系统内存),此时索引数据可以被搜索到,索引数据在文件系统缓存中以segment文件形式存放。
- es默认30分钟后或者文件系统缓存中的数据满了后会执行flush操作,将操作系统内存中的segment文件持久化到磁盘中,并删除translog文件,这个阶段后数据就永久存在elastic search中了。
3. 索引创建
现在对elastic search有了一个大概的了解,就开始进入主题了,索引的创建。索引创建时主要有两个创建点,setting和mapping,setting中描述的是索引的设置,mapping则类似数据库中的schema的定义,定义索引中字段的名称,数据类型,及字段相关的配置。一个mapping属于一个索引的type,6版本后只支持一个type,7版本后不用指定type信息,会默认生成一个_doc作为type。
索引有动态mapping映射和显示设置mapping。
3.1. 动态mapping映射
动态mapping映射(Dynamic Mappings)是在写入文档时,如果索引不存在,会自动创建索引,es会根据所写入文档信息来推算出字段的类型,如下
当集群中无test索引时,向test索引写入以下文档,显示写入成功
查看test索引mapping结构
可以看到es自动生成了test索引setting和mapping结构,es在5.0版本后对string类型进行了拆分成了text和keyword两种类型属性,text属性字段默认进行分词并创建倒排索引但不支持聚合操作,keyword类型字段不支持分词并将字符串全部内容作为单词进行倒排索引。日期类型的字符匹配为日期类型date,数值类型的值匹配为long。es6版本默认给索引设置5个主分片,一份副分片。
es类型自动识别:
字符串 | 1.匹配日期格式,设置为Date类型2.匹配数字则设置为float或者long类型,此识别模式默认关闭3.设置为text类型,并增加keyword类型的子字段 |
---|---|
布尔值 | boolean |
浮点数 | float |
整数 | long |
对象 | Object |
数组 | 由数组内的第一个非空数值的类型决定 |
null | 忽略 |
可手动设置mapping中的dynamic属性,该属性可被设置为true,false,strict三个值
- true:当有携带新的字段文档请求写入索引时,请求成功,文档成功写入,新字段可被索引,mapping中会出现该新字段
- false:当有携带新的字段文档请求写入索引时,请求成功,文档成功写入,但新字段无法被索引,mapping中不会出现该新字段,相当于该字段被过滤掉了
- strict:当有携带新的字段文档请求写入索引时会直接报错
设置test索引的dynamic属性为strict,该索引只包含name一个字段
向该索引插入含有新字段的文档,报错
3.2. 显示mapping设置
动态的进行mapping映射不需要手动设置索引信息,直接进行数据写入即可,很形象的印证了es的elastic性质。但es自动生成的索引结构又略显生硬,setting只有简单的分片数配置,对字符串类型的数据默认生成text和keyword两种类型显得臃肿,增加了不必要的开销,地理位置等数据类型推导出错,字段的属性设置都是默认。要最大程度的使用es还是需要进行手动的mapping设置,根据集群环境,数据在业务中的特性来设置最合理的索引结构。
3.2.1. setting常用设置
setting的设置属于索引级别的设置,该设置被该索引下所有的type使用(6版本后只允许有一个type)
-
索引分片数量设置,该设置决定索引在集群中分片数量,副分片数量代表着索引复制主分片份数。
- number_of_shards:主分片数量
- number_of_replicas:副分片数量
主副本分片的设置要根据使用场景灵活变通,在三个数据节点的环境下,如果es使用是写大于读,可以设置三个主分片,一份副本分片,在保证了基本的节点容灾及分布均衡也控制了写分片的数量。如果使用场景是读大于写,可以设置三个主分片,两份副分片,这样每个节点上会有一个主分片两个副分片,包含了一个索引的全部信息,这样能够保证每个节点一个分片,读请求在任意分片都是可执行的,而且主分片只有一个,这样搜索请求既可以负载到任一节点执行,同一个请求又不会复制到多个机器执行,减少了多余的查询开销。
-
索引refresh操作时间间隔设置,默认1s,在调大该参数时应该配合调大es的jvm head中indexing buf fer区域大小,buffer区域满了后会触发refresh操作,该参数的调大可以提高写索引性能
- index.refresh_interval:refresh时间间隔,索引写入后在该时间内不会被索引到
-
translog文件写入磁盘方式,将translog写入方式改为异步后可减少其写入次数,减少了es文件写入的开销
-
index.translog.durability
- request 每步操作都写入磁盘(默认策略),可靠性最高
- async 异步定时写
- index.translog.sync_interval:当translog写入方式设置为异步写入后的写入时间间隔
-
-
延迟分片时间设置,当有节点脱离集群后,es会重新分片,将脱离节点上主分片对应的副分片提升为主分片,并重新设置分片数,调大该时间后可以节省节点因网络拥挤等原因暂时性脱离集群导致的重新分片设置。
- index.unassigned.node_left.delayed_timeout:延迟节点失联后索引重新分片的超时时间
-
索引日志设置,针对慢索引以及满查询操作进行日志记录,定义日志的等级以及对应时间设置,下面时索引慢日志的设置,查询慢日志类似,将indexing替换为query
- index.indexing.slowlog.threshold.index.warn: warn级别时间
- index.indexing.slowlog.threshold.index.info: info级别时间
- index.indexing.slowlog.threshold.index.debug: debug级别时间
- index.indexing.slowlog.threshold.index.trace: trace级别时间
- index.indexing.slowlog.level: 记录级别
3.2.2. mapping字段设置
-
index:控制当前字段是否被索引以及指定分词器类型。es在5版本之前的字符串类型只有一个string,默认进行分词进行创建倒排索引,5版本之后字符串类型分为text和keyword两种,text默认分词。keyword不分词,会将整个字段创建倒排索引。index通常配置在text类型字段中。
- false:该字段不会被索引到
- analyzed:使用默认分词器进行分词
- not_analyzed:不进行分词,但可以被索引到,此时相当于keyword类型
- _source:原始文档存储,一般在搜索时返回文档内全部字段的信息,将_source关闭后进行搜索,只会返回文档的_id,文档内字段信息不会被存储同时不会被返回
关闭test索引的_source:
插入一条数据后进行搜索,只返回文档id信息,文档元数据不被保存:
也可以在_source设置中通过includes或excludes来控制元数据中只保存或不保存指定字段
- store: 该属性默认关闭,启用的字段会进行独立存储在store_fields字段中
- _all:该属性默认关闭,启用后每个文档都会生成一个超大字段的索引,其索引词是该文档所有字段的值构成
- null_value:对空值实现搜索
将mapping中name字段设置null_value
插入name为空的文档数据
搜索name值为空的文档
结果可看到name值为空的文档被搜索到
需要注意的是null_value属性只能在keyword属性字段中使用
- relations:es在处理数据关联关系时,通常有使用nested定义的嵌套json格式的文档数据和使用relations定义Parent/Child类型文档,nested属性的自文档数据和主文档数据在同一文档中,更新等操作都是一起进行。定义父子关系文档则可以使父文档和子文档分离
定义一个包含父子类型文档的索引
插入父类型文档
给父文档插入相应子文档
查询指定子文档内容,要将路由信息带上
还可以使用has_parent,has_child语句来根据父文档内容查询子文档或根据子文档内容查询父文档
4. 总结
Elastic Search以其优秀的搜索性能,易于水平扩展的分布式架构,广泛的用于数据搜索,分析等领域。但万丈高楼平地起,根据数据在使用时的特性以及集群环境构建出合理易用的索引对后续的数据操作有着事半功倍的效果。