在Elasticsearch中,文档归属于一种类型,而这些类型存在于索引中,我们可以画一些简单的对比图来类比传统关系型数据库
Relational DB -> Databases -> Tables -> Rows ->Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
Elasticsearch集群可以包含多个索引(indices),每个索引可以包含多个类型(types),每个类型包含多个文档(documents),然后每个文档包含多个字段(Fields)。
索引就像是关系数据库中的数据库,它是相关文档存储的地方,index附属是indices或indexes
索引(动词)表示把一个文档存储到索引(名词)里,以便它可以北检索或者查询。这很像SQL中的insert
关键字,差别是,如果文档已经存在,新的文档将覆盖旧的文档。
倒排索引 传统数据库为特定列增加一个索引,例如B-Tree索引来加速检索。
为了创建员工的目录,我们将进行如下操作:
employee
employeee
类型归属于索引megacorp
megacorp
索引存储在Elasticsearch
集群中实际上这些都是很容易的,尽管看起来有很多的步骤,我们通过一个命令执行完成的操作:
PUT /megacorp/employee/1
{
"first_name":"John",
"last_name":"Smith",
"age":25,
"about":"I love to go rock climbing",
"interests":["sports","music"]
}
我们看到path:/megacorp/employee/1
包含三部分信息:
名字 | 说明 |
---|---|
megacorp | 索引名 |
employee | 类型名 |
1 | 员工ID |
请求实体(JSON文档),包含了这个员工的所有信息。她的名字叫“John Smith”,25岁,喜欢攀岩。
很简单吧,它不需要你做额外的管理操作,比如创建索引或者定义每个字段的数据类型。我们能够直接索引,Elasticsearch已经内置所有的缺省设置,所有管理操作都是透明的。
接下来,让我们在目录中加入更多员工信息:
PUT /megacorp/employee/2
{
"first_name":"Jane",
"last_name":"Smith",
"age":32,
"about":"I like to collect rock albums",
"interests":["music"]
}
PUT /megacorp/employee/3
{
"first_name":"Douglas",
"last_name":"FIr",
"age":35,
"about":"I like to build cabinets",
"intersts":["forestry"]
}
GET
请求非常简单——你能轻松获取你想要的文档,让我们来进一步尝试一些东西,比如简单的搜索。
我们尝试一个最简单的搜索全部员工的请求:
GET /megacorp/employee/_search
你可以看到我们依然使用megacorp
索引和employee
类型,但是我们在结尾使用关键字_search
来取代原来的文档ID。响应内容的hit
数组中包含了我们所有的三个文档。默认情况下搜索会返回前10个结果。
{
"took":6,
"timed_out":false,
"_shards":{...},
"hit":{
"total":3,
"max_socre":1,
"hits":[
{
"_index":"megacorp",
"_type":"employee",
"_id":"3",
"_score":1,
"_source":{
"first_name":"Douglas",
"last_name":"Fir",
"age":35,
"about":"I like to build cabinets",
"insterests":["forestry"]
}
},
{
"_index":"megacorp",
"_type":"employee",
"_id":"1",
"_score":1,
"_source":{
"first_name":"John",
"last_name":"Smith",
"age":25,
"about":"I love to go rock climbing",
"insterents":["sports","music"]
}
},
{
"_index":"megacorp",
"_type":"employee",
"_id":"2",
"_score":1,
"_source":{
"first_name":"Jane",
"last_name":"Smith",
"age":32,
"about":"I like to collect rock albums",
"instersts":["music"]
}
}
]
}
}
响应内容不仅会告诉我们哪些文档被匹配到,而且这些文档内容完整的被包含在其中,我们会给用户展示搜索结果时需要用到的所有信息都有了。
接下来,让我们搜索姓氏中包含Smith
的员工,要做到这一点,我们将在命令中使用轻量级的搜索方法,这种方法常北称作查询字符串搜索,因为我们像传递URL参数一样去传递查询语句:
GET /megacorp/employee/_search?q=last_name:Smith
我们在请求中依旧使用_search
关键字,然后将查询语句传递给参数q=
。这样就可以得到所有姓氏为Smith的结果:
{
...
"hits":{
"total":2,
"max_score":0.30685282,
"hits":[
"_source":{
"first_name":"John",
"last_name":"Smith",
"age":25,
"about":"I love to go rock climbing",
"interests":["sports","music"]
}
},
{
...
"_source":{ "first_name":"Jane",
"last_name":"Smith",
"age":32,
"about":"I like to collect rock albums",
"insterests":["music"]
}
}
]
}
}
查询字符串搜索便于通过命令行完成特定(ad hoc)的搜索,但是他也有局限性。Elasticsearch提供丰富且灵活的查询语句叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询。
DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。我们可以这样表示之前关于“Smith” 的查询:
GET /megacorp/employee/_search
{
"query":{
"match":{
"last_name":"Smith"
}
}
}
这会返回与之前查询相同的结果,你可以看到有些东西改变了,我们不再使用查询字符串(query string)做为参数,而是使用请求体代替。这个请求体使用JSON表示,其中使用了match
语句(查询类型之一)。
我们让搜索稍微在变的复杂一些,我们依旧想要找到姓氏为“Smith”的员工,但是我们只想得到年龄大于30岁的员工。我们的语句将添加过滤器(filter),它使得我们高效率的执行一个结构化搜索:
GET /megacorp/employee/_search
{
"query":{
"filtered":{
"filter":{
"range":{
"age":{"gt":38}<1>
}
},
"query":{
"match":{
"last_name":"smith"<2>
}
}
}
}
}
<1>这部分查询属于区间过滤器(range filter),它用于查找所有年龄大于30岁的数据,gt为“greater than”的缩写
<2>这部分查询与之前的match语句一致
现在不要担心语法太多,我们将会在以后详细的讨论,你只要知道我们添加了一个过滤器filter用于执行区间搜索,然后重复利用了之前的match语句。现在我们搜索结果之显示了一个32岁且名字是“Jane Smith”的员工:
{
...
"hits": {
"total": 1,
"max_score": 0.30685282,
"hits": [
{
...
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [ "music" ]
}
}
]
}
}
到目前为止,搜索都很简单:搜索特定的名字,通过年龄筛选。让我们尝试一种更高级的搜索,全文搜索——一种传统数据库很难实现的功能。
我们将会搜索所有喜欢“rock climbing”的员工:
GET /megacorp/employee/_search
{
"query":{
"match":{
"about":"rock climbing"
}
}
}
你可以看到我们使用了之前的match
查询,从about
字段中搜索“rock climbing”,我们得到两个匹配文档;
{
...
"hits":{
"total":2,
"max_score":0.16273327,
"hits":[
{
...
"_score":016273327,<1>
"_source":{
"first_name":"John",
"last_name":"Smith",
"age":25,
"about":"I love to go rock climbing",
"interests":["sports","music"]
}
},
{
...
"_score":0.016878816,<2>
"_source":{
"first_name":"Jane",
"last_name":"Smith",
"age":32,
"about":"I like to collect rock albums",
"interests":["music"]
}
}
]
}
}
<1><2>结果相关性评分
默认情况下,Elasticsearch根据结果相关性评分来对结果集进行排序,所谓结果相关性评分,就是文档与查询条件的匹配程度。很显然,排名第一的John Smith
的about
字段明确的写道rock climbing
.
但是为什么Jane smith
也会出现在结果里呢?原因是rock
字段中被提及了。因为只有“rock”被提及而“climing”没有,所以她的_score
要低于John。
这个例子很好的解释了Elasticsearch如何在各种文本字段中进行全文搜索,并且返回相关性最大的结果集。相关性的概念非常重要,而这个概念在传统关系行数据库中是不可想象的,因为传统数据库对记录的查询只有匹配或者不匹配。
目前我们可以在字段中搜索单独的一个词,这挺好的,但是有时候你想要确切的匹配若干个单词或者短语。例如我们想要查询同时包含“rock”和“climbing”(并且是相邻的)的员工记录。
要做到这个,我们只要将match
查询变更为match_phrase
查询即可:
GET /megacorp/employee/_search
{
"query":{
"match_phrase":{
"about":"rock climbing"
}
}
}
毫无疑问,该查询返回John Smith的文档:
{
...
"hits":{
"total":1,
"max_score":0.23013961,
"hits":[
{
...
"_score":0.23013961,
"_source":{
"frist_name":"John",
"last_name":"Smith",
"age":25,
"about":"I love to go rock climbing",
"interests":["sports","music"]
}
}
]
}
}
很多应用喜欢从每个搜索结果中高亮匹配到的关键字,这样用户可以知道为什么这些文档和查询相匹配。在Elasticsearch中高亮片段是非常容易的。
让我们在之前的语句上增加highlight
参数
GET /megacorp/employee/_search
{
"query":{
"match_phrase":{
"about":"rock climbing"
}
},
"highlight":{
"fields":{
"about":{}
}
}
}