倒排索引
倒排索引作为ES的核心,底层基于Lucene进行实现。
倒排索引(Inverted Index)也叫反向索引,有反向索引必有正向索引。通俗地来讲,正向索引是通过文档ID找单词,类似于书的目录结构。反向索引则是通过单词找文档ID,类似于字典查词,首先必须知道单词的全拼,然后通过字典的索引页再去查找单词的详情。
倒排索引建立流程
- 内容爬取,停顿词过滤,比如一些无用的像"的",“了”之类的语气词/连接词
- 内容分词,提取关键词。一段文本经过分词器分词后转换成多个Term关键词。
- 根据关键词建立倒排索引。倒排索引包括Term Index(单词索引),Term Dictionary(单词字典),Posting List(倒排列表)
用户输入关键词进行搜索。
倒排索引具体组成
单词词典(Term Dictionary): 包含了所有数据在进行分词之后生成的单词(term),词典是由所有term构成的字符串集合。搜索引擎的通常索引单位是term,词典内每条索引项记载term本身的一些信息以及指向“倒排列表”的指针。ES 为了能快速查找到 term,将所有的 term 排了一个序,并采用二分法进行查找。
倒排列表(PostingList): 倒排列表记载了出现过某个单词的所有文档的文档列表记录,每条记录称为一个倒排索引项(Posting),其主要包括:
- 文档ID,用于获取原始信息
- 单词频率TF,记录该单词在该文档中的出现次数,用于后续相关性算分
- 位置Position,记录单词在文档中分词的位置,用于语句搜索(phrase query)
- 偏移Offset,记录单词在文档的开始和结束位置,实现高亮显示
根据倒排列表,即可获知某个单词在哪些文章中出现过。
单词索引(Term Index): ES 默认会对全部 text 字段进行分词并建立索引,导致Term Dictionary过大,无法存储在内存中。为了更快的找到某个Term,我们为单词建立索引。Term Index采用字典树结构,这棵树不会包含所有的 term,它包含的是 term 的一些前缀,通过 term index 可以快速地定位到 term dictionary 的某个 offset,然后从这个位置再往后顺序查找。就如上图所表示的。单词索引文件是为了加快对词典文件中词的查找速度,存储在内存中。
lucene 在这里还做了两点优化,一是 term dictionary 在磁盘上面是分 block 保存的,一个 block 内部利用公共前缀压缩,比如都是 Ab 开头的单词就可以把 Ab 省去。
二是 term index 在内存中是以 FST(finite state transducers)的数据结构保存的。
分词
在构建倒排索引的过程中,需要对文档内容进行分词,掌握分词要先懂两个名词:Analysis与Analyzer。
Analysis(文本分析)
即文本分析,是把全文本转化为一系列单词(term/token)的过程,也叫分词;Analysis是通过analyzer(分词器)来实现的,可以使用Elasticearch内置的分词器,也可以自己去定制一些分词器。
Analyzer(分词器)
由三部分组成
- Character Filters:原始文本处理
首先,字符串按顺序通过每个字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉HTML,或者将 & 转化成 and。
Tokenizer:按照规则切分为单词
字符串被分词器按照规则分为单个的单词。一个 whitespace的分词器遇到空格和标点的时候,可能会将文本拆分成词条。下图为ES分词器汇总 - Token Filters:字段过滤器,对切分单词加工、小写、删除 stopwords,增加同义词
词条按顺序通过每个字段过滤器 。这个过程可能会改变词条,例如,lowercase token filter 小写化(将ES转为es)、stop token filter 删除词条(例如, 像 a, and, the 等无用词),或者synonym token filter 增加词条(例如,像 jump 和 leap 这种同义词)。
分词测试
使用index中的具体字段的分词器进行测试
下面的案例就是使用了index_name中的my_test字段所用的分词器进行测试。
GET ip:port/index_name/_analyze { "field": "my_text", "text": "关注我,学习ES" }
测试结果如下,从结果我们可以推测my_text字段使用的是standard分词器,按照每个词进行切分,且做小写处理。
{ "tokens": [当前字段切分后出现的分词列表,由该词的文本、偏移量(开始和结束)、位置、以及类型组成; { "token": "关",//分词后的具体文本 "start_offset": 0,//起始位置 "end_offset": 1,//结束位置 "type": "<IDEOGRAPHIC>",//当前单词的类型 "position": 0//当前单词所在整个字段的位置。 }, { "token": "注", "start_offset": 1, "end_offset": 2, "type": "<IDEOGRAPHIC>", "position": 1 }, { "token": "我", "start_offset": 2, "end_offset": 3, "type": "<IDEOGRAPHIC>", "position": 2 }, { "token": "学", "start_offset": 4, "end_offset": 5, "type": "<IDEOGRAPHIC>", "position": 3 }, { "token": "习", "start_offset": 5, "end_offset": 6, "type": "<IDEOGRAPHIC>", "position": 4 }, { "token": "es", "start_offset": 6, "end_offset": 8, "type": "<ALPHANUM>", "position": 5 } ] }
也可以直接使用analyzer
POST ip:port/_analyze { "analyzer": "whitespace",//这里使用空格分词器 "text":"关注我 学习ES" }
可以看到,空格分词器直接采用空格进行切分,并不会对文中英文字母进行小写处理。
{ "tokens": [ { "token": "关注我", "start_offset": 0, "end_offset": 3, "type": "word", "position": 0 }, { "token": "学习ES", "start_offset": 4, "end_offset": 8, "type": "word", "position": 1 } ] }
从以上测试可以看出,分析器不仅将原始文档转换为term,而且还记录每个term的顺序或相对位置(用于短语查询或单词接近性查询),以及每个term的开始和结束字符偏移量(用于突出显示搜索摘要)。
mapping
ES中的mapping映射可以把它类比于数据库中的表结构定义 schema,它有以下几个作用:
- 定义索引中的字段的名称
- 定义字段的数据类型,比如字符串、数字、布尔
- 定义字段,倒排索引的相关配置,比如设置某个字段的分词器,是否可索引、记录 position 等
字段数据类型
字段的数据类型由字段的type属性指定,ES 字段类型主要有:核心类型、复杂类型、地理类型以及特殊类型,具体的数据类型如下图所示:
核心类型
从图中可以看出核心类型可以划分为字符串类型、数字类型、日期类型、布尔类型、基于 BASE64 的二进制类型、范围类型。
字符串类型
ES7.x有两种字符串类型:text和keyword,ES 5.x之后不再支持string 类型。
text 类型适用于需要被全文检索的字段,例如新闻正文、邮件内容等比较长的文字,text 类型会被分词器处理为一个个关键词后分别进行索引,支持模糊、精确查询,不支持聚合、排序操作。
keyword 与text不同,字段设置为此类型后,将不会进行分词操作直接索引。适合简短、结构化的字符串,可以用于过滤、排序、聚合检索,也可以用于精确查询。