第五讲—RESTful API探索(三)

本文大部分内容转载至(

        ElasticSearch权威指南中文版

        @路小磊

在本节中,我们会以一个具体的实力来复习一下ElasticSearch里的索引,搜索和聚合的功能。

一、索引

  1.建立一个员工目录,需求如下:

         1.数据能够包含多个值的标签、数字和纯文本。

         2.检索任何员工的所有信息。

         3.支持结构化搜索,例如查找30岁以上的员工。

         4.支持简单的全文搜索和更复杂的短语(phrase)搜索

         5.高亮搜索结果中的关键字

         6.能够利用图表管理分析这些数据

  2.索引员工文档

        我们首先要做的是存储员工数据,每个文档代表一个员工。在Elasticsearch中存储数据的行为就叫做索引(indexing),不过在索引之前,我们需要明确数据应该存储在哪里。

        在Elasticsearch中,文档归属于一种类型(type),而这些类型存在于索引(index)中,我们可以画一些简单的对比图来类比传统关系型数据库:

        Relational DB -> Databases -> Tables -> Rows -> Columns 
        Elasticsearch -> Indices -> Types -> Documents -> Fields 

        Elasticsearch集群可以包含多个索引(indices)(数据库),每一个索引可以包含多个类型(types)(表),每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。


        所以为了创建员工目录,我们将进行如下操作(如果没用特殊说明,以后这些操作默认在Sense下执行):

  • 为每个员工的文档(document)建立索引,每个文档包含了相应员工的所有信息。
  • 每个文档的类型为employee。
  • employee类型归属于索引megacorp。
  • megacorp索引存储在Elasticsearch集群中。
PUT /megacorp/employee/ 1
{
     "first_name" : "Card" ,
     "last_name" : "Minutch" ,
     "age" : 24 ,
     "about" : "I love basketball and games" ,
     "interests" :[ "LOL" , "Music" , "NBA" ]
}

        执行后返回:

{
    "_index" : "megacorp" ,
    "_type" : "employee" ,
    "_id" : "1" ,
    "_version" : 1 ,
    "created" : true
}

        我们看到path:/megacorp/employee/1包含三部分信息:

megacorp索引名
employee类型名
1这个员工的ID
名字
说明

        接下来,让我们在目录中加入更多员工信息:

PUT /megacorp/employee/ 2
{
     "first_name" : "YiLin" ,
     "last_name" : "Wang" ,
     "age" : 25 ,
     "about" : "She is a dancer" ,
     "interests" :[ "Movie" , "Dance" ]
}
PUT /megacorp/employee/ 3
{
     "first_name" : "Huan" ,
     "last_name" : "Zhou" ,
     "age" : 23 ,
     "about" : "I like singing!" ,
     "interests" :[ "Movie" , "Sing" , "NBA" ]
}

 

二、搜索

    1.检索文档

        现在Elasticsearch中已经存储了一些数据,我们可以根据业务需求开始工作了。第一个需求是能够检索单个员工的信息。

        1)、这对于Elasticsearch来说非常简单。我们只要执行HTTP GET请求并指出文档的“地址”——索引、类型和ID既可。根据这三部分信息,我们就可以返回原始JSON文档:

GET /megacorp/employee/ 1
{
    "_index" : "megacorp" ,
    "_type" : "employee" ,
    "_id" : "1" ,
    "_version" : 1 ,
    "found" : true ,
    "_source" : {
       "first_name" : "Card" ,
       "last_name" : "Minutch" ,
       "age" : 24 ,
       "about" : "i love basketball and games" ,
       "interests" : [
          "LOL" ,
          "Music" ,
          "NBA"
       ]
    }
}

    2.简单搜索

    GET请求非常简单——你能轻松获取你想要的文档。让我们来进一步尝试一些东西,比如简单的搜索!

        2)、我们尝试一个最简单的搜索全部员工的请求:

GET /megacorp/employee/_search

        你可以看到我们依然使用megacorp索引和employee类型,但是我们在结尾使用关键字_search来取代原来的文档ID。响应内容的hits数组中包含了我们所有的三个文档。默认情况下搜索会返回前10个结果。

{
    "took" : 33 ,
    "timed_out" : false ,
    "_shards" : {
       "total" : 5 ,
       "successful" : 5 ,
       "failed" : 0
    },
    "hits" : {
       "total" : 3 ,
       "max_score" : 1 ,
       "hits" : [
          {
             "_index" : "megacorp" ,
             "_type" : "employee" ,
             "_id" : "1" ,
             "_score" : 1 ,
             "_source" : {
                "first_name" : "Card" ,
                "last_name" : "Minutch" ,
                "age" : 24 ,
                "about" : "i love basketball and games" ,
                "interests" : [
                   "LOL" ,
                   "Music" ,
                   "NBA"
                ]
             }
          },
          {
             "_index" : "megacorp" ,
             "_type" : "employee" ,
             "_id" : "2" ,
             "_score" : 1 ,
             "_source" : {
                "first_name" : "YiLin" ,
                "last_name" : "Wang" ,
                "age" : 25 ,
                "about" : "She is a dancer" ,
                "interests" : [
                   "Movie" ,
                   "Dance"
                ]
             }
          },
          {
             "_index" : "megacorp" ,
             "_type" : "employee" ,
             "_id" : "3" ,
             "_score" : 1 ,
             "_source" : {
                "first_name" : "Huan" ,
                "last_name" : "Zhou" ,
                "age" : 23 ,
                "about" : "I like singing!" ,
                "interests" : [
                   "Movie" ,
                   "Sing" ,
                   "NBA"
                ]
             }
          }
       ]
    }
}

        3)、接下来,让我们搜索姓氏中包含“Minutch”的员工。要做到这一点,我们将在命令行中使用轻量级的搜索方法。这种方法常被称作查询字符串(query string)搜索,因为我们像传递URL参数一样去传递查询语句:

GET /megacorp/employee/_search?q=last_name=Minutch
{
    "took" : 15 ,
    "timed_out" : false ,
    "_shards" : {
       "total" : 5 ,
       "successful" : 5 ,
       "failed" : 0
    },
    "hits" : {
       "total" : 1 ,
       "max_score" : 0.01125201 ,
       "hits" : [
          {
             "_index" : "megacorp" ,
             "_type" : "employee" ,
             "_id" : "1" ,
             "_score" : 0.01125201 ,
             "_source" : {
                "first_name" : "Card" ,
                "last_name" : "Minutch" ,
                "age" : 24 ,
                "about" : "i love basketball and games" ,
                "interests" : [
                   "LOL" ,
                   "Music" ,
                   "NBA"
                ]
             }
          }
       ]
    }
}

    3.使用DSL语句查询

        查询字符串搜索便于通过命令行完成特定(ad hoc)的搜索,但是它也有局限性(参阅简单搜索章节)。Elasticsearch提供丰富且灵活的查询语言叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询。

        DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。我们可以这样表示之前关于“Minutch”的查询:

GET /megacorp/employee/_search
{
   "query" :{
     "match" : {
       "last_name" : "Minutch"
     }
   }
}

        这会返回与之前查询相同的结果。你可以看到有些东西改变了,我们不再使用查询字符串(query string)做为参数,而是使用请求体代替。这个请求体使用JSON表示,其中使用了match语句(查询类型之一,具体我们以后会学到)。

    4.更复杂的搜索

        1)、我们让搜索稍微再变的复杂一些。我们依旧想要找到姓氏为“Minutch”的员工,但是我们只想得到年龄大于30岁的员工。我们的语句将添加过滤器(filter),它使得我们高效率的执行一个结构化搜索:

GET /megacorp/employee/_search
{
   "query" :{
     "filtered" : {
       "query" : {
         "match" : {
           "last_name" : "Minutch" < 2 >
         }
       },
       "filter" : {
         "range" : {
           "age" : {
             "gte" : 30 < 1 >
           }
         }
       }
     }
   }
}
  • <1> 这部分查询属于区间过滤器(range filter),它用于查找所有年龄大于30岁的数据——gt为"greater than"的缩写。
  • <2> 这部分查询与之前的match语句(query)一致。

        结果如下:

{
    "took" : 9 ,
    "timed_out" : false ,
    "_shards" : {
       "total" : 5 ,
       "successful" : 5 ,
       "failed" : 0
    },
    "hits" : {
       "total" : 0 ,
       "max_score" : null ,
       "hits" : []
    }
}

 

        2)、我们依旧想要找到姓氏为“Minutch”的员工,但是我们只想得到年龄小于30岁的员工。我们的语句将添加过滤器(filter),它使得我们高效率的执行一个结构化搜索:

GET /megacorp/employee/_search
{
   "query" :{
     "filtered" : {
       "query" : {
         "match" : {
           "last_name" : "Minutch" < 2 >
         }
       },
       "filter" : {
         "range" : {
           "age" : {
             "lte" : 30 < 1 >
           }
         }
       }
     }
   }
}
{
    "took" : 8 ,
    "timed_out" : false ,
    "_shards" : {
       "total" : 5 ,
       "successful" : 5 ,
       "failed" : 0
    },
    "hits" : {
       "total" : 1 ,
       "max_score" : 0.30685282 ,
       "hits" : [
          {
             "_index" : "megacorp" ,
             "_type" : "employee" ,
             "_id" : "1" ,
             "_score" : 0.30685282 ,
             "_source" : {
                "first_name" : "Card" ,
                "last_name" : "Minutch" ,
                "age" : 24 ,
                "about" : "i love basketball and games" ,
                "interests" : [
                   "LOL" ,
                   "Music" ,
                   "NBA"
                ]
             }
          }
       ]
    }
}

 

    5.全文搜索

        到目前为止搜索都很简单:搜索特定的名字,通过年龄筛选。让我们尝试一种更高级的搜索,全文搜索——一种传统数据库很难实现的功能。

        我们将会搜索所有喜欢“basketball games dancer”的员工:

GET /megacorp/employee/_search
{
   "query" :{
     "match" : {
       "about" : "basketball games dancer"
     }
   }
}
{
    "took" : 13 ,
    "timed_out" : false ,
    "_shards" : {
       "total" : 5 ,
       "successful" : 5 ,
       "failed" : 0
    },
    "hits" : {
       "total" : 2 ,
       "max_score" : 0.05038611 ,
       "hits" : [
          {
             "_index" : "megacorp" ,
             "_type" : "employee" ,
             "_id" : "1" ,
             "_score" : 0.05038611 ,
             "_source" : {
                "first_name" : "Card" ,
                "last_name" : "Minutch" ,
                "age" : 24 ,
                "about" : "i love basketball and games" ,
                "interests" : [
                   "LOL" ,
                   "Music" ,
                   "NBA"
                ]
             }
          },
          {
             "_index" : "megacorp" ,
             "_type" : "employee" ,
             "_id" : "2" ,
             "_score" : 0.010844367 ,
             "_source" : {
                "first_name" : "YiLin" ,
                "last_name" : "Wang" ,
                "age" : 25 ,
                "about" : "She is a dancer" ,
                "interests" : [
                   "Movie" ,
                   "Dance"
                ]
             }
          }
       ]
    }
}

        默认情况下,Elasticsearch根据结果相关性评分来对结果集进行排序,所谓的「结果相关性评分」就是文档与查询条件的匹配程度。很显然,排名第一的Minutchabout字段明确的写到basketball and games

        但是为什么Wang也会出现在结果里呢?原因是“dancer”在她的abuot字段中被提及了。因为只有“dancer”被提及而“backetball games”没有,所以她的_score要低于Minutch。

        这个例子很好的解释了Elasticsearch如何在各种文本字段中进行全文搜索,并且返回相关性最大的结果集。相关性(relevance)的概念在Elasticsearch中非常重要,而这个概念在传统关系型数据库中是不可想象的,因为传统数据库对记录的查询只有匹配或者不匹配。

 

    6.高亮我们的搜索

        很多应用喜欢从每个搜索结果中高亮(highlight)匹配到的关键字,这样用户可以知道为什么这些文档和查询相匹配。在Elasticsearch中高亮片段是非常容易的。

        让我们在之前的语句上增加highlight参数:

GET /megacorp/employee/_search
{
   "query" :{
     "match" : {
       "about" : "basketball games Dancer"
     }
   },
   "highlight" : {
     "fields" : {
       "about" :{}
     }
   }
}

        当我们运行这个语句时,会命中与之前相同的结果,但是在返回结果中会有一个新的部分叫做highlight,这里包含了来自about字段中的文本,并且用<em></em>来标识匹配到的单词。

{
    "took" : 116 ,
    "timed_out" : false ,
    "_shards" : {
       "total" : 5 ,
       "successful" : 5 ,
       "failed" : 0
    },
    "hits" : {
       "total" : 2 ,
       "max_score" : 0.05038611 ,
       "hits" : [
          {
             "_index" : "megacorp" ,
             "_type" : "employee" ,
             "_id" : "1" ,
             "_score" : 0.05038611 ,
             "_source" : {
                "first_name" : "Card" ,
                "last_name" : "Minutch" ,
                "age" : 24 ,
                "about" : "i love basketball and games" ,
                "interests" : [
                   "LOL" ,
                   "Music" ,
                   "NBA"
                ]
             },
             "highlight" : {
                "about" : [
                   "i love <em>basketball</em> and <em>games</em>"
                ]
             }
          },
          {
             "_index" : "megacorp" ,
             "_type" : "employee" ,
             "_id" : "2" ,
             "_score" : 0.010844367 ,
             "_source" : {
                "first_name" : "YiLin" ,
                "last_name" : "Wang" ,
                "age" : 25 ,
                "about" : "She is a dancer" ,
                "interests" : [
                   "Movie" ,
                   "Dance"
                ]
             },
             "highlight" : {
                "about" : [
                   "She is a <em>dancer</em>"
                ]
             }
          }
       ]
    }
}

 

三、聚合

 

    1.分析

        最后,我们还有一个需求需要完成:允许管理者在职员目录中进行一些分析。 Elasticsearch有一个功能叫做聚合(aggregations),它允许你在数据上生成复杂的分析统计。它很像SQL中的GROUP BY但是功能更强大。

        1)、举个例子,让我们找到所有职员中最大的共同点(兴趣爱好)是什么:

GET /megacorp/employee/_search
{
   "aggs" : {
     "all_interests" : {
       "terms" : {
         "field" : "interests"
       }
     }
   }
}

        根据结果:两人爱看电影,两人爱nba,一个人爱跳舞....

{
    "took" : 27 ,
    "timed_out" : false ,
    "_shards" : {
       "total" : 5 ,
       "successful" : 5 ,
       "failed" : 0
    },
    "hits" : {
       ...
    },
    "aggregations" : {
       "all_interests" : {
          "doc_count_error_upper_bound" : 0 ,
          "sum_other_doc_count" : 0 ,
          "buckets" : [
             {
                "key" : "movie" ,
                "doc_count" : 2
             },
             {
                "key" : "nba" ,
                "doc_count" : 2
             },
             {
                "key" : "dance" ,
                "doc_count" : 1
             },
             {
                "key" : "lol" ,
                "doc_count" : 1
             },
             {
                "key" : "music" ,
                "doc_count" : 1
             },
             {
                "key" : "sing" ,
                "doc_count" : 1
             }
          ]
       }
    }
}

       2)、所有姓"Minutch"的人的爱好

GET /megacorp/employee/_search
{
   "query" : {
     "match" : {
       "last_name" : "Minutch"
     }
   },
   "aggs" : {
     "all_interests" : {
       "terms" : {
         "field" : "interests"
       }
     }
   }
}
{
    "took" : 15 ,
    "timed_out" : false ,
    "_shards" : {
       "total" : 5 ,
       "successful" : 5 ,
       "failed" : 0
    },
    "hits" : {
       "total" : 1 ,
       "max_score" : 0.30685282 ,
       "hits" : [
          {
             "_index" : "megacorp" ,
             "_type" : "employee" ,
             "_id" : "1" ,
             "_score" : 0.30685282 ,
             "_source" : {
                "first_name" : "Card" ,
                "last_name" : "Minutch" ,
                "age" : 24 ,
                "about" : "i love basketball and games" ,
                "interests" : [
                   "LOL" ,
                   "Music" ,
                   "NBA"
                ]
             }
          }
       ]
    },
    "aggregations" : {
       "all_interests" : {
          "doc_count_error_upper_bound" : 0 ,
          "sum_other_doc_count" : 0 ,
          "buckets" : [
             {
                "key" : "lol" ,
                "doc_count" : 1
             },
             {
                "key" : "music" ,
                "doc_count" : 1
             },
             {
                "key" : "nba" ,
                "doc_count" : 1
             }
          ]
       }
    }
}

        3)、聚合也允许分级汇总。例如,让我们统计每种兴趣下职员的平均年龄:

GET /megacorp/employee/_search
{
"aggs" : {
     "all_interests" : {
       "terms" : {
         "field" : "interests"
       },
       "aggs" : {
         "avg_age" : {
           "avg" : {
             "field" : "age"
           }
         }
       }
     }
   }
}
{
    "took" : 64 ,
    "timed_out" : false ,
    "_shards" : {
       "total" : 5 ,
       "successful" : 5 ,
       "failed" : 0
    },
    "hits" : {
       ...
    },
    "aggregations" : {
       "all_interests" : {
          "doc_count_error_upper_bound" : 0 ,
          "sum_other_doc_count" : 0 ,
          "buckets" : [
             {
                "key" : "movie" ,
                "doc_count" : 2 ,
                "avg_age" : {
                   "value" : 24
                }
             },
             {
                "key" : "nba" ,
                "doc_count" : 2 ,
                "avg_age" : {
                   "value" : 23.5
                }
             },
             {
                "key" : "dance" ,
                "doc_count" : 1 ,
                "avg_age" : {
                   "value" : 25
                }
             },
             {
                "key" : "lol" ,
                "doc_count" : 1 ,
                "avg_age" : {
                   "value" : 24
                }
             },
             {
                "key" : "music" ,
                "doc_count" : 1 ,
                "avg_age" : {
                   "value" : 24
                }
             },
             {
                "key" : "sing" ,
                "doc_count" : 1 ,
                "avg_age" : {
                   "value" : 23
                }
             }
          ]
       }
    }
}

 

        该聚合结果比之前的聚合结果要更加丰富。我们依然得到了兴趣以及数量(指具有该兴趣的员工人数)的列表,但是现在每个兴趣额外拥有avg_age字段来显示具有该兴趣员工的平均年龄。

        即使你还不理解语法,但你也可以大概感觉到通过这个特性可以完成相当复杂的聚合工作,你可以处理任何类型的数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值