Elasticsearch Tutorial

Lucene是Apache下的文本搜索引擎,而Elasticsearch是以lucene作为内核的,JSON based, distributed 网络服务。因此可以把Elasticsearch看作lucene封装后的分析引擎。

Easticsearch 基本概念

Document: 文档,以key-value的形式存储。Lucene把文档下不同类型(string, integer, date等)都看作是bytes。
Index索引:相当于database。 Type类型: 描述同一类document, 相当于表名。 Mapping: 数据库的schema, 记录Type的字段、属性一集字段的数据类型、索引方式

Elasticsearch的查询语言称为Query DSL(Query Domin-Specific-languasge)

Elasticsearch的文档索引方式

  • 倒排索引(inverted-index): 对于所有文档unique的词汇,维护一个出现文档的列表。从而当查询某几个词汇时,根据文档包含各词汇的数目,按相关性排序并返回。形式:word1: doc1, doc2; word2: doc1; word3: doc1, doc3.

  • Doc values: 当使用aggs用terms对某一文本field进行分桶计数时,倒排索引需要对每个word进行group by计数,效率十分低,因此一般采用doc values, 具体而言就是对倒排索引进行转置。doc1: word1, word2, word3; doc2: word4, word5. 这时计算唯一word(或者叫做token)的计数就更方便。和倒排索引一样,Doc, values一样默认对所有字段(数字、经纬度、日期、IP、not_analyzed字符串)开启。对于analysed的字符串,如果要使用terms桶,需要将该字段设置成fielddata,该操作将会对analysed字段生成doc values并存储在内存中,因此要谨慎该操作。更谨慎的做法是将该字段设置为not_analysed。

Elasticsearch数据类型

可以使用GET indexname/_mapping获取mapping信息, 完整的数据类型见docs

  • string: 包括text和keyword两种类型。前者一般用于长文本,是analyzed。后者一般用于类似于email地址,hostnames, status code, tags这种的短文本。但默认(?)情况下,一个字段如果是text类型的,则包含一个feilds的属性, 以支持子字段keyword, 该子字段的type是keyword, 因此同一个字段支持多种方式的索引方式。

Elasticsearch Put

使用put插入数据, 下面向longhash/employee表插入_id为1的document。


curl -XPUT 'localhost:9200/longhash/employee/1' -d '
{
    "first_name": "qianchao",
    "last_name": "liu",
    "age": "24",
    "interests": ["programming", "sport"]
}

'

Elasticsearch Query Language

检索数据库的语法格式:

curl -XGET 'localhost:9200/longhash/employee/_search' -d '
{
    "query":{
        "match":{
            "last_name": "liu"
        }
    },
    "highlight": {
        "fields": {
            "age":{}
        }
    }
}


'

from, size, 子列约束

通过from设置从第几个结果开始 size则相当用limit fileds:[“subcolumn”]用来约束要返回的子列, 但默认情况下fileds的支持是不开启的,官方更建议使用“_source”:[“subcolumn”]

{
  "from": 0,
  "size": 3,
  "_source": ["message"],
  "query": {
    "query_string": {
      "query": "message:bitcoin"
    }
  }
}

term

term要求完全匹配(and关系)传入的值。

term: 单条查询

{
    "query":{
        "term":{
            "message": "bitcoin"
        }
    }
}

terms: 多条查询, 通过minimum_match确定至少符合条件的数目

{
    "query":{
        "term":{
            "message": ["bitcoin", "ethereum"],
            "minimum_match":1
        }
    }
}

match查询

不同于term的完全匹配,match默认对字符串是分词后全文搜索Query(对数字、时间、bool或者not_analyzed字段仍是精确匹配Filter),并根据tf-idf值排序后返回结果, 可以使用operator该修改or的逻辑: match使用operator来设置匹配模式:and/or

{
  "query": {
    "match": {
      "message": {
        "query": "keyword1 keyword2",
        "operator": "and"
      }
    }
  }
}

使用slop匹配缺失值, slop为缺省内容的数目, 下面的语句会返回所有keyword1 xxx xxx keyword4

{
  "query": {
    "match": {
      "message": {
        "query": "keyword1 keyword4",
        "slop": 2
      }
    }
  }
}

使用multi_match,来对不同的字段,使用相同的规则, 只要有一个字段满足条件就会返回数据

{
    "multi_match":{
        "query" : "bitcoin"
        "fields": ["title", "body"]
    }
}

bool

所有符合条件都需要使用bool语句

  • “must”: 必须匹配
  • “should”: 至少满足一个
  • “must_not”: 不满足
  • “filter”: 必须匹配,但不评分
{
    "query":{
        "filtered":{
            "filter" :{
                
                    "bool":{
                        "must":{
                            "term":{
                                "message": "bitcoin"
                                }
                        },
                        "should": {
                            "range":{
                                "timestamp": {"gte": 15, "lte": 20}
                            }
                        },
                        "must_not": {
                            "term": {
                                "message": "eos"
                            }
                        }
                    }

            }
        }




    }
}

exists, missing

相当于sql中的is not null和is null

{
    "exists": {
        "field": "title"
    }
}

filter

上面我们写了很多条件筛选的语句,那么为什么这还需要filter关键词?这里是因为非filter的查询一般会进行doc评分,所以速度回比较慢,而一旦指定了filter查询,则进行精确查询。

例1, 使用term,将默认进行文档评分

{
    "query":{
        "term": {"price":20}
    }
}

例2,使用constant_score,取消文档评分, 常用来代替只有filter的bool语句

{
    "constant_score":{
        "filter": {
            "term": {"price":20}
        }
    }
}

例3, 但当使用constan_score:filter: term查询文本时,如果文本字段是analyzed时,使用term会抹去大小写,符号等,从而无法实现精确值查询,为避免该问题,需要将字段index设置为not_analyzed.

sort

 "query":{
     "bool":{
         "must": {
             "match":{
                 "tweet": "some text"
                 }},
         "filter":{
             "term":{
                 "user_id":1
             }
         }
     }
 }

 "sort": [
    {
      "@timestamp": "desc"
    }, {
      "_score": "desc"
    }
  ]

aggregation

buckets: 使用group by将文档划分成符合不同条件的子集。

metrics:count(), sum(), max()等计算。

查询各个颜色产品的数量。这里使用bucket的方式是terms, 其他分桶的方法包括histogram(field, interval), date_histogram(filed, interval) 。但上述两种histogram默认只返回文档数目非0的桶,如果想要x axis连续,需要设置两个额外的参数:”min_doc_count”:0, “extended_bounds”: {“min”: “2018-05-01”, “max”:”2018-06-01”}

{
    "size":0, // 只返回aggs的结果。
    "aggs": {
        "name_your_bucket":{
            "terms":{
                "filed": "color" // 这里是一个terms桶,每个color动态创建一个桶。
            }
        }
    }
}

查询各个颜色产品的的平均价格,这里使用的metrics是avg, 其他常用的包括max, min, sum, extended_stats, percentiles等:

{
    "size":0,
    "aggs": {
        "name_your_bucket":{
            "terms":{ // 这里是一个terms桶,每个color动态创建一个桶。
                "filed": "color" 
            },
            "aggs":{
                "name_your_metrics":{
                    "avg":{ // 这里是一个avg度量
                        "field":"price"
                    }
                }
            }
        }
    }
}

上面仅仅是进行聚合,如果想要加入筛选需要指定query的内容。上面的两个查询等价于在query里使用match_all。

{
    "size":0,
    "query": {
        "bool":{
            "filter": {
                "year": 2018
            } 
        }
    },
    "aggs":{
        "name_your_bucket":{
            "terms":{
                "field": "color"
            }
        }
    }
}

使用cardinality实现distinct,相当于select distinct(color) from table

{
    "size":0,
    "aggs":{
        "name_your_result":{
            "cardinality":{
                "field": "color“
            }
        }
    }
}