elasticsearch全解 (待续)

这篇具有很好参考价值的文章主要介绍了elasticsearch全解 (待续)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

elasticsearch

Elasticsearch 是一个基于Lucene的分布式搜索和分析引擎。

ES是elaticsearch简写, Elasticsearch是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。
Elasticsearch使用Java开发,在Apache许可条款下开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便

使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,使得全文检索变得简单

设计用途:用于分布式全文检索,通过HTTP使用JSON进行数据索引,速度快

ELK技术栈

elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域:

elasticsearch全解 (待续)
而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。

Lucene与Elasticsearch关系

  1. elasticsearch底层是基于lucene来实现的。

  2. Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。官网地址:https://lucene.apache.org/ 。

elasticsearch全解 (待续)

elasticsearch的发展历史:

  • 2004年Shay Banon基于Lucene开发了Compass
  • 2010年Shay Banon 重写了Compass,取名为Elasticsearch。

elasticsearch全解 (待续)

为什么不是其他搜索技术?

目前比较知名的搜索引擎技术排名:
elasticsearch全解 (待续)
虽然在早期,Apache Solr是最主要的搜索引擎技术,但随着发展elasticsearch已经渐渐超越了Solr,独占鳌头,Elasticsearch 与 Solr 的比较总结:

  1. 二者安装都很简单
  2. Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能
  3. Solr 支持更多格式的数据,而 Elasticsearch 仅支持json文件格式
  4. Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供
  5. Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch
  6. Solr 是传统搜索应用的有力解决方案,但 Elasticsearch 更适用于新兴的实时搜索应用

Elasticsearch核心概念

Cluster:集群

ES可以作为一个独立的单个搜索服务器。不过,为了处理大型数据集,实现容错和高可用性,ES可以运行在许多互相合作的服务器上。这些服务器的集合称为集群。

Node:节点

形成集群的每个服务器称为节点。

Shard:分片

  1. 当有大量的文档时,由于内存的限制、磁盘处理能力不足、无法足够快的响应客户端的请求等,一个节点可能不够。这种情况下,数据可以分为较小的分片。每个分片放到不同的服务器上。

  2. 当你查询的索引分布在多个分片上时,ES会把查询发送给每个相关的分片,并将结果组合在一起,而应用程序并不知道分片的存在。即:这个过程对用户来说是透明的。

Replia:副本

  1. 为提高查询吞吐量或实现高可用性,可以使用分片副本。
  2. 副本是一个分片的精确复制,每个分片可以有零个或多个副本。
  3. ES中可以有许多相同的分片,其中之一被选择更改索引操作,这种特殊的分片称为主分片。
  4. 当主分片丢失时,如:该分片所在的数据不可用时,集群将副本提升为新的主分片。

全文检索

  1. 全文检索就是对一篇文章进行索引,可以根据关键字搜索,类似于mysql里的like语句。
  2. 全文索引就是把内容根据词的意义进行分词,然后分别创建索引,例如”今日是周日我们出去玩” 可能会被分词成:“今天“,”周日“,“我们“,”出去玩“ 等token,这样当你搜索“周日” 或者 “出去玩” 都会把这句搜出来。

倒排索引

倒排索引中有两个非常重要的概念:

  • 文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息
  • 词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条

创建倒排索引是对正向索引的一种特殊处理,流程如下:

  • 将每一个文档的数据利用算法分词,得到一个个词条
  • 创建表,每行数据包括词条、词条所在文档id、位置等信息
  • 因为词条唯一性,可以给词条创建索引,例如hash表结构索引

elasticsearch全解 (待续)
倒排索引的搜索流程如下(以搜索"华为手机"为例):

1)用户输入条件"华为手机"进行搜索。

2)对用户输入内容分词,得到词条:华为手机

3)拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3。

4)拿着文档id到正向索引中查找具体文档。

elasticsearch全解 (待续)
虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。

正向和倒排

  • 正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程

  • 倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程

那么两者方式的优缺点是什么呢?

正向索引

  • 优点:
    • 可以给多个字段创建索引
    • 根据索引字段搜索、排序速度非常快
  • 缺点:
    • 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。

倒排索引

  • 优点:
    • 根据词条搜索、模糊搜索时,速度非常快
  • 缺点:
    • 只能给词条创建索引,而不是字段
    • 无法根据字段做排序

es的一些概念

elasticsearch中有很多独有的概念,与mysql中略有差别,但也有相似之处。

文档和字段

elasticsearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中:
elasticsearch全解 (待续)
而Json文档中往往包含很多的
字段(Field)
,类似于数据库中的列。

索引和映射

索引(Index),就是相同类型的文档的集合。

例如:

  • 所有用户文档,就可以组织在一起,称为用户的索引;
  • 所有商品的文档,可以组织在一起,称为商品的索引;
  • 所有订单的文档,可以组织在一起,称为订单的索引;

elasticsearch全解 (待续)
因此,我们可以把索引当做是数据库中的表。

数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。

mysql与elasticsearch

我们统一的把mysql与elasticsearch的概念做一下对比:

MySQL Elasticsearch 说明
Table Index 索引(index),就是文档的集合,类似数据库的表(table)
Row Document 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式
Column Field 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column)
Schema Mapping Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)
SQL DSL DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD

两者各自有自己的擅长支出:

  • Mysql:擅长事务类型操作,可以确保数据的安全和一致性

  • Elasticsearch:擅长海量数据的搜索、分析、计算

因此在企业中,往往是两者结合使用:

  • 对安全性要求较高的写操作,使用mysql实现
  • 对查询性能要求较高的搜索需求,使用elasticsearch实现
  • 两者再基于某种方式,实现数据的同步,保证一致性

elasticsearch全解 (待续)

ES逻辑设计(文档–>类型–>索引)

  1. 一个索引类型中,包含多个文档,比如说文档1,文档2。
  2. 当我们索引一篇文档时,可以通过这样的顺序找到它:索引▷类型▷文档ID,通过这个组合我们就能索引到某个具体的文档。注意:ID不必是整数,实际上它是个字符串。

docker安装elasticsearch(单机)

  1. 创建一个es专用网络
docker network create es-net
  1. 拉取镜像
# 拉取镜像
docker  pull  elasticsearch:7.17.5
docker  pull  kibana:7.17.5
  1. 建立对应文件夹
mkdir /elasticsearch
mkdir logs data plugins
# 不给权限会报错
chmod -R 777 $PWD
  1. 运行es
docker run -d \
	--name es \
    -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
    -e "discovery.type=single-node" \
    -v /elasticsearch/data:/usr/share/elasticsearch/data \
    -v /elasticsearch/logs:/usr/share/elasticsearch/logs \
    -v /elasticsearch/plugins:/usr/share/elasticsearch/plugins \
    --privileged=true \
    --restart=always \
    --network es-net \
    -p 9200:9200 \
    -p 9300:9300 \
	elasticsearch:7.17.5

命令解释:
-e “cluster.name=es-docker-cluster”:设置集群名称
-e “http.host=0.0.0.0”:监听的地址,可以外网访问
-e “ES_JAVA_OPTS=-Xms512m -Xmx512m”:内存大小
-e “discovery.type=single-node”:非集群模式
-v es-data:/usr/share/elasticsearch/data:挂载逻辑卷,绑定es的数据目录
-v es-logs:/usr/share/elasticsearch/logs:挂载逻辑卷,绑定es的日志目录
-v es-plugins:/usr/share/elasticsearch/plugins:挂载逻辑卷,绑定es的插件目录
–privileged:授予逻辑卷访问权
–network es-net :加入一个名为es-net的网络中
-p 9200:9200:端口映射配置
  1. 连接es验证
    elasticsearch全解 (待续)
  2. 接着部署kibana
docker run -d \
	--name kibana \
	-e ELASTICSEARCH_HOSTS=http://es:9200 \
	# 汉化
	-e "I18N_LOCALE=zh-CN" \
	--network=es-net \
	--restart=always \
	-p 5601:5601  \
	kibana:7.17.5

参数解释:
–network es-net :加入一个名为es-net的网络中,与elasticsearch在同一个网络中
-e ELASTICSEARCH_HOSTS=http://es:9200":设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch
-p 5601:5601:端口映射配置

kibana启动一般比较慢,需要多等待一会,可以通过命令:docker logs -f kibana查看日志。

elasticsearch全解 (待续)

  1. ik分词器安装
# 进入容器
docker exec -it elasticsearch /bin/bash
# 在线安装(可能会因为网络错误安装不上,反正我是下载不下来)
./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.5/elasticsearch-analysis-ik-7.17.5.zip

或去官网下载ik分词器压缩包:elasticsearch-analysis-ik-7.17.5.zip
elasticsearch全解 (待续)
上传到es容器的插件数据卷中:/elasticsearch/plugins,解压到ik文件夹中,重启容器docker restart es.
elasticsearch全解 (待续)
elasticsearch全解 (待续)

  1. 测试
GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "f612额贼泥马"
}

elasticsearch全解 (待续)
如果需要扩展词汇,打开IK分词器config目录,在IKAnalyzer.cfg.xml配置文件内容添加:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 *** 添加扩展词典-->
        <entry key="ext_dict">mywords.dic</entry>
</properties>

新建一个 mywords.dic,可以参考config目录下复制一个配置文件进行修改:

f612
额贼泥马

elasticsearch全解 (待续)
停用词词典同理,需要新增一个dic文件,然后写入禁止使用的词语,然后加入xml配置文件。

  1. 安装logstash
# 创建相应目录
mkdir logstash
# 拉取镜像
docker pull logstash:7.17.5
# 启动
docker run --name logstash -d \
-p 5044:5044 \
-p 9600:9600 \
--network=es-net \
--restart=always \
# 指定docker容器时间为上海时间,否则会出现八小时数据时差问题
# -e TZ=Asia/Shanghai \
# 限制docker的运行内存
-m 1024m
-v /logstash/config:/usr/share/logstash/config \
-v /logstash/data:/usr/share/logstash/data \
-v /logstash/pipeline:/user/share/logstash/pipeline \
--privileged=true \
logstash:7.17.5
  1. 配置文件写入
# config/logstash.yml
http.host: "0.0.0.0"
#根据实际修改es的ip:port
xpack.monitoring.elasticsearch.hosts: [ "http://192.168.71.130:9200" ]
# 主管道的Logstash配置路径,如果指定目录或通配符,配置文件将按字母顺序从目录中读取
path.config: /usr/share/logstash/config/conf.d/*.conf
#Logstash将其日志写到的目录
path.logs: /usr/share/logstash/logs

# config/conf.d/syslog.conf
input {
  file {
    #标签
    type => "systemlog-localhost"
    #采集点(这里一定要注意,由于我是docker启动的logstash,这里是容器内的文件,如果要生成外面日志索引,那么文件的路径一定要挂载正确)
    path => "/usr/share/logstash/logs/access_log.2021-03-19.log"
    #开始收集点
    start_position => "beginning"
    #扫描间隔时间,默认是1s,建议5s
    stat_interval => "5"
  }
}

output {
  elasticsearch {
  #集群的话,直接添加多个url
    hosts => ["172.17.0.3:9200"]
    #es的用户名和密码
	user =>"elastic"
	password =>"elastic"
	#建立的索引以日期区分
    index => "logstash-system-localhost-%{+YYYY.MM.dd}"
 }
 #在控制台输出logstash的日志
 stdout { codec=> rubydebug }
}

小tips

可以先运行一个默认的镜像,然后进入容器将需要的文件cp出来,然后删除容器,修改配置文件,再启动一个新的容器。

docker cp logstash:/usr/share/logstash/config /mydata/logstash/
docker cp logstash:/usr/share/logstash/data /mydata/logstash/
docker cp logstash:/usr/share/logstash/pipeline /mydata/logstash/

Postman客户端工具

如果直接通过浏览器向 Elasticsearch 服务器发请求,那么需要在发送的请求中包含
HTTP 标准的方法,而 HTTP 的大部分特性且仅支持 GET 和 POST 方法。所以为了能方便地进行客户端的访问,可以使用 Postman 软件Postman 是一款强大的网页调试工具,提供功能强大的 Web API 和 HTTP 请求调试。

软件功能强大,界面简洁明晰、操作方便快捷,设计得很人性化。 Postman 中文版能够发送任何类型的 HTTP 请求 (GET, HEAD, POST, PUT…),不仅能够表单提交,且可以附带任意类型请求体。

kibana

Kibana 是一款开源的数据分析和可视化平台,它是 Elastic Stack 成员之一,设计用于和 Elasticsearch 协作。

可以使用 Kibana 对 Elasticsearch 索引中的数据进行搜索、查看、交互操作。

可以很方便的利用图表、表格及地图对数据进行多元化的分析和呈现

索引操作

对比关系型数据库,创建索引就等同于创建数据库。
elasticsearch全解 (待续)
ES 里的 Index 可以看做一个库,而 Types 相当于表, Documents 则相当于表的行。这里 Types 的概念已经被逐渐弱化, Elasticsearch 6.X 中,一个 index 下已经只能包含一个type, Elasticsearch 7.X 中, Type 的概念已经被删除了。

新增

elasticsearch全解 (待续)

#新建一个lqz2的索引,索引分片数量为5,索引副本数量为1
# 如果不携带任何参数的请求,则默认创建主分片一个,副本分片一个
PUT lqz2
{
  "settings": {
    "index":{
      "number_of_shards":5,
      "number_of_replicas":1
    }
  }
}
'''
number_of_shards
每个索引的主分片数,默认值是 5 。这个配置在索引创建后不能修改。
number_of_replicas
每个主分片的副本数,默认值是 1 。对于活动的索引库,这个配置可以随时修改。
'''

查询索引配置

#获取lqz2索引的配置信息
GET lqz2/_settings
#获取所有索引的配置信息
GET _all/_settings
#同上
GET _settings
#获取lqz和lqz2索引的配置信息
GET lqz,lqz2/_settings
# 查看所有索引
GET _cat/indices?v
# 查看单个索引
GET shopping

elasticsearch全解 (待续)
elasticsearch全解 (待续)

更新索引

#修改索引副本数量为2
PUT lqz/_settings
{
  "number_of_replicas": 2
}
#如遇到报错:cluster_block_exception,因为
#这是由于ES新节点的数据目录data存储空间不足,导致从master主节点接收同步数据的时候失败,此时ES集群为了保护数据,会自动把索引分片index置为只读read-only
PUT  _all/_settings
{
"index": {
  "blocks": {
    "read_only_allow_delete": false
    }
  }
}

删除索引

#删除lqz索引
DELETE lqz

映射管理

在Elasticsearch 6.0.0或更高版本中创建的索引只包含一个mapping type。 在5.x中使用multiple mapping types创建的索引将继续像以前一样在Elasticsearch 6.x中运行。 Mapping types将在Elasticsearch 7.0.0中完全删除

映射介绍

在创建索引的时候,可以预先定义字段的类型及相关属性

Es会根据Json数据源的基础类型,猜测你想要映射的字段,将输入的数据转变成可以搜索的索引项。

Mapping是我们自己定义的字段数据类型,同时告诉es如何索引数据及是否可以被搜索

作用:会让索引建立的更加细致和完善

创建映射可以在索引刚创建的时候创建,也可以在索引创建完了之后再接着去创建,并且创建完了之后可以不停的追加映射,但是不能修改原有的映射

字段数据类型

string类型:text,keyword

数字类型:long,integer,short,byte,double,float

日期类型:data

布尔类型:boolean

binary类型:binary

复杂类型:object(实体,对象),nested(列表)

geo类型:geo-point,geo-shape(地理位置)

专业类型:ip,competion(搜索建议)

映射(索引库)参数

属性 描述 适合类型
store 值为yes表示存储,no表示不存储,默认为yes all
index yes表示分析,no表示不分析,默认为true text
null_value 如果字段为空,可以设置一个默认值,比如"NA"(传过来为空,不能搜索,na可以搜索) all
analyzer 可以设置索引和搜索时用的分析器,默认使用的是standard分析器,还可以使用whitespace,simple。都是英文分析器 all
include_in_all 默认es为每个文档定义一个特殊域_all,它的作用是让每个字段都被搜索到,如果想让某个字段不被搜索到,可以设置为false all
format 时间格式字符串模式 date
properties 该字段的子字段 all

创建映射

text类型会取出词做倒排索引,keyword不会被分词,原样存储,原样匹配

mapping类型一旦确定,以后就不能修改了

语法:

  • 请求方式:PUT
  • 请求路径:/索引,可以自定义
  • 请求参数:mapping映射
#6.x的版本没问题
PUT books
{
  "mappings": {
    "book":{
      "properties":{
        "title":{
          "type":"text",
         	"analyzer": "ik_max_word"
        },
        "price":{
          "type":"integer"
        },
        "addr":{
          "type":"keyword"
        },
        "company":{
          "properties":{
            "name":{"type":"text"},
            "company_addr":{"type":"text"},
            "employee_count":{"type":"integer"}
          }
        },
        "publish_date":{"type":"date","format":"yyy-MM-dd"}
       
      }
    }
  }
}

7.x版本以后

PUT books
{
  "mappings": {
    "properties":{
      "title":{
        "type":"text",
        "analyzer": "ik_max_word"
      },
      "price":{
        "type":"integer"
      },
      "addr":{
        "type":"keyword"
      },
      "company":{
        "properties":{
          "name":{"type":"text"},
          "company_addr":{"type":"text"},
          "employee_count":{"type":"integer"}
        }
      },
      "publish_date":{"type":"date","format":"yyy-MM-dd"}
      
    }
    
  }
}

查询映射

基本语法:

  • 请求方式:GET

  • 请求路径:/索引

  • 请求参数:无

格式:

GET /索引
#查看books索引的mapping
GET books/_mapping
#获取所有的mapping
GET _all/_mapping

elasticsearch全解 (待续)

修改映射

倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,因此索引库一旦创建,无法修改mapping

虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。

PUT /索引/_mapping
{
  "properties": {
    "新字段名":{
      "type": "integer"
    }
  }
}

elasticsearch全解 (待续)

删除映射

语法:

  • 请求方式:DELETE

  • 请求路径:/索引

  • 请求参数:无

格式:

DELETE /索引

总结

映射操作有哪些?

  • 创建索引库:PUT /索引
  • 查询索引库:GET /索引
  • 删除索引库:DELETE /索引
  • 添加字段:PUT /索引/_mapping

文档操作

注意:当执行PUT命令时,如果数据不存在,则新增该条数据,如果数据存在则修改该条数据。

创建文档

POST shopping/_doc
{
    "title":"小米手机",
    "category":"小米",
    "images":"http://www.gulixueyuan.com/xm.jpg",
    "price":3999.00
}

如果想要自定义唯一性标识,需要在创建时指定: http://127.0.0.1:9200/shopping/_doc/1

PUT lqz/doc/1
{
  "name":"顾老二",
  "age":30,
  "from": "gu",
  "desc": "皮肤黑、武器长、性格直",
  "tags": ["黑", "长", "直"]
}

所以,新增的语法为:

POST /索引库名/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    "字段3": {
        "子属性1": "值3",
        "子属性2": "值4"
    },
    // ...
}

查询文档

根据rest风格,新增是post,查询应该是get,不过查询一般都需要条件,这里我们把文档id带上。

GET /{索引库名称}/_doc/{id}

在 Postman或kibana 中,向 ES 服务器发 GET 请求 : http://127.0.0.1:9200/shopping/_doc/1

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1",
    "_version": 1,
    "_seq_no": 1,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "title": "小米手机",
        "category": "小米",
        "images": "http://www.gulixueyuan.com/xm.jpg",
        "price": 3999
    }
}

查找不存在的内容

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1001",
    "found": false
}

查看索引下所有数据,向 ES 服务器发 GET 请求 : http://127.0.0.1:9200/shopping/_search:

{
    "took": 726,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "shopping",
                "_type": "_doc",
                "_id": "_XPLhoIBg3C-pJdYUv6X",
                "_score": 1.0,
                "_source": {
                    "title": "小米手机",
                    "category": "小米",
                    "images": "http://www.gulixueyuan.com/xm.jpg",
                    "price": 3999.00
                }
            },
            {
                "_index": "shopping",
                "_type": "_doc",
                "_id": "1",
                "_score": 1.0,
                "_source": {
                    "title": "小米手机",
                    "category": "小米",
                    "images": "http://www.gulixueyuan.com/xm.jpg",
                    "price": 3999.00
                }
            }
        ]
    }
}

删除文档

删除使用DELETE请求,同样,需要根据id进行删除:

语法:

DELETE /{索引库名}/_doc/id值

示例:

# 根据id删除数据
DELETE /heima/_doc/1

修改文档

修改有两种方式:

  • 全量修改:直接覆盖原来的文档
  • 增量修改:修改文档中的部分字段

全量修改

全量修改是覆盖原来的文档,其本质是:

  • 根据指定的id删除文档
  • 新增一个相同id的文档

注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。

语法:

PUT /{索引库名}/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    // ... 略
}

示例:

PUT /heima/_doc/1
{
    "info": "黑马程序员高级Java讲师",
    "email": "zy@itcast.cn",
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

增量修改

增量修改是只修改指定id匹配的文档中的部分字段.

语法:

POST /{索引库名}/_update/文档id
{
    "doc": {
         "字段名": "新的值",
    }
}

示例:

POST /heima/_update/1
{
  "doc": {
    "email": "ZhaoYun@itcast.cn"
  }
}

总结

文档操作有哪些?

  • 创建文档:POST /{索引库名}/_doc/文档id { json文档 }
  • 查询文档:GET /{索引库名}/_doc/文档id
  • 删除文档:DELETE /{索引库名}/_doc/文档id
  • 修改文档:
    • 全量修改:PUT /{索引库名}/_doc/文档id { json文档 }
    • 增量修改:POST /{索引库名}/_update/文档id { “doc”: {字段}}

Elasticsearch之查询的两种方式

elasticsearch提供两种查询方式:

  1. 查询字符串(query string),简单查询,就像是像传递URL参数一样去传递查询语句,被称为简单搜索或查询字符串(query string)搜索。
  2. 另外一种是通过DSL语句来进行查询,被称为DSL查询(Query DSL),DSL是Elasticsearch提供的一种丰富且灵活的查询语言,该语言以json请求体的形式出现,通过restful请求与Elasticsearch进行交互。

查询字符串(一般不用)

GET lqz/doc/_search?q=from:gu
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 0.6931472,
    "hits" : [
      {
        "_index" : "lqz",
        "_type" : "doc",
        "_id" : "4",
        "_score" : 0.6931472,
        "_source" : {
          "name" : "石头",
          "age" : 29,
          "from" : "gu",
          "desc" : "粗中有细,狐假虎威",
          "tags" : [
            "粗",
            "大",
            "猛"
          ]
        }
      },
      {
        "_index" : "lqz",
        "_type" : "doc",
        "_id" : "1",
        "_score" : 0.2876821,
        "_source" : {
          "name" : "顾老二",
          "age" : 30,
          "from" : "gu",
          "desc" : "皮肤黑、武器长、性格直",
          "tags" : [
            "黑",
            "长",
            "直"
          ]
        }
      },
      {
        "_index" : "lqz",
        "_type" : "doc",
        "_id" : "3",
        "_score" : 0.2876821,
        "_source" : {
          "name" : "龙套偏房",
          "age" : 22,
          "from" : "gu",
          "desc" : "mmp,没怎么看,不知道怎么形容",
          "tags" : [
            "造数据",
            "真",
            "难"
          ]
        }
      }
    ]
  }
}

hits是返回的结果集——所有from属性为gu的结果集。重点中的重点是_score得分,根据算法算出跟查询条件的匹配度,匹配度高得分就高。

结构化查询

GET lqz/doc/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  }
}

上例,查询条件是一步步构建出来的,将查询条件添加到match中即可,而match则是查询所有from字段的值中含有gu的结果就会返回。

match查询

match_all查询全部

match_all的值为空,表示没有查询条件,那就是查询全部。就像select * from table_name一样。

GET lqz/doc/_search
{
	"query":{
		"match_all":{}
	}
}

查询指定字段

GET lqz/doc/_search
{
	"query":{
		"match_all":{}
	},
	# 只显示title字段
	# 相当于mysql的select
	"_source":["title"]
}

match_phrase短语查询

查询带"中国"的结果,而不是带"中","国"的结果

GET t1/doc/_search
{
  "query": {
    "match_phrase": {
      "title": {
        "query": "中国"
      }
    }
  }
}

要想搜索中国和世界相关的文档,但又忘记其余部分了,怎么做呢?

搜索中国和世界这两个指定词组时,但又不清楚两个词组之间有多少别的词间隔。那么在搜的时候就要留有一些余地。这时就要用到了slop了。相当于正则中的中国.*?世界。这个间隔默认为0

GET t1/doc/_search
{
  "query": {
    "match_phrase": {
      "title": {
      	# 查询中国 - - 世界的结果
        "query": "中国世界",
        # 间隔代表中国和世界之间有2个分词
        "slop": 2
      }
    }
  }
}

match_phrase_prefix(最左前缀查询)

GET t3/doc/_search
{
  "query": {
    "match_phrase_prefix": {
      # 查询desc字段以bea开头的结果
      "desc": "bea"
    }
  }
}

前缀查询是短语查询类似,但前缀查询可以更进一步的搜索词组,只不过它是和词组中最后一个词条进行前缀匹配(如搜这样的you are bea)。应用也非常的广泛,比如搜索框的提示信息,当使用这种行为进行搜索时,最好通过max_expansions来设置最大的前缀扩展数量,因为产生的结果会是一个很大的集合,不加限制的话,影响查询性能。

GET t3/doc/_search
{
  "query": {
    "match_phrase_prefix": {
      "desc": {
        "query": "bea",
        "max_expansions": 1
      }
    }
  }
}

该max_expansions设置定义了在停止搜索之前模糊查询将匹配的最大术语数,也可以对模糊查询的性能产生显着影响。但是,减少查询字词会产生负面影响,因为查询提前终止可能无法找到某些有效结果。重要的是要理解max_expansions查询限制在分片级别工作,这意味着即使设置为1,多个术语可能匹配,所有术语都来自不同的分片。此行为可能使其看起来好像max_expansions没有生效,因此请注意,计算返回的唯一术语不是确定是否有效的有效方法max_expansions。

我们只需知道该参数工作于分片层,也就是Lucene部分,使用前缀查询会非常的影响性能,要对结果集进行限制,就加上这个参数。

multi_match(多字段查询)

要在多个字段中查询同一个关键字,该怎么做呢?

GET t3/doc/_search
{
  "query": {
    "multi_match": {
       # 在title,desc字段中查找beautiful
      "query": "beautiful",
      "fields": ["title", "desc"]
    }
  }
}

除此之外,multi_match甚至可以当做match_phrase和match_phrase_prefix使用,只需要指定type类型即可:

GET t3/doc/_search
{
  "query": {
    "multi_match": {
      "query": "gi",
      "fields": ["title"],
      "type": "phrase_prefix"
    }
  }
}
GET t3/doc/_search
{
  "query": {
    "multi_match": {
      "query": "girl",
      "fields": ["title"],
      "type": "phrase"
    }
  }
}

match系列小结

  1. match:返回所有匹配的分词。
  2. match_all:查询全部。
  3. match_phrase:短语查询,在match的基础上进一步查询词组,可以指定slop分词间隔。
  4. match_phrase_prefix:前缀查询,根据短语中最后一个词组做前缀匹配,可以应用于搜索提示,但注意和max_expanions搭配。其实默认是50…
  5. multi_match:多字段查询,使用相当的灵活,可以完成match_phrase和match_phrase_prefix的工作。

精准查询

term查询

默认情况下,elasticsearch在对文档分析期间(将文档分词后保存到倒排索引中),会对文档进行分词,比如默认的标准分析器会对文档进行:

  1. 删除大多数的标点符号。
  2. 将文档分解为单个词条,我们称为token。
  3. 将token转为小写。
  4. 完事再保存到倒排索引上,当然,原文件还是要保存一分的,而倒排索引使用来查询的。

例如Beautiful girl!,在经过分析后是这样的了:

POST _analyze
{
  "analyzer": "standard",
  "text": "Beautiful girl!"
}
# 结果
["beautiful", "girl"]

而当在使用match查询时,elasticsearch同样会对查询关键字进行分析:

也就是对查询关键字Beautiful girl!进行分析,得到[“beautiful”, “girl”],然后分别将这两个单独的token去索引中进行查询,结果就是将多篇文档都返回。

这在有些情况下是非常好用的,但是,如果我们想查询确切的词怎么办?也就是精确查询,将Beautiful girl!当成一个token而不是分词后的两个token。

这就要用到了term查询了,term查询的是没有经过分析的查询关键字。

但是,这同样需要限制,如果你要查询的字段类型是text(因为elasticsearch会对文档进行分析,上面说过),那么你得到的可能是不尽如人意的结果或者压根没有结果:

所以,我们这里得到一个论证结果:不要使用term对类型是text的字段进行查询,要查询text类型的字段,请改用match查询。

GET w10/doc/_search
{
  "query": {
    "term": {
      "t1": "Beautiful"
    }
  }
}

有结果返回,因为elasticsearch在对文档进行分析时,倒排索引上存的是小写的beautiful,而查询的是大写的Beautiful。

要想使用term查询多个精确的值怎么办?

GET w10/doc/_search
{
  "query": {
    "terms": {
      "t1": ["beautiful", "sexy"]
    }
  }
}

range查询

范围查询,一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤。

// range查询
GET /indexName/_search
{
  "query": {
    "range": {
      "FIELD": {
        "gte": 10, // 这里的gte代表大于等于,gt则代表大于
        "lte": 20 // lte代表小于等于,lt则代表小于
      }
    }
  }
}

elasticsearch全解 (待续)

查询排序sort

降序:desc,升序:asc

GET lqz/doc/_search
{
	"query":{
		"match_all":{}
	},
	"sort":{
		# 按照price降序排序
		"price":{
			"order":"desc"
		}
	}
}

不是什么数据类型都能排序

注意:在排序的过程中,只能使用可排序的属性进行排序。

  1. 数字
  2. 日期

分页查询from/size

elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:

  • from:从第几个文档开始
  • size:总共查询几个文档

类似于mysql中的limit ?, ?

GET lqz/doc/_search
{
	"query":{
		"match_all":{}
	},
	# 从0开始
	"from":0,
	# 分页大小为2
	"size":2
}

对于elasticsearch来说,所有的条件都是可插拔的,彼此之间用,分割。

GET lqz/doc/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ], 
  "from": 4,
  "size": 2
}

深度分页问题

现在,我要查询990~1000的数据,查询逻辑要这么写:

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "from": 990, // 分页开始的位置,默认为0
  "size": 10, // 期望获取的文档总数
  "sort": [
    {"price": "asc"}
  ]
}

这里是查询990开始的数据,也就是 第990~第1000条 数据。

不过,elasticsearch内部分页时,必须先查询 0~1000条,然后截取其中的990 ~ 1000的这10条:

elasticsearch全解 (待续)
查询TOP1000,如果es是单点模式,这并无太大影响。

但是elasticsearch将来一定是集群,例如我集群有5个节点,我要查询TOP1000的数据,并不是每个节点查询200条就可以了。

因为节点A的TOP200,在另一个节点可能排到10000名以外了。

因此要想获取整个集群的TOP1000,必须先查询出每个节点的TOP1000,汇总结果后,重新排名,重新截取TOP1000。
elasticsearch全解 (待续)
那如果我要查询9900~10000的数据呢?是不是要先查询TOP10000呢?那每个节点都要查询10000条?汇总到内存中?

当查询分页深度较大时,汇总数据过多,对内存和CPU会产生非常大的压力,因此elasticsearch会禁止from+ size 超过10000的请求。

针对深度分页,ES提供了两种解决方案,官方文档:

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
  • scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用。

布尔查询bool

布尔查询是最常用的组合查询,根据子查询的规则,只有当文档满足所有子查询条件时,elasticsearch引擎才将结果返回。布尔查询支持的子查询条件共4中:

  1. must(and)
  2. should(or)
  3. must_not(not)
  4. filter

must

  1. must字段对应的是个列表,也就是说可以有多个并列的查询条件,一个文档满足各个子条件后才最终返回。
  2. must相当于数据库的&&。

假设想找出小米牌子,价格为3999元的。

GET lqz/doc/_search
{
	"query":{
		"bool":{
			"must":[{
				"match":{
					"category":"小米"
				}
			},{
				"match":{
					"price":3999.00
				}
			}]
		}
	}
}

should

  1. 或关系的不能用must的了,而是要用should,只要符合其中一个条件就返回。

假设想找出小米和华为的牌子。(should相当于数据库的||)

{
	"query": {
		"bool": {
			"should": [{
				"match": {
					"category": "小米"
				}
			}, {
				"match": {
					"category": "华为"
				}
			}],
		}

	}

}

must_not

要查询from既不是gu并且tags也不是可爱,还有age不是18的数据(条件中age对应的18写成整形还是字符串都没啥)

GET lqz/doc/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "match": {
            "from": "gu"
          }
        },
        {
          "match": {
            "tags": "可爱"
          }
        },
        {
          "match": {
            "age": 18
          }
        }
      ]
    }
  }
}

filter

要查询from为gu,age大于25的数据

GET lqz/doc/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "from": "gu"
          }
        }
      ],
      "filter": {
        "range": {
          "age": {
            "gt": 25
          }
        }
      }
    }
  }
}

这里就用到了filter条件过滤查询,过滤条件的范围用range表示,gt表示大于(gte大于等于,lt小于,lte小于等于)

查询from是gu,age在25~30之间的:

GET lqz/doc/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "from": "gu"
          }
        }
      ],
      "filter": {
        "range": {
          "age": {
            "gte": 25,
            "lte": 30
          }
        }
      }
    }
  }
}

如果在filter过滤条件中使用should的话,结果可能不会尽如人意!建议使用must代替。

注意:filter工作于bool查询内,不能放到外面

总结

  1. must:与关系,相当于关系型数据库中的and。
  2. should:或关系,相当于关系型数据库中的or。
  3. must_not:非关系,相当于关系型数据库中的not。
  4. filter:过滤条件。
  5. range:条件筛选范围。
  6. gt:大于,相当于关系型数据库中的>。
  7. gte:大于等于,相当于关系型数据库中的>=。
  8. lt:小于,相当于关系型数据库中的<。
  9. lte:小于等于,相当于关系型数据库中的<=。

查询结果过滤

结果过滤:_source

在所有的结果中,只需要查看name和age两个属性,其他的不要

GET lqz/doc/_search
{
  "query": {
    "match": {
      "name": "顾老二"
    }
  },
  "_source": ["name", "age"]
}

高亮查询

默认高亮显示

GET lqz/doc/_search
{
  "query": {
    "match": {
      "name": "石头"
    }
  },
  "highlight": {
    "fields": {
      "name": {}
    }
  }
}

使用highlight属性来实现结果高亮显示,需要的字段名称添加到fields内即可,elasticsearch会自动帮我们实现高亮。

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.5098256,
    "hits" : [
      {
        "_index" : "lqz",
        "_type" : "doc",
        "_id" : "4",
        "_score" : 1.5098256,
        "_source" : {
          "name" : "石头",
          "age" : 29,
          "from" : "gu",
          "desc" : "粗中有细,狐假虎威",
          "tags" : [
            "粗",
            "大",
            "猛"
          ]
        },
        "highlight" : {
          "name" : [
            "<em>石</em><em>头</em>"
          ]
        }
      }
    ]
  }
}

自定义高亮显示

GET lqz/chengyuan/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "highlight": {
    "pre_tags": "<b class='key' style='color:red'>",
    "post_tags": "</b>",
    "fields": {
      "from": {}
    }
  }
}

在highlight中,pre_tags用来实现我们的自定义标签的前半部分,在这里,我们也可以为自定义的标签添加属性和样式。post_tags实现标签的后半部分,组成一个完整的标签。至于标签中的内容,则还是交给fields来完成。

需要注意的是:自定义标签中属性或样式中的逗号一律用英文状态的单引号表示,应该与外部elasticsearch语法的双引号区分开。

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "lqz",
        "_type" : "chengyuan",
        "_id" : "1",
        "_score" : 0.5753642,
        "_source" : {
          "name" : "老二",
          "age" : 30,
          "sex" : "male",
          "birth" : "1070-10-11",
          "from" : "gu",
          "desc" : "皮肤黑,武器长,性格直",
          "tags" : [
            "黑",
            "长",
            "直"
          ]
        },
        "highlight" : {
          "name" : [
            "<b class='key' style='color:red'>老</b><b class='key' style='color:red'>二</b>"
          ]
        }
      }
    ]
  }

聚合

**聚合(aggregations)**可以让我们极其方便的实现对数据的统计、分析、运算。例如:

  • 什么品牌的手机最受欢迎?
  • 这些手机的平均价格、最高价格、最低价格?
  • 这些手机每月的销售情况如何?

实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现近实时搜索效果。

elasticsearch中也没玩出新花样:

  1. avg
  2. max
  3. min
  4. sum

avg

GET lqz/doc/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "aggs": {
    "my_avg": {
      "avg": {
        "field": "age"
      }
    }
  },
  "_source": ["name", "age"]
}

首先匹配查询from是gu的数据。在此基础上做查询平均值的操作,这里就用到了聚合函数,其语法被封装在aggs中,而my_avg则是为查询结果起个别名,封装了计算出的平均值。那么,要以什么属性作为条件呢?是age年龄,查年龄的什么呢?是avg,查平均年龄。

虽然我们已经使用_source对字段做了过滤,但是还不够,只想看平均值,就可以用size:

GET lqz/doc/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "aggs": {
    "my_avg": {
      "avg": {
        "field": "age"
      }
    }
  },
  "size": 0, 
  "_source": ["name", "age"]
}
{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "my_avg" : {
      "value" : 27.0
    }
  }
}

max

GET lqz/doc/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "aggs": {
    "my_max": {
      "max": {
        "field": "age"
      }
    }
  },
  "size": 0
}

只需要在查询条件中将avg替换成max即可。

min

GET lqz/doc/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "aggs": {
    "my_min": {
      "min": {
        "field": "age"
      }
    }
  },
  "size": 0
}

sum

GET lqz/doc/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "aggs": {
    "my_sum": {
      "sum": {
        "field": "age"
      }
    }
  },
  "size": 0
}

分组查询

查询所有人的年龄段,并且按照1520,2025,25~30分组,并且算出每组的平均年龄。

  1. 分组
GET lqz/doc/_search
{
  "size": 0, 
  "query": {
    "match_all": {}
  },
  "aggs": {
    "age_group": {
      "range": {
        "field": "age",
        "ranges": [
          {
            "from": 15,
            "to": 20
          },
          {
            "from": 20,
            "to": 25
          },
          {
            "from": 25,
            "to": 30
          }
        ]
      }
    }
  }
}

在aggs的自定义别名age_group中,使用range来做分组,field是以age为分组,分组使用ranges来做,from和to是范围,我们根据需求做出三组。

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 5,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "age_group" : {
      "buckets" : [
        {
          "key" : "15.0-20.0",
          "from" : 15.0,
          "to" : 20.0,
          "doc_count" : 1
        },
        {
          "key" : "20.0-25.0",
          "from" : 20.0,
          "to" : 25.0,
          "doc_count" : 1
        },
        {
          "key" : "25.0-30.0",
          "from" : 25.0,
          "to" : 30.0,
          "doc_count" : 2
        }
      ]
    }
  }
}
  1. 对每个小组内的数据做平均年龄处理
GET lqz/doc/_search
{
  "size": 0, 
  "query": {
    "match_all": {}
  },
  "aggs": {
    "age_group": {
      "range": {
        "field": "age",
        "ranges": [
          {
            "from": 15,
            "to": 20
          },
          {
            "from": 20,
            "to": 25
          },
          {
            "from": 25,
            "to": 30
          }
        ]
      },
      "aggs": {
        "my_avg": {
          "avg": {
            "field": "age"
          }
        }
      }
    }
  }
}

使用aggs对age做平均数处理,这样就可以了。

{
 "took" : 1,
 "timed_out" : false,
 "_shards" : {
   "total" : 5,
   "successful" : 5,
   "skipped" : 0,
   "failed" : 0
 },
 "hits" : {
   "total" : 5,
   "max_score" : 0.0,
   "hits" : [ ]
 },
 "aggregations" : {
   "age_group" : {
     "buckets" : [
       {
         "key" : "15.0-20.0",
         "from" : 15.0,
         "to" : 20.0,
         "doc_count" : 1,
         "my_avg" : {
           "value" : 18.0
         }
       },
       {
         "key" : "20.0-25.0",
         "from" : 20.0,
         "to" : 25.0,
         "doc_count" : 1,
         "my_avg" : {
           "value" : 22.0
         }
       },
       {
         "key" : "25.0-30.0",
         "from" : 25.0,
         "to" : 30.0,
         "doc_count" : 2,
         "my_avg" : {
           "value" : 27.0
         }
       }
     ]
   }
 }
}

注意:聚合函数的使用,一定是先查出结果,然后对结果使用聚合函数做处理

小结:

  • avg:求平均
  • max:最大值
  • min:最小值
  • sum:求和

聚合的种类

聚合常见的有三类:

  • **桶(Bucket)**聚合:用来对文档做分组

    • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
    • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
  • **度量(Metric)**聚合:用以计算一些值,比如:最大值、最小值、平均值等

    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求max、min、avg、sum等
  • **管道(pipeline)**聚合:其它聚合的结果为基础做聚合

**注意:**参加聚合的字段必须是keyword、日期、数值、布尔类型

Bucket聚合语法

现在,我们要统计所有数据中的酒店品牌有几种,其实就是按照品牌对数据分组。此时可以根据酒店品牌的名称做聚合,也就是Bucket聚合。

GET /hotel/_search
{
  "size": 0,  // 设置size为0,结果中不包含文档,只包含聚合结果
  "aggs": { // 定义聚合
    "brandAgg": { //给聚合起个名字
      "terms": { // 聚合的类型,按照品牌值聚合,所以选择term
        "field": "brand", // 参与聚合的字段
        "size": 20 // 希望获取的聚合结果数量
      }
    }
  }
}

结果如图:

elasticsearch全解 (待续)
聚合结果排序,默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序。

我们可以指定order属性,自定义聚合的排序方式:

GET /hotel/_search
{
  "size": 0, 
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "order": {
          "_count": "asc" // 按照_count升序排列
        },
        "size": 20
      }
    }
  }
}

限定聚合范围:

默认情况下,Bucket聚合是对索引库的所有文档做聚合,但真实场景下,用户会输入搜索条件,因此聚合必须是对搜索结果聚合。那么聚合必须添加限定条件。

我们可以限定要聚合的文档范围,只要添加query条件即可:

GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "lte": 200 // 只对200元以下的文档聚合
      }
    }
  }, 
  "size": 0, 
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 20
      }
    }
  }
}

这次,聚合得到的品牌明显变少了:

elasticsearch全解 (待续)

Metric聚合语法

现在我们需要对桶内的酒店做运算,获取每个品牌的用户评分的min、max、avg等值。

这就要用到Metric聚合了,例如stat聚合:就可以获取min、max、avg等结果。

语法如下:

GET /hotel/_search
{
  "size": 0, 
  "aggs": {
    "brandAgg": { 
      "terms": { 
        "field": "brand", 
        "size": 20
      },
      "aggs": { // 是brands聚合的子聚合,也就是分组后对每组分别计算
        "score_stats": { // 聚合名称
          "stats": { // 聚合类型,这里stats可以计算min、max、avg等
            "field": "score" // 聚合字段,这里是score
          }
        }
      }
    }
  }
}

这次的score_stats聚合是在brandAgg的聚合内部嵌套的子聚合。因为我们需要在每个桶分别计算。

另外,我们还可以给聚合结果做个排序,例如按照每个桶的酒店平均分做排序:

elasticsearch全解 (待续)

地理坐标查询

常见的使用场景包括:

  • 携程:搜索我附近的酒店
  • 滴滴:搜索我附近的出租车
  • 微信:搜索我附近的人

矩形范围查询

查询时,需要指定矩形的左上右下两个点的坐标,然后画出一个矩形,落在该矩形内的都是符合条件的点。

elasticsearch全解 (待续)

// geo_bounding_box查询
GET /indexName/_search
{
  "query": {
    "geo_bounding_box": {
      "FIELD": {
        "top_left": { // 左上点
          "lat": 31.1,
          "lon": 121.5
        },
        "bottom_right": { // 右下点
          "lat": 30.9,
          "lon": 121.7
        }
      }
    }
  }
}

附近查询

附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档。

换句话来说,在地图上找一个点作为圆心,以指定距离为半径,画一个圆,落在圆内的坐标都算符合条件:
elasticsearch全解 (待续)

// geo_distance 查询
GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km", // 半径
      "FIELD": "31.21,121.5" // 圆心
    }
  }
}

elasticsearch全解 (待续)

相关性算分

当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。

例如,我们搜索 “虹桥如家”,结果如下:

[
  {
    "_score" : 17.850193,
    "_source" : {
      "name" : "虹桥如家酒店真不错",
    }
  },
  {
    "_score" : 12.259849,
    "_source" : {
      "name" : "外滩如家酒店真不错",
    }
  },
  {
    "_score" : 11.91091,
    "_source" : {
      "name" : "迪士尼如家酒店真不错",
    }
  }
]

在elasticsearch中,早期使用的打分算法是TF-IDF算法,公式如下:
elasticsearch全解 (待续)
在后来的5.1版本升级中,elasticsearch将算法改进为BM25算法,公式如下:

elasticsearch全解 (待续)
TF-IDF算法有一各缺陷,就是词条频率越高,文档得分也会越高,单个词条对文档影响较大。而BM25则会让单个词条的算分有一个上限,曲线更加平滑:
elasticsearch全解 (待续)

自动补全

拼音分词器

要实现根据字母做补全,就必须对文档按照拼音分词。在GitHub上恰好有elasticsearch的拼音分词插件。地址:https://github.com/medcl/elasticsearch-analysis-pinyin

elasticsearch全解 (待续)
测试用法如下:

POST /_analyze
{
  "text": "如家酒店还不错",
  "analyzer": "pinyin"
}

elasticsearch全解 (待续)

自定义分词器

默认的拼音分词器会将每个汉字单独分为拼音,而我们希望的是每个词条形成一组拼音,需要对拼音分词器做个性化定制,形成自定义分词器。

elasticsearch中分词器(analyzer)的组成包含三部分:

  • character filters:在tokenizer之前对文本进行处理。例如删除字符、替换字符
  • tokenizer:将文本按照一定的规则切割成词条(term)。例如keyword,就是不分词;还有ik_smart
  • tokenizer filter:将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等

文档分词时会依次由这三部分来处理文档:
elasticsearch全解 (待续)
声明自定义分词器的语法如下:

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { // 自定义分词器
        "my_analyzer": {  // 分词器名称
          "tokenizer": "ik_max_word",
          "filter": "py"
        }
      },
      "filter": { // 自定义tokenizer filter
        "py": { // 过滤器名称
          "type": "pinyin", // 过滤器类型,这里是pinyin
		  "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "my_analyzer",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

elasticsearch全解 (待续)

自动补全查询

elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:

  • 参与补全查询的字段必须是completion类型。

  • 字段的内容一般是用来补全的多个词条形成的数组。

比如,一个这样的索引库:

// 创建索引库
PUT test
{
  "mappings": {
    "properties": {
      "title":{
        "type": "completion"
      }
    }
  }
}

然后插入下面的数据:

// 示例数据
POST test/_doc
{
  "title": ["Sony", "WH-1000XM3"]
}
POST test/_doc
{
  "title": ["SK-II", "PITERA"]
}
POST test/_doc
{
  "title": ["Nintendo", "switch"]
}

查询的DSL语句如下:

// 自动补全查询
GET /test/_search
{
  "suggest": {
    "title_suggest": {
      "text": "s", // 关键字
      "completion": {
        "field": "title", // 补全查询的字段
        "skip_duplicates": true, // 跳过重复的
        "size": 10 // 获取前10条结果
      }
    }
  }
}

实现酒店搜索框自动补全

// 酒店数据索引库
PUT /hotel
{
  "settings": {
    "analysis": {
      "analyzer": {
        "text_anlyzer": {
          "tokenizer": "ik_max_word",
          "filter": "py"
        },
        "completion_analyzer": {
          "tokenizer": "keyword",
          "filter": "py"
        }
      },
      "filter": {
        "py": {
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id":{
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "text_anlyzer",
        "search_analyzer": "ik_smart",
        "copy_to": "all"
      },
      "address":{
        "type": "keyword",
        "index": false
      },
      "price":{
        "type": "integer"
      },
      "score":{
        "type": "integer"
      },
      "brand":{
        "type": "keyword",
        "copy_to": "all"
      },
      "city":{
        "type": "keyword"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword",
        "copy_to": "all"
      },
      "location":{
        "type": "geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "text_anlyzer",
        "search_analyzer": "ik_smart"
      },
      "suggestion":{
          "type": "completion",
          "analyzer": "completion_analyzer"
      }
    }
  }
}

集群搭建,数据分片

单机的elasticsearch做数据存储,必然面临两个问题:海量数据存储问题、单点故障问题。

  • 海量数据存储问题:将索引库从逻辑上拆分为N个分片(shard),存储到多个节点
  • 单点故障问题:将分片数据在不同节点备份(replica )

ES集群相关概念

  • 集群(cluster):一组拥有共同的 cluster name 的 节点。

  • 节点(node) :集群中的一个 Elasticearch 实例

  • 分片(shard):索引可以被拆分为不同的部分进行存储,称为分片。在集群环境下,一个索引的不同分片可以拆分到不同的节点中

    解决问题:数据量太大,单点存储量有限的问题。

elasticsearch全解 (待续)

  • 主分片(Primary shard):相对于副本分片的定义。

  • 副本分片(Replica shard)每个主分片可以有一个或者多个副本,数据和主分片一样。

数据备份可以保证高可用,但是每个分片备份一份,所需要的节点数量就会翻一倍,成本实在是太高了!

为了在高可用和成本间寻求平衡,我们可以这样做:

  • 首先对数据分片,存储到不同节点
  • 然后对每个分片进行备份,放到对方节点,完成互相备份

这样可以大大减少所需要的服务节点数量,如图,我们以3分片,每个分片备份一份为例:

elasticsearch全解 (待续)
现在,每个分片都有1个备份,存储在3个节点:

  • node0:保存了分片0和1
  • node1:保存了分片0和2
  • node2:保存了分片1和2

副本Replicas

在一个网络 / 云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于
离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的, Elasticsearch 允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片(副本)。

复制分片之所以重要,有两个主要原因:

在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。
扩展你的搜索量/吞吐量,因为搜索可以在所有的副本上并行运行。
总之,每个索引可以被分成多个分片。一个索引也可以被复制 0 次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。

分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。

默认情况下,Elasticsearch 中的每个索引被分片 1 个主分片和 1 个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有 1 个主分片和另外 1 个复制分片(1 个完全拷贝),这样的话每个索引总共就有 2 个分片, 我们需要根据索引需要确定分片个数分配。

Allocation

将分片分配给某个节点的过程,包括分配主分片或者副本。如果是副本,还包含从主分片复制数据的过程。这个过程是由 master 节点完成的。

docker搭建ek集群

  1. 将配置文件也挂载出来:
-v /elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
  1. 修改配置文件
#集群名称 每个节点的集群名字要相同
cluster.name: es-docker-cluster
#本节点名称
node.name: node-1
#是不是有资格主节点
node.master: true
#是否存储数据
node.data: true
#跨域设置(head 插件需要这打开这两个配置)
http.cors.enabled: true
http.cors.allow-origin: "*"
http.max_content_length: 200mb
#http端口
http.port: 9200
#java端口
transport.tcp.port: 9300
#可以访问es集群的ip  0.0.0.0表示不绑定
network.bind_host: 0.0.0.0
#es集群相互通信的ip  0.0.0.0默认本地网络搜索
network.publish_host: 0.0.0.0
 
#7.x配置

# es7.x 之后新增的配置,初始化一个新的集群时需要此配置来选举 master
cluster.initial_master_nodes: ["node-1"]
#es7.x 之后新增的配置,节点发现
discovery.seed_hosts: ["linux1:9300","linux2:9300","linux3:9300"]
# gateway.recover_after_nodes: 2
# network.tcp.keep_alive: true
# network.tcp.no_delay: true
# transport.tcp.compress: true

#集群内同时启动的数据任务个数,默认是 2 个
# cluster.routing.allocation.cluster_concurrent_rebalance: 16
#添加或删除节点及负载均衡时并发恢复的线程个数,默认 4 个
# cluster.routing.allocation.node_concurrent_recoveries: 16
#初始化数据恢复时,并发恢复线程的个数,默认 4 个
# cluster.routing.allocation.node_initial_primaries_recoveries: 16

# discovery.zen.minimum_master_nodes: 2
# 一般的规则是集群节点数除以2(向下取整)再加一。比如3个节点集群要设置为2。这么着是为了防止脑裂(split brain)问题。
  1. 分别启动三个es

解决脑裂

集群职责划分:
elasticsearch全解 (待续)
默认情况下,集群中的任何一个节点都同时具备上述四种角色。

但是真实的集群一定要将集群职责分离:

  • master节点:对CPU要求高,但是内存要求第
  • data节点:对CPU和内存要求都高
  • coordinating节点:对网络带宽、CPU要求高

职责分离可以让我们根据不同节点的需求分配不同的硬件去部署。而且避免业务之间的互相干扰。

一个典型的es集群职责划分如图:

elasticsearch全解 (待续)

脑裂问题

脑裂是因为集群中的节点失联导致的。

例如一个集群中,主节点与其它节点失联:
elasticsearch全解 (待续)
当node3当选后,集群继续对外提供服务,node2和node3自成集群,node1自成集群,两个集群数据不同步,出现数据差异。

当网络恢复后,因为集群中有两个master节点,集群状态的不一致,出现脑裂的情况:

解决脑裂的方案是,要求选票超过 ( eligible节点数量 + 1 )/ 2 才能当选为主,因此eligible节点数量最好是奇数。对应配置项是discovery.zen.minimum_master_nodes,在es7.0以后,已经成为默认配置,因此一般不会发生脑裂问题

例如:3个节点形成的集群,选票必须超过 (3 + 1) / 2 ,也就是2票。node3得到node2和node3的选票,当选为主。node1只有自己1票,没有当选。集群中依然只有1个主节点,没有出现脑裂。

数据同步

elasticsearch中的酒店数据来自于mysql数据库,因此mysql数据发生改变时,elasticsearch也必须跟着改变,这个就是elasticsearch与mysql之间的数据同步
elasticsearch全解 (待续)
常见的数据同步方案有三种:

  • 同步调用
  • 异步通知
  • 监听binlog

同步调用

elasticsearch全解 (待续)

异步通知

elasticsearch全解 (待续)

监听binlog

elasticsearch全解 (待续)

选择

方式一:同步调用

  • 优点:实现简单,粗暴
  • 缺点:业务耦合度高

方式二:异步通知

  • 优点:低耦合,实现难度一般
  • 缺点:依赖mq的可靠性

方式三:监听binlog

  • 优点:完全解除服务间耦合
  • 缺点:开启binlog增加数据库负担、实现复杂度高

集群分布式存储

分片存储原理

elasticsearch会通过hash算法来计算文档应该存储到哪个分片:
elasticsearch全解 (待续)
说明:

  • _routing默认是文档的id
  • 算法与分片数量有关,因此索引库一旦创建,分片数量不能修改!

新增文档的流程如下:

elasticsearch全解 (待续)

解读:

  • 1)新增一个id=1的文档
  • 2)对id做hash运算,假如得到的是2,则应该存储到shard-2
  • 3)shard-2的主分片在node3节点,将数据路由到node3
  • 4)保存文档
  • 5)同步给shard-2的副本replica-2,在node2节点
  • 6)返回结果给coordinating-node节点

集群分布式查询

elasticsearch的查询分成两个阶段:

  • scatter phase:分散阶段,coordinating node会把请求分发到每一个分片

  • gather phase:聚集阶段,coordinating node汇总data node的搜索结果,并处理为最终结果集返回给用户

elasticsearch全解 (待续)

集群故障转移

集群的master节点会监控集群中的节点状态,如果发现有节点宕机,会立即将宕机节点的分片数据迁移到其它节点,确保数据安全,这个叫做故障转移。

1)例如一个集群结构如图:
elasticsearch全解 (待续)
现在,node1是主节点,其它两个节点是从节点。

2)突然,node1发生了故障:

elasticsearch全解 (待续)
宕机后的第一件事,需要重新选主,例如选中了node2:

elasticsearch全解 (待续)

node2成为主节点后,会检测集群监控状态,发现:shard-1、shard-0没有副本节点。因此需要将node1上的数据迁移到node2、node3:

elasticsearch全解 (待续)文章来源地址https://www.toymoban.com/news/detail-405130.html

到了这里,关于elasticsearch全解 (待续)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Elasticsearch:Elasticsearch 容量规划

    Elasticsearch 是一个可扩展的分布式系统,可为企业搜索、日志聚合、可观察性和安全性提供解决方案。 Elastic 解决方案建立在一个单一、灵活的技术堆栈之上,可以部署在任何地方。 要在自托管或云端运行生产环境 Elasticsearch,需要规划基础架构和集群配置,以确保健康且高

    2023年04月16日
    浏览(43)
  • Elasticsearch:提升 Elasticsearch 性能

    Elasticsearch 是为你的用户提供无缝搜索体验的不可或缺的工具。 在最近的 QCon 会议上,我遇到了很多的开发者。在他们的系统中,Elastic Stack 是不可缺少的工具,无论在搜索,可观测性或安全领域,Elastic Stack 都发挥着巨大的作用。我们在手机中常见的应用或者网站上的搜索基

    2023年04月08日
    浏览(36)
  • 【Elasticsearch】Elasticsearch基本使用

    1.1 安装ES Elasticsearch下载地址 要求:JDK1.8+ Elasticsearch 与 Tomcat 类似,下载安装包后解压即可使用。我这里下载的是最新的 7.6.2 版本 解压后,首先设置跨域支持,后面可能用得上(连接es相关工具)。在 ./config/elasticsearch.yml 中添加跨域支持 在 bin 目录上方地址栏输入 cmd 打开终端输入

    2024年02月08日
    浏览(42)
  • 【ElasticSearch01】ElasticSearch入门

    结构化数据 二维表等,保存到关系型数据库中例如mysql 非结构化数据 图像、视频、工作日志等,保存到Nosql数据库中,比如redis、mongodb中 半结构化数据 html、xml等保存到Nosql数据库中,比如redis、mongodb中 The Elastic Stack, 包括 Elasticsearch、 Kibana、 Beats 和 Logstash(也称为 ELK Stac

    2024年02月05日
    浏览(46)
  • 【Elasticsearch】初识elasticsearch

    目录 初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 1.1.2.ELK技术栈 1.1.3.elasticsearch和lucene 1.1.4.为什么不是其他搜索技术? 1.1.5.总结 1.2.倒排索引 1.2.1.正向索引 1.2.2.倒排索引 1.2.3.正向和倒排 1.3.es的一些概念 1.3.1.文档和字段 1.3.2.索引和映射 1.3.3.mysql与elasticsearch 1.1.1.elasticsea

    2024年02月13日
    浏览(37)
  • 【Elasticsearch】Elasticsearch 监控 合集

    一文搞懂 Elasticsearch 监控 如何监控Elasticsearch 为什么Elasticsearch查询变得这么慢了? 干货 | Elasticsearch Top10 监控指标 你不得不关注的 Elasticsearch Top X 关键指标

    2024年02月16日
    浏览(52)
  • Elasticsearch:(二)1.安装Elasticsearch

    安装java环境 安装Elasticsearch 安装kibana 安装Elasticsearch-head插件  本节文章主要讲解 Elasticsearch的安装。  jdk兼容性:支持一览表 | Elastic 操作系统兼容性:支持一览表 | Elastic 自身产品兼容性: 支持一览表 | Elastic jdk版本选择 :最好选择 jdk1.8、jdk11(官方给出的Java 9、Java 10、

    2024年04月23日
    浏览(31)
  • 【elasticsearch】elasticsearch—API文档

    elasticsearch—API文档 创建文档 查询文档 修改文档 再次查询文档: 删除文档 批量修改文档 批量删除文档

    2024年02月06日
    浏览(33)
  • 【ElasticSearch】ElasticSearch 内存设置原则

    由于ES构建基于lucene,而lucene设计强大之处在于lucene能够很好的利用操作系统内存来缓存索引数据,以提供快速的查询性能。lucene的索引文件segements是存储在单文件中的,并且不可变,对于OS来说,能够很友好地将索引文件保持在cache中,以便快速访问;因此,我们很有必要将一

    2024年02月14日
    浏览(38)
  • ElasticSearch笔记02-ElasticSearch入门

    ElasticSearch的官网,视频教程里用的Version是7.8.0,所以,我们也是用7.8.0版本的ElasticSearch。 下载地址:https://www.elastic.co/cn/downloads/past-releases#elasticsearch,然后搜索7.8.0版本即可。 按照视频里讲的,下载了Windows版本的ElasticSearch,当然,生产环境肯定是Linux版本的。 Windows版本的

    2024年02月09日
    浏览(63)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包