介绍和使用nginx 的记录

查询建议

It is also known as Search as you type or Type Ahead Search. It helps in navigating or guiding a user by prompting them with likely completions and alternatives to the text as they are typing it. It reduces the amount of character a user needs to type before executing any search actions, thereby enhancing the search experience of users.

查询建议是为了给用户提供良好的使用体验。主要包括拼写检查, 自动补全。

  • 读时分词: 用户查询的时候, ES 会对用户输入的关键词进行分词,分词的结果存在于内存中,当查询结束后,分词结果也消失。
  • 写时分词: 发生在文档写入时, ES 会对文档进行分词,将解决存入倒排索引,这部分最终以文件的形式存储在磁盘上。

ES 中处理分词部分叫做分词器,Analyzer。ES 自带了许多默认的分词器,比如 standard,keyword,whitespace。读和写时候都要指定使用的分词器。

analysis 包括 analyzer,tokenizer 和filter 等不同的组件。

Edge NGram Tokenizer

The edge_ngram tokenizer first breaks text down into words whenever it encounters one of a list of specified characters, then it emits N-grams of each word where the start of the N-gram is anchored to the beginning of the word. Edge N-Grams are useful for search-as-you-type queries.

和 Completion Suggester 的区别

When you need search-as-you-type for text which has a widely known order, such as movie or song titles, the completion suggester is a much more efficient choice than edge N-grams. Edge N-grams have the advantage when trying to autocomplete words that can appear in any order. 如果数据有特定的order,那么使用 completion suggester;否则可以考虑使用 Edge NGram Tokenizer。就家具搜索而言,更加适合使用 Edge NGram Tokenizer。 (这点不是很理解)

Usually we recommend using the same analyzer at index time and at search time. In the case of the edge_ngram tokenizer, the advice is different. It only makes sense to use the edge_ngram tokenizer at index time, to ensure that partial words are available for matching in the index. At search time, just search for the terms the user has typed in, for instance: Quick Fo. 简单说 edge_ngram 使用在index time

Suggester 介绍

下面的分类至少是没有错的,分成term suggester phrase suggester 和 completion suggester

  1. term suggester
  1. phrase suggester

  2. Completion Suggester

Completion Suggester

The completion suggester provides auto-complete/search-as-you-type functionality. This is a navigational feature to guide users to relevant results as they are typing, improving search precision. It is not meant for spell correction or did-you-mean functionality like the term or phrase suggesters. Completion Suggester 提供了 auto-complete 和 search as you type 功能。当用户输入的时候,提供一个相关的结果,有利于提高 search 的准确性。这个功能不是 spell correction 或者 did you mean (是由 term suggester 和 phrase suggester 实现)

Ideally, auto-complete functionality should be as fast as a user types to provide instant feedback relevant to what a user has already typed in. Hence, completion suggester is optimized for speed. The suggester uses data structures that enable fast lookups, but are costly to build and are stored in-memory. (理解事物是从定义开始, auto complete 是在用户输入的时候提供及时的反馈,返回相应的内容;相当消耗内存)

这个里面是可以设置权重的,然后根据权重返回结果。

Elasticsearch 入门教程 – completion suggest实现搜索提示

虽然是 14年的博客,但是写的不错。

completion,es实现的时候,是非常高性能的,会构建不是倒排索引,也不是正拍索引,就是纯的用于进行前缀搜索的一种特殊的数据结构,而且会全部放在内存中,所以auto completion进行的前缀搜索提示,性能是非常高的

Multi-field Partial Word Autocomplete in Elasticsearch Using nGrams

There are at least two broad types of autocomplete, what I will call Search Suggest, and Result Suggest. Search Suggest returns suggestions for search phrases, usually based on previously logged searches, ranked by popularity or some other metric. This is what Google does, and it is what you will see on many large e-commerce sites. This approach requires logging users' searches and ranking them so that the autocomplete suggestions evolve over time. There really isn’t a good way to implement this sort of feature without logging searches, which is why DuckDuckGo doesn’t have autocomplete. It also suffers from a chicken-and-egg problem in that it will not work well to begin with unless you have a good set of seed data. The second type of autocomplete is Result Suggest. In this case the suggestions are actual results rather than search phrase suggestions. An example of this is the Elasticsearch documentation guide. This style of autocomplete works well with a reasonably small data set, and it has the advantage of not requiring a large set of previously logged searches in order to be useful. In this post I’m going to describe a method of implementing Result Suggest using Elasticsearch. 从大的角度看有两种 autocomplete,一种是基于用户log 的方式,search suggest,另一种是 result suggest,其中后者是比较简单的。搜索不仅仅是单纯的检索,而是带有用户log的检索,这个和推荐还是有点重合,但需要有用户的log,才能做到 search suggest。 而我们当前的阶段更像是一种

result suggest 应该是下面这种:

需要的几点注意事项

  • Single field
  • Duplicate data
  • Prefix query only
  • No filtering, or advanced queries

建立 word autocompete 一般需要满足的要求

  • Partial word matching (es 中的autocompletion 是前缀匹配)
  • Multiple search fields (es 中的search filed 是单个的 )
  • filtered search (一般来说可能需要去重策略)

There are edgeNGram versions of both, which only generate tokens that start at the beginning of words (“front”) or end at the end of words (“back”). edgeNGram的定义

Quick and Dirty Autocomplete with Elasticsearch Completion Suggest

We realize, of course, that you might have a concern with this approach—such as data duplication. Remember, this is a quick and dirty method for implementing autocomplete. 这个的缺点是 data duplication。

设置 completion 的时候重点需要在mapping 中设置一个type 为 completion 的东西。

1
2
3
4
5
6
7
8
        "keyword_suggestion": {
          "type": "completion",
          "analyzer": "hanLp-index",
          "search_analyzer": "simple",
          "preserve_separators": true,
          "preserve_position_increments": true,
          "max_input_length": 50
        }

从数据的添加,completion 的设置 到 post 检索都有完整的代码

ES 搜索建议 Suggester 的问题

在建议词字段,建议词索引时使用 HanLP 分析器,搜索时使用 Simple 分析器。 就是对于中文搜索而言,是可以参考这种建议的

Completion Suggester,它主要针对的应用场景就是"Auto Completion"。 此场景下用户每输入一个字符的时候,就需要即时发送一次查询请求到后端查找匹配项,在用户输入速度较高的情况下对后端响应速度要求比较苛刻。索引并非通过倒排来完成,而是将analyze过的数据编码成FST和索引一起存放。对于一个open状态的索引,FST会被ES整个装载到内存里的,进行前缀查找速度极快。但是FST只能用于前缀查找,这也是Completion Suggester的局限所在

面向中文的,暂时不考虑。

Elasticsearch系列—前缀搜索和模糊搜索

很优秀的博客,不仅有讲解还有code。

  1. 前缀搜索语法

我们常见的可能有前缀搜需求的有邮编、产品序列号、快递单号、证件号的搜索,这些值的内容本身包含一定的逻辑分类含义 所以这种情况下是优先使用前缀搜索语法

  1. 通配符和正则搜索

通配符搜索和正则表达式搜索跟前缀搜索类型,只是功能更丰富一些。

  1. 即时搜索
  1. ngram

ngrams其实就是拆分关键词的一个滑动窗口,窗口的长度可以设置,我们拿"Elastic"举例,7种长度下的ngram:

1
2
3
4
5
6
7
长度1:[E,l,a,s,t,i,c]
长度2:[El,la,as,st,ti,ic]
长度3:[Ela,las,ast,sti,tic]
长度4:[Elas,last,asti,stic]
长度5:[Elast,lasti,astic]
长度6:[Elasti,lastic]
长度7:[Elastic]

还有一种特殊的edge ngram,拆词时它只留下首字母开头的词,如下:

1
2
3
4
5
6
7
长度1:E
长度2:El
长度3:Ela
长度4:Elas
长度5:Elast
长度6:Elasti
长度7:Elastic
  1. 搜索提示

执行搜索时,Elasticsearch从图的开始处顺着匹配路径一个字符一个字符地进行匹配,一旦它处于用户输入的末尾,Elasticsearch就会查找所有可能结束的当前路径,然后生成一个建议列表,并且把这个建议列表缓存在内存中。

性能方面completion suggest比任何一种基于词的查询都要快很多。

模糊搜索

fuzzy搜索可以针对输入拼写错误的单词,有一定的纠错功能,示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
GET /music/children/_search
{
  "query": {
    "fuzzy": {
      "name": {
        "value": "teath",
        "fuzziness": 2
      }
    }
  }
}

笔记二十八:自动补全与机遇上下文的提示

给出了两种自动补全的方式,有相关的code,是可以参考一下的

  1. 使用 Completion Suggester 一些步骤
  • 定义 Mapping,使用 “completion” type
  • 索引数据
  • 运行 “suggest” 查询,得到搜索建议
  1. Context Suggester

Completion Suggester 的扩展 可以在搜索中加入耕读偶读上下文信息,例如,输入 “star” 咖啡相关:starbucks 电影相关:star wars

  1. 需要考虑的事项
  • 精准度 Completion > Phrase > Term
  • 召回率 Term > Phrase > Completion
  • 性能 Completion > Phrase > Term

基于 completion suggest 实现搜索提示

使用一小段code 来讲解了一个知识点

一种用于前缀搜索的特殊数据结构,不是我们之前利用的倒排索引,会全部放在内存中,所以 auto completion 进行的前缀搜索提示,性能是非常高的

ElasticSearch搜索提示(Suggester)

感觉讲解的一般。

ES Suggesters基本的运作原理是将输入的文本分解为token,然后在索引的字典里查找相似的term并返回。 根据使用场景的不同,Elasticsearch里设计了4种类别的Suggester,分别是:

  • Term Suggester
  • Phrase Suggester
  • Completion Suggester
  • Context Suggester
  1. Term Suggester

两个term的相似性是如何判断的? ES使用了一种叫做Levenstein edit distance的算法,其核心思想就是一个词改动多少个字符就可以和另外一个词一致。 Term suggester还有其他很多可选参数来控制这个相似性的模糊程度,这里就不一一赘述了。Term suggester正如其名,只基于analyze过的单个term去提供建议,并不会考虑多个term之间的关系。

  1. Phrase suggester

Phrase suggester在Term suggester的基础上,会考量多个term之间的关系,比如是否同时出现在索引的原文里,相邻程度,以及词频等等

ElasticSearch搜索建议与上下文提示

博客从上面四个方面都给出了一个小的例子,属于很好的博客。

  • Term Suggester
  • Phrase Suggester
  • Complete Suggester
  • Context Suggester

资料整理

Elasticsearch Suggester 学习

Suggesters基本的运作原理是将输入的文本分解为token,然后在索引的字典里查找相似的term并返回。 根据使用场景的不同,Elasticsearch里设计了4种类别的Suggester,分别是:

  • Term Suggester
  • Phrase Suggester
  • Completion Suggester
  • Context Suggester
  1. Term Suggester

(这个是有code,如果需要可以学习一下)

有人可能会问,两个term的相似性是如何判断的? ES使用了一种叫做Levenstein edit distance的算法,其核心思想就是一个词改动多少个字符就可以和另外一个词一致。 Term suggester还有其他很多可选参数来控制这个相似性的模糊程度,这里就不一一赘述了。

Term suggester正如其名,只基于analyze过的单个term去提供建议,并不会考虑多个term之间的关系。API直接给出和用户输入文本相似的内容? 答案是有,这就要求助Phrase Suggester了

  1. Phrase Suggester

Phrase suggester在Term suggester的基础上,会考量多个term之间的关系,比如是否同时出现在索引的原文里,相邻程度,以及词频等等。

分页功能

Elasticsearch——分页查询From&Size VS scroll

  1. from-size"浅"分页

“浅"分页的概念是小博主自己定义的,可以理解为简单意义上的分页。它的原理很简单,就是查询前20条数据,然后截断前10条,只返回10-20的数据。这样其实白白浪费了前10条的查询。

1
2
3
4
5
6
{
    "from" : 0, "size" : 10,
    "query" : {
        "term" : { "user" : "kimchy" }
    }
}

其中,from定义了目标数据的偏移值,size定义当前返回的事件数目。 默认from为0,size为10,即所有的查询默认仅仅返回前10条数据。

from/size的鸡肋在于深度分页的时候,from+size值不能大于index.max_result_window参数(默认10000),否则会直接返回错误。

如果搜索size大于10000,需要设置index.max_result_window参数 ,默认为1000

1
2
3
4
5
6
PUT _settings
{
    "index": {
        "max_result_window": "10000000"
    }
}   
  1. scroll“深”分页

相对于from和size的分页来说,使用scroll可以模拟一个传统数据的游标,记录当前读取的文档信息位置。这个分页的用法,不是为了实时查询数据,而是为了一次性查询大量的数据(甚至是全部的数据)。

Elastic Search之分页展示

es提供了三种api方式进行分页:

  • from size
  • scroll
  • search_after
  1. from size

from/size的鸡肋在于深度分页的时候,from+size值不能大于index.max_result_window参数(默认10000),否则会直接返回错误。

在数据分片存储的情况下,页数越深,处理的文档就越多,占用的堆内存也就越大,耗时越长,效率越低,因此es有max_result_window参数来限制深度分析。

  1. scroll

scroll由于数据不是实时性的,所以不能用来做实时搜索,同时官方也建议尽量不要使用复杂的sort条件,使用_doc最高效。 不是实时搜索,可以大量返回数据。

  1. search_after

由于scroll不适用于实时搜索,因此可以用search_after来替代。search_after上一页的最后一条数据来获取下一页的,所以只能进行"下一页"翻页,而不能上一页,也不能进行自由翻页,且为了定位上一页的最后一条数据,需要保证每个文档值的唯一,比如文档的”_id"字段,search_after可以做实时搜索。

问题在于不能自动实现 向上翻页

elasticsearch 分页查询实现方案

  1. from+size 实现分页

示例:有三个节点node1、node2、node3,每个节点上有2个shard分片

1
2
3
4
5
1.client发送分页查询请求到node1(coordinating node)上,node1建立一个大小为from+size的优先级队列来存放查询结果;
2.node1将请求广播到涉及到的shards上;
3.每个shards在内部执行查询,把from+size条记录存到内部的优先级队列(top N表)中;
4.每个shards把缓存的from+size条记录返回给node1;
5.node1获取到各个shards数据后,进行合并并排序,选择前面的 from + size 条数据存到优先级队列,以便 fetch 阶段使用。

内部执行原理: 示例:有三个节点node1、node2、node3,每个节点上有2个shard分片

node1 node2 node3
shard1 shard3 shard5
shard2 shard4 shard6

from+size在深度分页时,会带来严重的性能问题: CPU、内存、IO、网络带宽 数据量越大,越往后翻页,性能越低

  1. scroll

可以把 scroll 理解为关系型数据库里的 cursor,因此,scroll 并不适合用来做实时搜索,而更适用于后台批处理任务,比如群发。可以把 scroll 分为初始化和遍历两步.

可以把 scroll 分为初始化和遍历两步, 初始化时将所有符合搜索条件的搜索结果缓存起来,可以想象成快照, 遍历时,从这个快照里取数据,也就是说,在初始化后对索引插入、删除、更新数据都不会影响遍历结果。

  • 初始化:
1
2
3
4
5
POST http://192.168.18.230:9200/bill/bill/_search?scroll=3m
{
    "query": { "match_all": {}},
    "size": 10 
}
  • 遍历:
1
2
3
4
POST http://192.168.18.230:9200/_search?scroll=3m
{
  "scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAHRCFi1BLWIzSHdhUkl1cC1rcjBueVhJZUEAAAAAAAB0QRYtQS1iM0h3YVJJdXAta3IwbnlYSWVBAAAAAAAAdEQWLUEtYjNId2FSSXVwLWtyMG55WEllQQAAAAAAAHRDFi1BLWIzSHdhUkl1cC1rcjBueVhJZUEAAAAAAAB0RRYtQS1iM0h3YVJJdXAta3IwbnlYSWVB"
}

(这个教程讲解是比较详细的, step by step)

  1. search after

Scroll 被推荐用于深度查询,但是contexts的代价是昂贵的,不推荐用于实时用户请求,而更适用于后台批处理任务,比如群发。

search_after 提供了一个实时的光标来避免深度分页的问题,其思想是使用前一页的结果来帮助检索下一页。

search_after 需要使用一个唯一值的字段作为排序字段,否则不能使用search_after方法; 推荐使用_uid 作为唯一值的排序字段.

1
2
3
4
5
6
7
8
9
GET twitter/tweet/_search
{
    "size": 10,
    "query": { "match_all": {}},
    "sort": [
        {"date": "asc"},
        {"_uid": "desc"}
    ]
}

} 每一条返回记录中会有一组 sort values ,查询下一页时,在search_after参数中指定上一页返回的 sort values

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
GET twitter/tweet/_search
{
    "size": 10,
    "query": { "match_all": {}},
    "search_after": [1463538857, "tweet#654323"],
    "sort": [
        {"date": "asc"},
        {"_uid": "desc"}
    ]
}

注意:search_after不能自由跳到一个随机页面,只能按照 sort values 跳转到下一页

  1. 总结
  • 深度分页不管是关系型数据库还是Elasticsearch还是其他搜索引擎,都会带来巨大性能开销,特别是在分布式情况下。
  • 有些问题可以考业务解决而不是靠技术解决,比如很多业务都对页码有限制,google 搜索,往后翻到一定页码就不行了。
  • scroll 并不适合用来做实时搜索,而更适用于后台批处理任务,比如群发。
  • search_after不能自由跳到一个随机页面,只能按照 sort values 跳转到下一页。

https://cloud.tencent.com/developer/article/1122833

深度的分页数据应该怎么办? es里面提供了两种方式来读取深度分页的数据:

  • 离线的读取深度分页数据的Scroll方法
  • 能够用于实时和高并发场景的searchAfter方法(5.x之后)

它的缺点就是维护一个search context需要占用很多资源,而且在快照建立之后数据变化如删除和更新操作是不能被感知到的,所以不能够用于实时和高并发的场景。searchAfter的方式通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景。

它的缺点是不能够随机跳转分页,只能是一页一页的向后翻,并且需要至少指定一个唯一不重复字段来排序。

此外还有一个与scorll的不同之处是searchAfter的读取数据的顺序会受索引的更新和删除影响而scroll不会,因为scroll读取的是不可变的快照。

我们先查询一页数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
GET twitter/_search
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "sort": [
        {"date": "asc"},
        {"_id": "desc"}
    ]
}

注意,上面用了两个字段来排序,第一个是业务字段可能不唯一,但是第二个id字段一定唯一不重复的。只有这样才能确保searchAfter的翻页顺序读取。

另外searchAfter的from字段一定要设置成0,不然会有问题。

第一个请求发出之后,我们需要获取第一个请求里面最后一条的数据的date和id,然后把这个信息传送到下一个批次,依次类推直到把所有的数据处理完。

如下第二个请求的查询体:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
GET twitter/_search
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "search_after": [1463538857, "654323"],
    "sort": [
        {"date": "asc"},
        {"_id": "desc"}
    ]
}

所以,现在的基本思路是使用 elasticsearch 中的 searchAfter 进行深度分页功能

ElasticSearch如何支持深度分页

分布式环境下的分页

  1. 服务端缓存——Scan and scroll API

  2. Search After

假设请求第一页的请求如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
GET twitter/tweet/_search
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "sort": [
        {"date": "asc"},
        {"_id": "desc"}
    ]
}

注意,这里为了避免sort字段相同值的导致排序不确定,这里增加了 _id 字段。 返回的结果会包含每个文档的sort字段的sort value。这个就是上面所说的 “live cursor”。

使用最后一个文档的sort value作为search after请求值,我们就可以这样子请求下一页结果了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
GET twitter/tweet/_search
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "search_after": [1463538857, "654323"],
    "sort": [
        {"date": "asc"},
        {"_id": "desc"}
    ]
}

注意到from变成了search_after了。现在是通过search_after来确定分页的开始位置。

search_after使用方式上跟scroll很像,但是相对于scroll它是无状态的(stateless),没有search context开销;而且它是每次请求都实时计算的,所以也没有一致性问题(相反,有索引变化的话,每次排序顺序会变化呢)。但是比起from+size方式,还是有同样的问题没法解决:就是只能顺序的翻页,不能随意跳页。

这个排序分页方案其实在app分页中大量使用。

由于app端的分页比较特殊,比如后台数据会近实时的发生变化,所以采用常规的分页算法 [(totalRecord + pageSize - 1) / pageSize] 是会有问题的。如果仍采用这种算法,当上推刷新时,就有可能加载到上一页已经看过的数据,比如用户当前正在看第2页的历史数据,如果此时后台数据源新增了一条数据,那么当用户继续上推操作查看第3页的历史数据时,就会把第2页的最后一条数据获取,并且会把该条数据作为第3页的第一条数据进行展示,这样是有问题的。所以在数据表设计时,需要在表中增加一个自增的orderId字段参与分页,然后分页时,需要将第一页的最后一条数据的orderId回传到后台,后台拿着这个orderId进行条件判断查询并且集合上面的分页算法就可以避免上面的问题(在新闻类的app中,经常使用createdTime作为orderId)。另一方便,app的滑动翻页其实就是顺序翻页,所以特别适合这种分页方式。

这一部分其实没有很懂

  1. 终极解决方案?

这篇文章: Efficiently Handling Deep Pagination In A Distributed Search Engine 介绍了一种方式,可以以牺牲一定的分页准确性来大幅度的提高分页性能,有点意思。 这里简单介绍

思路非常简单,效果非常明显,有机会的话可以试试。不过前提是底层存储引擎支持指定返回排序数据的大小。Solr是支持的(shards.rows)这篇文章也是基于Solr实现的,但是ES目前并不支持。

highlight高亮搜索 高亮搜索从功能的角度来说就比较蛋疼,比较鸡肋,可有可无的功能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
GET /_search
{
    "query" : {
        "match": { "content": "kimchy" }
    },
    "highlight" : {
        "fields" : {
            "content" : {}
        }
    }
}

autocomplete

  1. 下面是一套的教程

Autocomplete with Elasticsearch - Part 1: Prefix Queries

比较基础的 autocompletion

Autocomplete with Elasticsearch - Part 2: Index-Time Search-as-You-Type

Edge NGram Tokenizer

That’s why Elasticsearch refers to it as Index-Time Search-as-You-Type method. 是在index time 时候进行的预处理,感觉更像是 term 级别的。

Autocomplete with Elasticsearch - Part 3: Completion Suggester

这个应该是工业界使用的方式,深入试试,理解操作,感觉是没有什么问题的。

Autocompletion for Public Transportation

这个是一个project,看看理解一下就行

  1. es_simple_autocomplete_example_config

基于 n-gram 实现的 autocomplete。这个是code example ,可以好好学习一下,简单的demo (可以尝试)

1
2
# Make sure everything is indexed
curl -X POST "http://localhost:9200/autocomplete_test/_refresh"

Elasticsearch: Using Completion Suggester to build AutoComplete

这个是教程方面,没用

Building-a-search-engine-using-Elasticsearch

这个是建立后端服务器的过程,非常好的参考方案,里面实现了响应 index.html 和 autocomplete 的响应。非常好的资料

Building-a-search-engine-using-Elasticsearch

这个是建立后端服务器的过程,非常好的参考方案,里面实现了响应 index.html 和 autocomplete 的响应。

对应服务器上的项目: es_search_engine/

A detailed comparison between autocompletion strategies in ElasticSearch

1
2
3
4
Approach #1: Prefix Query + Aggregations
Approach #2: NGram Analyzer + Aggregations
Approach #3: Completion Suggester
Approach #4: Separate Index

文章总结了四种方式

Enriching your Elasticsearch Autocomplete with Context Suggesters

The major limitation of the basic completion suggester is that it looks for all documents in the index. However, you often want to serve suggestions based on some categories or criteria. For example, you may want to suggest film titles filtered by directors or you want to boost films titles based on their genre. 这个说明了为什么使用 context suggester 而不是 basic suggester

The major limitation of the basic completion suggester is that it looks for all documents in the index. However, you often want to serve suggestions based on some categories or criteria. For example, you may want to suggest film titles filtered by directors or you want to boost films titles based on their genre. 这个例子确实是没有弄很懂

能够run 起来,是非常不错的教程

这个文章的本意是不错的,给定了 context,然后去限定 suggester 的范围。(感觉有点不适用于我们的范围,但是是很好的例子,因为能够run 起来的项目不多了)

从文章的流程角度说,先是发送一个 suggest 的search 请求;如果行的话,那么再发送一个 真正的 search 请求。

新的资料

ElasticSearch7.7 suggesters 教程(最新,详解)

在 suggester 中可以设置 skip_duplicates =True 这样就过滤掉了重复的建议

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST news/_doc/7
{
  "title": "鄱阳湖",
  "body": "我家在鄱阳湖"
}

POST news/_search
{
  "suggest": {
    "YOUR_SUGGESTION_1": {
      "text": "鄱阳",
      "completion": {
        "field": "title.kw"
      }
    },
    "YOUR_SUGGESTION_2": {
      "text": "鄱阳",
      "completion": {
        "field": "title.kw",
        "skip_duplicates": true
      }
    }
  }

当设置为真时,这个选项会降低搜索速度,因为需要访问更多的建议才能找到顶部N

模糊搜索(Fuzzy queries)

完成建议器也支持模糊查询,这意味着你可以在你的搜索中有一个错误,但仍然可以得到结果

例如下面的两种方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
POST news/_search
{
  "suggest": {
    "YOUR_SUGGESTION": {
      "text": "鄱a",
      "completion": {
        "field": "title.kw",
        "fuzzy": {
          "fuzziness": 1
        }
      }
    }
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
POST news/_search
{
  "suggest": {
    "YOUR_SUGGESTION": {
      "text": "1鄱1",
      "completion": {
        "field": "title.kw",
        "skip_duplicates": true,
        "fuzzy": {
          "prefix_length": 0,
          "fuzziness": 2
        }
      }
    }
  }
}

也是支持正则匹配 (这部分不是需要的,可以不增加相应的功能)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST news/_search
{
  "suggest": {
    "YOUR_SUGGESTION": {
      "regex": "[p|w]y",
      "completion": {
        "field": "title.py"
      }
    }
  }
}


POST news/_search
{
  "suggest": {
    "YOUR_SUGGESTION": {
      "regex": "[p|w]",
      "completion": {
        "field": "title.py"
      }
    }
  }
}

ElasticSerach 7 教程

这个是针对 ES7 极好的翻译教程,可以认真学习一下。对于理解原理概念是非常有帮助的,需

我知道使用什么方式了, 使用关键词(标签和抽取的关键词 作为 suggest 的field,然后全文检索的时候,使用 真正的 全文text)

分布式方面的东西:

手把手带你体验ElasticSearch,了解当下最火的分布式搜索引擎。

Elasticsearch Suggester API(自动补全)

 新调研结果

Pinned Query x-pack

是7.8 版本的新功能,不是免费版。

Building-a-search-engine-using-Elasticsearch 项目分析

好好分析一下这个项目

(第一层目录已经完成了)

  1. scraper.py
1
2
            post_url = "http://localhost:9200/hacker/tutorials"
            post_autocomplete_url ="http://localhost:9200/autocomplete/titles"

总结:

  • 分成两个数据库(一个用于检索,一个用于autocomplete)
  • 写个脚本自动可以数据库的 (发送request)
  1. create_new_index.py

这个是create new index 的python 文件,内容比较简单。

  1. app.py

修改代码后需要手动启动服务器,如果设置 debug=True 表示开启debug 模式,那么可以自动加载。

如果想要外网访问:

1
app.run(host='0.0.0.0', port=80)

(如果是阿里云,那么注意设置安全组)

默认是 127.0.0.1 着这样的话只能是在本机进行访问。

(第二层目录)

routes search.py

  1. 关于blueprint 的学习

blueprint最主要的是解决路由的问题 (flask学习笔记之blueprint), 以下的一个demo。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. 创建 blueprint
from flask import Blueprint
 
admin_bp = Blueprint('admin', __name__)
 
@admin_bp.route('/index')
def index(name):
    return '<h1>Hello, this is admin blueprint</h1>'

# 2. 注册blueprint

from flask import Flask
from admin.admin_module import admin_bp
 
app = Flask(__name__)
app.register_blueprint(admin_bp, url_prefix='/admin')
 
if __name__ == '__main__':
    app.run(debug=True)

# 3. 之后就可以访问  http://localhost:5000/admin/index 

所谓的路由 就是一个个访问的路径。

如果没有使用 blueprint,那么一般是这样进行访问

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run()
  1. Flask 中的模板 render_template
如果是非常简单的 html 函数,可以简单一写。 但如果是很复杂html,那么就无法使用这样的方式。

所以引入了 render_temple 模板进行处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from flask importrender_template
from app importapp

@app.route('/')
@app.route('/index')
def index():
    user = {'nickname': 'Miguel'} # fake user
    return render_template("index.html",
        title = 'Home',
        user = user)     #这里模块里的第一个user指的是html里面的变量user,而第二个user指的是函数index里面的变量user

render_template的功能是对先引入index.html,同时根据后面传入的参数,对html进行修改渲染。Flask 为你配置 Jinja2 模板引擎。使用 render_template() 方法可以渲染模板,只需提供模板名称和需要作为参数传递给模板的变量就可简单执行。

其实不推荐使用 Flask 模板,更加推荐的是前后端分离。尽量不要使用 render_template 这种模式。

最简单的demo

1
2
3
4
5
6
7
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
   return '<html><body><h1>'Hello World'</h1></body></html>'
if __name__ == '__main__':
   app.run(debug = True)

search 中的字段直接设置多个字段,这样文字就不用整合到一起了? (设置多个fields)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
            payload = {
                "query": {
                    "query_string": {
                        "analyze_wildcard": True,
                        "query": str(search_term),
                        "fields": ["topic", "title", "url", "labels"]
                    }
                },
                "size": 50,
                "sort": [

                ]
            }

从这里发现的 autocomplete 和search是两个不同的数据库。(这样便于好更新,不同的log)

问题:当输入的时候自动返回了一些信息,发送了 request 去请求 autocomplete吗?

这个是去问前端的交互就ok了。

uwsgi-nginx-flask-docker

这个项目包含的内容是贼全,如果全部都能看完,那么python 就很厉害了

  1. 选择框架的问题

If you are starting a new project, you might benefit from a newer and faster framework based on ASGI instead of WSGI (Flask and Django are WSGI-based). You could use an ASGI framework like: FastAPI (which is based on Starlette) with this Docker image: tiangolo/uvicorn-gunicorn-fastapi. 如果是刚开始一个新的项目建议使用基于 ASCI 的框架(自动支持异步)

可以考虑这个框架 fastapi

About FastAPI framework, high performance, easy to learn, fast to code, ready for production

ES中的 aggregations

实战中使用的query 命令

以下命令是得到某个类别中所有的子类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
    "size": 0,
    "aggs":{
        "flight_dest":{
            "terms":{
                "field":"room_type"
            }
        }
    }
}

以下命令是使用多字段进行匹配,但是我绝得应该在bool 中 room_type 字段使用 must 去match,在其他的使用 should 去match,这样能保证出来的结果一定是特定的room type。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
    "query":{
        "bool":{
            "should":[
                {
                    "match":{
                        "room_type":"dining_room"
                    }
                },
                {
                    "match":{
                        "search_field":red
                    }
                }
            ]
        }
    },
    "sort":[
        {
            "_score":"desc"
        },
        {
            "_id":"desc"
        }
    ],
    "size":1000,
    "_source":[
        "img_url",
        "img_id",
        "search_field",
        "quality",
        "room_type",
        "style",
        "key_feature",
        "material"
    ]
}

我觉得使用以下的写法会效果更好。 将用户显性点击的room type 作为must,然后关键词匹配的结果作为 should 之类的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
GET bookdb_index/book/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "bool": {
            "should": [
              {"match": {"title": "Elasticsearch"}},
              {"match": {"title": "Solr"}}
            ]
          }
        },
        {
          "match": {"authors": "clinton gormely"}
        }
      ],
      "must_not": [
        {
          "match": {"authors": "radu gheorge"}
        }
      ]
    }
  }
}

关于bool查询中的should, 有两种情况:

  • 当should的同级存在must的时候,should中的条件可以满足也可以不满足,满足的越多得分越高
  • 当没有must的时候,默认should中的条件至少要满足一个

基于7.2 版本

  1. overview

使用Elasticsearch的过程中,除了全文检索,或多或少会做统计操作,而做统计操作势必会使用Elasticsearch聚合操作。

搜索引擎的搜索部分侧重于过滤和搜索,而聚合侧重于数据统计和分析。

ES中的聚合被分为两大类:Metric度量和bucket桶, metric很像SQL中的avg、max、min等方法,而bucket就有点类似group by了。

干货 | 通透理解Elasticsearch聚合

这个里面有code,可以按照code 去一步步理解这个过程。

  1. 实际场景

基于某特定分类的聚合统计结果。

基于月份的聚合统计结果。

  1. 分类

Metric聚合

基于一组文档进行聚合。所有的文档在一个检索集合里,文档被分成逻辑的分组。类比Mysql中的: MIN(), MAX(), STDDEV(), SUM() 操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
 1        单值Metric
 2                |
 3               v
 4SELECT AVG(price) FROM products
 5
 6
 7         多值Metric
 8          |          |
 9          v          v
10SELECT MIN(price), MAX(price) FROM products
11Metric聚合的DSL类比实现:
12{
13    "aggs":{
14        "avg_price":{
15            "avg":{
16                "field":"price"
17            }
18        }
19    }
20}

Metric聚合操作对比:

其中,Top hits子聚合用于返回分组中Top X匹配结果集,且支持通过source过滤选定字段值。

Bucketing聚合

基于检索构成了逻辑文档组,满足特定规则的文档放置到一个桶里,每一个桶关联一个key。类比Mysql中的group by操作,Mysql使用举例:

(这种其实就是我想要的,组合查询,满足多个条件的查询)

1
2
3
4
5
6
7
8
9
1           基于size 分桶 ...、
2SELECT size COUNT(*) FROM products GROUP BY size 
3
4+----------------------+
5| size     |  COUNT(*) |
6+----------------------+ 
7| S        |   123     | <--- set of rows with size = S
8| M        |   456     |
9| ...      |  ...      |

相应使用DSL 类比实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 1{
 2  "query": {
 3    "match": {
 4      "title": "Beach"
 5    }
 6  },
 7  "aggs": {
 8    "by_size": {
 9      "terms": {
10        "field": "size"
11      }
12    },
13    "by_material": {
14      "terms": {
15        "field": "material"
16      }
17    }
18  }
19}

(注意可以多了解一下 terms 这种aggregation)

Pipeline聚合

对聚合的结果而不是原始数据集进行操作。 想象一下,你有一个日间交易的网上商店,想要了解所有产品的按照库存日期分组的平均价格。

在SQL中你可以写:

1
SELECT in_stock_since, AVG(price) FROM products GROUP BY in_stock_since。

aggs 语法格式

1
2
3
4
5
"aggs”: {
    “name_of_aggregation”: {
      “type_of_aggregation”: {
        “field”: “document_field_name”
}

下面是比较好的总结。

1
2
3
4
5
6
7
8
9
aggs—This keyword shows that you are using an aggregation.

name_of_aggregation—This is the name of aggregation which the user defines.

type_of_aggregation—This is the type of aggregation being used.

field—This is the field keyword.

document_field_name—This is the column name of the document being targeted.

terms聚合,是按照某个字段中的值来分类。但是terms聚合有以下的问题:

我们想要获取name字段中出现频率最高的前5个。此时,客户端向ES发送聚合请求,主节点接收到请求后,会向每个独立的分片发送该请求。分片独立的计算自己分片上的前5个name,然后返回。当所有的分片结果都返回后,在主节点进行结果的合并,再求出频率最高的前5个,返回给客户端。这样就会造成一定的误差,比如最后返回的前5个中,有一个叫A的,有50个文档;B有49。 但是由于每个分片独立的保存信息,信息的分布也是不确定的。 有可能第一个分片中B的信息有2个,但是没有排到前5,所以没有在最后合并的结果中出现。 这就导致B的总数少计算了2,本来可能排到第一位,却排到了A的后面。

使用以下的方式改善上面的问题

为了改善上面的问题,就可以使用size和shard_size参数。

  • size参数规定了最后返回的term个数(默认是10个)
  • shard_size参数规定了每个分片上返回的个数
  • 如果shard_size小于size,那么分片也会按照size指定的个数计算

通过这两个参数,如果我们想要返回前5个,size=5;shard_size可以设置大于5,这样每个分片返回的词条信息就会增多,相应的误差几率也会减小。 (当每个分片返回的数量相对多一点的时候,最后得到的信息更好)

新的调研

ES主要的应用场景,一个是作为应用的前端检索缓存,提高海量数据的检索效率和做一些聚合检索,先从ES检索,然后再从后端存储检索。第二个是日志和监控,以ES为中心,Elastic公司发展起一个生态圈,之前叫ELK,也就是Elasticsearch、Logstash和Kibana,后来加入Beats,名字改成了Elastic Stack。以Kibana为入口,发展到上层的各种分析和可视化应用,包括热门的机器学习。

先理解ES的一些基本概念,可以和关系数据库做一个类比:

1
2
3
4
5
6
index:类似关系数据库的表,但是index不需要预先定义schema。
document:类似关系数据库的记录,json格式。
mapping:也就是类似关系数据的schema了,可以显式定义,也可以添加document时ES自动识别创建。可以指定mapping为严格模式,这样添加数据库就必须严格符合mapping中定义的字段。
type:从7.0开始删除了,其实就是mapping的名字,一个index下只有一个type,有点鸡肋。虽然删除了,但是其实内部默认还是有一个type,名字固定是_doc。
index template:创建index时自动对index做一些设置,例如index的主分片个数,复制分片个数,mapping字段的类型等等。
ilm:也就是index lifecycle management,可以对数据分为Hot,Warm,Cold,Delete四个阶段,可以相应的定义不同的策略。