type
status
date
slug
summary
tags
category
icon
password
一、初识elasticsearch
什么是ES索引
- elasticsearch是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。
- elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域。
- elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。

倒排索引
- 传统数据库(如MySQL)采用正向索引,例如给下表(tb_goods)中的id创建索引:基于文档id(或者其他字段)创建索引。查询词条时必须先找到文档,而后判断是否包含词条

- elasticsearch采用倒排索引:对文档内容分词,对词条创建索引,并记录词条所在文档的信息。查询时先根据词条查询到文档id,而后获取到文档
- 文档(document):每条数据就是一个文档
- 词条(term):文档按照语义分成的词语(对文档中的内容分词,得到的词语就是词条)

ES与Mysql的对比
- 文档:elasticsearch是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中。

- 索引(index):相同类型的文档的集合

- 映射(mapping):索引中文档的字段约束信息,类似表的结构约束
- 对比
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:擅长海量数据的搜索、分析、计算

安装
- 安装ES,推荐docker安装
- 安装kibana
- 安装IK分词器
- es在创建倒排索引时需要对文档分词;在搜索时,需要对用户输入内容分词。但默认的分词规则对中文处理并不友好。
- 发送post请求,在请求参数中使用json风格:
- analyzer:分词器类型,这里是默认的standard分词器
- text:要分词的内容
- 处理中文分词,一般会使用IK分词器.ik分词器包含两种模式:程序员
- ik_smart:最少切分,粗粒度:程序员
- ik_max_word:最细切分,细粒度,占用内容大:程序、员、程序员
- ik分词器-拓展词库
- 要拓展ik分词器的词库,只需要修改一个ik分词器目录中的config目录中的IkAnalyzer.cfg.xml文件,然后在名为ext.dic的文件中,添加想要拓展的词语即可:会将文件中的词语划分为一个词条
- ik分词器-停用词库
- 要禁用某些敏感词条,只需要修改一个ik分词器目录中的config目录中的IkAnalyzer.cfg.xml文件,然后在名为stopword.dic的文件中,添加想要拓展的词语即可,停用词不会作为词条:
二、操作索引库
Mapping属性
- mapping是对索引库中文档的约束,常见的mapping属性包括:
- type:字段数据类型,常见的简单类型有:
- 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
- 数值:long、integer、short、byte、double、float、
- 布尔:boolean
- 日期:date
- 对象:object
- ES中支持两种地理坐标数据类型:
- geo_point:由纬度(latitude)和经度(longitude)确定的一个点。例如:"32.8752345, 120.2981576"
- geo_shape:有多个geo_point组成的复杂几何图形。例如一条直线,"LINESTRING (-77.03653 38.897676, -77.009051 38.889939)"
- index:是否创建索引,默认为true
- analyzer:使用哪种分词器
- properties:该字段的子字段
- copy_to
- 根据多个字段实现搜索,而且要求性能比较好,字段支持字段拷贝可以使用copy_to属性将当前字段拷贝到指定字段。示例:
- 优势
- 灵活的字段组合: 使用
copy_to
可以根据需要将不同字段的值组合到一个目标字段中。这对于在搜索中跨多个字段执行查询或聚合操作很有用。 - 减少索引的复杂性: 在某些情况下,复杂的索引映射可能导致性能下降或复杂性增加。通过使用
copy_to
,你可以创建一个包含所有必要信息的单一字段,而不必在查询时跨多个字段进行操作。 - 支持全文搜索: 如果你在
copy_to
字段上使用了全文搜索的分析器(如standard
或text
),你可以执行全文搜索,而不仅仅是精确匹配。这对于包含文本内容的字段非常有用。 - 提高查询性能: 当需要执行多个字段的查询时,
copy_to
可以加速查询操作,因为你只需要搜索一个字段而不是多个字段。 - 聚合和分析: 通过将多个字段的值复制到一个字段中,你可以更容易地执行聚合和分析操作,因为你只需考虑一个字段。
- 劣势
- 需要注意的是,使用
copy_to
也可能会导致索引大小增加,因为它会在一个目标字段中存储多个字段的值。因此,在使用时要权衡索引大小和查询性能之间的需求。 - 示例
假设你有一个索引,其中存储了产品信息,包括产品名称 (
product_name
) 和产品描述 (product_description
),并你想要在一个单独的字段 all_text
中保存这两个字段的值以便进行全文搜索和聚合操作。首先,创建索引映射:
当你索引文档时,
product_name
和 product_description
字段的值都会被复制到 all_text
字段中:你可以执行全文搜索或聚合操作,只需考虑
all_text
字段这将会返回包含 "Elasticsearch" 关键词的文档,无论这个关键词是出现在
product_name
还是 product_description
中。使用
copy_to
可以帮助你简化查询和聚合操作,将多个字段的值合并到一个字段中,提高了灵活性和性能。mapping要考虑的问题:
字段名、数据类型、是否参与搜索、是否分词、如果分词,分词器是什么?
索引库操作
增加索引库
ES中通过Restful请求操作索引库、文档。请求内容用DSL语句来表示。创建索引库和mapping的DSL语法如下:
查看索引库语法:
GET /索引库名
示例:GET /heima
删除索引库的语法:
DELETE /索引库名
示例:DELETE /heima
修改索引库:
索引库和mapping一旦创建无法修改,但是可以添加新的字段,语法如下:
示例
在 Elasticsearch 中,一旦索引映射被创建,它的主要属性(如字段数据类型、分析器等)是不能被直接修改的。这是因为 Elasticsearch 使用 Lucene 索引引擎,它的设计原则之一是一旦索引被创建,其基本结构就不再可变。如果你尝试直接修改映射,通常需要创建一个新的索引,将数据重新索引到新的索引中,然后删除旧的索引。
然而,有一些映射属性是可以修改的,包括:
- 字段的索引选项: 你可以修改某个字段是否需要被索引、是否需要被存储、是否需要被分析等选项。这可以通过索引模板或索引设置来实现。
- 动态映射: 你可以通过索引设置来控制是否允许动态映射,以便 Elasticsearch 可以根据新字段的动态添加自动创建映射。
- 字段别名(Alias): 你可以创建和修改字段别名,以提供字段名称的向后兼容性和简化查询。
- 更新文档: 当你索引新文档时,你可以为文档的已有字段添加新的属性,但这只适用于新增文档的情况。
总之,虽然 Elasticsearch 允许一些映射属性的修改,但对于主要的映射结构修改来说,通常需要创建一个新的索引,并重新索引数据。因此,在设计索引映射时,需要仔细考虑数据模型和索引需求,以避免后续需要频繁修改映射的情况。对于已经存在的索引,如果需要更改主要映射属性,通常需要进行数据迁移的复杂操作。
文档操作
增加文档
新增文档的DSL语法如下:
示例
查看文档
GET /索引库名/_doc/文档id (get请求)
示例:GET /heima/_doc/1
删除文档
DELETE /索引库名/_doc/文档id (delete请求)
示例:DELETE /heima/_doc/1
修改文档
方式一:全量修改,会删除旧文档,添加新文档
示例
既能做修改又能做新增,如果id存在就删除插入(修改),如果id不存在就直接插入新增
方式二:增量修改,修改指定字段值
示例
动态映射Dynamic Mapping
动态映射(Dynamic Mapping):如果你希望允许动态添加新字段,你可以启用动态映射。默认开启。
当我们向ES中插入文档时,如果文档中字段没有对应的mapping,ES会帮助我们字段设置mapping,规则如下:
JSON类型
|
Elasticsearch类型
|
字符串
|
•日期格式字符串:mapping为date类型
•普通字符串:mapping为text类型,并添加keyword类型子字段
|
布尔值
|
boolean
|
浮点数
|
float
|
整数
|
long
|
对象嵌套
|
object,并添加properties
|
数组
|
由数组中的第一个非空类型决定
|
空值
|
忽略
|
我们插入一条新的数据,其中包含4个没有mapping的字段:

如果默认mapping规则不符合你的需求,一定要自己设置字段mapping
写操作version会增加
三、DSL
查询分类和基本语法
查询分类:
Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:
- 查询所有:查询出所有数据,一般测试用。例如:match_all
- 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
- match_query
- multi_match_query
- 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
- ids
- range
- term
- 地理(geo)查询:根据经纬度查询。例如:
- geo_distance
- geo_bounding_box
- 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
- bool
- function_score
基本查询语法:
全文检索查询
全文检索查询,会对用户输入内容分词,常用于搜索框搜索:

match查询:全文检索查询的一种,根据一个内容去查询,会对用户输入内容分词,然后去倒排索引库检索,语法:
multi_match:与match查询类似,只不过允许同时查询多个字段,语法:

参与搜索的字段越多,搜索的效率越低,推荐使用上边的方式,下边的字段通过copy_to聚合到了all这个字段中
精确查询
精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的有:
- term:根据词条精确值查询
- range:根据值的范围查询
地理查询
根据经纬度查询。常见的使用场景包括:
- 携程:搜索我附近的酒店
- 滴滴:搜索我附近的出租车
- 微信:搜索我附近的人
实现方式:
- geo_bounding_box:查询geo_point值落在某个矩形范围的所有文档

- geo_distance:查询到指定中心点小于某个距离值的所有文档

复合查询
复合(compound)查询:复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑,例如:
- fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名。例如百度竞价
相关性算分:
当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。例如,我们搜索 "虹桥如家",结果如下:


ES中用的是BM25算法,不会受到词频的影响。
- TF-IDF:在elasticsearch5.0之前,会随着词频增加而越来越大
- BM25:在elasticsearch5.0之后,会随着词频增加而增大,但增长曲线会趋于水平

Function Score Query:
Boolean Query
布尔查询是一个或多个查询子句的组合。子查询的组合方式有:
- must:必须匹配每个子查询,类似“与”
- should:选择性匹配子查询,类似“或”
- must_not:必须不匹配,不参与算分,类似“非”
- filter:必须匹配,不参与算分
案例:
需求:搜索名字包含“如家”,价格不高于400,在坐标31.21,121.5周围10km范围内的酒店。
查询结果处理
排序
elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。
案例:
- 对酒店数据按照用户评价降序排序,评价相同的按照价格升序排序
- 评价是score字段,价格是price字段,按照顺序添加两个排序规则即可。
- 简化的写法

- 实现对酒店数据按照到你的位置坐标的距离升序排序
- 获取经纬度的方式:https://lbs.amap.com/demo/jsapi-v2/example/map/click-to-get-lnglat/
- 经纬度也可以用一个字符串表示“经,纬度” 或者拆分成两个字段

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

深度分页问题
ES是分布式的,所以会面临深度分页问题。例如按price排序后,获取from = 990,size =10的数据:
- 首先在每个数据分片上都排序并查询前1000条文档。
- 然后将所有节点的结果聚合,在内存中重新排序选出前1000条文档
- 最后从这1000条中,选取从990开始的10条文档

如果搜索页数过深,或者结果集(from + size)越大,对内存和CPU的消耗也越高。因此ES设定结果集查询的上限是10000
深度分页解决方案
针对深度分页,ES提供了两种解决方案,官方文档:
- search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
- scroll:原理将排序数据形成快照,保存在内存。官方已经不推荐使用。
总结
from + size:
- 优点:支持随机翻页
- 缺点:深度分页问题,默认查询上限(from + size)是10000
- 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
after search:
- 优点:没有查询上限(单次查询的size不超过10000)
- 缺点:只能向后逐页查询,不支持随机翻页
- 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
scroll:
- 优点:没有查询上限(单次查询的size不超过10000)
- 缺点:会有额外内存消耗,并且搜索结果是非实时的
- 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 after search方案。
高亮
高亮:就是在搜索结果中把搜索关键字突出显示。
原理:
搜索结果中的关键字用标签标记出来
在页面中给标签添加css样式
语法:

数据聚合
聚合的分类
聚合(aggregations)可以实现对文档数据的统计、分析、运算。聚合常见的有三类:
- 桶(Bucket)聚合:用来对文档做分组,并统计每组数量
- TermAggregation:按照文档字段值分组
- Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
- 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等,对文档数据做计算
- Avg:求平均值
- Max:求最大值
- Min:求最小值
- Stats:同时求max、min、avg、sum等
- 管道(pipeline)聚合:其它聚合的结果为基础做聚合
参与聚合的字段类型必须是:不能是文本可分词的。
- keyword
- 数值
- 日期
- 布尔
DLS聚合
aggs代表聚合,与query同级:限定聚合的的文档范围
聚合必须的三要素:
- 聚合名称
- 聚合类型
- 聚合字段
聚合可配置属性有:
- size:指定聚合结果数量
- order:指定聚合结果排序方式
- field:指定聚合字段
DSL实现Bucket聚合

默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序。
我们可以修改结果排序方式:
默认情况下,Bucket聚合是对索引库的所有文档做聚合,我们可以限定要聚合的文档范围,只要添加query条件即可:
DSL实现Metrics 聚合
我们要求获取每个品牌的用户评分的min、max、avg等值.
我们可以利用stats聚合,聚合是可以嵌套的:

自动补全
当用户在搜索框输入字符时,我们应该提示出与该字符有关的搜索项,如图:

使用拼音分词
要实现根据字母做补全,就必须对文档按照拼音分词。在GitHub上恰好有elasticsearch的拼音分词插件。地址:https://github.com/medcl/elasticsearch-analysis-pinyin
安装方式与IK分词器一样,分三步:
- 解压
- 传到虚拟机中,elasticsearch的plugin目录
- 重启elasticsearch
- 测试

自定义分词器


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

我们可以在创建索引库时,通过settings来配置自定义的analyzer(分词器):

拼音分词器适合在创建倒排索引的时候使用,但不能在搜索的时候使用。如果使用会有问题:

因此字段在创建倒排索引时应该用my_analyzer分词器;字段在搜索时应该使用ik_smart分词器;
completion suggester查询
elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:
- 参与补全查询的字段必须是completion类型。
- 字段的内容一般是用来补全的多个词条形成的数组。

查询语法如下:
四、RestClient
RestClient:ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址:https://www.elastic.co/guide/en/elasticsearch/client/index.html
初始化
- 引入es的RestHighLevelClient依赖:
- 因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:
- 初始化RestHighLevelClient:
索引库操作
索引库操作的基本步骤:
- 初始化RestHighLevelClient
- 创建XxxIndexRequest。XXX是Create、Get、Delete
- 准备DSL(Create时需要)
- 发送请求。调用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete
- 创建索引库

- 删除索引库、判断索引库存在
- 修改索引库
文档操作
文档操作的基本步骤:
- 初始化RestHighLevelClient
- 创建XxxRequest。XXX是Index、Get、Update、Delete
- 准备参数(Index和Update时需要)
- 发送请求。调用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete
- 解析结果(Get时需要)
- 新增文档


- 查询文档


- 更新文档
修改文档数据有两种方式:
方式一:全量更新。再次写入id一样的文档,就会删除旧文档,添加新文档
方式二:局部更新。只更新部分字段,我们演示方式二


- 删除文档


- 批量导入文档
利用JavaRestClient中的Bulk批处理,实现批量新增文档

DSL操作
RestAPI中其中构建DSL是通过HighLevelRestClient中的resource()来实现的,其中包含了查询、排序、分页、高亮等所有功能:

RestAPI中其中构建查询条件的核心部分是由一个名为QueryBuilders的工具类提供的,其中包含了各种查询方法:

- 所有搜索DSL的构建,记住一个API:SearchRequest的source()方法。
- 高亮结果解析是参考JSON结果,逐层解析
查询的基本步骤是:
- 创建SearchRequest对象
- 准备Request.source(),也就是DSL。
- QueryBuilders来构建查询条件。要构建查询条件,只要记住一个类:QueryBuilders
- 传入Request.source() 的 query() 方法
- 发送请求,得到结果
- 解析结果(参考JSON结果,从外到内,逐层解析)
match_all全匹配
查询

解析

match全文检索
全文检索的match和multi_match查询与match_all的API基本一致。差别是查询条件,也就是query的部分。同样是利用QueryBuilders提供的方法:

精确匹配
精确查询常见的有term查询和range查询,同样利用QueryBuilders实现:

复合查询
精确查询常见的有term查询和range查询,同样利用QueryBuilders实现:


明确多个条件之间的逻辑关系,同时要对参数做好过滤,进行非空判断。
排序和分页
搜索结果的排序和分页是与query同级的参数,对应的API如下:


高亮
高亮API包括请求DSL构建和结果解析两部分。我们先看请求的DSL构建:


聚合


多个聚合条件,多个聚合结果

自动补全

五、数据同步
elasticsearch中的数据来自于mysql数据库,因此mysql数据发生改变时,elasticsearch也必须跟着改变,这个就是elasticsearch与mysql之间的数据同步。
问题:
在同一个服务中我们可以实现在操作mysql的时候更新es,但是在微服务中,例如负责酒店管理(操作mysql )的业务与负责酒店搜索(操作elasticsearch )的业务可能在两个不同的微服务上,数据同步该如何实现呢?
同步的方式



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

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

- 优点:完全解除服务间耦合
- 缺点:开启binlog增加数据库负担、实现复杂度高
六、elasticsearch集群
单机的elasticsearch做数据存储,必然面临两个问题:海量数据存储问题、单点故障问题。
• 海量数据存储问题:将索引库从逻辑上拆分为N个分片(shard),存储到多个节点
• 单点故障问题:将分片数据在不同节点备份(replica )

ES集群的节点角色
elasticsearch中集群节点有不同的职责划分:

默认是身兼数职,在实际开发中各司其职,不同的角色对于硬件、环境要求不一样
elasticsearch中的每个节点角色都有自己不同的职责,因此建议集群部署时,每个节点都有独立的角色。

ES集群的脑裂
默认情况下,每个节点都是master eligible节点,因此一旦master节点宕机,其它候选节点会选举一个成为主节点。当主节点与其他节点网络故障时,可能发生脑裂问题。
为了避免脑裂,需要要求选票超过 ( eligible节点数量 + 1 )/ 2 才能当选为主,因此eligible节点数量最好是奇数。对应配置项是discovery.zen.minimum_master_nodes,在es7.0以后,已经成为默认配置,因此一般不会发生脑裂问题。


分布式新增和查询流程
新增
当新增文档时,应该保存到不同分片,保证数据均衡,那么coordinating node如何确定数据该存储到哪个分片呢?
elasticsearch会通过hash算法来计算文档应该存储到哪个分片:

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

查询
elasticsearch的查询分成两个阶段:
- scatter phase:分散阶段,coordinating node会把请求分发到每一个分片
- gather phase:聚集阶段,coordinating node汇总data node的搜索结果,并处理为最终结果集返回给用户

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

node1出现问题,先选主节点。master宕机后,EligibleMaster选举为新的主节点

如果是数据节点挂了,进行数据迁移,确保每个分片都有一个备份。• master节点监控分片、节点状态,将故障节点上的分片转移到正常节点,确保数据安全。

- 作者:coderma4k
- 链接:https://coderma4k.com//article/636ecebf-641d-4c2d-8c14-a7d64e75cb31
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。