ES的基础概念
简述
-
Elasticsearch是Elastic Stack核心的分布式搜索和分析引擎,基于Java编程语言构建,可以在主流硬件平台上运行
-
在存储、计算和分析方面,Elasticsearch运行执行和并多种类型的搜索
-
适用于不断涌现的各种新用例,并在充分保障集群安全的前提下具有极高的可用性及容错性
-
支持通过自定义打分、自定义排序、高亮等机制召回期望的结果数据,通过跨机房/跨机架感知、异地容灾等策略,为用户提供高可用、高并发、低延时、用户体验好的搜索服务
-
ES是实时的分布式搜索分析引擎,内部使用Lucene做索引与搜索
-
特性
- 索引结构
- 支持分片(shard)
- 动态更新索引
- 近实时搜索
- 段合并
特点
- 使用简单的RESTful API、天然兼容多语言开发
- 支持水平横向拓展节点,通过增加节点来实现负载均衡及增强集群可靠性
- 面向文档,不使用"表"来存储数据,而使用"文档"来存储数据
- 无模式,无需定义好字段类型、长度等,可以直接导入文档数据
- 近实时存储,使每个字段都被索引且可用于搜索
- 响应块,海量数据下实现秒级响应速度
- 易扩展,支持处理PB级的结构化或非结构化数据
- 多租户,支持多个业务公用Elasticsearch服务,并确保各个业务间数据的隔离性
- 支持多种编程语言,包含但不限于Java、Python、C#、PHP、Python、Ruby等
同类产品(竞品)
- Apache Solr
- Splunk
- OpenSearch
- Doris
- ClickHouse
基础概念
集群
- 是一组Elasticsearch节点的集合
- 节点通过唯一的"集群名称"来进行标识属于该集群
节点
- 是指一个Elasticsearch实例
- 节点可以部署在物理机或者虚拟机上
- 每个节点都有唯一的标识名称,在部署多节点集群环境的时候注意不要写错节点名称
索引(Index)
简述
- 是用于存储和管理相关数据的逻辑容器
- 索引可以看作数据库中的一个表,它包含了一组具有相似结构的文档
- 在ES中,数据以JSON格式的文档存储在索引内
- 每个索引具有唯一的名称,以便在执行搜索、更新和删除操作时进行引用
- 索引的名称由用户自定义,但必须全部小写
- 通过将数据划分为不同的索引,用户可以更有效的管理和查询相关数据
索引命名规范
- 只能使用小写字母,不能使用大写字母
- 不能使用特殊字符
- 不能以"-“、”_“、”+"作为开始字符
- 不能命名为".“或者”…"
- 不能超过255字符
- 不建议使用中文命名
创建索引
命令
PUT 索引名称
索引配置
- settings(设置)
- 静态设置(static index settings),只允许在创建索引时或者针对已关闭的索引进行设置
- 动态设置(dynamic index setting),可以借助更新设置(update settings)的方式进行动态更新,更新后立即生效
- 常见配置项,参考官方文档
- mappings(映射)
- 参考Mapping篇的配置
- aliases(别名)
- 一个索引可以创建多个别名
- 一个别名也可以指向多个索引
获取索引
命令
- GET 索引名称
- GET 索引名称/_search,获取索引下的数据信息
删除索引
命令
- DELETE 索引名称,物理删除,能立马释放磁盘空间
修改索引
- 为已有索引添加别名
- 动态更新索引settings部分
- 动态更新索引的部分mapping字段信息
索引模板
简述
指在创建新索引时,索引名称匹配预先设计的索引模板时,按照索引模板指定的规则进行创建索引
分类
普通模板
组件模板
- 核心组件
- component_mapping_template
- component_settings_template
- 使用组件模板可以更细粒度的操作,在变更时范围更小
动态模板
- 动态模板是索引模板内部的⼀个更细致的概念,它特别关注于处理未知或动态字段的映射规则
- 在实际应⽤中,数据源可能包含事先⽆法预⻅的所有字段,动态模板可以指定如何根据字段名或字段内容动态地确定其映射类型和属性
命令
-
创建
PUT _index_template/{模板名称}
-
删除
-
修改
DELETE _index_template/{模板名称}
-
查询
GET _index_template/{模板名称}
reindex
简述
- 该命令是将文档从一个索引复制到另一个索引,即迁移文档
- 迁移时的源索引必须存在,目标索引不存在时,会自动创建索引
同集群索引之间全量数据迁移
-
源索引设置检索条件,通过增加query查询条件
-
基于script脚本的索引迁移,增加script脚本块来实现
-
基于预处理管道的数据迁移,通过指定pipeline管道处理器来实现
-
示例
POST _reindex { "conflicts" : "proceed", "sources": { "index": "{source_index_name}" }, "dest" : { "index" : "{target_index_name}" } }
跨集群数据迁移
- 配置白名单,在elasticsearch.yml配置文件中设置
- 迁移命令与同集群的实现方式一致,但需要增加remote字段来指定源索引所在服务器地址
取消reindex任务
-
查看reindex任务列表
GET _tasks?datailed=true&actions=*reindex
-
通过任务ID查看reindex任务详细
GET /_tasks/{task_id}
-
取消reindex任务
POST _tasks/{task_id}/_cancel
业务不掉线的数据迁移
该方式也被称为业务零掉线数据迁移
- 通过reindex+别名来实现
- 以别名的形式从后台向前台提供服务
- 同时,新建索引并设置好Mapping
- 然后进行数据reindex
业务场景
- 多索引检索可以通过别名来实现
- POST index_1,index_2/search
- POST index_*/search
- 使用别名关联已有索引
常见问题
-
使用别名默认不允许批量插入操作
-
通过索引别名查找实际索引名称
GET _cat/aliases?v
-
使用别名和基于索引的检索效率一样吗
- 索引别名只是物理索引的软链接的名称而已,在相同的检索条件下的检索效率是一致的
-
如果想更新映射,可以通过更新模板来实现吗
- 不可以
- 一旦建立了映射,除几个特定的类型,其它类型都不支持更新,除非进行reindex操作
- 一旦建立了索引,对索引模板的更新将不会影响已经存在的索引
- 更新模板仅适用于新创建的索引
- 动态模板更新仅会影响索引中的新字段
分片(Shard)
- 分片(shard)是将索引切分为多个较小的部分的过程,这些被切分出来的每个部分称为一个分片
- 分片的主要目的是提高搜索性能和容错能力,因为单个物理机器的性能是有瓶颈的,直接往物理机上存数据,存储数量有上限
- 通过分片,可以将数据分布到集群的所有节点,从而承载更大的数据量
- 每个分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力
- 每个索引默认有一个主分片一个副本分片,创建索引时可以指定分片数量,但一旦确定就不能修改
副本(Replica)
- ES还提供了副本分片(replica),它是分片的备份,用于防止数据丢失
- 当主分片出现物理故障时,副本分片可以顶上去,负责数据的检索等只读请求
- 副本分片的数量是可以动态修改的
文档(Document)
简述
- 文档是指ES中索引的JSON对象
- 文档中的数据由键值对构成
- 每个文档在索引中都有唯一的ID
新增文档
-
单条写入
#如果索引不存在,会自动创建索引 PUT {host}/{index_name}/_doc/{number} POST {host}/{index_name}/_doc/
-
批量写入
POST {host}/{index_name}/_bulk
删除文档
-
单条删除
DELETE {host}/{index_name}/_doc/{number}
-
批量删除
#通过查询条件进行删除文档 POST {host}/{index_name}/_delete_by_query
修改文档
-
配置项
- _source字段必须设置为true,如果该值为false,执行update会报错,默认值为true;
-
在原有字段上新增字段
POST {host}/{index_name}/_update/{number} { "doc" : { "{field_name}" : {field_value} } }
-
在原有字段的基础上修改字段的值
-
存在这个字段就更新该字段的值,不存在就插入该字段并赋值
更新文档
- 全部文档更新,即_source值覆盖更新
- 批量文档更新,通过查询条件批量更新_source字段信息
- 基于ingest预处理管道更新。如果使用script和ingest两种方式都能解决,建议使用ingest,该方式便于理解和调试
取消更新
- 处理流程
- 获取该更新任务的任务ID
- 通过该任务ID查看任务详细信息
- 通过取消任务达到取消更新的目的
字段(Field)
简述
- 字段是ES中最小的单个数据单元,类似于关系型数据库表中的字段
- 可以根据不同的业务定义不同的数据类型和数据长度
常见配置
- doc_values
- 是否开启正排索引,即doc_values,默认为开启的
- 是一种列式存储结构,是在索引时创建的
- 当字段索引时,ES为了能够快速检索,会把字段的值加入倒排索引中,同时它也会存储该字段的 doc_values
- fielddata
- 是否开启基于内存中的正排索引(仅限于text类型可使用该属性),默认情况下被禁止
- 是基于内存的数据结构、在查询时创建
- 是指字段在进行聚合、排序时,ES从磁盘上读取每个字段的完整倒排索引,通过反转词项与文档之间的关系(正排索引),并将结果在JVM堆的内存中构建出来,其被称为fielddata
- _source
- 是否存储该文档的原始JSON,默认情况下是开启的
- 该字段本身未构建索引,即不可被搜索,如果存储了该字段,仅在查询时可以将其返回
- 如果禁用了该字段后,Update、update_by_query和reindex API,以及高亮操作将不可用。因此在设计时还需谨慎选择
- store
- 是否存储该字段,默认是开启的
- 如果禁用,则在查询时,不能指定该字段进行返回,但可以从_source中查询获取到该字段的值
- null_value
- 设置空值的默认值,设置默认值时应该与该字段得类型相匹配
- 如果字段的值为null(空数组或者null值得数组),将视为该字段没有值,意味着该字段被视为丢弃,也就是不能被索引或搜索
- text类型不支持null_value
映射(Mapping)
简述
- 自动或手动为 index 中的 _doc 建立的一种数据结构和相关配置,简称为 mapping映射
- 就是给索引中的某些字段指定一下数据类型
- 就是给索引中的字段关联数据结构的机制。它指定了存储在索引中的字段的字段类型和相关属性
- 映射创建后,已经定义了的字段不能被更新,除非通过reindex操作来进行更新,不包括以下特例
- Object对象可以添加新的属性
- 在已经存在的字段里面可以添加fields,以构成一个字段多种类型
- ignore_above是可以更新的
设计映射时需要考虑的事项
- 字段名称
- 字段类型
- 是否需要分词
- 是否需要索引
- 是否需要存储
- 是否需要多字段类型
数据结构
元字段
- 标识
- _index,文档所属的索引
- _id,文档的ID
- 文档源
- _source,文档正文的原始JSON对象
- _size,source字段的大小(以字节为单位)
- 索引
- _field_names,给定文档中包含非空值的所有字段
- _ignored,表示被忽略的字段
- 路由
- _routing,将给定文档路由到指定的分片
- 其它
- _meta,特定的元数据,用于给索引加必要的注释信息
- _tier,文档所属索引的数据层级别
数据类型
基本数据类型
- binary,编码为Base64字符的二进制类型
- boolean,布尔类型
- keyword,支持精准匹配的类型,如:keyword、const_keyword、wildcard
- number,数值类型,如:integer、long、float、double
- data,日期类型,如:data、data_nanos
- alias,别名类型,区别于索引别名,此处是指字段的别名
- text,全文检索类型
复杂数据类型
- Array,数组类型。数组类型要求一个组内的数据类型一致
- Object,JSON对象类型
- Nested,嵌套数据类型
- Join,父子关联类型
- Flattened,将原来一个复杂的Object或者Nested嵌套多字段类型统一映射为扁平的单字段类型
专用数据类型
- 坐标数据类型,用于保存地理位置详细信息,例如表示经纬度信息的geo_point数据类型
- IP类型,表示IPV4或者IPV6地址
- completion类型,是ES中的一种专用字段类型,旨在实现高效的自动补全功能。该类型用于快速查找和返回符合查询条件的建议,从而实现如搜索框自动补全等交互性强的功能,极大提升用户体验
多字段类型
- multi_fields
- 该类型允许我们在同一个字段上定义多个子字段,每个子字段可以有不同的数据类型
- 一个常见的用例是在对一个字段进行全文搜索的同时,保存原始字段的值,以便进行精确匹配或者排序
- 例如,我们可以定义一个"fullname"字段,其中包含一个"text"类型的子字段用于全文搜索,以及一个"keyword"类型的子字段用于精确匹配和排序
映射方式
- 动态映射
- ES会自动的根据存入的数据判断数据类型,这种自动分析就叫动态映射,便于快速开发,但不严谨
- 支持动态检测的数据类型包括boolean、float、long、Object、Array、data。其它类型均会被处理为text
- 静态映射
- 该方式也称为显示映射
- 需要明确文档中各个字段的类型以及相关属性设置
分词
简述
分词是将文本转换成一系列单词的过程,也可以叫做文本分析,在ES中被称为Analysis
为什么需要分词
- 语义维度
- 单个得字或字符往往很难描述所表达得语义,而通过词(多个字或多个字符)来描述语义更加明确
- 在语义维度通过词和词组进行分词,便于进行语义的分析
- 存储维度
- 通过词或词组进行索引,所需要的存储空间会更小
- 时间维度
- 通过词或词组建立倒排索引,使得我们能以O(1)的时间复杂度进行查找目标文档
分词发生的阶段
- 写入数据阶段
- 创建文档和更新文档(Index time)时,对相应的文档进行分词处理
- 执行检索阶段
- 查询时,会对查询语句进行分词
常见分词器
- Standard 标准分词器
- Standard是ES的默认分词器,具备按词切分、支持多语言;小写处理等特点
- Simple Analyzer 简单分词器
- 简单分词器有着按照非字母切分、小写处理的特点
- Whitespace Analyzer 空格分词器
- 这个分词器很好理解,就是将文本根据空格进行分词
- Stop Analyzer 语气助词分词器
- Stop Word指的是语气助词等修饰性的词语,比如the、an、的、这等等
- Keyword Analyzer 关键字分词器
- Keyword分词器是一个比较特殊的存在,它不会对输入的句子进行分词,而是直接将输入作为一个单词输出,也就是说会将输入看成是一个整体
- Pattern Analyzer 正则分词器
- 正则分词器很好理解,就是根据我们提供的正则表达式来进行分词,默认会按照 \W+ ,即非字词的符号作为分隔符
- Langueage Analyzer 多语言分词器
- 多语言分词器提供了30+种常见语言的分词器
中文分词器
IK分词器
- IK分词器可以实现中英文单词的切分,支持ik_smart、ik_maxword等模式,可以自定义词库和支持热更新分词词典
- IK自带词典并不完备,建议自己结合业务添加所属业务的词典
- IK采用动态添加词典的方式,建议修改IK分词插件源码,与MySQL数据库结合,以灵活支持动态词典的更新
jieba 分词器
- jieba分词器是Python中最流行的分词系统,支持分词和词性标注,支持繁体分词和自定义词典、并行分词等
Hanlp 分词器
- 由一系列模型与算法组成的Java工具包,目标是普及自然语言处理在生产环境中的应用
THULAC 分词器
- THU Lexical Analyzer for Chinese,由清华大学自然语言处理与社会人文计算实验室研制推出的一套中文词法分析工具包,具有中文分词和词性标注功能
自定义分词器
当ES提供的分词器不能满足我们的分词需要的时候,我们可以通过自定义分词器来满足我们的工作需要
自定义分词的三个阶段
Character Filter
- 简述
- 字符过滤器,将原始文本作为字符流接收,并通过添加、删除或更改字符来转换字符流
- 最先到达分词器的组件是Character Filter,他会在 Tokenizer 之前对文本进行处理,比如增加、删除、或替换字符等
- 分类
- HTML Strip Character Filter,用于处理HTML文本中的元素
- Mapping Character Filter,用于替换指定的字符
- Pattern Replace Character Filter,可以基于正则表达式替换指定的字符
Tokenizer
- 简述
- Tokenizer会将原始文本按照一定的规则切分为单词
- 分类
- Standard Tokenizer,标准分词器
- Letter Tokenizer,字母分词器
- Lowercase Tokenizer,小写转化分词器
Token Filter
- 简述
- 在分词后,对字符流进一步处理,例如:转换大小写、删除(去除停用词)和新增(添加同义词)
- 自定义分词的API
数据预处理
简述
- 实际文档写入索引之前,对文档进行的预先处理(转换、富化)
- 默认情况下,所有节点都默认启动ingest角色,因此任何节点都可以完成数据的预处理任务
预处理器分类
- append
- convert
- CSV
- data
- drop
- enrich
- fingerprint
- foreach
- lowercase
- remove
- rename
- script
- set
- sort
- split
- trim
常见问题
- ingest角色的必要性
- 当集群数据量级够大且预处理任务繁重时,建议拆分节点角色,并与独立主节点、独立协调节点一样,设置独立专用的ingest节点
- 何时指定预处理管道
- 创建索引、创建模板、更新索引、reindex以及update_by_query环节都可以指定预处理管道
script,脚本
简述
- 一般来说应避免使用脚本,主要是使用脚本会导致性能低,而基础的CRUD也不需要使用脚本
- 如果需要使用脚本,则应优先选择Painless脚本和expressions引擎
- 常用于解决复杂业务问题,如:自定义评分、自定义文本相关度、自定义过滤、自定义聚合分析、自定义更新、自定义reindex等
- 各版本支持的脚本
- MVEL脚本。ES1.4以下
- Groovy脚本。ES5.0以下
- Painless脚本。ES5.0以上
Painless脚本
简述
- Painless是一种简单、安全的脚本语言,专为与ES一起使用而设计
- 是新版本ES的默认脚本语言,可以安全的用于内联和存储脚本
特点
- 性能较优,运行速度比其它方案都要块
- 安全性强,使用白名单来限制函数与字段的访问,避免了可能的安全隐患
- 可选输入,变量和参数可以使用显示类型或动态def类型
- 上手简单,拓展了Java基本语法,并兼容groove风格的脚本语言特性
脚本标准模板
-
模块
"script" : { "lang" : "", "source" : "", "params" : {} }
-
lang,代表language,即脚本语言,默认Painless
-
source,脚本的核心部分
-
params,传递给脚本使用的变量参数
使用场景
- 自定义字段
- 自定义评分
- 自定义更新
- 自定义reindex
元数据
简述
- 每个文档都有于其相关的元数据,比如_index、_type和_id
- 当创建映射类型时,可以定制其中一些元数据字段,完整的介绍参考官方手册
ES中存储的数据种类
- state,元数据信息
- index,Luncene生成的索引文件
- translog,事务日志文件
元数据信息分类
- nodes/0/_state/*.st
- 集群层面元数据
- nodes/0/indices/{index_uuid}/_state/*.st
- 索引层面元信息
- nodes/0/indices/{index_uuid}/0/_state/*.st
- 分片层面元信息
ES中的数据结构
- MetaData(集群层)
- clusterUUID
- settings
- templates
- IndexMetaData(索引层)
- numberOfShards
- mappings
- ShardStateMetaData(分片层)
- version
- indexUUID
- primary
元数据的持久化
- 只有具备Master资格的节点和数据节点可以持久化集群状态
- 当收到主节点发布的集群状态时,节点判断元信息是否发生变化,如果发生变化,则将其持久化到磁盘中
元数据的恢复
- 当集群完全重启时,各个节点保存的元数据信息可能不同,此时需要选择正确的元数据作为权威元数据
- gateway的recovery负责找到正确的元数据,应用到集群
元数据恢复流程分析
- Master选举成功后,判断其持有的集群状态中是否存在STATEj_NOT_RECOVERED_BLOCK
- 不存在,则说明元数据已经恢复,跳过gateway恢复过程
- 存在,则进行等待
- Master从各个节点主动获取元数据信息
- 从获取的元数据信息中选择版本号最大的作为最新元数据,包括集群级、索引级
- 两者确认之后,调用allocation模块的reroute,对未分配的分片执行分配,主分片分配过程中会异步获取各个shard级别元数据,默认超时为13秒
集群级、索引级的权威元数据选择
- 收集所有节点的元信息
- 通过版本号选取权威元数据
Lucene文件
简述
- Lucene中基本的概念包括index、document、field和term
- 一个index包含一系列的document
- 一次对Lucene索引的搜索需要搜索全部分段
数据层级关系
document
fields
field
terms
term
bytes
分段
- Lucene索引可能由多个分段(segment)组成,每个分段是完全独立的索引,可以独立执行搜索
- 产生新的分段的场景
- refresh操作产生一个Lucene分段。为新添加的documents创建新的分段
- 已存在的分段合并,产生新分段
文件命名规则
- 属于一个分段的所有文件都具有相同的名称和不同的拓展名
客户端API
- REST接口
- 使用9200端口通信
- 采用JSON over Http方式
- Java REST API接口
- 是对原生的REST接口的封装
- Java API接口
- 使用9300端口通信,数据序列化为二进制