🛫️ElasticSearch从入门到出门

313 阅读18分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情

概述

ElasticSearch我们一般简称为es,在这里先贴出来官网地址: www.elastic.co/cn/

它是基于Lucene的分布式的Restful风格的搜索、存储和分析引擎 ,java开发。

它具有高性能、高可用、可伸缩、易维护的特点。体现在可以处理PB级数据,具有很高的容错性和比较全的文档,支持横向和纵向扩展。

应用领域

  • 搜索引擎(全文检索、高亮、搜索推荐等)
  • 用户行为日志(用户点击消费、浏览收藏、点赞评论等)
  • BI系统,数据分析
  • GItHub
  • ELK(日志搜索)
    ……

核心概念

倒排索引

英文 inverted index

这里与正序和倒序排列这个作以区分,倒排索引指的是:可以简单理解为我们平时根据id去查内容的,倒排索引恰好相反,是根据内容去查id的。至于怎么去查就涉及到在将内容存进es中时所做的处理了,比如通过分词器分词作关联等。

倒排索引的概念是基于MySQL这样的正向索引而言的,倒排索引中有两个非常重要的概念:

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

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

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

如图:

image.png

倒排索引的搜索流程如下(以搜索"胡八一行政桌"为例):

1)用户输入条件"胡八一行政桌"​​进行搜索。

2)对用户输入内容分词,得到词条:胡八一​​、行政桌​​。

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

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

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

正排索引

英文 forward index

可以简单理解为通过id去查内容,例如常见的mysql中的索引。

cluster

集群

集群概述

es集群相关核心名词有:

  1. cluster_name:集群名称
  2. status:健康值状态 green 、yellow 、red
  3. timed_out:是否超时
  4. number_of_nodes:节点数量
  5. number_of_data_nodes:数据节点数量
  6. active_primary_shards:活跃的主分片数
  7. active_shards:活跃的总分片数
  8. relocating_shards:迁移中的分片数
  9. initializing_shards:初始化中的分片数
  10. unassigned_shards:未分配的分片数
  11. active_shards_percent_as_number:活跃分片的百分比

集群搭建

  1. 下载ElasticSearch的安装包
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.3-linux-x86_64.tar.gz
//解压文件
tar -zxvf elasticsearch-7.17.3-linux-x86_64.tar.gz -C /usr/local 

  1. 修改 jvm.options 配置
-Xms1024m -Xmx1024m
  1. 配置 elasticsearch.yml 文件
//集群名称,三台集群,要配置相同的集群名称
cluster.name: my-application 
//节点名称 
node.name: node1 
//是否有资格被选举为master,ES默认集群中第一台机器为主节点 
node.master: true 
//是否存储数据 
node.data: true 
//最⼤集群节点数,为了避免脑裂,集群节点数最少为 半数+1 
node.max_local_storage_nodes: 3 
//数据目录 
path.data: /usr/local/node1/data 
//log目录 
path.logs: /usr/local/node1/logs 
//修改 network.host 为 0.0.0.0,表示对外开放,如对特定ip开放则改为指定ip 
network.host: 0.0.0.0 
//设置对外服务http端口,默认为9200 
http.port: 9200 
//内部节点之间沟通端⼝ 
transport.tcp.port: 9300 
//写⼊候选主节点的设备地址,在开启服务后可以被选为主节点 
discovery.seed_hosts: ["localhost:9300", "localhost:9301", "localhost:9302"] 
//初始化⼀个新的集群时需要此配置来选举master 
cluster.initial_master_nodes: ["node1", "node2","node3"] 
//设置集群中N个节点启动时进行数据恢复,默认为1 
gateway.recover_after_nodes: 3 
//下面的两个配置在安装elasticsearch-head的时候会用到 
//开启跨域访问支持,默认为false 
http.cors.enabled: true 
//跨域访问允许的域名地址,(允许所有域名)以上使用正则 
http.cors.allow-origin: "*" 
//关闭xpack 
xpack.security.enabled: false

  1. 复制当前es构建其他数据节点
//拷贝三份文件 
cp -r /usr/local/elasticsearch-7.17.3/ /usr/local/node1 
cp -r /usr/local/elasticsearch-7.17.3/ /usr/local/node2 
cp -r /usr/local/elasticsearch-7.17.3/ /usr/local/node3
  1. 修改node2和node3两个数据节点的配置文件,先修改node2数据节点
//集群名称,三台集群,要配置相同的集群名称
cluster.name: my-application 
//节点名称 
node.name: node2 
//是否有资格被选举为master,ES默认集群中第一台机器为主节点 
node.master: true 
//是否存储数据 
node.data: true 
//最⼤集群节点数,为了避免脑裂,集群节点数最少为 半数+1 
node.max_local_storage_nodes: 3 
//数据目录 
path.data: /usr/local/node2/data 
//log目录 
path.logs: /usr/local/node2/logs 
//修改 network.host 为 0.0.0.0,表示对外开放,如对特定ip开放则改为指定ip 
network.host: 0.0.0.0 
//设置对外服务http端口,默认为9200,可更改端口不为9200 
http.port: 9201 
//内部节点之间沟通端⼝ 
transport.tcp.port: 9301 
//写⼊候选主节点的设备地址,在开启服务后可以被选为主节点 
discovery.seed_hosts: ["localhost:9300", "localhost:9301", "localhost:9302"] 
//初始化⼀个新的集群时需要此配置来选举master 
cluster.initial_master_nodes: ["node1", "node2","node3"] 
//设置集群中N个节点启动时进行数据恢复,默认为1 
gateway.recover_after_nodes: 3 
//下面的两个配置在安装elasticsearch-head的时候会用到 
//开启跨域访问支持,默认为false 
http.cors.enabled: true 
//跨域访问允许的域名地址,(允许所有域名)以上使用正则 
http.cors.allow-origin: "*" 
//关闭xpack 
xpack.security.enabled: false
  1. 再先修改node3数据节点:
//集群名称,三台集群,要配置相同的集群名称
cluster.name: my-application 
//节点名称 
node.name: node3 
//是否有资格被选举为master,ES默认集群中第一台机器为主节点 
node.master: true 
//是否存储数据 
node.data: true 
//最⼤集群节点数,为了避免脑裂,集群节点数最少为 半数+1 
node.max_local_storage_nodes: 3 
//数据目录 
path.data: /usr/local/node3/data 
//log目录 
path.logs: /usr/local/node3/logs 
//修改 network.host 为 0.0.0.0,表示对外开放,如对特定ip开放则改为指定ip 
network.host: 0.0.0.0 
//设置对外服务http端口,默认为9200,可更改端口不为9200 
http.port: 9202 
//内部节点之间沟通端⼝ 
transport.tcp.port: 9302 
//写⼊候选主节点的设备地址,在开启服务后可以被选为主节点 
discovery.seed_hosts: ["localhost:9300", "localhost:9301", "localhost:9302"] 
//初始化⼀个新的集群时需要此配置来选举master 
cluster.initial_master_nodes: ["node1", "node2","node3"] 
//设置集群中N个节点启动时进行数据恢复,默认为1 
gateway.recover_after_nodes: 3 
//下面的两个配置在安装elasticsearch-head的时候会用到 
//开启跨域访问支持,默认为false 
http.cors.enabled: true 
//跨域访问允许的域名地址,(允许所有域名)以上使用正则 
http.cors.allow-origin: "*" 
//关闭xpack 
xpack.security.enabled: false
  1. 创建ES存储数据和log目录,根据之前每个节点的配置文件内配置path进行创建或修改
mkdir -p /usr/local/node1/data 
mkdir -p /usr/local/node1/logs
mkdir -p /usr/local/node2/data 
mkdir -p /usr/local/node2/logs 
mkdir -p /usr/local/node3/data 
mkdir -p /usr/local/node3/logs
  1. 创建普通用户,由于ES需要使用非root用户来启动,所以下面创建一个普通用户
//创建elasticsearch用户组 
groupadd elasticsearch 
//创建新用户elasticsearch ,设置用户组为elasticsearch ,密码123456 
useradd elasticsearch -g elasticsearch -p 123456 
//授权,更改/elasticsearch-7.17.3文件夹所属用户及用户组为elasticsearch:elasticsearch 
chown -R elasticsearch:elasticsearch /usr/local/elasticsearch-7.17.3/ 
//切换用户elasticsearch 
su elasticsearch
  1. 给节点文件授权
chown -R elasticsearch:elasticsearch /usr/local/node1 
chown -R elasticsearch:elasticsearch /usr/local/node2 
chown -R elasticsearch:elasticsearch /usr/local/node3

由es的集群搭建可以看出,es自己是支持集群的,不用再借助第三方技术来支持es的集群

node

节点

一个节点≠一台服务器主机

es的节点类型:

Master

主节点,每个集群都有且只有一个

尽量避免Master节点又是数据节点: node.data = true

Voting

投票节点 ode.voting_only = true(仅投票节点,即使配置了data.master = true,也不会参选, 但是仍然可以作为数据节点)。

Coordinating

协调节点每一个节点都隐式的是一个协调节点,如果同时设置了data.master = false和data.data=false,那么此节点将成为仅协调节点。

Master-eligible node

候选节点可以通过选举成为Master的节点

Data node

数据节点主要负责数据存储和查询服务

field

数据字段

  • 字符串类型:
-   text:一把被用于全文检索。 将当前Field进行分词。
-   keyword:当前Field不会被分词。
  • 数值类型:

    • long:取值范围为-9223372036854774808~922337203685477480(-2的63次方到2的63次方-1),占用8个字节
    • integer:取值范围为-2147483648~2147483647(-2的31次方到2的31次方-1),占用4个字节
    • short:取值范围为-32768~32767(-2的15次方到2的15次方-1),占用2个字节
    • byte:取值范围为-128~127(-2的7次方到2的7次方-1),占用1个字节
    • double:1.797693e+308~ 4.9000000e-324 (e+308表示是乘以10的308次方,e-324表示乘以10的负324次方)占用8个字节
    • float:3.402823e+38 ~ 1.401298e-45(e+38表示是乘以10的38次方,e-45表示乘以10的负45次方),占用4个字节
    • half_float:精度比float小一半。
    • scaled_float:根据一个long和scaled来表达一个浮点型,long-345,scaled-100 -> 3.45
  • 时间类型:

    • date类型,针对时间类型指定具体的格式
  • 布尔类型:

    • boolean类型,表达true和false
  • 二进制类型:

    • binary类型暂时支持Base64 encode string
  • 范围类型:

    • long_range:赋值时,无需指定具体的内容,只需要存储一个范围即可,指定gt,lt,gte,lte
    • integer_range:同上
    • double_range:同上
    • float_range:同上
    • date_range:同上
    • ip_range:同上
  • 经纬度类型:

    • geo_point:用来存储经纬度的
  • ip类型:

    • ip:可以存储IPV4或者IPV6

document

文档

document的核心元数据

document的核心元数据有三个:_index、_type、_id

_index元数据

  • 代表一个document存放在哪个index中
  • 类似的数据放在一个索引中,非类似的数据放在不同的索引中:product index(包含了所有的商品)、sales index(包含了所有的商品销售数据)、inventory index(包含了所有库存的相关数据)
  • index中包含了很多类似的document: 类似是什么意思呢,其实指的就是说,这些document的fields很大一部分是相同的,你说你放了3个document,每个document的fields都完全不一样,这就不是类似了,就不太适合放到一个index里面去了
  • 索引名称必须是小写,不能用下划线开头,不包含逗号

_type元数据(7.x以后去掉了)

  • 代表document属于index的哪个类别
  • 一个索引通常会划分为多个type,逻辑上对index有些许不同的几类数据进行分类
  • type名称可以是大写或者小写,但是同时不能用下划线开头,不能包含逗号

_id元数据

  • 代表document的唯一标识,与_index和_type一起可以起唯一标识和定位一个document
  • 我们可以手动指定document的id,也可以不指定,由es自动为我们创建一个id

type (7.x以后去掉了)

类型

index

索引

类似关系型数据库的数据库

mapping

mapping就是ES数据字段field的type元数据,ES在创建索引的时候,dynamic mapping会自动为不同的数据指定相应mapping,mapping中包含了字段的类型、搜索方式(exact value或者full text)、分词器等 mapping是对索引库中文档的约束。

索引库就类似数据库表,mapping映射就类似表的结构。

我们要向es中存储数据,必须先创建“库”和“表”。

  • type:字段数据类型,常见的简单类型有:

    • 字符串:text(可分词的文本)、keyword(精确值,例如:产品、来源、ip地址)

      keyword类型只能整体搜索,不支持搜索部分内容

    • 数值:long、integer、short、byte、double、float、

    • 布尔:boolean

    • 日期:date

    • 对象:object

shard

分片

一台服务器上无法存储大量数据,ES把一个index里面的数据分成多个shard分布式的存储在多个服务器上(对大的索引分片,拆成多个,分不到不同的节点上)。ES就是通过shard来解决节点的容量上限问题的,通过主分片可以将数据分布到集群内的所有节点上。主分片数是在索引创建时指定的,一般不允许修改,除非Reindex。一个索引中的数据保存在多个分片中(默认为一个)相当于水平分表。一个分片表示一个Lucene的实例,它本身就是一个完整的搜索引擎。我们的文档被存储和索引到分片内,这些对应用程序是透明的,即应用程序直接与索引交互而不是分片。

分词

我们在往es中存入document的时候会将 内容进行分词,es有自带的默认的分词器,但是如果是中文的话我们一般用ik分词器比较多,可以自定义词典,也就是自定义一些词语放在config里面,在分词的时候使用这些词语分词即可。 在查询的时候根据是否match,我们会将查询句子进行分词,需要看是关键字查询还是全文搜索,进行分词不分词匹配或者分词匹配。 所谓分词就是将一个句子拆成一个一个字或者一些词语。

语法

CRUD

  • 创建索引:PUT /索引名称?pretty
  • 查询索引:GET _cat/indices?v
  • 删除索引:DELETE /msb_index?pretty
  • GET /product/_doc/_search 查询所有
  • GET /product/_doc/1 根据id查询

Searchtimeout

Timeout机制:假设用户查询结果有1W条数据,但是需要10″才能查询完毕,但是用户设置了1″的timeout,那么不管当前一共查询到了多少数据,都会在1″后ES讲停止查询,并返回当前数据。

用法:GET /_search?timeout=1s/ms/m

Query_string

  • 查询所有:GET /product/_search
  • 带参数:GET /product/_search?q=name:xiaomi
  • 分页:GET /product/_search?from=0&size=2&sort=price:asc

Query DSL

Phrase search:短语搜索,和全文检索相反,“nfc phone”会作为一个短语去检索 。

基于短语(Term-based)的查询:
像term(在filter上下文中)或fuzzy一类的查询是低级查询,它们没有分析阶段。这些查询在单一的短语上执行。例如对单词food的term查询会在倒排索引里精确查找food这个词,并对每个包含这个单词的文档计算TF/IDF相关度_score(5.1版本升级中,elasticsearch将算法改进为BM25)
term查询只在倒排查询里精确低查找特定短语,而不会匹配短语的其它变形,如food或FOOD。不管短语怎样被加入索引,都只匹配倒排索引里的准确值。如果在一个设置了not_analyzed的字段为["Food", " Warter"]建索引,或者在一个用whitespace解析器解析的字段为Food Warter建索引,都会在倒排索引里加入两个索引FoodWarter

query-term:不会被分词

GET /product/_search

{
  "query": {
    "term": {
      "name": "nfc"
    }
  }
}

GET /product/_search

{
  "query": {
    "term": {
      "name": "nfc phone" 这里因为没有分词,所以查询没有结果
    }
  }
}

全文(Full-text)检索

matchquery_string这样的查询是高级查询,他们会对字段进行分析:

  • 如果检索一个dateinteger字段,他们会把查询语句作为日期或者整数格式数据。
  • 如果检索一个精确值(not_analyzed)字符串字段,他们会把整个查询语句作为一个短语。
  • 如果检索一个全文(analyzed)字段,查询会先用适当的解析器解析查询语句,产生需要查询的短语列表。然后对列表中的每个短语执行低级查询,合并查询结果,得到最终的文档相关度。
GET /product/_search

{

  "query": {

    "match": {

      "name": "xiaomi nfc zhineng phone"

    }

  }

}

#验证分词

GET /_analyze

{

  "analyzer": "standard",

  "text":"xiaomi nfc zhineng phone"

}

Query and filter:查询和过滤

bool:可以组合多个查询条件,bool查询也是采用more_matches_is_better的机制,因此满足must和should子句的文档将会合并起来计算分值。

Compound queries:复合查询

复合(compound)查询:复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑。常见的有两种:

  • fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名
  • bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索

Highlight search:高亮

注意:

  • 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
  • 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
  • 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false

使用场景:在百度等搜索后,会对结果中出现搜索字段的部分进行高亮处理。

高亮原理

高亮显示的实现分为两步:

  • 给文档中的所有关键字都添加一个标签,例如<em>标签
  • 页面给<em>标签编写CSS样式

语法

GET /hotel/_search { "query": { "match": { "FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询 } }, "highlight": { "fields": { // 指定要高亮的字段 "FIELD": { //【要和上面的查询字段FIELD一致】 "pre_tags": "", // 用来标记高亮字段的前置标签 "post_tags": "" // 用来标记高亮字段的后置标签 } } } }

Deep paging问题

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

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

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

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

集群情况的深度分页:

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

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

Search After 分页方式是 ES 5 新增的一种分页查询方式,其实现的思路同 Scroll 分页方式基本一致,通过记录上一次分页的位置标识,来进行下一次分页数据的查询。相比于 Scroll 分页方式,它的优点是可以实时体现数据的变化,解决了查询快照导致的查询结果延迟问题。

scroll 分页方式类似关系型数据库中的 cursor(游标),首次查询时会生成并缓存快照,返回给客户端快照读取的位置参数(scroll_id),后续每次请求都会通过 scroll_id 访问快照实现快速查询需要的数据,有效降低查询和存储的性能损耗。

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

查询TOP1000,如果es是单点模式,这并无太大影响。

但是elasticsearch基本都是集群部署,例如我们集群有5个节点,需要查询TOP1000的数据,并不是每个节点查询200条就可以了。

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

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

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