Elasticsearch8.x学习

Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎,它能很方便的使大量数据具有搜索、分析和探索的能力。Elasticsearch 具有良好的水平伸缩特性,在实际情况下,可以进行水平扩容的方式,存储海量的数据。

数据被存储到Elasticsearch集群中时,Elasticsearch利用分词的特性对数据创建索引(倒排索引),因为Elasticsearch是分布式的,那么一个索引可以被分成多个部分(称为分片)存储,并且引入副本的机制来提高可靠性。

一、概述

Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎,它能很方便的使大量数据具有搜索、分析和探索的能力。Elasticsearch 具有良好的水平伸缩特性,在实际情况下,可以进行水平扩容的方式,存储海量的数据。

数据被存储到Elasticsearch集群中时,Elasticsearch利用分词的特性对数据创建索引(倒排索引),因为Elasticsearch是分布式的,那么一个索引可以被分成多个部分(称为分片)存储,并且引入副本的机制来提高可靠性。

二、ES集群安装

在三个节点上分别解压,请修改elasticsearch.yml,三个节点上,cluster.name必须相同,node.name,network.host必须不相同,其余保持原样即可搭建一个三个节点的ES集群。

# 集群名称
cluster.name: orkasgb_es_cluster
# 节点名称
node.name: node-01
# ip地址
network.host: node-01
# 是不是由资格做主节点
# node.master: true
# 表示节点是否存储数据
# node.data: true
# 节点是否具有预处理能力
# node.ingest: true
# 页面端口号
http.port: 9200
# head插件需要的参数设置,跨域配置
http.cors.allow-origin: "*"
http.cors.enabled: true
http.max_content_length: 200mb
# JVM的内存能swap到磁盘,不能则需要配置为true
bootstrap.memory_lock: false
# 同一安装路径最多可以启动的节点
# node.max_local_storage_nodes: 1
# 数据、日志、插件存放路径
path.data: /orkasgb/data/elasticsearch
path.logs: /orkasgb/logs/elasticsearch
# path.plugins: /orkasgb/software/elasticsearch-8.0.0/plugins
# 集群内部通信端口
transport.port: 9300
# 集群节点
discovery.seed_hosts: ["node-01:9300","node-02:9300","node-03:9300"]
# 初始化集群时要用,类似于选举主节点
cluster.initial_master_nodes: ["node-01","node-02","node-03"]
# 远程重建索引时的ip白名单
reindex.remote.whitelist: ["node-01:9200","node-02:9200","node-03:9200"]
# 其他额外参数
gateway.recover_after_data_nodes: 2
network.tcp.keep_alive: true
network.tcp.no_delay: true
transport.compress: true
# ssh安全模式
xpack.security.transport.ssl.enabled: false
xpack.security.enabled: false

三、常用命令操作

1、索引相关:

创建索引

方法 : PUT

请求路径 : shopping

{
     
     
"settings":{
     
     
"number_of_shards": "3", //设置分片个数,默认1个分片
"number_of_replicas": "2" //设置副本个数,默认1个副本
}
}       

删除索引

方法 : DELETE

请求路径 : shopping

2、文档相关:

分页查询

方法 : POST

请求路径 : shopping/_search

请求体:

{
     
     
"from": 0, // 当前页起始数据索引:((页码 - 1 ) * size)
"size": 2  // 每一页的数据条数
}

按照单条件查询

方法 : POST

请求路径 : shopping/_search

请求体:

{
     
     
"query": {
     
     
"match": {
     
     
"product_name": "小米手机X7" // 按照单条件匹配
}
},
"highlight": {
     
     
"pre_tags": ["<font color=red>"],
"post_tags": ["</font>"],
"fields": {
     
        
"product_name": {
     
     } // 高亮显示
}
}
}

多条件查询

方法 : POST

请求路径 : shopping/_search

请求体:

{
     
     
"query": {
     
     
"bool": {
     
     
"should": [ // 条件满足其中一个即可
{
     
     
 "match": {
     
     
   "product_name": "小米手机X6"
 }
},    
{
     
     
 "match": {
     
     
   "product_price": 1006
 }
}
]
}
}
}
===============================================
{
     
     
"query": {
     
     
"bool": {
     
     
"must": [ // 条件必须全部满足
{
     
     
 "match": {
     
      // match:查询时,会进行分词查询,即查询条件会被拆分,满足其中的一部分或者全部均可以查询到。
            // match_phrase:查询时,不会进行分词查询,即查询条件必须作为一个整体且必须全部满足才能查询到。
   "product_name": "小米手机X6"
 }
},  
{
     
         
 "match": {
     
     
   "product_price": 1006
 }
}
]
}
}
}

全量查询

方法 : POST

请求路径 : shopping/_search

请求体:

{
     
     
"query": {
     
     
"match_all": {
     
     } // 全量查询
}
}

范围查询

方法 : POST

请求路径 : shopping/_search

请求体:

{
     
     
"query": {
     
     
"bool": {
     
     
"filter": {
     
     
"range": {
     
      // product_price大于2000的记录
"product_price": {
     
      
"gt": 2000
}
}
}
}
}
}

排序

方法 : POST

请求路径 : shopping/_search

请求体:

{
     
     
"sort": {
     
     
"product_price": {
     
      // 对价格按照降序排序
"order": "desc"
}
}
}
}

分组

方法 : POST

请求路径 : shopping/_search

请求体:

{
     
     
"aggs": {
     
      // 代表是聚合操作
"name_group": {
     
      // 分组名称,自定义即可
"terms": {
     
      // 分组
"field": "product_price" // 要分组的字段
}
}
},
"size": 0 // 不显示原始数据
}

平均值

方法 : POST

请求路径 : shopping/_search

请求体:

{
     
     
"aggs": {
     
      // 代表是聚合操作
"price_avg": {
     
      // 名称,自定义即可
"avg": {
     
      // 求平均值
"field": "product_price" // 要求平均值的字段
}
}
},
"size": 0 // 不显示原始数据
}

3、映射关系

mapping

方法 : PUT

请求路径 : shopping/user/_mapping

请求体:

{
     
     
"properties": {
     
     
"name": {
     
     
"type": "text", // 设置name字段的类型为text,支持分词
"index": "true", // 设置name字段可以被索引,也就是能用来当做查询条件来查询
"analyzer": "ik_max_word", // 分词器,在索引时,会去看字段有没有设定analyzer,有定义的话就用定义的,没定义就用ES预设的
"search_analyzer": "ik_smart" // 分词器,在查询时,会先去看字段有没有设定search_analyzer,没有再去看有没有设定analyzer,有定义的话就用定义的,如果都没有没定义就用ES预设的
},
"sex": {
     
     
"type": "keyword", // 设置sex字段的类型为keyword,不支持分词
"index": "true" 
},
"phone": {
     
     
"type": "text",
"index": "false" // 设置name字段不可以被索引,不能用来当做查询条件来查询
}
}
}

四、系统架构

1、Elasticsearch基本名词

索引(index):

在高版本中的Elasticsearh中,索引的概念等同于Mysql中表的概念。Elasticsearch中的索引可以直接存储数据。

文档(document):

真正的数据,存储一条数据就是一份文档。存储格式为JOSN,等同于mysql中的一条数据。

字段(field):

具体的字段名称,等同于mysql中的列名。

映射(mapping):

字段内容的规则定义,比如,某个字段存储的内容使用的分词器,是否能被索引(查询时当做查询条件),默认值等。

索引分片(shard):

就是将索引切分成多个部分,存储在不同的节点上,在分布式环境下,用来提高吞吐量。

副本(replica):

索引分片的备份,shard和replica一般存储在不同的节点上,用来提高高可靠性

2、Elasticsearch写流程

客户端请求到达某一个节点,该节点就作为一个协调器,协调器通过路由计算,得到主分片号,比如0,那么该数据就应该写到这个主分S0上,协调器讲将写请求发送到S0对应的节点上。

S0对应的节点收到这个写请求的时候,将数据写入磁盘。并将数据并发的发送到其他的副本所在的节点上,一旦所有的副本分片都报告写请求处理成功,所有的节点都会向协调器报告处理成功,那么协调器就会处理结果返回给客户端。

3、Elasticsearch读流程

  • 全文检索

    查询请求到达某一个节点上时,该节点就作为一个协调器。该协调器以广播的形式将查询请求分发到其他相关的节点上,其他的节点根据查询请求,在相关的分片上查询到数据后返回到协调器上,最后由协调器统一返回给客户端。整个查询过程可以分为两个过程:

    • 分发:查询请求发送到某一个节点上,比如node-01上,那么node-01就成为协调器,将查询请求分发到其他相关的节点,并且会创建一个空队列。
    • 聚合:其他的节点在对应的分片上查询数据,每个分片将查询数据返回到node-01协调器上,协调器将这些数据加入到队列中,进行合并、排序操作,最终形成一个有序的结果返回给客户端。
  • 路由查询(根据ID查询)

    • 查询请求到达某一个节点上时,该节点就作为一个协调器。该协调器根据文档ID计算(shard = hash(document_id) % (num_of_primary_shards)),找到对应的分片号。

    • 协调器将请求发送给对该分片号对应的节点上,然后查询该节点上对应的分片,并将数据返回到协调器上,由协调器统一返回给客户端。

    • 出于负载均衡开考虑,协调器会把每一个请求通过轮询所有分片的方式发送到不同的分片上。

4、分片原理:

ES通过将大数据量的索引进行水平切分,形成不同的数据块,这些数据块被称为切片。这类似于Mysql的分库分表。向一个多分片的索引中写入数据的时候,通过路由计算,来确认数据将写入哪个分片中,所以在创建索引的时候,就需要指定分片的数量,并且一旦分片数量确认,是无法修改的。

ES默认为一个索引创建1个主分片和1个副本,在创建索引的时候使用settings属性指定,每个分片必须有零到多个副本。ES通过分片功能使得索引在搜索规模和性能上有很大的提升。

小结:

  • 将数据切分为多个分片是为了提高可处理数据的容量和方便后续水平扩展,为分片做副本是为了提高集群的稳定性,提高容灾特性和吞吐量。
  • 副本越多,数据越保险,但是却会消耗大量的资源,分片越多,数据就会越分散,避免数据过于集中导致某个节点压力过大。
  • 副本和分片的数量不一定是越多越好,过多的分片,会占用大量的CPU以及内存等资源,过多的副本,副本之间数据同步的会占用大量网络资源等。

5、倒排索引

倒排索引一般由词典和倒排文件构成。

词条:

索引里面的最小的存储单元或者查询单元,对于英文就是一个单词,对于中文来说就是分词后的一个词。

词典:

词条的集合构成词典,类似于字典。是一些文档中出现的单词构成的字符串集合,这些字符串不但包含了单词本身的一些信息,还有包含了指向倒排列表的指针。

倒排列表:

倒排表记录的时某个词在文档中出现的位置以及在哪些文档中出现过。每一条记录称为一个倒排项。使用的倒排列表中还会记录某个词在文档集合中出现的次数,这个次数在搜索结果中排序计算中有非常重要的作用。

倒排文件:

某一个词的倒排列表往往都是顺序的存储在磁盘上的某个文件中,这些文件被称为倒排文件。倒排文件是倒排索引的物理存储文件。

6、分词器

  • ES自带的分词器,标准分词器

方法 : POST

请求路径 : _analyze

{
     
     
"analyzer": "standard", // ES自带的分词器,标准分词器
"text":"我感动天"
} 
{
     
     
"tokens": [
{
     
     
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "<IDEOGRAPHIC>",
"position": 0
},
{
     
     
"token": "感",
"start_offset": 1,
"end_offset": 2,
"type": "<IDEOGRAPHIC>",
"position": 1
},
{
     
     
"token": "动",
"start_offset": 2,
"end_offset": 3,
"type": "<IDEOGRAPHIC>",
"position": 2
},
{
     
     
"token": "天",
"start_offset": 3,
"end_offset": 4,
"type": "<IDEOGRAPHIC>",
"position": 3
}
]
}
{
     
     
"analyzer": "standard",
"text":"this is an apple!"
}
{
     
     
"tokens": [
{
     
     
"token": "this",
"start_offset": 0,
"end_offset": 4,
"type": "<ALPHANUM>",
"position": 0
},
{
     
     
"token": "is",
"start_offset": 5,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
     
     
"token": "an",
"start_offset": 8,
"end_offset": 10,
"type": "<ALPHANUM>",
"position": 2
},
{
     
     
"token": "apple",
"start_offset": 11,
"end_offset": 16,
"type": "<ALPHANUM>",
"position": 3
}
]
}
  • IK分词器

方法 : POST

请求路径 : _analyze

{
     
     
"analyzer": "ik_smart", // ik_smart分词器
"text":"我感动天"
} 
{
     
     
"tokens": [
{
     
     
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
     
     
"token": "感动",
"start_offset": 1,
"end_offset": 3,
"type": "CN_WORD",
"position": 1
},
{
     
     
"token": "天",
"start_offset": 3,
"end_offset": 4,
"type": "CN_CHAR",
"position": 2
}
]
}
{
     
     
"analyzer": "ik_smart",
"text":"this is an apple!"
}
{
     
     
"tokens": [
{
     
     
"token": "apple",
"start_offset": 11,
"end_offset": 16,
"type": "ENGLISH",
"position": 0
}
]
}

7、发现机制、选举机制

ES内部可以将相同的cluster.name的节点归纳到同一个ES集群中,而实现这个功能的就是Zen Discovery发现机制。Zen Discovery是ES内部默认实现的发现机制,它默认提供单播和多播发现方式。

ES默认配置为单播的方式去发现节点,以防止节点无意中加入集群。我们在配置ES时可以为ES提供一些它应该去尝试连接的节点列表(discovery.zen.ping.unicast.hosts),这样就可以以这些指定的节点组成集群。

当ES启动后,先从各个节点认为的Master中选举,规则很简单,按照ID的字典顺序排序后取第一个,如果各个节点都没有可供选择的Matser,那么直接从所有的节点中选择Master,选举规则也是按照ID的字典顺序排序取第一个。

选举的时候有一个限制条件就是,如果发现的节点达不到最小值的限制(discovery.zen.minium_master_nodes),那么就不开始选举,直到节点数达到这个最小值限制才能开始选举。如果当前节点就是Master,那么必须要等到节点数据达到这个最小值限制才开始提供服务。因为所有的节点都会遵循这个规则,那么直到最后选举出来Master的时候 ,所有节点的信息都应该时对等的。但是在分布式系统下,节点上的信息可能存在不对等的情况,这就会产生脑裂现象,而discovery.zen.minium_master_nodes这个参数也可以避免脑裂问题。

8、节点角色

ES中,只有候选节点才能参与Master选举,其他节点不参与Master节点选举。主节点主要负责创建索引、删除索引、分配分片、跟踪哪些节点是集群中的节点、监控这些节点的状态等。

虽然ES中有主节点区分,但是用户的请求是可以发往任何一个节点上,当某一个节点收到请求时,由该节点负责分发请求,收集结果并返回给客户端,而不用转发给主节点,再由主节点做这些事情。该节点被称为协调节点,协调节点不需要指定,而是由集群中的任意一台节点充当。

9、脑裂现象

如果由于网络等原因,一个集群中,出现了多个Master节点,导致数据更新不一致,分片混乱,这种现象称为脑裂现。

脑裂现象一般可能由以下几个原因造成:

  • 网络原因:集群中网络原因,导致一些节点访问不到Master节点,那么就会认为Master挂掉了,此时就会重新选举Master,并且就会由该Master重新分配分片。
  • 节点负载:如果一个节点既是主节点又是数据节点,那么当访问量很大的时候,就会出现相应延迟的现象,一旦出现长时间不能响应,其他节点也会认为Master挂掉了,此时就会重新选举Master。

解决脑裂现象的措施:

  • discovery.zen.ping_timeout:适当的调整相应等待时间,避免误判。默认3秒。
  • discovery.zen.minium_master_nodes:当节点满足这个参数的值的时候,才会触发Master选举,默认值1。
  • 角色分离:即对候选主节点和数据节点进行角色分离,这样就可以减少主节点的压力,降低响应延迟,避免误判。

10、存储原理

索引文件是以段的形式存储在磁盘上,而段就是将索引文件拆分成一个个的小文件,这些小文件叫做段,每一个段就是一个倒排索引,并且具有不可变的特性,因此一旦索引文件被写到磁盘上,就不可再次被修改。

ES的存储底层采用了分段存储模式,避免了锁的出现,大大提高了读写性能。每个段被写到磁盘上后,就会生成一个提交点,提交点就是一个用来记录段提交后段信息的文件。一旦段有了提交点,那么这个段只能拥有读的权限,而失去了写权限,所以,在ES中,一旦数据被写入磁盘,就无法修改。相反,段在内存中,只拥有写权限,没有读权限,此时,数据不提供读功能。

既然段被写入磁盘后,不能被修改,那么删除和修改由是如何处理的呢?

  • 新增:由于数据是新增的,那么直接创建一个新的段即可。
  • 删除:由于不可修改,所以对于删除操作来说,只是会在段中增加一个.del文件,类似于给这个段打上了一个删除标记。实际上这个段在集群中还是存在的,查询的时候还是会被查询到的,只是会在返回客户端的时候提前从结果集中移除了而已。
  • 修改:由于数据不可修改,那么修改操作就相当于是删除和新增两个操作。首先在旧的段中新增一个.del文件,然后再将该段的新版本创建一个新的段,那么这两个段在查询的时候依旧可能被同时查询到,只是旧版本的段的数据会在返回客户端的时候提前从结果集中移除了而已。

段的不可修改性的优缺点:

  • 优点:
    1. 不需要锁:因为不存在修改,那么就不用担心并发修改的问题。
    2. 由于不可修改性,将段中的数据读到内存中,只要内存足够大,那么数据就可以一直被缓存,大部分请求直接读取内存中的数据即可返回,而不需要命中磁盘,这一点可以和Hbae进行比对。
  • 缺点:
    1. 旧数据不能立即被删除,而是被标记成待删除的文件保留在磁盘上,占用磁盘空间。
    2. 对于频繁更新的数据来说,每次都是标记旧的文件,创建新的文件,同样造成磁盘空间浪费。
    3. 查询时由于被标记删除的文件一样会被查询到,再返回刀片客户端前需要被移除,这样增加了查询时的负担。

11、延迟写策略

ES在将索引写入磁盘上的时候,并不是直接写入磁盘的,而是调用Fsync延迟写入磁盘。如果是直接写入磁盘,磁盘I/O消费就会严重影响整个ES的性能。

每当有新的数据被写入时,ES会先将数据写入内存,在内存和磁盘之间的时文件系统缓存。当达到默认时间1s的时候或者内存达到一定量的时候,数据就会被refresh(内存数据刷新到文件系统缓存的过程)到新的段上,此时段开启查询检索权限,并被缓存到文件系统缓存中,稍后被刷新到磁盘上,并生成提交点。此过程进行的同时,新的数据还会被继续写入内存,但是不提供检查权限。

默认情况下,每个分片每1s(refresh_interval,可以适当的调大)就会被refresh一次,refresh时开启查询检索权限,所以ES是一个近实时搜索的搜索引擎。

12、事务

因为写数据是将数据先写到内存中,随将数据refresh到文新系统缓存中,在断电或者重启时就存在一个数据丢失的风险,为了避免这种数据丢失的风险ES引入事务的概念,当数据被写入内存的同时,并将数据一并追加到事务日志中。在断电或者重启时ES不仅需要根据提交点去加载已经提交过的数据,还需要根据事务日志(Translog)里面的记录,将为持久化的数据重新持久化到磁盘上。这样就避免了数据丢失的可能。

13、段合并

由于段每1s就会生成一个段,那么长时间内,段的数量就会很庞大。ES在后台会定期的处理这些数量多的小段的问题,小的段被合并成大的段,大的段被合并成更大的段。

段合并的过程中,会选择一些大小相近的段,这些段既可以是已经提交过的,也可以是未提交过的,合并的过程中,被标记删除的段会被删除掉,新的段会被flush到磁盘上,并且生成新的提交点,此时段是可以被检索的。

五、调优

  • 给每个文档设置具有良好压缩性能的序列模式ID,随机的ID压缩比很低。
  • 不需要做模糊查询的字段,使用keyword代替text,避免创建索引的时候对这些词进行分词。
  • 如果对于实时性要求不是很高,可以适当的调高分片刷新的时间点(refresh_interval,默认1s,可以更改为30s)。
  • 在做大量的数据的批量导入的时候,可以先关闭副本(number_of_replicas=0),导入数据之后,在开启。
  • 减少映射字段,之存储需要提供检索,聚合,排序功能的字段,其他的多余的无关的字段存储在Hbase等其他设备上。在根据ES查询出关键信息时,然后再去Hbase中查询出其他字段。
  • 创建索引时,指定路由routing的值,使数据能精确的分配到具体的分片中,提高查询效率和负载均衡,这一点类似于hbase的预分区。

猜你喜欢

转载自blog.csdn.net/qq_22610595/article/details/124258795