ElasticSearch 基础使用和理解

Page content

部署和启动

docker-compose.yml

version: '2.2'
services:
  node01:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.11.1
    container_name: node01
    environment:
      - node.name=node01
      - cluster.name=es-cluster-7
      - discovery.seed_hosts=node01,node02,node03
      - cluster.initial_master_nodes=node01,node02,node03
      - "ES_JAVA_OPTS=-Xms128m -Xmx128m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - es-data01:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
      - 9300:9300
    networks:
      - es-network

  node02:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.11.1
    container_name: node02
    environment:
      - node.name=node02
      - cluster.name=es-cluster-7
      - discovery.seed_hosts=node01,node02,node03
      - cluster.initial_master_nodes=node01,node02,node03
      - "ES_JAVA_OPTS=-Xms128m -Xmx128m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - es-data02:/usr/share/elasticsearch/data
    ports:
      - 9201:9201
      - 9301:9301
    networks:
      - es-network

  node03:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.11.1
    container_name: node03
    environment:
      - node.name=node03
      - cluster.name=es-cluster-7
      - discovery.seed_hosts=node01,node02,node03
      - cluster.initial_master_nodes=node01,node02,node03
      - "ES_JAVA_OPTS=-Xms128m -Xmx128m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - es-data03:/usr/share/elasticsearch/data
    ports:
      - 9202:9202
      - 9302:9302
    networks:
      - es-network

  kibana:
    image: docker.elastic.co/kibana/kibana:7.11.1
    environment:
      ELASTICSEARCH_HOSTS: http://node01:9200
    ports:
      - 5601:5601
    networks:
      - es-network
    depends_on:
      - node01

volumes:
  es-data01:
    driver: local
  es-data02:
    driver: local
  es-data03:
    driver: local

networks:
  es-network:
    driver: bridge

来https://www.elastic.co/guide/en/elastic-stack-get-started/current/get-started-stack-docker.html

启动和检查

# 启动
sudo docker-compose up
# 集群监控
curl localhost:9200/_cat/health
=> 1627449664 05:21:04 es-cluster-7 green 3 3 12 6 0 0 0 0 - 100.0%
# 数据和索引文件挂载位置
/usr/share/elasticsearch/data

title: “ElasticSearch(1):基础查询” date: 2021-07-16T22:37:19+08:00 Summary: 包括 Elasticsearch 的CRUD和基础检索方式 draft: false categories:

  • notes

文档创建和删除

创建文档

ElasticSearch 提供创建一篇文档的接口如下,如果这是索引的第一篇文档,索引也会被同时创建。

PUT /<target>/_doc/<_id>
POST /<target>/_doc/
PUT /<target>/_create/<_id>
POST /<target>/_create/<_id>

下面的例子利用PUT方法创建一个 id 为1的文档:

1626509597720.png

其中,version字段为1,并且result的值为created

另一种创建文档的方式是通过POST,又ES自动生成一个全局唯一的 _id 给新的文档:

1626509630285.png

更新的文档

1626509615300.png

如果重复对这个文档执行PUT操作,那么ES就会转变为更新,并且自增version字段。

查询索引的信息

因为创建文档的同时会自动创建索引以及和请求结构相关的mappings(类似数据库的表结构schema)

直接通过GET索引名就可以查询到索引的信息:

GET /<index_name>

例如:

1626511976668.png

通常会得到三个信息:

  • aliases: 用于别名
  • mappings:索引的字段信息,如图中的索引 index-001 是根据 POST/PUT 请求的字段自动生成的,并且自动推导成对应的类型,但是对于字符串类型(text)在默认请求下,会新增一个子字段keyword,用于精准匹配查询。
  • settings:包含了 ElasticSearch 配置、分片等信息

查看文档

文档索引完成后,能够通过下面的查询查到新加入的文档:

GET /<index_name>/_doc/1  # 找到 _id 为 1 的文档
GET /<index_name>/_search # 查找出该索引下的所有文档

删除文档

DELETE /<index>/_doc/<_id>

例如:

1626511896634.png

如果找到了,会返回200OK,并且 found 判断是否存在文档,_version 字段在删除成功后会自增

常见的查询方式

准备查询数据

PUT /megacorp/employee/1
{
    "first_name" : "John",
    "last_name" :  "Smith",
    "age" :        25,
    "about" :      "I love to go rock climbing",
    "interests": [ "sports", "music" ]
}
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",
    "interests":  [ "forestry" ]
}

PS: megacorp 是官方的例子,在下面的例子中,创建的方式和前面叙述的有些不同,官方的例子是在ElasticSearch 2.X出的, 在索引和文档之间,还存在类型这一概念,虽然在后续的版本中可能不再维护,但是在这里用做例子并无大碍。

请求的方式

  • 基于 URL 的搜索方式:

    GET /megacorp/employee/_search?q=last_name:Smith

  • 基于 Request Body 的搜索方式

    GET /megacorp/_search
    {
      "query": {
        "query_string": {
          "fields": ["last_name"],
          "query": "Smith"
        }
      }
    }
    

一个完整的请求

GET /megacorp/_search
{
  "query" : {
      "match" : {
          "last_name" : "Smith"
      }
  },
  "from": 0,
  "size": 2,
  "_source": ["first_name", "last_name"],
  "sort": [{"age":  "desc"}]
}
  • query:相当于SQL中的WHERE子句
  • from/size: 和SQL的 FROM/LIMIT 用法一致,用于分页
  • _source: 相当于 SELECT
  • sort:对应 ORDER BY

查询返回结构

IMG_0560.jpg

查询字符串(query_string)

GET /megacorp/_search
{
  "query": {
    "query_string": {
      "fields": ["about"],
      "query": "I AND cabinets"
    }
  }
}

简单查询字串(simpile_query_string)

simpile_query_stringquery_string 的一种优化方式,能够将 AND/OR 关键词简化:

GET /megacorp/_search
{
  "query": {
    "simple_query_string": {
      "fields": ["about"],
      "query": "I + cabinets"
    }
  }
}

全文搜索(match)

GET /megacorp/employee/_search
{
    "query" : {
        "match" : {
            "about" : "rock climbing"
        }
    }
}

查询在 about 属性上喜欢 rock climbing 的人。rock albumsrock climbing 都会被命中。如果字段是设置了 not_analyzed 或者是日期、数字、布尔,也会给定精确匹配的值。

短语检索(match_phrase)

GET /megacorp/employee/_search
{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    }
}

只有完全含有短语 rock climbing 的文档才被检索

多字段查询(multi_match)

允许在多个字段上执行相同的查询

GET /megacorp/_search
{
  "query": {
    "multi_match": {
      "query": "like music",
      "fields": ["about", "interests"]
    }
  }
}

范围查询(range)

GET /megacorp/_search
{
  "query": {
    "range": {
        "age": {
            "gte":  20,
            "lt":   33
        }
    }
  }
}
  • gt: 大于
  • gte: 大于等于
  • lt: 小于
  • lte: 小于等于

精确值查找(term/terms)

term 查询被用于精确值匹配,这些精确值可能是数字、时间、布尔或者那些 not_analyzed 的字符串

GET /megacorp/_search
{
  "query": {
    "term": { "age": { "value": "25"} }
  }
}

terms 查询是 term 的多值版本

GET /megacorp/_search
{
  "query": {
    "terms": {
      "age": ["25", "32"]
    }
  }
}

存在性查找(exsists/missing)

exsists 和 missing 是一个逻辑相反的关系,用于判断字段是否有值,类似于 SQL 的WHERE FieldA IS NOT null

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

前缀查询(match_prefix)

因为目前只有三条记录,分别是:

"first_name" : "John",
"first_name" : "Jane",
"first_name" : "Douglas",

当期待用 J 去匹配John和Jane在first_name字段匹配不会成功,而需要使用字段first_name.keyword

GET /megacorp/_search
{
  "query": {
    "prefix": {
      "first_name.keyword": {
        "value": "J"
      }
    }
  }
}

或者使用小写的 j,因为 first_name 是一个被分词的字段(analyzed),在经过一系列的分词器和转化后,存储在倒排索引是小写的单词,而 first_name.keyword 字段是 first_name 的不做分词版本,可以用大写的 J 匹配到。

GET /megacorp/_search
{
  "query": {
    "prefix": {
      "first_name": {
        "value": "j"
      }
    }
  }
}

模糊查询和正则表达式(wildcard/regexp)

因为语句会被es分解成词,match查询的最小模糊匹配是词,利用模糊查询就能将模糊粒度降低到字母,如:

GET /megacorp/_search
{
  "query": {
    "wildcard": {
      "about": {
        "value": "c*"
      }
    }
  }
}

或者:

GET /megacorp/_search
{
  "query": {
    "regexp": {
      "about": {
        "value": "c.*"
      }
    }
  }
}

数据在索引时的预处理有助于提高前缀匹配的效率,而通配符和正则表达式查询只能在查询时完成,尽管这些查询有其应用场景,但使用仍需谨慎。

prefix 、 wildcard 和 regexp 查询是基于词操作的,像语句“Quick brown fox”如果设置了analyzed 就会被分解成 quick 、 brown 和 fox。

{ "regexp": { "title": "br.*" }}

语句能够检索到,但是下面这些组合了词语的查询不行

{ "regexp": { "title": "Qu.*" }}
{ "regexp": { "title": "quick br*" }}

复合查询

复合查询使用 bool 查询来实现逻辑的组合,接受以下四种参数:

  • must: 文档必须匹配这些条件才能被包含进来。
  • must_not:文档必须不匹配这些条件才能被包含进来。
  • should: 如果满足这些语句中的任意语句,将增加 _score ,否则,无任何影响。它们主要用于修正每个文档的相关性得分
  • filter: 必须匹配,但它以不评分、过滤模式来进行。这些语句对评分没有贡献,只是根据过滤标准来排除或包含文档。

结构为:

"query": {
	"bool": {
	  "must": [ SUB_QUERY ],
	  "must_not": [ SUB_QUERY ]
	}
}

例如:

GET /megacorp/_search
{
  "query": {
    "bool": {
      "must": [ {"range": { "age": { "gte": 30 } }} ],
	    "must_not": [ {"match": { "about": "cabinets" }} ]
    }
  }
}

过滤查询(filter)

GET /megacorp/_search
{
  "query": {
    "bool": {
      "must": {"match" : {"about" : "like build"}},
      "filter": {
        "bool": {
          "must" : {"range": {"age": { "gte": 30 }}}
        }
      }
    }
  }
}

将查询移到 bool 查询的 filterbool 语句中,例如像年龄这样的字段,只需要进行过滤,不需要放在查询做,所以可以放到filter中来优化查询性能。

过滤查询(constant_score)

constant_score 是filter的另外一种形式,通常用在只进行filter,而不用查询相关性分的情况。

如下:

GET /megacorp/_search
{
  "query": {
    "constant_score": {
      "filter": {"range": {"age": { "gte": 30 }}}
    }
  }
}

分析文档:分词

请求分析

POST _analyze
{
  "tokenizer": "standard",
  "filter":  [ "lowercase", "asciifolding" ],
  "text":      "Is this déja vu?"
}

为索引设置不同的分词器

PUT my-index-000002
{
  "settings": {
    "analysis": {
      "analyzer": {
        "std_english": {
          "type":      "standard",
          "stopwords": "_english_"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "my_text": {
        "type":     "text",
        "analyzer": "standard",
        "fields": {
          "english": {
            "type":     "text",
            "analyzer": "std_english"
          }
        }
      }
    }
  }
}

POST my-index-000002/_analyze
{
  "field": "my_text",
  "text": "The old brown cow"
}

POST my-index-000002/_analyze
{
  "field": "my_text.english",
  "text": "The old brown cow"
}

分析器也能在elastic search的启动配置中设置。

分词过程

文档加入索引前,都会经过系列处理:

  1. 字符过滤 (char_filter)
  2. 文本切分 (tokenizer)
  3. 分词过滤 (filter)
  4. 分词索引
POST _analyze
{
  "char_filter": ["html_strip"],
  "tokenizer" : "whitespace",
  "filter": ["stop"],
  "text":      "<body> share your experiece with NoSql and big data technologies </body>"
}

给定一个语句 text<body> share your experiece with NoSql and big data technologies </body>

并且设置相应的分词配置。