IK分词器深入学习(1)-分词理论

背景介绍

词是表达语义的最小单位,分词是搜索引擎的基石。

在搜索引擎中,倒排索引就是由一个个Term所构成,分词器通过对自然语言的理解,拆分出Term。分词的输入是一串连续的文字,对于英文”girl is beautiful.boy is handsome.”分词比较容易,可以根据空格和标点符号分割,那最终会分为girl/is/beautiful/boy/is/handsome(一般来说,is作为停用词),是不是很容易?但这种方式在中文中却不行,”女孩美丽,男孩帅气。”这句话应该分为”女孩/美丽/男孩/帅气”,不是简单的靠空格和符号就能实现的。

上例提出了中文分词的背景,那么总要有理论来解决中文分词的问题。经过几十年的研究,中文分词方法也取得了长足的发展,产生了众多的分词方法,主要分为基于字符串匹配的分词方法基于统计的分词方法基于理解的分词方法三大流派。

##基于字符串匹配的分词方法

基于字符串匹配的分词算法也叫做机械分词算法,一般都需要事先建立足够大的分词词典,然后将待分词文本中的字符串与分词词典中的词进行匹配,如果匹配成功,该字符串当做一个词从待分词文本中切分出来,否则不切分。

下面介绍下主流的字符串匹配算法

  • 最大正向匹配分词算法(Forward Maximum Matching,FMM)

    这是字符串匹配分词算法中最基本的一种分词算法,统计结果表明,单纯使用最大正向匹配算法的错误率为1/169。优点是原理简单,切分速度快,易与实现。缺点是歧义处理不精确。

  • 最大逆向匹配分词算法(Reverse Maximum Matching,RMM)

    与FMM算法的基本思想相同,不同的是该方法从待切分的汉字串的末位开始处理,每次不匹配时去掉最前面的一个汉字,继续匹配。优点同FMM一样,而且最大逆向匹配算法对交集型歧义字段的处理精度比正向的要高,错误率在1/245左右,缺点是不能完全排除歧义现象。

  • 双向最大匹配算法(Bi-directction Matching method,BMM)

    双向匹配算法基本原理是同时进行FMM和RMM扫描,然后分析两种扫描的结果。如果两种扫描结果一致,则认为不存在歧义现象;如果不一致,则需要定位到歧义字段处理。优点是提高了分词的准确率,消除了部分歧义现象。缺点是算法执行要做双向扫描,时间复杂度会有所增加。

  • 全切分算法

    全切分算法是指切分出字符串中所有可能的词语,该算法可以大大提高切分的召回率,识别出所有可能的歧义现象。但缺点是算法复杂度比较高。

总结:

字符串匹配分词方法,逻辑简单清晰,易实现,分词性能较高。但是严重依赖字典,如果某些词在字典中未登陆,则会对分词的精度产生较大的影响。另外算法的效率很大层面体现在字典的数据结构上,如果用Hash表的方式,检索最快但是内存占用大,一般情况下会牺牲部分性能来降低词典的容量比如TRIE索引树词典、逐词二分词典、整词二分词典。

基于统计的分词方法

下面介绍下统计分词的常用分词方法

  • N元文法模型(N-gram)

    令C=C1C2…Cm.C 是待切分的汉字串,W=W1W2…Wn.W 是切分的结果。
    设P(WlC)是汉字串C切分为W的某种估计概率。Wa,Wb,⋯.Wk是C的所有可能的切分方案。那么,基于统计的切分模型就是这样的一种分词模型,它能够找到目的词串W ,使得W 满足:
      P(W|C)=MAX(P(Wa|C),P(Wb|C)…P(Wk|C)),
    即估计概率为最大之词串。我们称函数P(W|C)为评价函数。一般的基于统计的分词模型的评价函数,都是根据贝叶斯公式.同时结合系统本身的资源限制,经过一定的简化,近似得来的。

    根据贝叶斯公式, 有:P(W|C)=P(W) P(C|W)/P(C),对于C的多种切分方案,P(C)是一常数,而P(C|W)是在给定词串的条件下出现字串C的概率,故P(C|W)=1。所以 ,我们用P(W)来代替P(W|C)。那么,如何估计P(W)呢?最直接的估计P(W)的方法利用词的n-gram,即:
      P(W)=P(W1) P(W2lW1) P(W3|W1W2)⋯P(Wk|W1,W2…Wk-1)
      但是,由于当前的计算机技术和我们现有的语料资源所限,这种方法存在致命的缺陷:

      ①对于有6万词的词典而言,仅词和词的bigram就可能需要60000 x 60000=3600M的统计空间,这是当前的计算机硬件水平所难以接受的,更不要说更大的n-gram 了。
      ②需要与上述空间相当的熟语料,否则就会产生训练语料不足所产生的数据稀疏问题。
      ③由于不同领域的语料库的用词有所差异,针对某一个领域的语料库统计出来的n-gram,若用于其它领域,其效果难以预料,而目前通过语料库搭配来克服领域差民间的方法尚未成熟。

      因此,利用词的n-gram 直接估计P(W)的方法,在目前是不可行的。基于上述的原因,大多数基于统计的分词模型都没有直接采用上述公式,而是采用各种各样的估计方法,从不同的角度,实现对P(W)的近似。

  • 隐马尔可夫模型(Hidden Markov Model,HMM)

##

ElasticSearch之updateByQuery使用

#背景

有时需要通过某些查询条件,更新文档某一个字段,这样的诉求,用SQL表达就是update t_student set class=’优秀的小男生’ where age = 10 and score = 100 and sex = 1。ES有updateByQuery API 和 deleteByQuery API,这样一条DSL就可以搞定类似的需求。

例子

批量更新id = 9627361的文档,admin_rank数值++,address更新成”我是_update_by_query”,name_alias_array字段,更新为一个集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST hotel_20190513/hotel/_update_by_query
{
"script": {
"inline": "ctx._source.admin_rank++;ctx._source['address']='我是_update_by_query';ctx._source['name_alias_array']=['哈哈','hei嘿']",
"lang": "painless"
},
"query": {
"bool": {"must": [
{"term": {
"id": {
"value": 9627361
}
}}
]}
}
}

删除entity_type = hotel_name的所有文档。

1
2
3
4
5
6
7
8
9
10
POST entity_hotel_20190528/entity_hotel/_delete_by_query
{
"query": {
"term": {
"entity_type": {
"value": "hotel_name"
}
}
}
}

golang例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
updateByQuery := client.UpdateByQuery().Query(elastic.NewTermQuery("entity_id", node.TagId)).Script(elastic.NewScriptInline(script)).Index(index).Type(indexType)
out, err := updateByQuery.Do(context.TODO())
if err != nil {
panic(err)
}
b, err := json.Marshal(out)
if err != nil {
panic(err)
}
got := string(b)
updated := tool.GoJson(got).Get("updated").ToString()
if tool.IsEmpty(updated) {
updated = "0"
}
println("执行成功.tagId=" + tool.ToString(node.TagId) + "更新" + updated + "个文档.")

参考文档

官方文档

lucene系列-第一章-lucene包结构及工作流程

lucene包结构及工作流程

Lucene的analysis模块主要负责词法分析及语言处理而形成Term。
Lucene的index模块主要负责索引的创建,里面有IndexWriter。
Lucene的store模块主要负责索引的读写。
Lucene的QueryParser主要负责语法分析。
Lucene的search模块主要负责对索引的搜索。
Lucene的similarity模块主要负责对相关性打分的实现。

img

包名 功能
org.apache.lucene.analysis 语言分析器,主要用于的切词,支持中文主要是扩展此类
org.apache.lucene.document 索引存储时的文档结构管理,类似于关系型数据库的表结构
org.apache.lucene.index 索引管理,包括索引建立、删除等
org.apache.lucene.queryParser 查询分析器,实现查询关键词间的运算,如与、或、非等
org.apache.lucene.search 检索管理,根据查询条件,检索得到结果
org.apache.lucene.store 数据存储管理,主要包括一些底层的I/O操作
org.apache.lucene.util 一些公用类

ElasticSearch集群的搭建

#相关技术点

in-memory buffer是通过refresh操作写到文件缓冲区的,refresh的过程除了在文件缓冲区生成这个文件以外,还会让底层Lucene的index reader打开这个文件,让新生成的这个文件对搜索可见。

周五的时候跟同事讨论下了,不知道这样理解对不对–refresh类似于 outputstream flush(此时数据从 java heap 写到 memory buffer),之后(按照大神上面说的)index reader读取新文件,然后等待触发flush(这个则类似于 outputstream close,此时数据从 memory buffer 写到 disk),请大神赐教

集群配置

集群包括3台物理机共12个节点,每台物理机部署1个Master节点,1个Client节点,2个Data节点。

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
38
# ---------------------------------- Cluster -----------------------------------
#集群名称
cluster.name:
# ------------------------------------ Node ------------------------------------
#节点名称
node.name:
#是否是Master Node
node.master: true
#是否是Data Node
node.data: false
#是否是Ingest Node
node.ingest: false
node.attr.rack: first
# ----------------------------------- Paths ------------------------------------
# Path to log files:
#可以指定es的数据存储目录,默认存储在es_home/data目录下
#path.data: /path/to/data
#可以指定es的日志存储目录,默认存储在es_home/logs目录下
#path.logs: /path/to/logs
# ----------------------------------- Memory -----------------------------------
#锁定物理内存地址,防止elasticsearch内存被交换出去,也就是避免es使用swap交换分区
bootstrap.memory_lock: true
#CentOS下不支持SecComp,故而需要设置成false
bootstrap.system_call_filter: false
# ---------------------------------- Network -----------------------------------
network.host: 192.168.1.97
http.port: 9200
transport.tcp.port: 9300
# --------------------------------- Discovery ----------------------------------
#
discovery.zen.ping.unicast.hosts: ["192.168.1.97:9300","192.168.1.98:9300","192.168.1.113:9300"]
#理论上是 masternum/2+1,防止脑裂
discovery.zen.minimum_master_nodes: 2
# ---------------------------------- Various -----------------------------------
xpack.security.enabled: false
#支持跨域访问
http.cors.enabled: true
http.cors.allow-origin: "*"

参考文档

Linux sandboxing机制

从内核文件系统看文件读写过程

Lucence分词原理研究

#研究背景

​ 近期工作需要优化公司的分词器,需要改写IK源码,故而知其然知其所以然,Lucene分词相关的原理和实现研究透彻之后,可以研究ES IK分词器的源码了。以下源码的研究,是基于Lucene 6.6.0版本,Lucene源码中文档很规范,结合文档和源码学习效率很高。本文的所有内容,均来自于Lucene的文档和代码示例。

#类图

​ 如类图所示,Lucene中和分词相关的最核心的几个类。Analyzer用于构建TokenStream(用于分析文本),提供了用于提取文本生成词项(term)的策略。为了定义哪些Analyzer就绪,子类必须实现createComponents方法,组件分词时,调用tokenStream方法。

​ TokenStream枚举了一系列tokens,它们来自documents.field或者query text。TokenStream是一个抽象类,具体的实现分为Tokenizer和TokenFilter。Tokennizer的输入是Reader,TokenFilter的输入是另一个tokenFilter。

img

TokenStream API工作流程

1、初始化TokenStream时添加对应的属性
2、客户端调用TokenStream.reset()
3、客户端获取需要访问的属性的本地引用
4、客户端调用incrementToken()直到返回false,每次调用就可以获取对应的token的属性
5、客户端调用end()方法让TokenStream执行扫尾操作
6、客户端使用完TokenStream后调用close()释放资源

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
public static void main(String[] args) throws Exception {
Analyzer analyzer = new StandardAnalyzer();
String str = "中华人民";
TokenStream stream = analyzer.tokenStream("content", new StringReader(str));
CharTermAttribute attr = stream.addAttribute(CharTermAttribute.class); //1,3
PositionIncrementAttribute posIncr = stream.addAttribute(PositionIncrementAttribute.class); //1,3
OffsetAttribute offset = stream.addAttribute(OffsetAttribute.class); //1,3
TypeAttribute type = stream.addAttribute(TypeAttribute.class); //1,3
stream.reset(); //2
int position = 0;
while (stream.incrementToken()) { //4
int increment = posIncr.getPositionIncrement();
if (increment > 0) {
position = position + increment;
System.out.print(position + ": ");
}

System.out.println(attr.toString() + "," + offset.startOffset() + "->" + offset.endOffset() + "," + type.type());
}
stream.end(); //5
stream.close(); //6
analyzer.close();
}


执行结果:
1: 中,0->1,<IDEOGRAPHIC>
2: 华,1->2,<IDEOGRAPHIC>
3: 人,2->3,<IDEOGRAPHIC>
4: 民,3->4,<IDEOGRAPHIC>

ElasticSearch Scroll

ElasticSearch Scroll

##深分页

比如pageIndex=10,pageSize=20,{“start”: 180, “size”: 20},相当于每个分片召回了200条数据,排序之后截断前20条返回。但是如果pageIndex=1000,每个分片需要召回20000条数据,排序,最后仍然是取前20条,扔掉19980条。深分页的实现方式代价是很大的。

Scroll

为了解决以上深分页,在页码太大情况下性能的问题,ES提供了Scroll方式。

scroll 查询 可以用来对 Elasticsearch 有效地执行大批量的文档查询,而又不用付出深度分页那种代价。

游标查询允许我们 先做查询初始化,然后再批量地拉取结果。 这有点儿像传统数据库中的 cursor 。

游标查询会取某个时间点的快照数据。 查询初始化之后索引上的任何变化会被它忽略。 它通过保存旧的数据文件来实现这个特性,结果就像保留初始化时的索引 视图 一样。

深度分页的代价根源是结果集全局排序,如果去掉全局排序的特性的话查询结果的成本就会很低。 游标查询用字段 _doc 来排序。 这个指令让 Elasticsearch 仅仅从还有结果的分片返回下一批结果。

启用游标查询可以通过在查询的时候设置参数 scroll 的值为我们期望的游标查询的过期时间。 游标查询的过期时间会在每次做查询的时候刷新,所以这个时间只需要足够处理当前批的结果就可以了,而不是处理查询结果的所有文档的所需时间。 这个过期时间的参数很重要,因为保持这个游标查询窗口需要消耗资源,所以我们期望如果不再需要维护这种资源就该早点儿释放掉。 设置这个超时能够让 Elasticsearch 在稍后空闲的时候自动释放这部分资源。

例如:依据查询条件开启滚动,镜像有效期为5分钟。这个查询结果中包含scroll_id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
curl -XPOST 'http://localhost:9200/es_cluster/goods_index/goods/_search?scroll=5m' -d '{
"size": 20,
"query": {
"bool": {
"filter": [
{
"exists": {
"field": "skuUpTime"
}
}
]
}
}
}'

下次查询的时候,就可以直接用scroll_id来查询了

1
2
3
4
curl -XPOST 'http://localhost:9200/es_cluster/scroll' -d '{
"scroll": "5m",
"scroll_id": "DnF1ZXJ5VGhlbkZldGNoJgAAAAA8lsqeFjRwN19JcDBKVFd1cy04dkZucUZ4dHcAAAAAPlQH_BZIMUU1VFA2dVIxMk10M0xoWW1sSEtRAAAAADtYOdcWUVBlYXZCNmRTSEtXV2llZTE4dkEwZwAAAAA6dfbHFkdEMS1VY21CVHFhSFpFS1ZrVnBwSVEAAAAAPaL6vBZsZmRFeHRBeFFJaWlVOXNWV2xnZlB3AAAAADy6UDUWRU9NVVRadG5STnV4cThrb2VMdHhqUQAAAAA9V-f6FnNYSV83SHZVVHpDTHVLZWFiNmVwcVEAAAAAPMlOFhY5Zk5ESlBZdFI5bVVEclI4b2paTld3AAAAADn6zjwWZEJ5RU9VOThTUW1HUnB5UzlOT18xUQAAAAA9EBaGFmM4Ykh6SHkxU3JPYkFFRFZMWVRiQUEAAAAAPSKSJxZVUlRzY0luTFJTQzQ3WTZydUxoQVN3AAAAADyw6E4WS21DZHdWX21TRXlkdzVvYUpRcVBKUQAAAAA7sX3BFnh4UC1rMGhDU1FlZFFSdjVDVVc0d3cAAAAAPMIY9RZla0lLajhFQlMtYUVOZDE1U0tXekRnAAAAADtYOdgWUVBlYXZCNmRTSEtXV2llZTE4dkEwZwAAAAA5-s49FmRCeUVPVTk4U1FtR1JweVM5Tk9fMVEAAAAAPMlOFxY5Zk5ESlBZdFI5bVVEclI4b2paTld3AAAAADzjiUkWMll6VHRaUDRUV2VjNXA4LUxfMlFxdwAAAAA8cKq6FndTQ2JfOWlWUXlHNHdNMUZ3ejZVZUEAAAAAPHCquRZ3U0NiXzlpVlF5RzR3TTFGd3o2VWVBAAAAADzx948Wdlk0dzdJWnBRWHEyRWFsVEhsVUdiQQAAAAA9cxFHFnpwdy1EaXFXUmR5REM5ZW5CYTB0ZEEAAAAAPXMRSBZ6cHctRGlxV1JkeURDOWVuQmEwdGRBAAAAAD1DIxMWM0E3dW1PbkVUQzZMSUJXV21LaHNiZwAAAAA9QyMUFjNBN3VtT25FVEM2TElCV1dtS2hzYmcAAAAAPPeZ9hZyY0luUVRFb1RsNmF1YWhZaVEwWUFBAAAAADz3mfcWcmNJblFURW9UbDZhdWFoWWlRMFlBQQAAAAA6pq_PFjRDZjN6XzBSU25DRE5PWlA2RFRUR2cAAAAAOqavzhY0Q2Yzel8wUlNuQ0ROT1pQNkRUVEdnAAAAADzcYCcWQ2xwZk15U01RNk9oelRQR2w2bTRmdwAAAAA84x3vFk8wYjdmdV9aU2E2QzREc045bUx4UUEAAAAAPPTj5RZoYlctclkyblM3cXpDeEZTa05FNlNnAAAAADzrVQEWLU9xeUFOVHRUQTZtd2R2eG5MTF96UQAAAAA861UAFi1PcXlBTlR0VEE2bXdkdnhuTExfelEAAAAAO1D_vBZheEpkUFp4UFNXS19BWDdvMTNkS1BnAAAAADuxfcIWeHhQLWswaENTUWVkUVJ2NUNVVzR3dwAAAAA7ungNFlpqWVViMnZsVDU2LXg4VzBlTlZ0cWcAAAAAPaL6vRZsZmRFeHRBeFFJaWlVOXNWV2xnZlB3"
}'

elasticSearch系列笔记(5)-客户端负载均衡

常用负载均衡策略

ES客户端负载均衡策略

集群嗅探

elasticSearch系列笔记(1)-Hello ElasticSearch

读了上面的小故事,是不是觉得对ElasticSearch的前世今生,兴趣十足呢?!我计划写一篇ElasticSearch的系列学习笔记,深入浅出,不仅方便大家学习,还能够让我更加深入的了解这门技术。学习一门新技术的第一课就是hello world.
接下来的章节,实操ElasticSearch hello world。

ElasticSearch简介

回忆时光

       
许多年前,一个刚结婚的名叫 Shay Banon 的失业开发者,跟着他的妻子去了伦敦,他的妻子在那里学习厨师(感觉国内的女生很少有喜欢做饭的,反而男生喜欢做饭)。 在寻找一个赚钱的工作的时候,为了给他的妻子做一个食谱搜索引擎,他开始使用 Lucene 的一个早期版本。

       
直接使用 Lucene 是很难的,因此 Shay 开始做一个抽象层,Java 开发者使用它可以很简单的给他们的程序添加搜索功能。 他发布了他的第一个开源项目 Compass。

       
后来 Shay 获得了一份工作,主要是高性能,分布式环境下的内存数据网格。这个对于高性能,实时,分布式搜索引擎的需求尤为突出, 他决定重写 Compass,把它变为一个独立的服务并取名 Elasticsearch。

       
第一个公开版本在2010年2月发布,从此以后,Elasticsearch 已经成为了 Github 上最活跃的项目之一,他拥有超过300名 contributors(目前736名 contributors )。 一家公司已经开始围绕 Elasticsearch 提供商业服务,并开发新的特性,但是,Elasticsearch 将永远开源并对所有人可用。

       
据说,Shay 的妻子还在等着她的食谱搜索引擎…

切入正题

       
读了上面的小故事,是不是觉得对ElasticSearch的前世今生,兴趣十足呢?!我计划写一篇ElasticSearch的系列学习笔记,深入浅出,不仅方便大家学习,还能够让我更加深入的了解这门技术。学习一门新技术的第一课就是hello world.
接下来的章节,实操ElasticSearch hello world。

Mac ElasticSearch 安装

1、Java环境的安装配置

2、brew install elasticSearch(仅适用于Mac)

3、brew info elasticSearch,会显示如下信息表示安装成功

elasticSearch: stable 6.2.4, HEAD
Distributed search & analytics engine
https://www.elastic.co/products/elasticsearch
/usr/local/Cellar/elasticSearch/6.2.4 (112 files, 30.8MB)
  Built from source on 2018-05-29 at 11:10:53
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/elasticsearch.rb
==> Requirements
Required: java = 1.8 ✔
==> Options
--HEAD
    Install HEAD version
==> Caveats

4、brew services start elasticsearch

启动后localhost:9200,打印如下,说明ElasticSearch启动成功
{
  "name" : "yptOhLD",
  "cluster_name" : "elasticsearch_1",
  "cluster_uuid" : "_na_",
  "version" : {
    "number" : "6.2.4",
    "build_hash" : "ccec39f",
    "build_date" : "2018-04-12T20:37:28.497551Z",
    "build_snapshot" : false,
    "lucene_version" : "7.2.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

5、brew install kibana(仅适用于Mac)
6、brew services start kibana

7、打开kibana localhost:5601

基于以上7个步骤,环境问题搞定,下面来真实的操作一下ElasticSearch。

ElasticSearch Hello World

创建索引(create index)

curl -XPUT "http://localhost:9200/goods_index"

curl -XGET "http://localhost:9200/goods_index"

{
  "goods_index": { #索引名称
    "aliases": {},
    "mappings": {},
    "settings": {
      "index": {
        "creation_date": "1530627979046",
        "number_of_shards": "5", #5个分片
        "number_of_replicas": "1", #一个备份
        "uuid": "kTy_hHlQQRaVPygKHZpUYQ",
        "version": {
          "created": "6020499"
        },
        "provided_name": "goods_index"
      }
    }
  }
}

索引文档(index document)

curl -XPUT "http://localhost:9200/goods_index/goods/12343333" -H 'Content-Type:application/json' -d'
{
  "skuName": "华为手机",
  "url":"https://item.jd.com/6946605.html"
}'

索引数据之后,再执行查看索引详情,会发现mappings属性多了一个名字叫goods的type,goods下面多了skuName和url两个properties。
{
  "goods_index": {
    "aliases": {},
    "mappings": {
      "goods": {
        "properties": {
          "skuName": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "url": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    },
    "settings": {
      "index": {
        "creation_date": "1530627979046",
        "number_of_shards": "5",
        "number_of_replicas": "1",
        "uuid": "kTy_hHlQQRaVPygKHZpUYQ",
        "version": {
          "created": "6020499"
        },
        "provided_name": "goods_index"
      }
    }
  }
}

查询文档(query document)

curl -XGET "http://localhost:9200/goods_index/goods/_search"

执行上面的查询语句,得到如下的结果集。

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1,
    "hits": [
      {
        "_index": "goods_index",
        "_type": "goods",
        "_id": "12343333",
        "_score": 1,
        "_source": {
          "skuName": "华为手机",
          "url": "https://item.jd.com/6946605.html"
        }
      }
    ]
  }
}

小节

       
ElasticSearch的Hello world,是不是比java,python难一些呢?不过还好,一个检索系统,就此拉开了帷幕。这就是ElasticSearch推崇的“开箱即用”的理念。但是要学好ElasticSearch可不是一件容易的事情,细心的同学,可能会发现有一些细节:

1、什么是索引?

2、什么是文档?

3、索引和文档是什么样的关系?

……

请持续关注我的博客,我们逐渐从ElasticSearch小白,变成大牛。

参考文献

elasticSearch权威指南2.x

elasticSearch reference 6.2

elasticSearch系列笔记(4)-elasticSearch搜索类型

ElasticSearch本地源码调试

转自:https://donlianli.iteye.com/blog/2094305

es在查询时,可以指定搜索类型为QUERY_THEN_FETCH,QUERY_AND_FEATCH,DFS_QUERY_THEN_FEATCH和DFS_QUERY_AND_FEATCH。那么这4种搜索类型有什么区别?

分布式搜索背景介绍:

ES天生就是为分布式而生,但分布式有分布式的缺点。比如要搜索某个单词,但是数据却分别在5个分片(Shard)上面,这5个分片可能在5台主机上面。因为全文搜索天生就要排序(按照匹配度进行排名),但数据却在5个分片上,如何得到最后正确的排序呢?ES是这样做的,大概分两步。

step1、ES客户端会将这个搜索词同时向5个分片发起搜索请求,这叫Scatter,

step2、这5个分片基于本Shard独立完成搜索,然后将符合条件的结果全部返回,这一步叫Gather。

客户端将返回的结果进行重新排序和排名,最后返回给用户。也就是说,ES的一次搜索,是一次scatter/gather过程(这个跟mapreduce也很类似).

然而这其中有两个问题。

第一、数量问题。比如,用户需要搜索”双黄连”,要求返回最符合条件的前10条。但在5个分片中,可能都存储着双黄连相关的数据。所以ES会向这5个分片都发出查询请求,并且要求每个分片都返回符合条件的10条记录。当ES得到返回的结果后,进行整体排序,然后取最符合条件的前10条返给用户。这种情况,ES5个shard最多会收到10*5=50条记录,这样返回给用户的结果数量会多于用户请求的数量。

第二、排名问题。上面搜索,每个分片计算分值都是基于自己的分片数据进行计算的。计算分值使用的词频率和其他信息都是基于自己的分片进行的,而ES进行整体排名是基于每个分片计算后的分值进行排序的,这就可能会导致排名不准确的问题。如果我们想更精确的控制排序,应该先将计算排序和排名相关的信息(词频率等)从5个分片收集上来,进行统一计算,然后使用整体的词频率去每个分片进行查询。

这两个问题,估计ES也没有什么较好的解决方法,最终把选择的权利交给用户,方法就是在搜索的时候指定query type。

1、query and fetch

向索引的所有分片(shard)都发出查询请求,各分片返回的时候把元素文档(document)和计算后的排名信息一起返回。这种搜索方式是最快的。因为相比下面的几种搜索方式,这种查询方法只需要去shard查询一次。但是各个shard返回的结果的数量之和可能是用户要求的size的n倍。

2、query then fetch(默认的搜索方式)

如果你搜索时,没有指定搜索方式,就是使用的这种搜索方式。这种搜索方式,大概分两个步骤,第一步,先向所有的shard发出请求,各分片只返回排序和排名相关的信息(注意,不包括文档document),然后按照各分片返回的分数进行重新排序和排名,取前size个文档。然后进行第二步,去相关的shard取document。这种方式返回的document与用户要求的size是相等的。

3、DFS query and fetch

这种方式比第一种方式多了一个初始化散发(initial scatter)步骤,有这一步,据说可以更精确控制搜索打分和排名。

4、DFS query then fetch

比第2

方式多了一个

初始化散发(initial scatter)步骤。

DSF是什么缩写?初始化散发是一个什么样的过程?

从es的官方网站我们可以指定,初始化散发其实就是在进行真正的查询之前,先把各个分片的词频率和文档频率收集一下,然后进行词搜索的时候,各分片依据全局的词频率和文档频率进行搜索和排名。显然如果使用DFS_QUERY_THEN_FETCH这种查询方式,效率是最低的,因为一个搜索,可能要请求3次分片。但,使用DFS方法,搜索精度应该是最高的。

至于DFS是什么缩写,没有找到相关资料,这个D可能是Distributed,F可能是frequency的缩写,至于S可能是Scatter的缩写,整个单词可能是分布式词频率和文档频率散发的缩写。

总结一下,从性能考虑QUERY_AND_FETCH是最快的,DFS_QUERY_THEN_FETCH是最慢的。从搜索的准确度来说,DFS要比非DFS的准确度更高。