Elasticsearch 科普与应用

前言

本次主要目的是科普下Elasticsearch(以下简称:ES),同时分享下目前自己使用到的ES的情况。

章一:什么是ES

简介:

Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它能很方便的使大量数据具有搜索、分析和探索的能力。充分利用Elasticsearch的水平伸缩性,能使数据在生产环境变得更有价值。Elasticsearch 的实现原理主要分为以下几个步骤,首先用户将数据提交到Elasticsearch 数据库中,再通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据,当用户搜索数据时候,再根据权重将结果排名,打分,再将返回结果呈现给用户。

Elasticsearch是与名为Logstash的数据收集和日志解析引擎以及名为Kibana的分析和可视化平台一起开发的。这三个产品被设计成一个集成解决方案,称为"Elastic Stack"(以前称为"ELK stack")。

Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。Elasticsearch是分布式的,这意味着索引可以被分成分片,每个分片可以有0个或多个副本。每个节点托管一个或多个分片,并充当协调器将操作委托给正确的分片。再平衡和路由是自动完成的。相关数据通常存储在同一个索引中,该索引由一个或多个主分片和零个或多个复制分片组成。一旦创建了索引,就不能更改主分片的数量。

Elasticsearch使用Lucene,并试图通过JSON和Java API提供其所有特性。它支持facetting和percolating,如果新文档与注册查询匹配,这对于通知非常有用。另一个特性称为"网关",处理索引的长期持久性;例如,在服务器崩溃的情况下,可以从网关恢复索引。Elasticsearch支持实时GET请求,适合作为NoSQL数据存储,但缺少分布式事务。

ES整体架构:

结构:

  • 为方便理解,可以对照下ES和MySQL的关系:

ES数据架构的主要概念与关系数据库Mysql对比表

Type的概念,是适用于早期的ES版本,后续的ES版本,未使用到此概念。

章二:ES中的核心概念

集群

一个集群,集群中有多个节点,其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。es的一个概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,因为从外部来看es集群,在逻辑上是个整体,你与任何一个节点的通信和与整个es集群通信是等价的。

节点

Elasticsearch集群中服务分多个角色

Master节点:

负责集群内调度决策,集群状态、节点信息、索引映射、分片信息、路由信息,Master真正主节点是通过选举诞生的,一般一个集群内至少要有三个Master可竞选成员,防止主节点损坏。

Data存储节点:

用于存储数据及计算,分片的主从副本,热数据节点,冷数据节点;

Client协调节点:

协调多个副本数据查询服务,聚合各个副本的返回结果,返回给客户端;

Elasticsearch的两次查询:

  1. 多节点及多分片能够提高系统的写性能,但是这会让数据分散在多个Data节点当中,Elasticsearch并不知道我们要找的文档,到底保存在哪个分片的哪个segment文件中。
  2. 所以,为了均衡各个数据节点的性能压力,Elasticsearch每次查询都是请求 所有 索引所在的Data节点,查询请求时协调节点会在相同数据分片多个副本中,随机选出一个节点发送查询请求,从而实现负载均衡。
  3. 而收到请求的副本会根据关键词权重对结果先进行一次排序,当协调节点拿到所有副本返回的文档ID列表后,会再次对结果汇总排序,最后才会用 DocId去各个副本Fetch具体的文档数据将结果返回。
  4. 可以说,Elasticsearch通过这个方式实现了所有分片的大数据集的全文检索,但这种方式也同时加大了Elasticsearch对数据查询请求的耗时。

Kibana计算节点:

作用是实时统计分析、聚合分析统计数据、图形聚合展示。

倒排索引

倒排索引是区别于正排索引的概念:

正排索引:是以文档对象的唯一 ID 作为索引,以文档内容作为记录。

倒排索引:Inverted index,指的是将文档内容中的单词作为索引,将包含该词的文档 ID 作为记录。

举例:

倒排索引-组成

单词词典(Term Dictionary)

单词词典的实现一般用B+树,B+树构造的可视化过程网址:B+ Tree Visualization

倒排列表(Posting List)

倒排列表记录了单词对应的文档集合,有倒排索引项(Posting)组成

倒排索引项主要包含如下信息:

  1. 文档id用于获取原始信息
  2. 单词频率(TF,Term Frequency),记录该单词在该文档中出现的次数,用于后续相关性算分
  3. 位置(Posting),记录单词在文档中的分词位置(多个),用于做词语搜索(Phrase Query)
  4. 偏移(Offset),记录单词在文档的开始和结束位置,用于高亮显示

倒排索引不可变的好处

  • 不需要锁,提升并发能力,避免锁的问题
  • 数据不变,一直保存在os cache中,只要cache内存足够
  • filter cache一直驻留在内存,因为数据不变
  • 可以压缩,节省cpu和io开销

分片

代表索引分片,es可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。

副本

代表索引副本,es可以设置多个索引的副本,副本的作用一是提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复。二是提高es的查询效率,es会自动对搜索请求进行负载均衡。

分词

分词是将文本转换成一系列单词(Term or Token)的过程,也可以叫文本分析,在ES里面称为Analysis

分词器

分词器是ES中专门处理分词的组件,英文为Analyzer,它的组成如下:

  • Character Filters:针对原始文本进行处理,比如去除html标签
  • Tokenizer:将原始文本按照一定规则切分为单词
  • Token Filters:针对Tokenizer处理的单词进行再加工,比如转小写、删除或增新等处理

分词器调用顺序

预定义的分词器

Standard Analyzer

默认分词器

按词切分,支持多语言

小写处理

Simple Analyzer

按照非字母切分

小写处理

Whitespace Analyzer

空白字符作为分隔符

Stop Analyzer

相比Simple Analyzer多了去除请用词处理

停用词指语气助词等修饰性词语,如the, an, 的, 这等

Keyword Analyzer

不分词,直接将输入作为一个单词输出

Pattern Analyzer

通过正则表达式自定义分隔符

默认是\W+,即非字词的符号作为分隔符

Language Analyzer 提供了30+种常见语言的分词器

自定义分词

Character Filters

在Tokenizer之前对原始文本进行处理,比如增加、删除或替换字符等

自带的如下:

1.HTML Strip Character Filter:去除HTML标签和转换HTML实体

2.Mapping Character Filter:进行字符替换操作

3.Pattern Replace Character Filter:进行正则匹配替换

会影响后续tokenizer解析的position和offset信息

Tokenizers

将原始文本按照一定规则切分为单词(term or token)

自带的如下:

1.standard 按照单词进行分割

2.letter 按照非字符类进行分割

3.whitespace 按照空格进行分割

4.UAX URL Email 按照standard进行分割,但不会分割邮箱和URL

5.Ngram 和 Edge NGram 连词分割

6.Path Hierarchy 按照文件路径进行分割

Token Filters

  • 对于tokenizer输出的单词(term)进行增加、删除、修改等操作
  • 自带的如下:
    1.lowercase 将所有term转为小写
    2.stop 删除停用词
    3.Ngram 和 Edge NGram 连词分割
    4.Synonym 添加近义词的term

自定义分词

自定义分词需要在索引配置中设定 char_filter、tokenizer、filter、analyzer等

分词使用说明

分词会在如下两个时机使用:

创建或更新文档时(Index Time),会对相应的文档进行分词处理

查询时(Search Time),会对查询语句进行分词

  1. 查询时通过analyzer指定分词器

  2. 通过index mapping设置search_analyzer实现

  3. 一般不需要特别指定查询时分词器,直接使用索引分词器即可,否则会出现无法匹配的情况

  4. 分词使用建议

    1. 明确字段是否需要分词,不需要分词的字段就将type设置为keyword,可以节省空间和提高写性能
    2. 善用_analyze API,查看文档的分词结果

recovery

代表数据恢复或叫数据重新分布,es在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。

章三:ES的常规操作(具体的查询问题以及优化下一期展开)

官方文档:

www.elastic.co/guide/cn/el...(基于 Elasticsearch 2.x 版本,有些内容可能已经过时。)

索引写入流程图:

  1. 客户端选择一个node发送请求过去,这个node就是coordinating node (协调节点)

  2. coordinating node,对document进行路由,将请求转发给对应的node

  3. 实际上的node上的primary shard处理请求,然后将数据同步到replica node

  4. coordinating node,如果发现primary node和所有的replica node都搞定之后,就会返回请求到客户端

  5. ES 使用了一个内存缓冲区 Buffer,先把要写入的数据放进 buffer;同时将数据写入 translog 日志文件(其实是些os cache)。

  6. refresh:

    1. buffer数据满/1s定时器到期会将buffer写入操作系统segment file中,进入cache立马就能搜索到,所以说es是近实时(NRT,near real-time)的
  7. flush:

    1. tanslog超过指定大小/30min定时器到期会触发commit操作

      1. 将对应的cache刷到磁盘file,
      2. commit point写入磁盘,commit point里面包含对应的所有的segment file
  8. translog 默认5s把cache fsync到磁盘,所以es宕机会有最大5s窗口的丢失数据

索引查询流程图:

读取数据

  1. 客户端发送任何一个请求到任意一个node,成为coordinate node
  2. coordinate node 对document进行路由,将请求rr轮训转发到对应的node,在primary shard 以及所有的replica中随机选择一个,让读请求负载均衡,
  3. 接受请求的node,返回document给coordinate note
  4. coordinate node返回给客户端

搜索过程

  1. 客户端发送一个请求给coordinate node
  2. 协调节点将搜索的请求转发给所有的shard对应的primary shard 或replica shard
  3. query phase:每一个shard 将自己搜索的结果(其实也就是一些唯一标识),返回给协调节点,有协调节点进行数据的合并,排序,分页等操作,产出最后的结果
  4. fetch phase ,接着由协调节点,根据唯一标识去各个节点进行拉去数据,最总返回给客户端

创建索引

json 复制代码
PUT indexName1
{
    "mappings" : {
      "dynamic" : "strict",
      "properties" : {
        "advertiser_name" : {
          "type" : "object",
          "enabled" : false
        },
        "body" : {
          "type" : "text"
        },
        "cdn_url" : {
          "type" : "object",
          "enabled" : false
        },
        "created_at" : {
          "type" : "long"
        },
        "days" : {
          "type" : "short"
        },
        "domain" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword"
            }
          },
        },
        "heat_degree" : {
          "type" : "short"
        },
        "html_url" : {
          "type" : "object",
          "enabled" : false
        },
        "interaction" : {
          "type" : "long"
        },
        "message" : {
          "type" : "text",
        },
        "page_logo" : {
          "type" : "object",
          "enabled" : false
        },
        "sub_domain" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword"
            }
          },
        }
      }
    },
    "settings" : {
      "index" : {
        "refresh_interval" : "300s",
        "indexing" : {
          "slowlog" : {
            "level" : "info",
            "threshold" : {
              "index" : {
                "warn" : "200ms",
                "trace" : "20ms",
                "debug" : "50ms",
                "info" : "100ms"
              }
            },
            "source" : "1000"
          }
        },
        "unassigned" : {
          "node_left" : {
            "delayed_timeout" : "5m"
          }
        },
        "analysis" : {
          "analyzer" : {
            "zf_analyzer" : {
              "filter" : [
                "lowercase",
                "stop",
                "snowball"
              ],
              "char_filter" : [
                "my_mapping"
              ],
              "type" : "custom",
              "tokenizer" : "standard"
            }
          },
          "char_filter" : {
            "my_mapping" : {
              "type" : "mapping",
              "mappings" : [
                ".=> @",
                ":=> @"
              ]
            }
          }
        },
        "priority" : "100",
        "number_of_replicas" : "1",
        "routing" : {
          "allocation" : {
            "require" : {
              "box_type" : "hot"
            }
          }
        },
        "search" : {
          "slowlog" : {
            "level" : "info",
            "threshold" : {
              "fetch" : {
                "warn" : "200ms",
                "trace" : "50ms",
                "debug" : "80ms",
                "info" : "100ms"
              },
              "query" : {
                "warn" : "500ms",
                "trace" : "50ms",
                "debug" : "100ms",
                "info" : "200ms"
              }
            }
          }
        },
        "number_of_shards" : "12"
      }
    }
}

打开/关闭索引

bash 复制代码
POST indexName1/_close
POST indexName1,indexName2/_close

POST indexName1/_open
POST indexName1,indexName2/_open

设置索引刷新时间

bash 复制代码
PUT indexName1/_settings
{
  "refresh_interval": "90s"
}

查询个数

bash 复制代码
GET indexName1/_count
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "sales": {
            "gte": 1000
          }
        }
      }
    }
  }
}

查询所有

bash 复制代码
//查询所有
GET /indexName1/_doc/_search
{
  "query":{
    "match_all":{}
  }
}

查询不区分大小写

bash 复制代码
//新建索引,字段不区分大小写
PUT indexName1
{
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "store_name": {
        "type": "keyword",
        "normalizer": "my_normalizer"
      },
      "store_id": {
        "type": "keyword"
      }
    }
  },
  "settings": {
    "index": {
      "search": {
        "slowlog": {
          "level": "info",
          "threshold": {
            "fetch": {
              "warn": "200ms",
              "trace": "50ms",
              "debug": "80ms",
              "info": "100ms"
            },
            "query": {
              "warn": "500ms",
              "trace": "50ms",
              "debug": "100ms",
              "info": "200ms"
            }
          }
        }
      },
      "refresh_interval": "10s",
      "indexing": {
        "slowlog": {
          "level": "info",
          "threshold": {
            "index": {
              "warn": "200ms",
              "trace": "20ms",
              "debug": "50ms",
              "info": "100ms"
            }
          },
          "source": "1000"
        }
      },
      "analysis": {
        "normalizer": {
          "my_normalizer": {
            "filter": "lowercase",
            "type": "custom"
          }
        }
      },
      "number_of_shards": "1",
      "unassigned": {
        "node_left": {
          "delayed_timeout": "5m"
        }
      },
      "number_of_replicas": "0"
    }
  }
}



//插入数据


PUT /indexName1/_doc/1
{
    "store_name":"HighSteel",
    "store_id":"com.google.highsteel"
    
}

PUT /indexName1/_doc/2
{
    "store_name":"KKKKKKOOOOO",
    "store_id":"com.google.highsteel"
    
}

//查询所有
GET /indexName1/_doc/_search
{
  "query":{
    "match_all":{}
  }
}

//模糊查询,不区分大小写
GET indexName1/_search
{
  "query":{
        "wildcard" : {
            "store_name": "*KKkkkKOooOO*"
        }
  }
}

章四:ES的优化项

设置分片数

bash 复制代码
#创建索引时指定,指定后,不能进行修改
"number_of_shards" : "12"

副本数

bash 复制代码
#设置副本数
PUT /indexName1/_settings
{
    "number_of_replicas": 1
}

内存

内存不用超过节点内存的一半,另外一半给Lucene使用。

冷热节点

bash 复制代码
PUT indexName1/_settings
{ 
	  "index.routing.allocation.require.box_type": "warm"
}

索引模版,索引生命周期

  • 索引生命周期管理策略:help.aliyun.com/document_de...

  • 可以配置按大小或者按时间(天)来生成新的索引,索引名称,自动+1。旧索引自动切换为冷索引。新生成的索引为热索引。

    • eg:index-01,index-02

查询索引信息

返回的统计信息和 节点统计 的输出很相似:search 、 fetch 、 get 、 index 、 bulk 、 segment counts 等等。

bash 复制代码
GET indexName1/_stats

segment合并

ini 复制代码
#查看集群上索引的删除条目的情况
GET _cat/indices/?v&s=docs.deleted:desc

#对某个索引,进行段合并
POST indexName1/_forcemerge?max_num_segments=1&only_expunge_deletes=true

#查看正在进行的forcemerge任务
GET _tasks?detailed=true&actions=*forcemerge&human

#取消任务
POST _tasks/J2FOnHkDSIGJ43AaTDniJQ:5929987436/_cancel

慢写入,慢查询

慢查询

ini 复制代码
#查询条件
search_time_ms:[5000 TO *]

慢写入

ini 复制代码
#查询条件
index_time_ms:[200 TO *]

章五:ES的注意事项

索引字段不能修改,只能新增

segment段合并,forcemerge,磁盘空间问题考虑。

fielddata占用内存空间问题

ini 复制代码
indices.fielddata.memory_size_in_bytes

_cat/segments/indexname?v&h=shard,segment,size,size.memory

#fielddata具体字段占用
GET _cat/fielddata?v&s=size:desc

#fielddata具体索引占用
GET _cat/indices?v&h=index,fielddata.memory_size&s=fielddata.memory_size:desc

flush(sync_interval)

  1. tanslog超过指定大小/30min定时器到期会触发commit操作

    1. 将对应的cache刷到磁盘file,
    2. commit point写入磁盘,commit point里面包含对应的所有的segment file

refresh(refresh_interval)

  • buffer数据满/1s定时器到期会将buffer写入操作系统segment file中,进入cache立马就能搜索到,所以说es是近实时(NRT,near real-time)的
  • 使用建议:对于实时性要求不高且想优化写入的业务场景,建议根据业务实际调大刷新频率。

修改索引非readonly

bash 复制代码
PUT /indexName1/_settings
{
    "index.blocks.read_only_allow_delete": null
}

热索引、冷索引(自己定义,未用索引模版+索引生命周期管理)

冷热切换索引

bash 复制代码
PUT indexName1*/_settings
{ 
	  "index.routing.allocation.require.box_type": "warm"
}
相关推荐
不是笨小孩i2 小时前
Git常用指令
大数据·git·elasticsearch
+码农快讯+5 小时前
Git入门学习(1)
git·学习·elasticsearch
码爸9 小时前
java 执行es中的sql
java·sql·elasticsearch
学习使我快乐——玉祥12 小时前
es查询语法
大数据·elasticsearch·搜索引擎
徐*红14 小时前
Elasticsearch 8.+ 版本查询方式
大数据·elasticsearch
码爸15 小时前
flink 例子(scala)
大数据·elasticsearch·flink·scala
txtsteve15 小时前
es由一个集群迁移到另外一个集群es的数据迁移
大数据·elasticsearch·搜索引擎
工作中的程序员15 小时前
ES 索引或索引模板
大数据·数据库·elasticsearch
Lill_bin1 天前
深入理解ElasticSearch集群:架构、高可用性与数据一致性
大数据·分布式·elasticsearch·搜索引擎·zookeeper·架构·全文检索
RwTo1 天前
Elasticsearch 聚合搜索
大数据·elasticsearch·搜索引擎·全文检索