一、索引
通俗的来讲正向索引就是通过key去找value,反向索引就是通过value去找key
1.1 正向索引
以文档ID为索引,表中记录文档中每个字的位置信息,查找时扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档。
这种组织方法在建立索引的时候结构比较简单,建立比较方便易于维护
若是有新的文档接入,则直接新建一个索引块,挂接在原索引文件的后面
若是有文档删除,则直接找到该文档号对应的文档对应的索引信息,将其直接删除。
缺点:
索引检索效率太低,只能在一般简单的场景下才可以使用
1.2 反向索引
倒排表以关键字作为索引,表中关键字所对应的记录表项记录了出现这个词的所有文档
一个表项就是一个字段,它记录了文档id和字符在该文档中出现的位置情况
优缺点
查询的时候可以一次得到查询关键字所对饮的文档,所以查询效率较高
但是由于每个字词对应的文档数量在动态的变化,所以倒排表的建立和维护比较复杂
1.3 倒排索引的组成
主要由单词词典和倒排列表组成
单词词典
倒排列表的重要组成,记录所有文档单词,一般比较大,记录单词到倒排列表的关联信息。
但此词典一般用B+Trees来实现,存储在内存:
倒排列表
倒排文档记录了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息及频率,每条记录称为一个倒排项
倒排列表存储在磁盘中,主要包含一下信息
文档id
单词频率
位置
偏移
单词字段和倒排列表整合在一起的结构如下
1.4 索引的更新策略
搜索引擎需要处理的集合往往都是动态的集合,即在建好初始的文档之后不断由新的文档加入,同时原先的文档集合有些文档可能被修改或删除。
动态索引通过在内存中维护临时索引,可以实现对动态文档和实时搜索的支持
服务器内存总是有限的,随着新加入系统的文档越来越多,临时索引消耗的内存也会随之增加。
当最初分配的内存将被使用完时,要考虑将临时索引的内容更新到磁盘索引中,以释放内存空间来容纳后续的新进来的文档
索引更新的基本思想
倒排索引就是对初始文档集合建立的索引结构,一般单词词典都存储在内存,对应的倒排列表存储在磁盘文件中
临时索引是在内存中实时建立的倒排索引,其结构和前述的倒排索引是一样的,区别在于词典和列表都在内存中存储
新文档进入系统时,实时解析文件并将其加入到临时索引结构中。
删除文档列表则用来存储已被删除的文档的相应的文档ID,形成一个文档ID列表。
修改文档可以认为是旧文档先被删除,然后系统在增加一篇新的文档,通过这种间接方式实现对文档的更新
1.4.1 完全重建策略
完全重建策略是一个很直接的方法,当新增文档达到一定数量,将新增文档和原先的老文档进行合并
然后利用建立静态索引的方式,对所有文档重新建立索引。新索引建立完成后,老的索引被遗弃释 放
优缺点:
因为重建索引需要较长时间,在进行索引重建的过程中,内存中仍然需要维护老索引来对用户 的查询做出响应
这种策略适合比较小的文档集合
1.4.2 再合并策略
在和并过程中,需要依次遍历增量索引和老索引单词词典中包含的单词以及其对应的倒排列表,可可用两个指针分别指向增量索引和老索引目前需要合并的单词。
如果增量索引中指针指向的单词ID小于老索引中指针指向的单词ID,则说明这个单词在老索引中没有出现过,直接将这个单词对应的倒排列表写入新索引的倒排列表中,同时增量索引单词指针指向下一个单词
如果俩个单词ID相等,则先将老索引中这个单词对应的倒排列表加入新索引,然后在把增量索引这个单词对应的倒排列表追加到其后。
如果新的索引指向的单词ID大于老索引指针指向的单词ID,则直接将老索引中对应的倒排列表加入新索引的倒排文件中,老索引的单词指针指向下一个单词。
俩个索引的所有单词都遍历完后,新索引建成。
优缺点
在合并策略是效率非常高的一种索引策略,主要是因为在对老索引进行遍历时,因为已经按照索引单词的词典顺序由低到高排好顺序,所以可以顺序读取文件内容,减少磁盘寻道时间。
对于老索引中的很多单词来说,尽管其倒排列表并为发生任何变化,但是也需要将其从老索引中读取出来并写入新索引中,这样就会造成很大的磁盘输入输出消耗。
1.4.3 原地更新策略
在索引更新过程中,如果老索引的倒排列表没有变化,可以不需要读取这些信息,而只是对那些倒 排列表变化的单词进行处理,或者是直接将发生变化的倒排列表追加到老索引的末尾,即只更新增增量索引里出现的单词相关信息,这样就可以减少大量的磁盘读写操作,提升系统执行效率。
存在问题:
对于倒排文件中的两个相邻单词,为了在查询时加快读写速度,其倒排列表一般是顺序存储的,这导致没有空余位置来追加新信息。为了能够支持追加操作,原地更新策略在初始建立的索引中,会在每一个单词的倒排列表末尾预留出一定的磁盘空间。当预留空间不足时,需要在磁盘中找到一块完整的连续存储区,将增量索引对应的倒排列表追加到其后,实现倒排列表的“迁移”工作
原地更新策略出发点很好,但是实验数据证明其索引更新效率比再合并策略低,主要有两个原因:
在这种方法中,对倒排列表的“迁移”是比较常见的。这个策略需要对磁盘可用空间进行维护和管理,这种维护和查找成本非常高,这成为该方法效率的一个瓶颈。
由于存在数据迁移,某些单词及其对应的倒排列表会从老索引中溢出,这样就破坏了单词的连续性,导致在进行索引合并的时候不能进行顺序读取,必需维护一个单词到其倒排文件相应位置的映射表,这样不仅降低了磁盘读写速度,而且需要大量的内存来存储这种映射信息。
1.4.4 混合策略
混合策略一般会将单词根据不同性质进行分类 ,不同类别单词,对其索引采取不同的索引更新策 略
常见的作法:
根据单词的倒排列表长度进行区分
因为有些单词经常在不同的文档中出现,所以其对应的倒排列表较长
而有些单词很少见,则其倒排列表就较短。
根据这一性质将单词划分为长倒排列表单词和短倒排列表单词。
长倒排列表单词采取原地更新策略,而短倒排列表单词则采取再合并策略
二、分词器
2.1 定义
文本分析是把全文本转换一系列单词(term/token)的过程,也叫分词。Analysis是通过Analyzer来实现的
分词器的作用就是把整篇文档,按一定的语义切分成一个一个的词条,目标是提升文档的召回率,并降低无效数据的噪音
recall召回率,也叫搜索性,指搜索的时候,增加能够搜索到的结果的数量。
降噪:指降低文档中一些低相关性词条对整体搜索排序结果的干扰。
2.2 组成
分析器(analyzer)都由三种构件块组成的:character filters , tokenizers , token filters
character filters字符过滤器
在一段文本进行分词之前,现进行预处理
比如说最常见的就是,过滤html标签(hello --> hello),& --> and(I&you --> I and you)
tokenizers分词器
英文分词可以根据空格将单词分开,中文分词比较复杂,可以采用机器学习算法来分词。
token filters Token过滤器
将切分的单词进行加工
大小写转换(例将“Quick”转为小写),去掉词(例如停用词像“a”、“and”、“the”等)或者增加词
analyzer=CharFilters(0个或多个)+Tokenizers(恰好一个)+TokenFilters(0个或多个)
2.3 内置分词器
Standard Analyzer - 默认分词器,按词切分,小写处理
Simple Analyzer - 按照非字母切分(符号被过滤), 小写处理
Whitespace Analyzer - 按照空格切分,不转小写
Stop Analyzer - 小写处理,停用词过滤(the,a,is)
Keyword Analyzer - 不分词,直接将输入当作输出
Patter Analyzer - 正则表达式,默认\W+(非字符分割)
Language - 提供了30多种常见语言的分词器
Customer Analyzer 自定义分词器
2.4 中文分词器
推荐使用IK分词器
三、ElasticSearch简介
3.1 ElasticSearch
ElasticSearch是一个基于Luncene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于Result web接口。
优点:
分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
实时分析的分布式搜索引擎。
可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。
各节点组成对等的网络结构,某些节点出现故障时会自动分配其他节点代替其进行工作。
四、ES数据存储结构
4.1 索引index
索引是文档(Document)的容器,是一类文档的集合
索引(名词)
类比传统的关系型数据库领域来说,索引相当于SQL中的一个数据库(Database)。
索引由其名称(必须为全小写字符)进行标识。
索引(动词)
保存一个文档到索引(名词)的过程。
这非常类似于SQL语句中的 INSERT关键词。如果该文档已存在时那就相当于数据库的update
索引(倒排索引)
关系型数据库通过增加一个B+树索引到指定的列上,以便提升数据检索速度。
索引ElasticSearch 使用了一个叫做 倒排索引 的结构来达到相同的目的。
4.2 文档Document
Document Index 里面单条的记录称为Document(文档)。等同于关系型数据库表中的行。
_index 文档所属索引名称。
_type文档所属类型名。
_id Doc的主键。在写入的时候,可以指定该Doc的ID值,如果不指定,则系统自动生成一个唯一的UUID的值
4.3 字段Field
一行数据中心包含很多的字段,类似于关系数据库的列Column
4.4 类型Type
从6.0.0开始单个索引中只能有一个类型,7.0.0以后将将不建议使用,8.0.0 以后完全不支持。
索引和文档中间还有个类型的概念,每个索引下可以建立多个类型,文档存储时需要指定index和type
五、映射mapping
5.1 ES字段类型主要有:核心类型、复杂类型、地理类型以及特殊类型
5.2 Dynamic Mapping
动态映射是Elasticsearch的一个重要特性:
不需要提前创建index、定义mapping信息和type类型
可以直接向ES中插入文档数据时, ES会根据每个新field可能的数据类型, 自动为其配置type等,mapping信息
这个过程就是动态映射
约束策略
dynamic设为true时,新增字段的文档写入时,Mapping同时被更新
dynamic设为false时,Mapping不会被更新,新增字段的数据无法被索引,但是会出现在 _source中
dynamic设为strict,文档将写入失败
六、分片与备份
分片分为俩种,主分片和副本
主分片用于解决数据水平扩展的问题,通过分片,可以将数据分布到集群内的所有节点之上
一个分片是一个运行的ES实例
分片数在索引创建时指定,后续不允许修改,除非Reindex
副本用于解决数据高可用的问题,副本是主分片的拷贝
副本分片数,可以动态调整
增加副本数,还可以在一定程度上提高服务的可用性(读取的吞吐)
分片的设定
分片数量设置过大
导致后续无法增加节点实现水平扩展
单个分片数据量过大,导致数据重新分片耗时
分片数设置过小,7.0之后,默认主分片是1,解决了over-sharding的问题
影响搜索结果的相关性打分,影响统计结果的准确性
单个节点上过多分片,会导致资源浪费,同时会影响性能
七、ES数据架构
7.1 基本概念
index
一个ES集群中可以按需创建任意数目的索引
Document
文档是索引和搜索的原子单位,它是包含了一个或多个域(Field)的容器,基于JSON格式进行表示。文档由一个或多个域组成,每个域拥有一个名字及一个或多个值,有多个值的域通常称为"多值域"。每个文档可以存储不同的域集
Node
集群是由一个或者多个拥有相同cluster.name配置的节点组成
节点的分类
主节点:负责管理集群范围内的所有变更
数据节点:存储数据和其对应的倒排索引,默认每一个节点都是数据节点(包括主节点)
协调节点:如果node.master和node.data属性均为false,此节点称为协调节点,用来用来响应客户端的请求,均衡每个节点的负载
Shard
一个索引中数据保存在多个分片中,相当于水平分表
我们的文档被存储和索引到分片内,但应用程序直接与索引而不是与分片进行交互
分片是数据的容器,文档保存在分片内,分片又被分配到集群中的各个节点中
索引建立的时候就已经确定了分片的个数,但是副本分片数可以随时修改
默认情况下一个索引会有五个分片,而其副本数可以有任意数量,默认1个
Replaction
一个分片可以是主分片或副本分片
主分片可以用来写入数据和读取数据
副本分片负责读取数据
主分片和副本分片的状态决定了集群的健康状态,相同的副本分片不会被存放在同一个节点中
7.2 ES数据Write流程
名词类比Hbase
Shard--Region
Lucne--buffer--系统缓存
Segment--MemStore
TransLog--Hlog
Shard和Replication的路由原则
每个index由多个shard组成,每个shard上有一个主节点和多个副本节点,副本个数可配
但每次写入的时候,写入请求会根据_routing原则选择发送给哪个shard
index Request中可以设置使用哪个Field的值作为参数
如果index没有设置,则使用mapping中的配置
如果mapping中也没有配置,则使用id作为路由,然后通过_id的hash值选出primary shard
请求会发送给primary shard ,在primary shard上发送成功后,再从primary shard上将请求发送给多个Replica shard
数据安全策略
ES为了保证IO读写性能,一般是每隔一段时间才会把Luncene的Segment写入磁盘中
ES学习了数据库中的处理方式:增加CommitLog模块,ES中叫TransLog
写入请求到达Shard后,先写buffer文件,创建好索引,此时索引还在内存中,接着去写TransLog
写完TransLog后,刷新TransLog数据到磁盘上,写磁盘成功后,请求返回给用户
具体操作细节
ES是先写内存再写TransLog
写buffer后,文档并不是可被搜索的,需要通过Refresh把内存中的对象转换为完整的Sagement后,再次reopen后才能被搜索
一般这个时间设置为1秒,导致写入ES的文档,最快需要1秒才可以被搜到
segment里的文档可以被搜索到,但是尚写入硬盘,如果此时发生断电,则这些文档可能会丢失
ES每5秒或是等待一次写操作执行完成之后将TransLog写入到磁盘中,操作记录被写入磁盘,ES才会将操作成功结果返回给发送此次请求的客户端
translog--保证安全--> buffer segment
每隔一秒生成一个segment,而translog文件会越来越大
每隔30分钟或者translog变得很大,则执行一次fsync操作,此时所有在文件系统缓存中的segment将被写入磁盘,而translog将被删除
7.3 ES数据update流程
ES的索引是不能修改的,因此更新和删除操作并不是直接在原索引上进行
收到Update请求后,从Segment或者TransLog中读取同id的完整Doc,记录版本号为V1 = 345
将版本V1的全量Doc和请求中的部分字段Doc合并为一个完整的Doc,同时更新内存中的versionMap
获取到完整的Doc之后,Update请求就变成了Post/put请求
加锁
再次从versionMap中获取最大版本号V2=346
检查版本是否冲突,如果冲突(V1==V2),则回退到开始的"Update doc"阶段,重新执行,如果不冲突,则执行最新的Add请求。
在Index Doc阶段,首先将Version+1得到V3,再将Doc加入到Luncene中,Luncene中会删除同Id下的以存在的doc id,然后再增加新Doc。写入Lucene成功后,将当前V3更新到versionMap中。
释放锁,部分更新的流程就结束了。
7.4 ES数据的Delete流程
ES是这么操作的:如果是删除请求的话,提交的时候会生成一个.del文件,里面将某个doc标识为delete状态,那么搜索的时候根据.del文件就知道这个doc被删除了,客户端搜索的时候,发现数据在.del文件中标志为删除就不会搜索出来了,
7.5 ES写入数据的完整流程
红色:Client Node(协调节点)
绿色:primary Node(主分片节点)
蓝色:Replica Node(副本分片节点)
7.5.1 客户端节点
Ingest Pipeline
在这一步可以对原始文档做一些处理,比如HTML解析,自定义的处理,具体处理逻辑可以通过插件来实现。在Elasticsearch中,由于Ingest Pipeline会比较耗费CPU等资源,可以设置专门的Ingest Node,专门用来处理Ingest Pipeline逻辑。
如果当前Node不能执行Ingest Pipeline,则会将请求发给另一台可以执行Ingest Pipeline的Node。
Auto Create Index
判断当前Index是否存在,如果不存在,则需要自动创建Index,这里需要和Master交互。也可通过配置当前关闭自动创建Index的功能。
Set Routing
设置路由条件,如果Request中指定了路由条件,则直接使用Request中的Routing,否则使用Mapping中配置的,如果Mapping中无配置,则使用默认的_id字段值
在这一步中,如果没有指定id字段,则会自动生成一个唯一的_id字段,目前使用的是UUID。
Construct BulkShardRequest
由于Bulk Request中会包括多个(Index/Update/Delete)请求,这些请求根据routing可能会落在多个shard上执行,这一步会按Shard挑拣Single Write Request,同一个Shard中的请求聚集在一 起,构建BulkShardRequest,每个BulkShardRequest对应一个Shard。
Send Request To Primary
这一步会将每一个BulkShardRequest请求发送给相应Shard的Primary Node。
7.5.2 主分片节点
Index or Update or Delete
循环执行每个Single Write Request,对于每个Request,根据操作类型
其中,Create/Index是直接新增Doc,Delete是直接根据_id删除Doc,Update会稍微复杂些,同上
7.5.3 副本分片节点
Index or Delete
根据请求类型是Index还是Delete,选择不同的执行逻辑。这里没有Update,是因为在Primary Node中已经将Update转换成了Index或Delete请求了。
Parse Doc
以上都和Primary Node中逻辑一致。