目录
- 检索需求
- 倒排索引与全文检索
- elasticsearch的原理
- elasticsearch的使用
- 应用与扩展
1 检索需求
需求特点:
非结构化数据
大数据量
模糊检索
分词检索
拼音检索
…
关系型数据库已不能满足使用要求
数据库,CRUD,其中检索最常用
传统数据库有局限
模糊检索性能差
字段很长时无法索引,每次都要对每条记录的所有文本进行扫描
不能将搜索词拆分,搜索“吃鸡战场”就搜不出来“刺激战场”,无法实现拼音检索等
2 倒排索引与全文检索
全文检索大体分两个过程,索引创建 (Indexing) 和搜索索引 (Search) 。
索引创建:将现实世界中所有的结构化和非结构化数据提取信息,创建索引的过程。
搜索索引:就是得到用户的查询请求,搜索创建的索引,然后返回结果的过程。
2.1 倒排索引
与倒排索引相对的正向索引存储的是每个文档包含哪些字符串,例如通过文档id查询内容;
倒排索引存储的是字符串到文件的映射
左边保存的是一系列字符串,称为词典 。词典里的词条称为词条
每个词条都指向包含此词条的文档(Document)链表,此文档链表称为倒排表 (Posting List)。
词条(Term):索引里面最小的存储和查询单元,对于英文来说是一个单词,对于中文来说一般指分词后的一个词。
词典(Term Dictionary):或字典,是词条Term的集合。词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
倒排表(Post list):倒排表记录的是某个词在哪些文档里出现过以及出现的位置。每条记录称为一个倒排项(Posting)。倒排表记录的不单是文档编号,还存储了词频等信息。
倒排文件(Inverted File):所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件被称之为倒排文件,倒排文件是存储倒排索引的物理文件。
例如,寻找既包含字符串“lucene”又包含字符串“solr”的文档,需要以下几步:
- 取出包含字符串“lucene”的文档链表。
- 取出包含字符串“solr”的文档链表。
-
通过合并链表,找出既包含“lucene”又包含“solr”的文件。
2.2 创建索引
第一步:一些要索引的原文档(Document)。
文档一:
Students should be allowed to go out with their friends, but not allowed to drink beer.
文档二:
My friend Jerry went to school to see his students but found them drunk which is not allowed.
第二步:将原文档传给分词器(Tokenizer)。
- 将文档分成一个一个单独的单词。
- 去除标点符号。
- 去除停词(Stop word) 。
经过分词(Tokenizer) 后得到的结果称为词元(Token) 。
“Students”,“allowed”,“go”,“their”,“friends”,“allowed”,“drink”,“beer”,“My”,“friend”,“Jerry”,“went”,“school”,“see”,“his”,“students”,“found”,“them”,“drunk”,“allowed”。
所谓停词(Stop word)就是一种语言中最普通的一些单词,由于没有特别的意义,因而大多数情况下不能成为搜索的关键词,因而创建索引时,这种词会被去掉而减少索引的大小。
英语中停词(Stop word)如:“the”,“a”,“this”等。
对于每一种语言的分词组件(Tokenizer),都有一个停词(stop word)集合。
第三步:将得到的词元(Token)传给语言处理组件(Linguistic Processor)。
- 变为小写(Lowercase)
- 将单词缩减为词根形式,如“cars ”到“car ”。(stemming)
- 将单词转变为词根形式,如“drove ”到“drive ”。(lemmatization)
- 中文的话还会进行同义词转换、拼音转换等
语言处理组件(linguistic processor)的结果称为词(Term) 。
“student”,“allow”,“go”,“their”,“friend”,“allow”,“drink”,“beer”,“my”,“friend”,“jerry”,“go”,“school”,“see”,“his”,“student”,“find”,“them”,“drink”,“allow”。
语言处理组件(linguistic processor)主要是对得到的词元(Token)做一些同语言相关的处理。
第四步:将得到的词(Term)传给索引组件(Indexer)。
-
利用得到的词(Term)创建一个词典。
-
对词典按字母顺序进行排序。
-
合并相同的词(Term) 成为文档倒排(Posting List) 链表。
Document Frequency
即文档频次,表示总共有多少文件包含这个词。
Frequency
即词频率,表示此文件中包含了几个这个词。
创建索引的过程就是将文档转换为词典和倒排表
2.3 搜索索引
第一步:搜索词处理
- 分词
- 语法规则解析,and、or、not
第二步:搜索索引
lucene AND learned NOT Hadoop
在反向索引表中,分别找出包含lucene,learn,hadoop的文档链表
对包含lucene,learn的链表进行合并操作,得到既包含lucene又包含learn的文档链表
将此链表与hadoop的文档链表进行差操作,去除包含hadoop的文档
第三步:计算相关度并排序
- 计算词的权重
影响一个词(Term)在一篇文档中的重要性主要有两个因素:
Term Frequency (tf):即此Term在此文档中出现了多少次。tf 越大说明越重要。
Document Frequency (df):即有多少文档包含次Term。df 越大说明越不重要。
即:在当前文档中出现的次数越多,在其他文档中出现的次数越少
词(Term)在文档中出现的次数越多,说明此词(Term)对该文档越重要,如“搜索”这个词,在本文档中出现的次数很多,说明本文档主要就是讲这方面的事的。然而在一篇英语文档中,this出现的次数更多,就说明越重要吗?不是的,这是由第二个因素进行调整,第二个因素说明,有越多的文档包含此词(Term), 说明此词(Term)太普通,不足以区分这些文档,因而重要性越低。
- 计算相关度
把文档看作一系列词(Term),每一个词(Term)都有一个权重(Term weight),
所有此文档中词(term)的权重(term weight) 看作一个向量。
Document = {term1, term2, …… ,term N}
Document Vector = {weight1, weight2, …… ,weight N}
查询语句也看作一个简单的文档,也用向量来表示。
把所有搜索出的文档向量及查询向量放到一个N维空间中,每个词(term)是一维。
不同的词(Term)根据自己在文档中的权重来影响文档相关性的打分计算。
两个向量之间的夹角越小,相关性越大。
即:搜索语句和文档中含有的相同且权重高的词越多,相关性越大
- 索引过程:
a) 有一系列被索引文件
b) 被索引文件经过语法分析和语言处理形成一系列词(Term) 。
c) 经过索引创建形成词典和反向索引表。
d) 通过索引存储将索引写入硬盘。 - 搜索过程:
a) 用户输入查询语句。
b) 对查询语句经过语法分析和语言分析得到一系列词(Term) 。
c) 通过语法分析得到一个查询树。
d) 通过索引存储将索引读入到内存。
e) 利用查询树搜索索引,从而得到每个词(Term) 的文档链表,对文档链表进行交,差,并得到结果文档。
f) 将搜索到的结果文档对查询的相关性进行排序。
g) 返回查询结果给用户。
3 elasticsearch的概念
- 云里面的每个白色正方形的盒子代表一个节点——Node。
- 在一个或者多个节点直接,多个绿色小方块组合在一起形成一个ElasticSearch的索引。
- 在一个索引下,分布在多个节点里的绿色小方块称为分片——Shard。
- 一个ElasticSearch的Shard本质上是一个Lucene Index。
- 在Lucene里面有很多小的segment,我们可以把它们看成Lucene内部的mini-index。里面存储的是倒排文件、词典、缓存等。
3.1 lucene
Lucene 只是一个工具包,它不是一个完整的全文检索引擎,它实现了倒排索引的查询结构。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。
目前以 Lucene 为基础建立的开源可用全文搜索引擎主要是 Solr 和 Elasticsearch。
Solr 和 Elasticsearch 都是比较成熟的全文搜索引擎,能完成的功能和性能也基本一样。但是 ES 本身就具有分布式的特性和易安装使用的特点,而Solr的分布式需要借助第三方来实现,例如通过使用zookeeper来达到分布式协调管理。
3.2 集群(cluster)与节点(node)
ES集群由一个或多个Elasticsearch节点组成。
一个Elasticsearch服务启动实例就是一个节点(Node)。
一个Elasticsearch服务启动实例就是一个节点(Node)。节点通过node.name来设置节点名称,如果不设置则在启动时给节点分配一个随机通用唯一标识符作为名称。
Zen Discovery是Elasticsearch的内置默认发现模块
假设这时节点Node1服务宕机了或者网络不可用了,那么主节点上主分片S0也就不可用了。幸运的是还存在另外两个节点能正常工作,这时ES会重新选举新的主节点,而且这两个节点上存在我们的所需要的S0的所有数据,我们会将S0的副本分片提升为主分片,这个提升主分片的过程是瞬间发生的。此时集群的状态将会为 yellow。
3.3 分片(shards)
ES支持PB级全文搜索,当索引上的数据量太大的时候,ES通过水平拆分的方式将一个索引上的数据拆分出来分配到不同的数据块上,拆分出来的数据库块称之为一个分片。
shard = hash(routing) % number_of_primary_shards
分片的数量和副本数量都是可以通过创建索引时的settings来配置。
PUT /myIndex
{
"settings" : {
"number_of_shards" : 5,
"number_of_replicas" : 1
}
}
这类似于MySql的分库分表,只不过Mysql分库分表需要借助第三方组件而ES内部自身实现了此功能。
routing值是一个任意字符串,它默认是_id但也可以自定义。这个routing字符串通过哈希函数生成一个数字,然后除以主切片的数量得到一个余数(remainder),余数的范围永远是0到number_of_primary_shards - 1,这个数字就是特定文档所在的分片。
这也解释了为什么主分片的数量只能在创建索引时定义且不能修改:如果主分片的数量在未来改变了,所有先前的路由值就失效了,文档也就永远找不到了。
3.4 副本(Replicas)
副本就是对分片的Copy。
将数据分片是为了提高可处理数据的容量和易于进行水平扩展,为分片做副本是为了提高集群的稳定性和提高并发量。索引的分片数和副本数也不是越多越好
每个主分片都有一个或多个副本分片,当主分片异常时,副本可以提供数据的查询等操作。主分片和对应的副本分片是不会在同一个节点上的,所以副本分片数的最大值是 n -1(其中n为节点数)。
3.5 段(Segment)
索引文件被拆分为多个子文件,则每个子文件叫作段, 每一个段本身都是一个倒排索引,并且段具有不变性,一旦索引的数据被写入硬盘,就不可再修改。
段的概念提出主要是因为:在早期全文检索中为整个文档集合建立了一个很大的倒排索引,并将其写入磁盘中。如果索引有更新,就需要重新全量创建一个索引来替换原来的索引。这种方式在数据量很大时效率很低,并且由于创建一次索引的成本很高,所以对数据的更新不能过于频繁,也就不能保证时效性。
索引文件分段存储并且不可修改,那么新增、更新和删除如何处理呢?
3.6 映射(Mapping)
映射是用于定义ES对索引中字段的存储类型、分词方式和是否存储等信息,就像数据库中的 schema 。
对字段类型根据数据格式自动识别的映射称之为动态映射(Dynamic mapping),我们创建索引时具体定义字段类型的映射称之为静态映射或显示映射(Explicit mapping)。
映射是用于定义ES对索引中字段的存储类型、分词方式和是否存储等信息,就像数据库中的 schema ,描述了文档可能具有的字段或属性、每个字段的数据类型。只不过关系型数据库建表时必须指定字段类型,而ES对于字段类型可以不指定然后动态对字段类型猜测,也可以在创建索引时具体指定字段的类型。
3.7 写入数据
- 客户端向ES1节点(协调节点)发送写请求,通过路由计算公式得到值为0,则当前数据应被写到主分片S0上。
- ES1节点将请求转发到S0主分片所在的节点ES3,ES3接受请求并写入到内存。
- 并发将数据复制到两个副本分片R0上,一旦所有的副本分片都报告成功,则节点ES3将向协调节点报告成功,协调节点向客户端报告成功。
- 达到默认时间或达到一定量后,会触发refresh,由内存刷新到文件缓存系统生成新的段,这时数据才能被检索到;再然后才被刷新到磁盘。
延迟写策略
每当有新增的数据时,就将其先写入到内存中,在内存和磁盘之间是文件系统缓存,当达到默认的时间(1秒钟)或者内存的数据达到一定量时,会触发一次刷新(Refresh),将内存中的数据生成到一个新的段上并缓存到文件缓存系统 上,稍后再被刷新到磁盘中并生成提交点。
这里的内存使用的是ES的JVM内存,而文件缓存系统使用的是操作系统的内存。新的数据会继续的被写入内存,但内存中的数据并不是以段的形式存储的,因此不能提供检索功能。由内存刷新到文件缓存系统的时候会生成了新的段,并将段打开以供搜索使用,而不需要等到被刷新到磁盘。
在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh (即内存刷新到文件缓存系统)。 默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是近实时搜索,因为文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。我们也可以手动触发 refresh,POST /_refresh 刷新所有索引,POST /nba/_refresh刷新指定的索引。
4 elasticsearch的使用
4.1 index api
检查
GET /_cat/health?v
查看索引
GET /_cat/indices?v
创建索引
PUT /test_index?pretty
删除索引
DELETE /test_index
4.2 doc api
创建文档
PUT /index/type/id
{
"json数据"
}
根据id获取文档
GET /index/Type/id
更新文档
POST /index/type/id/_update
{
"doc": {
"name": “xxxxx"
}
}
删除文档
DELETE /index/type/id
4.3 search api
查询方式
Term query(不分词)
Range query(范围)
Exists query(字段有值)
Wildcard query(通配符)
…
匹配方式
match
match_phrase
组合查询
Bool query(布尔,加权得分)
Dis_max query(分离最大化,最大得分)
Nested query(嵌套)
…
Match
对搜索词分词
任一term匹配就会被返回
Match phrase
对搜索词分词
match_phase中的所有term都出现在待查询字段之中
待查询字段之中的所有term都必须和match_phase具有相同的顺序
4.4 bool query
must: 匹配must下查询条件的doc会被返回
must_not: 匹配must_not下查询条件的doc不会被返回
should: 至少匹配should下一个查询条件的doc会被返回
filter: 匹配filter下查询条件的doc会被会被返回。
4.5 query & filter
filter:仅仅只是按照搜索条件过滤出需要的数据而已,不计算任何相关度分数,对相关度没有任何影响
query:会去计算每个文档相对于搜索条件的相关度,并按照相关度进行排序
filter的性能比query的性能高,es通常是先执行filter,之后执行query,先把query的执行集缩小,这样可以提高query的执行效率。
GET /_search
{
"query": {
"filtered": {
"query": { "match": { "email": "business opportunity" }},
"filter": { "term": { "folder": "inbox" }}
}
}
}
因为filter不计算相关性得分,不排序,而且会缓存,所以:filter的性能比query的性能高,es通常是先执行filter,之后执行query,先把query的执行集缩小,这样可以提高query的执行效率。
我们通常把不影响排序的查询条件放到filter里面
4.6 排序、高亮
高亮,不同字段设置不同的标签
number_of_fragments fragment 是指一段连续的文字。返回结果最多可以包含几段不连续的文字。默认是5。
fragment_size 一段 fragment 包含多少个字符。默认100。
no_match_size 即使字段中没有关键字命中,也可以返回一段文字,该参数表示从开始多少个字符被返回。
4.7 聚合(Aggregations)
类似于SQL语言中的group by,avg,sum等函数
桶(Buckets):符合条件的文档的集合,相当于SQL中的group by。比如,在users表中,按“地区”聚合,一个人将被分到北京桶或上海桶或其他桶里;按“性别”聚合,一个人将被分到男桶或女桶
指标(Metrics):基于Buckets的基础上进行统计分析,相当于SQL中的count,avg,sum等。比如,按“地区”聚合,计算每个地区的人数,平均年龄等
4.8 脚本(script)
查询,对查询结果做二次处理,返回我们想要的格式
更新,过滤符合条件的数据并批量修改
4.9 管道(pipeline)
pipeline 就是在文档写入数据节点之前进行一系列的数据预处理
例:添加数据插入时间字段的脚本
4.10 自定义分词器
Character Filters(字符过滤器):
针对原始文本处理,比如增加,删除及替换字符串;
0个或多个;
html_strip mapping pattern_replace
Tokenizer(分词器):
按照规则分为单词;有且只有一个;
standard ngram whitespace keyword
ik pinyin dynamic_synonym
Token Filters(标记过滤器):
将切分的单词进行加工,小写,删除stopwords,
过滤,增加同义词; 0个或多个;
lowercase uppercase length
5 应用与扩展
5.1 应用
- 当mysql中数据量比较大时模糊查询会很慢,
-- 于是将查询字段同步到es中进行检索
- 要求实现一次搜索可以同时实现拼音、同义词、模糊、分词检索 ;还可以进行检索提示与纠错
-- 在一个字段上使用多种分词方式;使用字段别名实现跨索引检索;通过拼音及前置匹配等方式实现检索提示与纠错
5.2 扩展
- 多shard场景下相关度分数(relevance score)不准确问题
-- es默认在一个shard中统计的,不是索引里所有的primary shard,当数据分布不平均的时候,就可能出现在一个shard里的得分高但在全部shard的得分却低
-- 数据尽量平均分布;主分片数设为1;搜索附带search_type=dfs_query_then_fetch参数
- 查询结果“震荡”问题
-- 主分片和副本分片可能不一致,导致最终在主分片和副本分片上计算得到的得分不同,而导致最终的查询结果不一致。
-- 用preference参数指定分片查询的优先级,可以通过该参数来控制搜索时的索引数据分片。
-
深度分页问题
-
性能调优
出处:
https://blog.csdn.net/qq_16162981/article/details/70142166
https://www.cnblogs.com/jajian/p/11223992.html
https://www.elastic.co/guide
(欢迎转载,但必须在文章页面明显位置给出原文链接)