Elasticsearch检索实战

原文:https://www.fanhaobai.com/2017/08/elasticsearch-search.html

随着公司房源数据的急剧增多,现搜索引擎 Solr 的搜索效率和建立索引效率显著降低,而 Elasticsearch 是一个实时的分布式搜索和分析引擎,它是基于全文搜索引擎 Apache Lucene 之上,接入 Elasticsearch 是必然之选。本文是我学习使用 Elasticsearch 检索的笔记。

Elasticsearch 支持 RESTful API 方式检索,查询结果以 JSON 格式响应,文中示例数据见 这里。有关 Elasticsearch 详细使用说明,见 官方文档

Url

检索 url 中需包含 索引名_search为查询关键字。例如 http://es.fanhaobai.com/rooms/_search 的 rooms 为索引名,此时表示无任何条件检索,检索结果为:

GET /rooms/_search

{
   "took": 6,
   "timed_out": false,
   "_shards": { ... },
   "hits": {
      "total": 3,
      "max_score": 1,
      "hits": [
         {
            "_index": "rooms",
            "_type": "room_info",
            "_id": "3",
            "_score": 1,
            "_source": {
               "resblockId": "1111027377528",
               "resblockName": "金隅丽港城",
               "houseId": 1087599828743,
               "cityCode": 110000,
               "size": 10.5,
               "bizcircleCode": [ "18335711" ],
               "bizcircleName": [ "望京" ],
           "price": 2300
            }
         },
         {
            ... ...
            "_source": {
               "resblockId": "1111047349969",
               "resblockName": "融泽嘉园",
               "houseId": 1087817932553,
               "cityCode": 110000,
               "size": 10.35,
               "bizcircleCode": [ "611100314" ],
               "bizcircleName": [ "西二旗" ],
               "price": 2500
            }
         },
     ... ...
      ]
   }
}

注:Elasticsearch 官方偏向于使用 GET 方式(能更好描述信息检索的行为),GET 方式可以携带请求体,但是由于不被广泛支持,所以 Elasticsearch 也支持 POST 请求。后续查询语言使用 POST 方式。

当我们确定了需要检索文档的 url 后,就可以使用查询语法进行检索,Elasticsearch 支持以下 Query string(查询字符串)和 DSL(结构化)2 种检索语句。

检索语句

Query string

我们可以直接在 get 请求时的 url 后追加q=查询参数,这种方法常被称作 query string 搜索,因为我们像传递 url 参数一样去传递查询语句。例如查询小区 id 为 1111027374551 的房源信息:

GET /rooms/_search?q=resblockId:1111027374551

//查询结果,无关信息已省略
{
   "hits": [
      {
         "_source": {
            "resblockId": "1111027374551",
            "resblockName": "国风北京二期",
            ... ...
         }
      }
   ]
}

虽然查询字符串便于查询特定的搜索,但是它也有局限性。

DSL

DSL 查询以 JSON 请求体的形式出现,它允许构建更加复杂、强大的查询。DSL 方式查询上述 query string 查询条件则为:

POST /rooms/_search

{
   "query": {
      "term": {
         "resblockId": "1111027374551"
      }
   }
}

term 语句为过滤类型之一,后面再进行说明。使用 DSL 语句查询支持 filter(过滤器)、match(全文检索)等复杂检索场景。

基本检索

Elasticsearch 支持为 2 种检索行为,它们都是使用 DSL 语句来表达检索条件,分别为 query (结构化查询)和 filter(结构化搜索)。

说明:后续将使用 SQL 对比 DSL 语法进行搜索条件示例。

结构化查询

结构化查询支持全文检索,会对检索结果进行相关性计算。使用结构化查询,需要传递 query 参数:

{ "query": your_query }
//your_query为{}表示空查询

注:后续查询中不再列出 query 参数,只列出 your_query(查询内容)。

MATCH_ALL查询

match_all 查询简单的匹配所有文档。在没有指定查询方式时,它是默认的查询。查询所有房源信息:

-- SQL表述
SELECT * FROM rooms

match_all 查询为:

{ "match_all": {}}

MATCH查询

match 查询为全文搜索,类似于 SQL 的 LIKE 查询。查询小区名中包含“嘉”的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE resblockName LIKE '%嘉%'

match 查询为:

{ "match": { "resblockName": "嘉" }}
//结果
"hits": [
    {
        "_source": {
            "resblockId": "1111047349969",
            "resblockName": "融泽嘉园",
            ... ...
        }
    }
]

MULTI_MATCH查询

multi_match 查询可以在多个字段上执行相同的 match 查询:

{
    "multi_match": {
        "query":  "京",
        "fields": [ "resblockName", "bizcircleName" ]
    }
}

RANGE查询

range 查询能检索出那些落在指定区间内的文档,类似于 SQL 的 BETWEEN 操作。range 查询被允许的操作符有:

操作符 操作关系
gt 大于
gte 大于等于
lt 小于
lte 小于等于

查询价格在 (2000, 2500] 的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE price BETWEEN 2000 AND 2500 AND price != 2000

range 查询为:

{
    "range": {
        "price": {
            "gt": 2000,
            "lte": 2500
        }
    }
}

TERM查询

term 查询用于精确值匹配,可能是数字、时间、布尔。例如查询房屋 id 为 1087599828743 的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE houseId = 1087599828743

term 查询为:

{ "term": { "houseId": 1087599828743 }}

TERMS查询

terms 查询同 term 查询,但它允许指定多值进行匹配,类似于 SQL 的 IN 操作。例如查询房屋 id 为 1087599828743 或者 1087817932342 的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE houseId IN (1087599828743, 1087817932342)

terms 查询为:

{ "terms": { "houseId": [ 1087599828743, 1087817932342 ] }}

term 查询和 terms 查询都不分析输入的文本, 不会进行相关性计算。

EXISTS查询和MISSING查询

exists 查询和 missing 查询被用于查找那些指定字段中有值和无值的文档,类似于 SQL 中的 IS NOT NULL 和 IS NULL 查询。查询价格有效的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE price IS NOT NULL

exists 查询为:

{ "exists": { "field": "price" }}

BOOL查询

我们时常需要将多个条件的结构进行逻辑与和或操作,等同于 SQL 的 AND 和 OR,这时就应该使用 bool 子句合并多子句结果。 共有 3 种 bool 查询,分别为 must(AND)、must_not(NOT)、should(OR)。

操作符 描述
must AND 关系,必须 匹配这些条件才能检索出来
must_not NOT 关系,必须不 匹配这些条件才能检索出来
should OR 关系,至少匹配一条 条件才能检索出来
filter 必须 匹配,不参与评分

查询小区中包含“嘉”字或者房屋 id 为 1087599828743 的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE (resblockName LIKE '%嘉%' OR houseId = 1087599828743) AND (cityCode = 110000)

bool 查询为:

{
  "bool": {
     "must": {
        "term": {"cityCode": 110000 }
     },
     "should": [
        { "term": { "houseId": 1087599828743 }},
        { "match": { "resblockName": "嘉" }}
     ]
  }
}

使用 filter 语句来使得其子句不参与评分过程,减少评分可以有效地优化性能。重写前面的例子:

{
  "bool": {
     "should": [
        { "match": { "resblockName": "嘉" }}
     ],
     "filter" : {
        "bool": {
           "must": { "term": { "cityCode": 110000 }},
           "should": [
              { "term": { "houseId": 1087599828743 }}
           ]
        }
     }
  }
}

bool 查询可以相互的进行嵌套,已完成非常复杂的查询条件。

CONSTANT_SCORE查询

constant_score 查询将一个不变的常量评分应用于所有匹配的文档。它被经常用于你只需要执行一个 filter(过滤器)而没有其它查询(评分查询)的情况下。

{
    "constant_score": {
        "filter": {
            "term": { "houseId": 1087599828743 } 
        }
    }
}

结构化搜索

结构化搜索的查询适合确定值数据(数字、日期、时间),这些类型数据都有明确的格式。结构化搜索结果始终是是或非,结构化搜索不关心文档的相关性或分数,它只是简单的包含或排除文档,由于结构化搜索使用到过滤器,在查询时需要传递 filter 参数,由于 DSL 语法查询必须以 query 开始,所以 filter 需要放置在 query 里,因此结构化查询的结构为:

{
    "query": {
        "constant_score": { 
            "filter": {
                //your_filters
            }
        }
    }
}

注:后续搜索中不再列出 query 参数,只列出 your_filters(过滤内容)。

结构化搜索一样存在很多过滤器 term、terms、range、exists、missing、bool,我们在结构化查询中都已经接触过了。

TERM搜索

最为常用的 term 搜索用于查询精确值,可以用它处理数字(number)、布尔值(boolean)、日期(date)以及文本(text)。查询小区 id 为 1111027377528 的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE resblockId = "1111027377528"

term 搜索为:

{ "term": { "resblockId": "1111027377528" }}

类似XHDK-A-1293-#fJ3这样的文本直接使用 term 查询时,可能无法获取到期望的结果。是因为 Elasticsearch 在建立索引时,会将该数据分析成 xhdk、a、1293、#fj3 字样,这并不是我们期望的,可以通过指定 not_analyzed 告诉 Elasticsearch 在建立索引时无需分析该字段值。

TERMS搜索

terms 搜索使用方式和 term 基本一致,而 terms 是搜索字段多值的情况。查询商圈 code 为 18335711 或者 611100314 的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE bizcircleCode IN (18335711, 611100314)

terms搜索为:

{ "terms": { "bizcircleCode": [ "18335711", "611100314" ] }}

RANGE搜索

在进行范围过滤查询时使用 range 搜索,支持数字、字母、日期的范围查询。查询面积在 [15, 25] 平米之间的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE size BETWEEN 10 AND 25

range 搜索为:

{
    "range": {
        "size": {
            "gte": 10,
            "lte": 25
        }
    }
}

range 搜索使用在日期上:

{
    "range": {
        "date": {
            "gt": "2017-01-01 00:00:00",
            "lt": "2017-01-07 00:00:00"
        }
    }
}

EXISTS和MISSING搜索

exists 和 missing 搜索是针对某些字段值存在和缺失的查询。查询房屋面积存在的房源列表:

-- SQL表述
SELECT * FROM rooms WHERE size IS NOT NULL

exists 搜索为:

{"exists": { "field": "size" }}

missing 搜索刚好和 exists 搜索相反,但语法一致。

BOOL组合搜索

bool 过滤器是为了解决过滤多个值或字段的问题,它可以接受多个其他过滤器作为子过滤器,并将这些过滤器结合成各式各样的逻辑组合。

bool 过滤器的组成部分,同 bool 查询一致:

{
   "bool": {
      "must":     [],
      "should":   [],
      "must_not": [],
   }
}

类似于如下 SQL 查询条件:

SELECT * FROM rooms WHERE (bizcircleCode = 18335711 AND price BETWEEN 2000 AND 2500) OR (bizcircleCode = 611100314 AND price >= 2500)

使用 bool 过滤器实现为:

{
   "bool": {
       "should": [
           { 
              "term": { "bizcircleCode": "18335711" },
              "range": { "price": { "gte": 2000, "lte": 25000 }}
           }, 
           { 
              "term": { "bizcircleCode": "611100314" },
              "range": { "price": { "gte": 2500 }}
           } 
       ]
   }
}

区别:结构化查询会进行相关性计算,因此不会缓存检索结果;而结构化搜索会缓存搜索结果,因此具有较高的检索效率,在不需要全文搜索或者其它任何需要影响相关性得分的查询中建议只使用结构化搜索。当然,结构化查询和结构化搜索可以配合使用。

聚合

该部分较复杂,已单独使用文章进行说明,见 Elasticsearch检索 — 聚合和LBS 部分。

_source子句

某些时候可能不需要返回文档的全部字段,这时就可以使用 _source 子句指定返回需要的字段。只返回需要的房源信息字段:

{
   "_source": [ "cityCode", "houseId", "price", "resblockName" ]
}

sort子句

简单排序

排序是使用比较多的推荐方式,在 Elasticsearch 中,默认会按照相关性进行排序,相关性得分由一个浮点数进行表示,并在搜索结果中通过_score参数返回(未参与相关性评分时分数为 1), 默认是按_score降序排序。

sort 方式有 desc、asc 两种。将房源查询结果按照价格升序排列:

{
   "sort": {
      "price": { "order": "asc" }}
   }
}

多级排序

当存在多级排序的场景时,结果首先按第一个条件排序,仅当结果集的第一个 sort 值完全相同时才会按照第二个条件进行排序,以此类推。

{
   "sort": [
      { "price": { "order": "asc" }},
      { "_score": { "order": "desc" }}  //price一直时,按照相关性降序
   ]
}

字段多指排序

当字段值为 多值字段多指排序,Elasticsearch 会对于数字或日期类型将多值字段转为单值。转化有 min 、max 、avg、 sum 这 4 种模式。

例如,将房源查询结果按照商圈 code 升序排列:

{
   "sort": {
      "bizcircleCode": {
         "order": "asc",
         "mode":  "min"
      }
   }
}

分页子句

和 SQL 使用 LIMIT 关键字返回单 page 结果的方法相同,Elasticsearch 接受 from(初始结果数量)和 size(应该返回结果数量) 参数:

{
   "size": 8,
   "from": 1
}

验证查询合法性

在实际应用中,查询可能变得非常的复杂,理解起来就有点困难了。不过可以使用validate-queryAPI来验证查询合法性。

GET /room/_validate/query
{
   "query": { "resblockName": { "match": "嘉" }}
}

合法的 query 返回信息:

{
   "valid":         false,
   "_shards": {
      "total":       1,
      "successful":  1,
      "failed":      0
   }
}

最后

别的业务线已经投入 Elasticsearch 使用有段时间了,找房业务线正由 Solr 切换为 Elasticsearch,各个系统有一个探索和磨合的过程。当然,Elasticsearch 我们已经服务化了,对 DSL 语法也进行了一些简化,同时支持了定制化业务。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容