Elasticsearch简介&基本使用
简介
Elasticsearch是一个分布式、可扩展、实时的搜索与数据分析引擎
Elasticsearch被用作全文检索、结构化检索、分析以及这三个功能的组合
Elasticsearch 是使用 Java 编写的,它的内部使用 Lucene 做索引与搜索,但是它的目的是使全文检索变得简单, 通过隐藏 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API
Elasticsearch描述
- 一个分布式的实时文档存储,每个字段都可以被索引与搜索
- 一个分布式实时分析搜索引擎
- 能胜任上百个服务节点的扩展,并支持PB级别的结构化或者非结构化数据
Elasticsearch 是面向文档的,意味着它存储整个对象或 文档。Elasticsearch 不仅存储文档,而且 索引 每个文档的内容使之可以被检索。在 Elasticsearch 中,对文档进行索引、检索、排序和过滤,而不是对行列数据。这是一种完全不同的思考数据的方式,也是 Elasticsearch 能支持复杂全文检索的原因
存储什么样的数据——文档
在 Elasticsearch 中,术语 文档 有着特定的含义。它是指最顶层或者根对象, 这个根对象被序列化成 JSON 并存储到 Elasticsearch 中,指定了唯一 ID
一个文档不仅仅包含它的数据 ,也包含 元数据 —— 有关 文档的信息。 三个必须的元数据元素如下:
-
_index
文档在哪存放
一个 索引 应该是因共同的特性被分组到一起的文档集合,如产品 products
-
_type
文档表示的对象类别
数据可能在索引中只是松散的组合在一起,但是通常明确定义一些数据中的子分区是很有用的,如不同的产品类别, “electronics” 、 “kitchen”
-
_id
文档唯一标识
ID 是一个字符串, 当它和 _index 以及 _type 组合就可以唯一确定 Elasticsearch 中的一个文档,当创建一个新的文档,可以提供自己的 _id ,也可以让 Elasticsearch 自动生成
自己设置id
PUT /website/blog/123 { "title": "My first blog entry", "text": "Just trying this out...", "date": "2014/01/01" }
自动生成id
POST /website/blog/ { "title": "My second blog entry", "text": "Still trying this out...", "date": "2014/01/01" }
检查文档是否存在
如果只想检查一个文档是否存在 ,不想关心内容,那么用 HEAD 方法来代替 GET 方法。 HEAD 请求没有返回体,只返回一个 HTTP 请求报头
curl -i -XHEAD http://localhost:9200/website/blog/123
返回
HTTP/1.1 200 OK (存在) / HTTP/1.1 404 Not Found (不存在)
分布式文档存储中如何找到数据所在的分片——路由一个文档到一个分片中
当创建文档时,这个文档会根据一定的规则被存储在某个分片中,在取文档时根据同样的规则找到对应的分片,这个过程是根据下面这个公式决定的
shard = hash(routing) % number_of_primary_shards
routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值
routing 通过 hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到 余数
这个分布在 0 到 number_of_primary_shards-1 之间的余数,就是所寻求的文档所在分片的位置
数据为什么可以被搜索——倒排索引
Elasticsearch 使用一种称为 倒排索引 的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表
例如,假设有两个文档,每个文档的 content 域包含如下内容:
1.The quick brown fox jumped over the lazy dog
2.Quick brown foxes leap over lazy dogs in summer
创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。结果如下所示
Term Doc_1 Doc_2
-------------------------
Quick | | X
The | X |
brown | X | X
dog | X |
dogs | | X
fox | X |
foxes | | X
in | | X
jumped | X |
lazy | X | X
leap | | X
over | X | X
quick | X |
summer | | X
the | X |
------------------------
现在,如果想搜索 quick brown ,我们只需要查找包含每个词条的文档:
Term Doc_1 Doc_2
-------------------------
brown | X | X
quick | X |
------------------------
Total | 2 | 1
如上,两个文档都会被匹配到
如何提高倒排索引词条的可搜索性——分析与分析器
分析包含下面的过程:
- 首先,将一块文本分成适合于倒排索引的独立的词条
-
之后,将这些词条统一化为标准格式以提高它们的”可搜索性”,或者recall分析器执行上面的工作。分析器实际上是将三个功能封装到了一个包里
-
字符过滤器
首先,字符串按顺序通过每个字符过滤器,他们的任务是在分词前整理字符串,一个字符过滤器可以用来去掉HTML,或者将&转化成’and’
-
分词器
其次,字符串被分词器分为单个的词条,一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条
-
Token过滤器
最后,词条按顺序通过每个token过滤器,这个过程可能会改变词条(例如,小写化Quick),删除词条(像 a
,
and,
the 等无用词),或者增加词条(例如,像jump和leap这种同义词)
-
搜索到的多条结果如何排序——相关性
在 Elasticsearch 中, 相关性得分 由一个浮点数进行表示,并在搜索结果中通过 _score 参数返回, 默认排序是 _score 降序,_score 的评分越高,相关性越高
查询语句会为每个文档生成一个 _score 字段。评分的计算方式取决于查询类型 不同的查询语句用于不同的目的: fuzzy 查询会计算与关键词的拼写相似程度,terms 查询会计算 找到的内容与关键词组成部分匹配的百分比,但是通常说的 relevance 是用来计算全文本字段的值相对于全文本检索词相似程度的算法
Elasticsearch 的相似度算法 被定义为检索词频率/反向文档频率, TF/IDF ,包括以下内容
-
检索词频率
检索词在该字段出现的频率,出现频率越高,相关性也越高,字段中出现过5次要比只出现过1次的相关性高
-
反向文档频率
每个检索词在索引中出现的频率,频率越高,相关性越低,检索词出现在多数文档中会比出现在少数文档中的权重更低
-
字段长度准则
字段的长度是多少,长度越长,相关性越低,检索词出现在一个短的title要比同样的词出现在一个长的content字段权重更大
理解评分标准
当调试一条复杂的查询语句时, 想要理解 _score 究竟是如何计算是比较困难的。Elasticsearch 在 每个查询语句中都有一个 explain 参数,将 explain 设为 true 就可以得到更详细的信息
GET /_search?explain
{
"query" : { "match" : { "tweet" : "honeymoon" }}
}
返回值提供了_explanation,每个入口都包含一个description、value、details字段,它分别表示计算的类型、计算结果和任何需要的计算细节
"_explanation": {
"description": "weight(tweet:honeymoon in 0)
[PerFieldSimilarity], result of:",
"value": 0.076713204,
"details": [
{
"description": "fieldWeight in 0, product of:",
"value": 0.076713204,
"details": [
{
// 检索词频率
// 检索词 `honeymoon` 在这个文档的 `tweet` 字段中的出现次数
"description": "tf(freq=1.0), with freq of:",
"value": 1,
"details": [
{
"description": "termFreq=1.0",
"value": 1
}
]
},
{
// 反向文档频率
// 检索词 `honeymoon` 在索引上所有文档的 `tweet` 字段中出现的次数
"description": "idf(docFreq=1, maxDocs=1)",
"value": 0.30685282
},
{
// 字段长度准则
// 在这个文档中, `tweet` 字段内容的长度 -- 内容越长,值越小
"description": "fieldNorm(doc=0)",
"value": 0.25,
}
]
}
]
}
JSON 形式的 explain 描述是难以阅读的, 但是转成 YAML 会好很多,只需要在参数中加上 format=yaml
理解文档是如何被匹配到的
当 explain 选项加到某一文档上时, explain api 会帮助你理解为何这个文档会被匹配,更重要的是,一个文档为何没有被匹配
GET /us/tweet/12/_explain
{
"query" : {
"bool" : {
"filter" : { "term" : { "user_id" : 2 }},
"must" : { "match" : { "tweet" : "honeymoon" }}
}
}
}
在description元素中会说明原因
"failure to match filter: cache(user_id:[2 TO 2])"
RESTful API 使用
所有其他语言可以使用 RESTful API 通过端口 9200 和 Elasticsearch 进行通信
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'
示例:
curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
"query": {
"match_all": {}
}
}
'
后面将用缩写格式来展示这些 curl 示例,如
GET /_count
{
"query": {
"match_all": {}
}
}
存储文档
PUT /megacorp/employee/1
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
megacorp
索引名称
employee
类型名称
1
特定雇员的ID
创建新文档(已存在则提示)
PUT /website/blog/123/_create
{ ... }
请求成功执行(文档不存在),返回元数据和一个 201 Created 的 HTTP 响应码
具有相同的 _index 、 _type 和 _id 的文档已经存在,返回409 Conflict 响应码,以及如下的错误信息
{
"error": {
"root_cause": [
{
"type": "document_already_exists_exception",
"reason": "[blog][123]: document already exists",
"shard": "0",
"index": "website"
}
],
"type": "document_already_exists_exception",
"reason": "[blog][123]: document already exists",
"shard": "0",
"index": "website"
},
"status": 409
}
删除文档
DELETE /website/blog/123
如果找到该文档,返回200 ok 的 HTTP 响应码和
{
"found" : true,
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 3 // _version的值会增加
}
如果文档没有找到,返回 404 Not Found 的响应码和类似这样的响应体
{
"found" : false,
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 4
// 即使文档不存在( Found 是 false ), _version 值仍然会增加,
用来确保这些改变在跨多节点时以正确的顺序执行
}
删除文档不会立即将文档从磁盘中删除,只是将文档标记为已删除状态。随着不断的索引更多的数据,Elasticsearch 将会在后台清理标记为已删除的文档
更新文档
在 Elasticsearch 中文档是 不可改变 的,不能修改它们。 相反,如果想要更新现有的文档,需要 重建索引 或者进行替换
PUT /website/blog/123
{
"title": "My first blog entry",
"text": "I am starting to get the hang of this...",
"date": "2014/01/02"
}
返回
{
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 2,
// created 标志设置成 false ,是因为相同的索引、类型和 ID 的文档已经存在
"created": false
}
通过update API可以对文档的某个位置进行部分更新,与之前描述的 检索-修改-重建索引 的处理过程相同。 区别在于这个过程发生在分片内部,这样就避免了多次请求的网络开销。通过减少检索和重建索引步骤之间的时间,减少了其他进程的变更带来冲突的可能性
新增字段tags和views
POST /website/blog/1/_update
{
"doc" : {
"tags" : [ "testing" ],
"views": 0
}
}
使用脚本部分更新文档
脚本可以在 update API中用来改变 _source 的字段内容, 它在更新脚本中称为 ctx._source 。 例如,可以使用脚本来增加博客文章中 views 的数量
POST /website/blog/1/_update
{
"script" : "ctx._source.views+=1"
}
使用脚本给 tags 数组添加一个新的标签
POST /website/blog/1/_update
{
"script" : "ctx._source.tags+=new_tag",
"params" : {
"new_tag" : "search"
}
}
通过设置 ctx.op 为 delete 来删除基于其内容的文档
POST /website/blog/1/_update
{
"script" : "ctx.op = ctx._source.views == count ? 'delete' : 'none'",
"params" : {
"count": 1
}
}
如果尝试更新一个不存在的文档,那么更新操作将会失败,可以使用 upsert 参数,指定如果文档不存在就应该先创建它
POST /website/pageviews/1/_update
{
"script" : "ctx._source.views+=1",
"upsert": {
"views": 1
}
}
对于部分更新的很多使用场景,文档已经被改变也没有关系,它们发生的先后顺序其实不太重要, 如果冲突发生了,唯一需要做的就是尝试再次更新,可以通过 设置参数 retry_on_conflict 来自动完成, 这个参数规定了失败之前 update 应该重试的次数,它的默认值为 0
POST /website/pageviews/1/_update?retry_on_conflict=5
{
"script" : "ctx._source.views+=1",
"upsert": {
"views": 0
}
}
检索文档
GET /megacorp/employee/1
返回
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
}
取回多个文档
如果需要从 Elasticsearch 检索很多文档,那么使用 multi-get 或者 mget API 来将这些检索请求放在一个请求中,将比逐个文档请求更快地检索到全部文档
mget API 要求有一个 docs 数组作为参数,每个 元素包含需要检索文档的元数据, 包括 _index 、 _type 和 _id 。如果想检索一个或者多个特定的字段,可以通过 _source 参数来指定这些字段的名字
GET /_mget
{
"docs" : [
{
"_index" : "website",
"_type" : "blog",
"_id" : 2
},
{
"_index" : "website",
"_type" : "pageviews",
"_id" : 1,
"_source": "views"
}
]
}
返回
{
"docs" : [
{
"_index" : "website",
"_id" : "2",
"_type" : "blog",
"found" : true,
"_source" : {
"text" : "This is a piece of cake...",
"title" : "My first external blog entry"
},
"_version" : 10
},
{
"_index" : "website",
"_id" : "1",
"_type" : "pageviews",
"found" : true,
"_version" : 2,
"_source" : {
"views" : 2
}
}
]
}
如果所有文档的 _index 和 _type 都是相同的,可以只传一个 ids 数组,而不是整个 docs 数组
GET /website/blog/_mget
{
"ids" : [ "2", "1" ]
}
如果请求的文档不存在,将在响应体中被报告
{
"docs" : [
{
"_index" : "website",
"_type" : "blog",
"_id" : "2",
"_version" : 10,
"found" : true,
"_source" : {
"title": "My first external blog entry",
"text": "This is a piece of cake..."
}
},
{
"_index" : "website",
"_type" : "blog",
"_id" : "1",
"found" : false
}
]
}
每个文档都是单独检索和报告的,某一个文档未能找到并不影响其他文档的检索
即使有某个文档没有找到,上述请求的 HTTP 状态码仍然是 200 。事实上,即使请求 没有 找到任何文档,它的状态码依然是 200 –因为 mget 请求本身已经成功执行。 为了确定某个文档查找是成功或者失败,需要检查 found 标记
搜索
空搜索
没有指定任何查询的空搜索 ,它简单地返回集群中所有索引下的所有文档
GET /_search
返回
{
// 一个 hits 数组包含所查询结果的前十个文档
"hits" : {
// 表示匹配到的文档总数
"total" : 14,
"hits" : [
{
"_index": "us",
"_type": "tweet",
"_id": "7",
"_score": 1,
"_source": {
"date": "2014-09-17",
"name": "John Smith",
"tweet": "The Query DSL is really powerful and flexible",
"user_id": 2
}
},
... 9 RESULTS REMOVED ...
],
// max_score 值是与查询所匹配文档的 _score 的最大值
"max_score" : 1
},
// 执行整个搜索请求耗费了多少毫秒
"took" : 4,
// 查询中参与分片的总数,以及这些分片成功了多少个失败了多少个
"_shards" : {
"failed" : 0,
"successful" : 10,
"total" : 10
},
// 查询是否超时,指定 timeout,如 GET /_search?timeout=10ms
"timed_out" : false
}
分页
- size
显示应该返回的结果数量,默认是 10 -
from
显示应该跳过的初始结果数量,默认是 0GET /_search?size=5&from=5 或 POST /_search { "from": 30, "size": 10 }
使用查询表达式搜索
一条复合语句可以合并 任何 其它查询语句,包括复合语句,这就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑
GET /megacorp/employee/_search
{
"query" : {
// 将多查询组合在一起
"bool": {
// 文档 必须 匹配这些条件才能被包含进来,对应的 must_not 表示文档 必须不 匹配这些条件才能被包含进来
"must": {
// 查询last_name字段中包含smith
"match" : {
"last_name" : "smith"
},
// 在多个字段上执行相同的 match 查询
"multi_match": {
"query": "full text search",
"fields": [ "title", "body" ]
}
},
// 必须 匹配,但它以不评分、过滤模式来进行。这些语句对评分没有贡献,只是根据过滤标准来排除或包含文档
"filter": {
"bool": {
"must": [
// gt(大于) gte(大于等于) lt(小于) lte(小于等于)
{ "range": { "date": { "gte": "2014-01-01" }}},
{ "range": { "price": { "lte": 29.99 }}}
]
}
},
// 如果满足这些语句中的任意语句,将增加 _score ,否则,无任何影响。它们主要用于修正每个文档的相关性得分
"should": [
{ "match": { "tag": "starred" }}
]
}
}
}
term 查询
term 查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些 not_analyzed 的字符串
{ "term": { "age": 26 }}
{ "term": { "date": "2014-09-01" }}
{ "term": { "public": true }}
{ "term": { "tag": "full_text" }}
terms 查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件
{ "terms": { "tag": [ "search", "full_text", "nosql" ] }}
exists 查询和 missing 查询
被用于查找那些指定字段中有值 (exists) 或无值 (missing) 的文档。这与SQL中的 IS_NULL (missing) 和 NOT IS_NULL (exists) 在本质上具有共性
{
"exists": {
"field": "title"
}
}
constant_score 查询
将一个不变的常量评分应用于所有匹配的文档,可以使用它来取代只有 filter 语句的 bool 查询。在性能上是完全相同的,但对于提高查询简洁性和清晰度有很大帮助
{
"constant_score": {
"filter": {
"term": { "category": "ebooks" }
}
}
}
验证查询
validate-query API 可以用来验证查询是否合法
GET /gb/tweet/_validate/query?explain
{
"query": {
"tweet" : {
"match" : "really powerful"
}
}
}
返回
{
"valid" : false,
"_shards" : { ... },
"explanations" : [ {
"index" : "gb",
// 不合法
"valid" : false,
// 原因
"error" : "org.elasticsearch.index.query.QueryParsingException:
[gb] No query registered for [tweet]"
} ]
}
高亮搜索
GET /megacorp/employee/_search
{
"query" : {
"match_phrase" : // 仅匹配同时包含 “rock” 和 “climbing” ,并且 二者以短语 “rock climbing” 的形式
{
"about" : "rock climbing"
}
},
"highlight": {
"fields" : {
"about" : {}
}
}
}
返回
{
...
"hits": {
"total": 1,
"max_score": 0.23013961,
"hits": [
{
...
"_score": 0.23013961,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
},
"highlight": {
"about": [
"I love to go <em>rock</em> <em>climbing</em>"
]
}
}
]
}
}
排序
多级排序
匹配的结果首先按照日期排序,然后按照相关性排序
GET /_search
{
"query" : {
"bool" : {
"must": { "match": { "tweet": "manage text search" }},
"filter" : { "term" : { "user_id" : 2 }}
}
},
"sort": [
{ "date": { "order": "desc" }},
{ "_score": { "order": "desc" }}
]
}
排序条件的顺序是很重要的。结果首先按第一个条件排序,仅当结果集的第一个 sort 值完全相同时才会按照第二个条件进行排序,以此类推
字段多值的排序
一种情形是字段有多个值的排序, 需要记住这些值并没有固有的顺序;一个多值的字段仅仅是多个值的包装
对于数字或日期,可以将多值字段减为单值,这可以通过使用 min 、 max 、 avg 或是 sum 排序模式 。 例如可以按照每个 date 字段中的最早日期进行排序
"sort": {
"dates": {
"order": "asc",
"mode": "min"
}
}
冲突处理
使用 index API 更新文档 ,可以一次性读取原始文档,做出修改,然后重新索引 整个文档 。 最近的索引请求将获胜:无论最后哪一个文档被索引,都将被唯一存储在 Elasticsearch 中。如果其他人同时更改这个文档,他们的更改将丢失
如下例,web_1 对 stock_count 所做的更改已经丢失,因为 web_2 不知道它的 stock_count 的拷贝已经过期,这有可能会造成卖出的商品超过实际的库存
变更越频繁,读数据和更新数据的间隙越长,也就越可能丢失变更
在数据库领域中,有两种方法通常被用来确保并发更新时变更不会丢失
-
悲观并发控制
这种方法被关系型数据库广泛使用,它假定有变更冲突可能发生,因此阻塞访问资源以防止冲突。 一个典型的例子是读取一行数据之前先将其锁住,确保只有放置锁的线程能够对这行数据进行修改
-
乐观并发控制
Elasticsearch 中使用的这种方法假定冲突是不可能发生的,并且不会阻塞正在尝试的操作。 然而,如果源数据在读写当中被修改,更新将会失败。应用程序接下来将决定该如何解决冲突。 例如,可以重试更新、使用新的数据、或者将相关情况报告给用户
Elasticsearch 是分布式的。当文档创建、更新或删除时, 新版本的文档必须复制到集群中的其他节点。Elasticsearch 也是异步和并发的,这意味着这些复制请求被并行发送,并且到达目的地时也许顺序是乱的。Elasticsearch 需要一种方法确保文档的旧版本不会覆盖新的版本
当尝试通过重建文档的索引来保存修改,指定 version 为修改会被应用的版本
PUT /website/blog/1?version=1
{
"title": "My first blog entry",
"text": "Starting to get the hang of this..."
}
// 只有文档现在的 _version 为 1 时,本次更新才能成功,否则返回 409 Conflict HTTP 响应码,和一个如下所示的响应体
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[blog][1]: version conflict, current [2], provided [1]",
"index": "website",
"shard": "3"
}
],
"type": "version_conflict_engine_exception",
// 文档的当前 _version 号是 2 ,但指定的更新版本号为 1
"reason": "[blog][1]: version conflict, current [2], provided [1]",
"index": "website",
"shard": "3"
},
"status": 409
}
通过外部系统使用版本控制
外部版本号的处理方式和之前讨论的内部版本号的处理方式有些不同, Elasticsearch 不是检查当前 _version 和请求中指定的版本号是否相同, 而是检查当前 _version 是否 小于 指定的版本号(版本号必须是大于零的整数, 且小于 9.2E+18 — 一个 Java 中 long 类型的正值)。 如果请求成功,外部的版本号作为文档的新 _version 进行存储
在 创建 新文档时指定外部版本号
PUT /website/blog/2?version=5&version_type=external
{
"title": "My first external blog entry",
"text": "Starting to get the hang of this..."
}
更新这个文档,指定一个新的 version 号是 10
PUT /website/blog/2?version=10&version_type=external
{
"title": "My first external blog entry",
"text": "This is a piece of cake..."
}
类型和映射
类型 在 Elasticsearch 中表示一类相似的文档。 类型由 名称,比如 user 或 blogpost 和 映射 组成
映射, 就像数据库中的 schema ,描述了文档可能具有的字段或 属性 、 每个字段的数据类型—比如 string, integer 或 date —以及Lucene是如何索引和存储这些字段的
根对象
映射的最高一层被称为 根对象 ,它可能包含下面几项:
- 一个 properties 节点,列出了文档中可能包含的每个字段的映射
- 各种元数据字段,它们都以一个下划线开头,例如 _type 、 _id 和 _source
- 设置项,控制如何动态处理新的字段,例如 analyzer 、 dynamic_date_formats 和 dynamic_templates
- 其他设置,可以同时应用在根对象和其他 object 类型的字段上,例如 enabled 、 dynamic 和 include_in_all
属性
-
type
字段的数据类型,例如 string 或 date
-
index
字段是否应当被当成全文来搜索( analyzed ),或被当成一个准确的值( not_analyzed ),还是完全不可被搜索( no )
-
analyzer
确定在索引和搜索时全文字段使用的 analyzer
元数据:_source字段
Elasticsearch 在 _source 字段存储代表文档体的JSON字符串。和所有被存储的字段一样, _source 字段在被写入磁盘之前先会被压缩
这个字段的存储,意味着下面的这些:
- 搜索结果包括了整个可用的文档——不需要额外的从另一个的数据仓库来取文档
- 如果没有 _source 字段,部分 update 请求不会生效
- 当映射改变时,需要重新索引数据,有了_source字段可以直接从Elasticsearch这样做,而不必从另一个(通常是速度更慢的)数据仓库取回所有文档
- 当不需要看到整个文档时,单个字段可以从 _source 字段提取和通过 get 或者 search 请求返回
- 调试查询语句更加简单,因为你可以直接看到每个文档包括什么,而不是从一列id猜测它们的内容
元数据:_all字段
_all 字段:一个把其它字段值 当作一个大字符串来索引的特殊字段,_all 字段在新应用的探索阶段,当还不清楚文档的最终结构时是比较有用的。可以使用这个字段来做任何查询,并且有很大可能找到需要的文档
GET /_search
{
"match": {
"_all": "john smith marketing"
}
}
元数据:文档标识
文档标识与四个元数据字段相关:
-
_id
文档的ID字符串
-
_type
文档的类型名
-
_index
文档所在的索引
-
_uid
_type和_id连接在一起构成type#id
默认情况下, _uid 字段是被存储(可取回)和索引(可搜索)的。 _type 字段被索引但是没有存储, _id 和 _index 字段则既没有被索引也没有被存储,这意味着它们并不是真实存在的
映射
为了能够将时间域视为时间,数字域视为数字,字符串域视为全文或精确值字符串, Elasticsearch 需要知道每个域中数据的类型。这个信息包含在映射中
Elasticsearch 支持 如下简单域类型:
- 字符串: string
- 整数 : byte, short, integer, long
- 浮点数: float, double
- 布尔型: boolean
-
日期: date
Elasticsearch 会使用 动态映射 ,通过JSON中基本数据类型,尝试猜测域类型,使用如下规则
JSON type | 域 type |
---|---|
布尔型:true或者false | boolean |
整数:123 | long |
浮点数:123.45 | double |
字符串:有效日期:yyyy-MM-dd | date |
字符串:foo bar | string |
如果通过引号( “123” )索引一个数字,它会被映射为 string 类型,而不是 long 。但是,如果这个域已经映射为 long ,那么 Elasticsearch 会尝试将这个字符串转化为 long ,如果无法转化,则抛出一个异常
查看映射
通过 /_mapping ,可以查看 Elasticsearch 在一个或多个索引中的一个或多个类型的映射
GET /gb/_mapping/tweet
返回
{
"gb": {
"mappings": {
"tweet": {
"properties": {
"date": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
},
"name": {
"type": "string"
},
"tweet": {
"type": "string"
},
"user_id": {
"type": "long"
}
}
}
}
}
}
自定义域映射
自定义映射允许执行下面的操作:
- 全文字符串域和精确值字符串域的区别
- 使用特定语言分析器
- 优化域以适应部分匹配
- 指定自定义数据格式
域最重要的属性是type,对于不是string的域,一般只需要设置type
{
"number_of_clicks": {
"type": "integer"
}
}
默认, string 类型域会被认为包含全文
string 域映射的两个最重要 属性是 index 和 analyzer
index
index属性控制怎样索引字符串,它可以是下面三个值:
analyzed
首先分析字符串,然后索引它,以全文索引这个域
not_analyzed
索引这个域,所以它能够被搜索,但索引的是精确值,不会对它进行分析
no
不索引这个域,这个域不会被搜索到
string 域 index 属性默认是 analyzed
修改通过如下方式
{
"tag": {
"type": "string",
"index": "not_analyzed"
}
}
其他简单类型(例如 long , double , date 等)也接受 index 参数,但有意义的值只有 no 和 not_analyzed , 因为它们永远不会被分析
analyzer
对于 analyzed 字符串域,用 analyzer 属性指定在搜索和索引时使用的分析器。默认, Elasticsearch 使用 standard 分析器,可以指定一个内置的分析器替代它,例如 whitespace 、 simple 和 english
{
"tweet": {
"type": "string",
"analyzer": "english"
}
}
更新映射
首次创建一个索引的时候,可以指定类型的映射。也可以使用 /_mapping 为新类型(或者为存在的类型更新映射)增加映射
尽管可以增加一个存在的映射,但不能修改存在的域映射。如果一个域的映射已经存在,那么该域的数据可能已经被索引。如果意图修改这个域的映射,索引的数据可能会出错,不能被正常的搜索
可以更新一个映射来添加一个新域,但不能将一个存在的域从 analyzed 改为 not_analyzed
创建一个新索引,指定 tweet 域使用 english 分析器:
PUT /gb
{
"mappings": {
"tweet" : {
"properties" : {
"tweet" : {
"type" : "string",
"analyzer": "english"
},
"date" : {
"type" : "date"
},
"name" : {
"type" : "string"
},
"user_id" : {
"type" : "long"
}
}
}
}
}
增加一个新的名为 tag 的 not_analyzed 的文本域,使用 _mapping
PUT /gb/_mapping/tweet
{
"properties" : {
"tag" : {
"type" : "string",
"index": "not_analyzed"
}
}
}
动态映射
当 Elasticsearch 遇到文档中以前 未遇到的字段,它用 dynamic mapping 来确定字段的数据类型并自动把新的字段添加到类型映射
可以用 dynamic 配置来控制
-
true
动态添加新的字段–缺省
-
false
忽略新的字段
-
strict
如果遇到新字段抛出异常
配置参数 dynamic 可以用在根 object 或任何 object 类型的字段上。可以将 dynamic 的默认值设置为 strict , 而只在指定的内部对象中开启它
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic": "strict",
"properties": {
"title": { "type": "string"},
"stash": {
"type": "object",
"dynamic": true
}
}
}
}
}
如果遇到新字段,对象 my_type 就会抛出异常,而内部对象 stash 遇到新字段就会动态创建新字段
PUT /my_index/my_type/1
{
"title": "This doc adds a new field",
"stash": { "new_field": "Success!" }
}
自定义动态映射
日期检测
当 Elasticsearch 遇到一个新的字符串字段时,它会检测这个字段是否包含一个可识别的日期,比如 2014-01-01 。 如果它像日期,这个字段就会被作为 date 类型添加。否则,它会被作为 string 类型添加
日期检测可以通过在根对象上设置 date_detection 为 false 来关闭:
PUT /my_index
{
"mappings": {
"my_type": {
"date_detection": false
}
}
}
动态模板
使用 dynamic_templates ,可以完全控制 新检测生成字段的映射。甚至可以通过字段名称或数据类型来应用不同的映射
模板按照顺序来检测;第一个匹配的模板会被启用
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic_templates": [
{ "es": {
"match": "*_es",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"analyzer": "spanish"
}
}},
{ "en": {
"match": "*",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"analyzer": "english"
}
}}
]
}}}
索引
通过使用 index API ,文档可以被 索引 —— 存储和使文档可被搜索
这个索引采用的是默认的配置,新的字段通过动态映射的方式被添加到类型映射
想禁止自动创建索引,可以通过在 config/elasticsearch.yml 的每个节点下添加下面的配置
action.auto_create_index: false
删除索引
DELETE /my_index
// 删除多个索引
DELETE /index_one,index_two
DELETE /index_*
// 删除全部索引
DELETE /_all
DELETE /*
创建一个自定义分析器
PUT /my_index
{
"settings": {
"analysis": {
"char_filter": {
"&_to_and": {
"type": "mapping",
"mappings": [ "&=> and "]
}},
"filter": {
"my_stopwords": {
"type": "stop",
"stopwords": [ "the", "a" ]
}},
"analyzer": {
"my_analyzer": {
"type": "custom",
"char_filter": [ "html_strip", "&_to_and" ],
"tokenizer": "standard",
"filter": [ "lowercase", "my_stopwords" ]
}}
}}}
索引被创建以后,使用 analyze API 来 测试这个新的分析器
GET /my_index/_analyze?analyzer=my_analyzer
The quick & brown fox
把这个分析器应用在一个 string 字段上
PUT /my_index/_mapping/my_type
{
"properties": {
"title": {
"type": "string",
"analyzer": "my_analyzer"
}
}
}
参考:
Elasticsearch: 权威指南
https://www.elastic.co/guide/cn/elasticsearch/guide/current/foreword_id.html
下一篇: 科学家发现地底超长隧道,引起世界轰动