es使用称为倒排索引的结构达到快速全文搜索的目的。一个倒排索引包含一系列不同的单词,这些单词出现在任何一个文档,对于每个单词,对应着所有它出现的文档。比如说,我们有2个文档,每个文档有一个conteng字段。内容如下:“
The quick brown fox jumped over the lazy dog”“
Quick brown foxes leap over lazy dogs in summer”为了创建倒排索引,我们首先对每个字段进行分词,我们称之为terms或者tokens,创建了一些列有序列表,然后列举了每个单词所出现的文档,结果如下:Term
Doc_1 Doc_2-------------------------Quick
| | XThe
| X |brown
| X | Xdog
| X |dogs
| | Xfox
| X |foxes
| | Xin
| | Xjumped
| X |lazy
| X | Xleap
| | Xover
| X | Xquick
| X |summer
| | Xthe
| X |------------------------现在,如果我们想搜索"quick
brown",我们只需要找到每个单词出现的文档。Term
Doc_1 Doc_2-------------------------brown
| X | Xquick
| X |------------------------Total
| 2 | 1两个文档都匹配,但是第一个文档有更高的匹配度,如果我们采用一个简单的相似算法,我们可以说,第一个文档比第2个文档有更高的匹配度。也更相关。但是,仍然有一些问题。"Quick"和"quick"看起来是不同的单词,但是用户通常认为是一样的。"fox"和"foxes"更相似,还有"dog"和"dogs",具有共同的词根。"jumped"和"leap",尽管不具备相同的词根,意思上是一样的。它们是同义词。对于以上的索引,一个搜索"+Quick
+ fox"不可能匹配任何文档。单词"Quick"
和单词 "fox"必须在同一个文档里以满足查询要求,但是第一个文档包括"quick
fox"
而第2个文档包含 "Quick
foxes".我们的用户有理由希望两个文档都匹配,我们可以做的更好。如果我们把单词归一化到标准格式,我们就可以达到上面的目标。这种情况下,虽然单词不是完全一致,但是也足够相似保证相关性。比如:"Quick"
可以小写为 "quick"."foxes"
可以提取词根成为 "fox".
类似的
"dogs"
可以成为 "dog"."jumped"
和 "leap"
是同义词,可以索引为一个单词 "jump".那么,现在的索引就是:Term
Doc_1 Doc_2-------------------------brown
| X | Xdog
| X | Xfox
| X | Xin
| | Xjump
| X | Xlazy
| X | Xover
| X | Xquick
| X | Xsummer
| | Xthe
| X | X------------------------我们的搜索
"+Quick
+fox"
仍然失败,因为我们不再有Quick在索引里,尽管如此,如果我们采用同样的归一化规则,我们可以用在查询字符串上,它就变为
"+quick
+fox",这样就可以匹配到文档。这一点非常重要,你只能找到那些在你的索引里出现的单词,所以,索引过的文本和查询字符串都需要遵循同一种归一化规则。分词和归一化叫做分析http://my.oschina.net/qiangzigege/blog/265360什么叫分析过程?1)将文本块分词,以倒排索引的方式2)归一化到标准形式来提高可搜索性。这个工作的执行者叫做分析器,一个分析器包含以下3个功能:1)字母过滤首先,字符串依次通过任何一个字符过滤器,过滤器的工作是清洗字符串,也就是说先清洗再分词。一个字符过滤器可以去除HTML字符,也可以转换"&"变为"and".2)分词下一步,字符串被分词为很多个单词,一个简单的分词器也许依靠空格或者标点符号来分词。3)单词过滤最后,每个单词传给单词过滤器,它们可以将单词小写或者删除单词比如a,and,the,etc.或者增加单词比如jump和leap.es提供了很多字符串过滤器,分词器和单词过滤器。这些单元可以组合起来使用,后面再说。内置的分析器尽管如此,es也包含了很多内置的分词器,你可以直接使用。我们列举了最重要的一些,演示区别。所用的文本基于:"Set
the shape to semi-transparent by calling set_trans(5)"标准分析器标准分析器是es默认的分析器,如果文本可能是各种语言,总的来说选择标准分析器是没错的。它把文本分成单词(由Unicode决定),移除大部分标点符号,最终,将所有单词小写化,这样,结果如下:set,
the, shape, to, semi, transparent, by, calling, set_trans, 5PS:先清洗,再分词,再归一化。简单analyzer这个分析器将文本分词的规则是每个字符是不是一个letter,然后将所有单词小写化,结果如下:set,
the, shape, to, semi, transparent, by, calling, set,
trans空格analyzer依靠空格来分词,不将单词小写化,结果如下:Set,
the, shape, to, semi-transparent, by, calling, set_trans(5)语言分析器特定语言分析器是可用的,可以处理特殊字符。比如,英语分析器附带很多英语过滤单词集合,这些集合包括没啥意义的单词。那分析器就会去掉这些单词,可以对英语单词进行提取词干,这是因为英语分析器知道英语的语法规则。结果如下:set,
shape, semi, transpar, call, set_tran, 5注意:"transparent",
"calling",
and "set_trans"
已经归一化到词根形式。什么时候用分析器当我们索引一个文档,它的整个文档被分析成单词,单词用来创建倒排索引。尽管如下,当我们搜索全文字段,我们也需要将查询字符串进行同样的分析过程。实际上也就是说,你之前如何对全文做索引的,这个时候也要对查询字符串做同样的归一化处理。这样才可以保证可以查出来数据。后面说全文搜索,当你查询一个全文字段,查询将对查询字符串应用同样的分析器来产生一系列分词后的单词。当你查询一个具体的字段,查询就不会对查询字符串进行分析,仅仅搜索具体的值。现在你就理解了之前的:date字段包含了一个具体的值:一个单词
"2014-09-15"._all字段是一个全文字段,所以分析器已经把日期分成3个单词:
"2014",
"09"
and "15".当我们在_all字段里搜索2014,有12个结果,因为都包含2014这个单词。GET
/_search?q=2014
# 12
results当我们在_all字段里搜索2014-09-15,
首先会把查询字符串分词为"2014",
"09"
and "15".仍然包含了12个tweets,因为都包含2014.GET
/_search?q=2014-09-15
# 12
results !当我们查询date字段,值为2014-09-15,
搜索会寻找具体的日期,结果只有1个:GET
/_search?q=date:2014-09-15
# 1
result当我们查询date字段,值为2014,就找不到文档。GET
/_search?q=date:2014
# 0
results !测试分析器如果你是一个新手,有时会很难理解分词的具体原理和存储索引的过程。(不看源码你永远不可能知道,just
read the fuc*ing source code please!!!)为了更好的理解怎样运行的,你可以使用分析API来看文本如何分析的。在查询字符串参数里指定你想用哪个分析器,body里指定分析的文本。GET
/_analyze?analyzer=standardText
to analyze结果如下:{ "tokens":
[ { "token":
"text", "start_offset":
0, "end_offset":
4, "type":
"<ALPHANUM>", "position":
1 }, { "token":
"to", "start_offset":
5, "end_offset":
7, "type":
"<ALPHANUM>", "position":
2 }, { "token":
"analyze", "start_offset":
8, "end_offset":
15, "type":
"<ALPHANUM>", "position":
3 } ]}这些单词就是真实的被存储在索引里的单词。position表明单词出现的顺序,start_offset
和 end_offset 表明 字符在原始文本里的位置。分析API对于理解es的索引很有用。指定分析器当es发现有需要索引的String字段,自动认为是全文字符串字段,用标准分析器来分析。有的时候你可能不想要这个分析器,纳尼?也许你想采用一个不同的分析器,因为你觉得它更合适。也许你还希望一个string字段就是一个字段,不需要认为是全文字段,比如说字符串类型的userid和内部身份。
http://my.oschina.net/qiangzigege/blog/265553
索引里的每个文档有一个type,每一个type有它自己的映射模式,一个映射指定了type里的字段,每个字段的数据类型,并且字段如何被es处理。一个映射也被用来配置元数据。我们会后续详细讨论映射。核心简单字段类型es支持下列简单的字段类型:String:
stringWhole
number: byte, short, integer, longFloating
point: float, doubleBoolean:
booleanDate:
date当你索引一个文档,而这个文档包含了一个新的字段,es会动态映射此字段,规则如下:JSON
type: Field type:Boolean:
true or false "boolean"Whole
number: 123
"long"Floating
point: 123.45
"double"String,
valid date: "2014-09-15"
"date"String:
"foo
bar"
"string"这也就意味着,如果你索引
“123”,就映射成字符串,而不是long.尽管如此,如果这个字段已经映射成long,es将尝试按此转换,失败则抛出异常。查看映射使用/_mapping来查看已有的映射关系。比如查看index:gb
type:tweet的映射GET
/gb/_mapping/tweet结果:{ "gb":
{ "mappings":
{ "tweet":
{ "properties":
{ "date":
{ "type":
"date", "format":
"dateOptionalTime" }, "name":
{ "type":
"string" }, "tweet":
{ "type":
"string" }, "user_id":
{ "type":
"long" } } } } }}定制字段的映射字段最重要的属性就是type,{ "number_of_clicks":
{ "type":
"integer" }}string类型的字段,默认,认为是全文搜索,也就是说,值会传递给分析器,搜索之前也会将查询字符串进行全文分词再搜索。字符串字段最重要的2个映射关系是index和analyser.indexindex属性决定字符串如何被索引,有3个值:1
analyzed先分词,再索引,全文搜索2
not_analyzed索引这个字段,这样可以被搜索,但是直接索引,不分词。3
no不要索引这个字段,这个字段也不会被搜出来。string字段默认是analyzed.下面阐述了不分词的做法{ "tag":
{ "type":
"string", "index":
"not_analyzed" }}其它简单的字段类型-long,
double,
date etc ,也可以接收index参数,不过只可以取值no
,not_analyzed.analyzer对于需要分词的string字段,analyzer属性决定使用哪个分词器,默认,使用standard分词器。但是你可以设置为一个内置的分词器,比如whitespace,simple,english.{ "tweet":
{ "type":
"string", "analyzer":
"english" }}更新映射你可以在创建索引时指定映射,另外,你可以使用/_mapping来修改映射或者增加映射。如果一个字段已经在映射里存在,这也许意味着这个字段的数据已经被索引了,如果你想这个字段的映射,已经索引过的数据就会出错。我们可以通过增加一个新的字段来修改映射,但是我们不能修改已经存在的字段从分词到不分词。删除gb索引DELETE
/gb然后创建一个索引,指定tweet字段使用english分词器。PUT
/gb { "mappings":
{ "tweet"
: { "properties"
: { "tweet"
: { "type"
: "string", "analyzer":
"english" }, "date"
: { "type"
: "date" }, "name"
: { "type"
: "string" }, "user_id"
: { "type"
: "long" } } } }}这就创建了一个带映射关系的索引。下面修改映射,增加一个新的不分词的字段tag到tweet映射中。PUT
/gb/_mapping/tweet{ "properties"
: { "tag"
: { "type"
: "string", "index":
"not_analyzed" } }}测试映射可以使用分词API来测试映射,GET
/gb/_analyze?field=tweetBlack-cats
GET
/gb/_analyze?field=tagBlack-cats
前者2个分词,后者一个分词。
http://my.oschina.net/qiangzigege/blog/269856
除了简单的数据类型,JSON还有null值,数组和对象,es都支持。多值字段:有可能我们想我们的标签字段包含好几个标签,可以用数组:{
"tag": [ "search", "nosql" ]}这没有什么特别的,任何字段可以包含0,1或者多个值,这跟一个全文字段产生多个分词道理是一样的。这意味着,一个数组的所有值必须是同样的数据类型,你不能混杂两种数据类型,如果你通过索引一个数组而创建了一个新的字段,es将使用第一个值的数据类型来决定整个字段的数据类型。数组的元素没有顺序,你不能说第一个元素和最后一个元素,就是一个集合。空字段数组,可以为空,事实上,Lucene是没有办法存储空值的,所以,一个没有值的字段被认为是一个空的字段。以下四种字段可以认为是空的,不会被索引:"empty_string":
"","null_value":
null,"empty_array":
[],"array_with_null_value":
[ null ]多层对象最后一个JSON数据类型是对象object,比如哈希,字典和数组。数据对象嵌套是常见的,比如:{ "tweet":
"Elasticsearch is very flexible", "user":
{ "id":
"@johnsmith", "gender":
"male", "age":
26, "name":
{ "full":
"John Smith", "first":
"John", "last":
"Smith" } }}内部对象的映射es会探测到新的对象字段并且映射为object类型,映射如下:{ "gb":
{ "tweet":
{ "properties":
{ "tweet":
{ "type": "string" }, "user":
{ "type":
"object", "properties":
{ "id":
{ "type": "string" }, "gender":
{ "type": "string" }, "age":
{ "type": "long" }, "name":
{ "type":
"object", "properties":
{ "full":
{ "type": "string" }, "first":
{ "type": "string" }, "last":
{ "type": "string" } } } } } } } }}内部对象如何被索引?Lucene不知道内部对象,一个Lucene文档包含一个平级的k/v结构。为了让es索引内部对象,我们的文档会转化如下:{ "tweet":
[elasticsearch, flexible, very], "user.id":
[@johnsmith], "user.gender":
[male], "user.age":
[26], "user.name.full":
[john, smith], "user.name.first":
[john], "user.name.last":
[smith]}Lucene仅仅索引简单的值,不是复杂的结构。内部对象数组最后,思考下,一个内部对象数组如何被索引,比如说如下:{ "followers":
[ {
"age": 35, "name": "Mary White"}, {
"age": 26, "name": "Alex Jones"}, {
"age": 19, "name": "Lisa Smith"} ]}这个文档将被转化如下的结构{ "followers.age":
[19, 26, 35], "followers.name":
[alex, jones, lisa, smith, mary, white]}那么{age:
35} 和 {name: Mary White} 已经丢失了Is
there a follower who is 26 years old?这个可以回答Is
there a follower who is 26 years old and who is called Alex Jones?这个问题就无法回答。后续再讨论这个问题。http://my.oschina.net/qiangzigege/blog/270948
1205

被折叠的 条评论
为什么被折叠?



