Elasticsearch 是一个开源的、分布式的全文搜索和分析引擎,基于 Apache Lucene 构建。它提供了快速的搜索能力,支持大规模的数据分析,广泛应用于日志分析、全文搜索、监控系统和商业智能等领域。ES操作指令是基于restAPI构建,也就是说,操作ES需要向其服务端口发送http请求,ES会根据请求类型、uri、请求参数等,决定具体行为。
文章中涉及的指令操作,都会在Kibana提供的devtool中执行,可以查看我的文章Elasticsearch集群及Kibana部署流程,对其进行部署后学习。
1. 存储数据结构
ES通过索引定义存储数据的结构,什么是索引呢?我们可以类型数据库的表,索引和数据库的表极为类似,可以存储一种数据类型的数据。在6版本及之前,索引可以存储多种数据类型的数据,更像是数据库,而7版本及之后,索引只可以存储一种数据结构。
ES通过json类型存储数据,索引本质上就是一种json结构,其中包含了多个属性,其中mapping属性中定义了索引中存储类型的结构,我们称其为映射,映射就可以类比为数据库的表结构,其中定义了索引可以存储那种结构的json,以及作为搜索引擎时,其属性的搜索规则等。
除了映射外,索引中还包含setting属性,其定义了当前索引的一切特性,甚至集群表现,如果当前索引的集群分片数,副本数(ES的集群分片可以针对索引设置,大大提高了优化性能的可操作性)等索引特性。
除此之外,还包括aliaes(索引别名),完整属性如下:
{"settings": {// 索引的基础配置部分"number_of_shards": 1, // 主分片数量,决定索引的水平扩展性"number_of_replicas": 1, // 每个主分片的副本数,提升数据冗余性和查询性能"refresh_interval": "1s", // 刷新间隔时间,默认 1 秒。数据可见性延迟的控制参数"max_result_window": 10000, // 查询分页的最大窗口大小,默认 10000"max_inner_result_window": 100, // 嵌套分页的最大窗口大小,控制内嵌对象的分页深度"max_rescore_window": 10000, // 重新评分窗口的最大值"max_docvalue_fields_search": 100, // 查询中允许的 doc_value 字段数量上限,避免性能问题"max_script_fields": 32, // 查询中允许的脚本字段最大数量"max_ngram_diff": 1, // ngram 分析器的最小和最大长度之差,影响文本分析的粒度"max_shingle_diff": 3, // shingle 分析器的最小和最大长度之差,影响分词的组合方式"analysis": {// 定义分析器、字符过滤器、词汇过滤器等分析组件"analyzer": {"default": {"type": "standard" // 默认使用标准分词器,适合通用文本分词},"custom_analyzer": {"type": "custom","tokenizer": "whitespace", // 自定义分词器,使用空白分词"filter": ["lowercase"] // 词汇过滤器,转换为小写字母}}},"index.lifecycle.name": "my_policy", // 生命周期策略名称,自动管理索引的生命周期"index.lifecycle.rollover_alias": "my_alias", // 用于自动 rollover 的别名,适合日志索引管理"routing_partition_size": 1, // 路由分区大小,影响数据路由的灵活性"codec": "best_compression", // 压缩算法,可选 `best_compression` 提高存储效率"translog.durability": "request", // 事务日志的持久性,`request` 表示每次请求刷盘"priority": 1, // 索引优先级,适用于恢复时控制恢复顺序"queries.cache.enabled": true // 查询缓存开关,提升重复查询性能},"mappings": {// 索引的字段映射配置"dynamic": "strict", // 动态映射设置,`strict` 表示严格模式,禁止未知字段"date_detection": true, // 自动日期检测,启用时会自动识别日期字段"numeric_detection": true, // 自动数值检测,启用时会自动识别整数和浮点数"dynamic_date_formats": ["yyyy-MM-dd"], // 定义动态日期格式,用于日期类型自动识别"properties": {"example_field": {"type": "text", // 字段的数据类型,如 text、keyword、integer、date 等"analyzer": "standard", // 指定索引时的分析器"search_analyzer": "whitespace",// 指定搜索时的,与 analyzer 可以不同"boost": 1.0, // 相关性评分的权重,值越高权重越大"index": true, // 是否对该字段建立索引,默认 true"store": false, // 是否将字段单独存储在 _source 外部"doc_values": true, // 启用或禁用 doc_values,适用于排序、聚合"norms": true, // 控制是否启用字段的归一化信息,用于相关性评分"similarity": "BM25", // 设置相似性算法,默认使用 BM25,可选 classic 等"null_value": "NULL", // 当字段为空时的默认值"ignore_above": 256, // 忽略超过指定长度的字符串,适用于 keyword 类型"position_increment_gap": 100, // 控制数组中元素的短语查询间隔"index_options": "positions", // 控制索引选项,如 docs, freqs, positions"term_vector": "yes", // 存储词条向量信息,用于高亮显示等"coerce": true, // 强制将非预期类型转换为指定类型,适用于数值类型"copy_to": "another_field", // 将字段内容复制到另一个字段,用于合并搜索"eager_global_ordinals": false, // 提前加载全局排序,适用于高频使用的 keyword 字段"scaling_factor": 100, // 用于存储浮点数时将其转换为 long 类型的倍率"format": "yyyy-MM-dd", // 日期格式,用于 date 类型字段的格式定义"fields": { // 定义多字段结构,支持多个索引方式"keyword": {"type": "keyword","ignore_above": 256}},"enabled": true, // 是否启用该字段,适用于 object 类型字段"dynamic": "strict", // 控制动态映射,strict 表示未知字段会报错"time_series_dimension": true, // 是否将字段设为时间序列维度,适用于时序数据"meta": { // 字段的元数据,用于附加描述信息"description": "Example field for documentation purposes."},"split_queries_on_whitespace": false // 用于 text 类型,控制查询是否按空格分词}}"enabled": true // 启用或禁用整个索引映射,`false` 会忽略整个映射},"aliases": {// 索引的别名部分"my_alias": {}, // 简单别名,直接指向索引"filtered_alias": {// 带筛选条件的别名,适用于部分查询场景"filter": {"term": { "active": true } // 仅匹配 active 为 true 的文档},"routing": "1" // 路由设置,控制查询或写入操作的路由方式}}
}
这就是一个非常完整的一个索引,当然其中绝大部份属性,我们都使用不到。
数据类型
Elasticsearch(ES)支持多种数据类型,但部分数据类型同常见语言中的不一致。以下是 ES 中常用的数据类型及其详细说明(不建议记住,大部分用不上):
1. 核心基础类型
text
:适用于全文搜索的文本数据类型。通常用于长文本字段,如产品描述、文章内容等。text
类型字段会进行分词和倒排索引,支持全文搜索。keyword
:适用于精确匹配的文本数据。keyword
类型字段不分词,用于存储不需要分析的字符串,如用户名、标签等。适合排序、聚合和过滤。integer
:32 位整数类型,用于存储整型数据。适用于数值范围在 ±2,147,483,647 的数据。long
:64 位整数类型,用于存储更大范围的整型数据,数值范围在 ±9,223,372,036,854,775,807。float
:32 位浮点数类型,适合存储带有小数的数值,精度较低。double
:64 位浮点数类型,精度更高,适合存储高精度的数值数据。boolean
:布尔类型,用于存储true
或false
值。date
:用于存储日期数据,可以支持多种日期格式(如yyyy-MM-dd
),也可以存储时间戳(毫秒或秒)。支持日期范围查询。binary
:用于存储二进制数据(Base64 编码格式)。通常用于存储图片、文件等非结构化数据,不支持搜索。
2. 复合类型
object
:用于存储 JSON 对象。object
类型字段可以包含子字段,适合嵌套数据结构。注意:object
类型的子字段会被展开并作为顶级字段索引。nested
:嵌套对象类型,用于存储数组中的 JSON 对象。与object
不同,nested
类型会为数组中的每个对象单独创建一个索引,支持更复杂的嵌套查询。
3. 专用类型
geo_point
:用于存储地理坐标点(经纬度),支持地理位置的距离计算和位置范围查询。常用于地理位置检索。geo_shape
:用于存储复杂的地理形状,如多边形、线条等。适合更高级的地理空间计算,比如区域包含、相交等查询。ip
:用于存储 IP 地址(IPv4 和 IPv6),支持范围查询和 IP 地址过滤。range
:用于存储一个范围,支持以下几种范围类型:integer_range
:整型范围。float_range
:浮点数范围。long_range
:长整型范围。double_range
:双精度浮点数范围。date_range
:日期范围,适合时间段查询。
4. 专门的数据类型(Elasticsearch 7.10 引入)
alias
:字段别名,指向索引中现有字段的引用。用于访问实际字段的数据或改变字段名称,而不需要复制字段内容。dense_vector
和sparse_vector
:用于存储向量数据,通常用于机器学习和相似性计算。rank_feature
和rank_features
:用于存储特征值,用于机器学习模型的排序字段。
操作指令
对于索引结构中的属性进行操作或执行ES提供的一些指令时,通常需要在前面加一个 _ ,而我们自己定义的属性则不需要
1.添加索引
我们可以通过发送get请求的方式添加索引,具体如下:
2. 查询索引
查询索引需要通过get请求加索引名称,具体如下:
3. 查询全部索引
通过get请求加_cat/indices?v的uri的形式,查询全部索引,具体如下
_cat不仅仅是查询所有索引的作用,他的主要作用是查询当前ES集群的元数据信息。
其中可以看到我们刚刚创建的索引
4. 查询当前索引是否存在
我们可以通过Head请求查询索引是否存在,具体如下
5. 修改索引属性(mapping,setting,alias)
在上文我们已经说过,当修改索引属性时,需要在属性前加上一个 _ ,所以我们可以通过put请求,加上/索引名/_索引属性的形式,进行修改,具体如下:
注:这种修改操作,对于原来没有的属性会进行新增,而对于已经有的属性则会修改,不过索引中并非所有属性都能够修改,如果修改不能修改的属性则会报错
6. 删除索引
通过delete请求,可以删除索引,具体如下:
7. 索引批处理
我们还可以通过_mget和_bulk对索引进行批处理,实例如下:
图中docs是一个数组可以传入多个条件,并且以其他任何属性为条件
_mget用于批量查询,而剩下的增删改则是全权有bulk负责
除了删除外,其他的都是第一行是操作及搜索条件,第二行是新数据。
index和create都是插入数据,不同的是index对于已经存在的数据会进行覆盖,而create会报错。
2. 文档
索引中的属性定义了存储数据的结构,及索引的特性,而真实的数据则存储在索引所对应的文档中,一次文档查询的结果如下:
{"took": 5, // 查询耗时,单位为毫秒,表示从发送请求到获得结果的时间"timed_out": false, // 是否超时,false 表示查询在规定时间内完成"_shards": { // 与查询相关的分片统计信息"total": 5, // 查询涉及的分片总数"successful": 5, // 成功响应的分片数量"skipped": 0, // 被跳过的分片数量(例如,索引为空时的分片可能被跳过)"failed": 0 // 查询失败的分片数量},"hits": { // 匹配的文档集合"total": { // 匹配的文档总数"value": 2, // 查询条件下的匹配文档数量"relation": "eq" // 匹配总数的关系。eq 表示精确数量,gte 表示大于或等于该数量},"max_score": 1.0, // 最高相关性得分,表示匹配度最高的文档的得分"hits": [ // 匹配的文档数组,每个元素代表一个文档的完整信息{"_index": "my_index", // 文档所属的索引名称"_type": "_doc", // 文档的类型(7.x 版本后仅为 _doc)"_id": "1", // 文档的唯一标识符,等同于数据库中的主键"_score": 1.0, // 文档的相关性得分,表示文档与查询的匹配程度"_source": { // 文档的实际内容"name": "Alice", // 文档中的字段"age": 30, // 文档中的字段}},{"_index": "my_index","_type": "_doc","_id": "2","_score": 1.0,"_source": {"name": "Bob","age": 25,"email": "bob@example.com","status": "inactive"}}]}
}
其中hits.hits
数组为查询结果列表,而列表中的每个元素的_source
属性则是具体数据。
具体操作
我们再次创建一个索引,用于定义数据结构,然后在执行文档相关操作,创建指令如下:
PUT test_index
{"mappings": {"properties": {"name": {"type": "text"},"age": {"type": "integer"}}}
}
1. 新增数据
通过post或put请求,以及/索引名/_doc的uri/id(id也可以不写,由ES自动生成),可以完成新增一条数据,具体如下:
2. 查询数据
通过get请求,以及_search,搜索全部数据(search可以分词搜索,下面说)
也可以通过_doc根据id查找
3. 更改数据
通过_update,可以将指定字段更改(没有指定的不会改变)
4. 删除数据
通过delete请求可以删除数据
3. ES的搜索功能
ES中并非所有类型都支持搜索功能,并且支持搜索功能的类型,而可以进行搜索的类型有可以细分为精确搜索(只支持精确搜索),分词搜索和范围搜索
精确搜索
-
keyword
:用于精确匹配的字符串搜索,支持过滤、排序和聚合,但不分词。 -
boolean
:可以用于布尔值的精确匹配查询。
分词搜索
text
:用于全文搜索,支持分词、相关性评分、模糊搜索等。
范围搜索
-
date
:支持日期范围查询,可以用于过滤、排序、聚合。 -
ip
:可以用于 IP 地址的精确匹配和范围查询(支持 IPv4 和 IPv6)。 -
geo_point
:用于地理坐标点查询,支持地理距离计算和区域范围查询。 -
range
(包括integer_range
、float_range
、long_range
、double_range
、date_range
):可以用于范围查询和范围过滤。
剩余的二进制数据**binary
,以及object
(非嵌套对象)和nested
**:支持复杂的嵌套对象都不支持搜索(对象字段支持搜索)。
具体操作
为了方便操作,我们执行以下指令创建一个索引,并插入数据:
#创建索引
PUT /test_search_index
{"mappings": {"properties": {"title": {"type": "text"},"tags": {"type": "keyword"},"publish_date": {"type": "date","format": "yyyy-MM-dd"},"author": {"type": "keyword"},"views": {"type": "integer"},"price": {"type": "float"},"active": {"type": "boolean"},"ip_address": {"type": "ip"},"location": {"type": "geo_point"}}}
}
#一次性插入多条数据
POST /test_search_index/_bulk
{ "index": { "_id": 1 }}
{ "title": "Elasticsearch Guide", "tags": ["search", "guide"], "publish_date": "2023-01-01", "author": "Alice", "views": 100, "price": 9.99, "active": true, "ip_address": "192.168.1.1", "location": "40.730610,-73.935242" }
{ "index": { "_id": 2 }}
{ "title": "Advanced Search", "tags": ["elasticsearch", "search"], "publish_date": "2023-02-01", "author": "Bob", "views": 200, "price": 15.50, "active": false, "ip_address": "192.168.1.2", "location": "34.052235,-118.243683" }
{ "index": { "_id": 3 }}
{ "title": "Elasticsearch and Python", "tags": ["python", "elasticsearch"], "publish_date": "2023-03-15", "author": "Carol", "views": 150, "price": 20.00, "active": true, "ip_address": "192.168.1.3", "location": "51.507351,-0.127758" }
{ "index": { "_id": 4 }}
{ "title": "Text Analysis with Elasticsearch", "tags": ["analysis", "text"], "publish_date": "2023-04-20", "author": "Dave", "views": 300, "price": 25.99, "active": true, "ip_address": "192.168.1.4", "location": "35.689487,139.691711" }
{ "index": { "_id": 5 }}
{ "title": "Geo Search", "tags": ["geo", "search"], "publish_date": "2023-05-10", "author": "Eve", "views": 250, "price": 30.00, "active": false, "ip_address": "192.168.1.5", "location": "48.856613,2.352222" }
{ "index": { "_id": 6 }}
{ "title": "Keyword Search", "tags": ["keyword", "exact"], "publish_date": "2023-06-12", "author": "Frank", "views": 120, "price": 8.99, "active": true, "ip_address": "192.168.1.6", "location": "55.755825,37.617298" }
{ "index": { "_id": 7 }}
{ "title": "Boolean Queries", "tags": ["boolean", "queries"], "publish_date": "2023-07-15", "author": "Grace", "views": 80, "price": 12.49, "active": false, "ip_address": "192.168.1.7", "location": "39.904202,116.407394" }
{ "index": { "_id": 8 }}
{ "title": "Range Queries", "tags": ["range", "queries"], "publish_date": "2023-08-20", "author": "Hank", "views": 180, "price": 17.75, "active": true, "ip_address": "192.168.1.8", "location": "37.774929,-122.419418" }
{ "index": { "_id": 9 }}
{ "title": "Sorting Results", "tags": ["sorting", "results"], "publish_date": "2023-09-25", "author": "Ivy", "views": 220, "price": 22.00, "active": true, "ip_address": "192.168.1.9", "location": "40.712776,-74.005974" }
{ "index": { "_id": 10 }}
{ "title": "Faceted Search", "tags": ["facets", "search"], "publish_date": "2023-10-30", "author": "Jack", "views": 90, "price": 19.95, "active": false, "ip_address": "192.168.1.10", "location": "41.878113,-87.629799" }
1. 精确搜索
除了必须精确查找的类型外,很多范围查找的也可以使用精确查找,在图中我们也可以发现一个问题,就是tags属性不是数组吗?不过ES可并没有提供一个数组类型啊!
实际上这个数组字段的类型就是keyword,ES中的数组元素必须为同一个类型,也就是是其索引中设置的类型。
搜索中我们使用了
query.term
来进行搜索,除此之外还有一个query.match
搜索的,是用来做分词搜索的,区别在于query.match
在查询是会讲查询语句进行分词处理,而query.term
则不会。
2. 范围搜索
3. 分词查询
text是唯一支持分词查询的类型,我们可以在创建改字段时通过改变analyzer属性,选择分析器(默认是standard),然后存入的文本就会根据分词的规则对文本进行分词,生成倒排索引,那么什么是倒排索引呢?假设我门存入一段文字,我爱你中国,那么这段文字可能会被分词为我、爱、你、中国、我爱你,这些词,这这些词又会作为索引指向文本,当我们搜索我爱你时,就会找到这个索引,并返回对应文本,这就是倒排索引。
我们可以通过可以如下请求,对title字段进行搜索,为了方便展示,我们用source属性进行限制,只让他展示title:
通过图中结果我们可以推测我们的文本被分词为Elasticsearch和guide两个词,然后去ES中搜索倒排索引,并根据结果进行评分(也就是结果的_score属性),评分越高,结果越靠前,最终结果如图所示。
过滤器
过滤器本质是是和query相同的属性,作用和语法都相同,不同的是,filter不会对结果进行评分排序,并且会对结果进行缓存。也就是说filter的查询性能比query更好,这里就不举例了,和query语法一样。
组合查询
组合查询通过如下语法实现:
GET /my_index/_search
{"query": {"bool": {"must": [{ "term": { "status": "active" }}, // 必须为 active{ "range": { "age": { "gte": 25, "lte": 40 }}} // 年龄在 25 到 40 之间],"should": [{ "match": { "description": "Elasticsearch" }} // 描述包含 Elasticsearch],"must_not": [{ "match": { "description": "beginner" }} // 描述不包含 beginner],"minimum_should_match": 2}}
}
其中bool代表了组合查询,而其中must中的查询条件必须全部满足,而should中包含的条件只需要满足一个即可,must_not则是必须不满足,而minimum_should_match表示的是当前组合查询中的should语句知道满足多少条时才返回true,也就是不再只是满足一条就返回true了。
must得到的结果仍然要参与评分,我们可以用filter替换must,实现和过滤器一样的效果。
bool本身也可以作为组合查询的条件之一,也就是死后缩bool也可以作为bool的属性。
4. 分析器
Elasticsearch对于text类型的处理依赖于分析器,分析器能够将text类型文本,按照既定的规则处理为可以搜索的一个个倒排索引。分析器的整个执行过程分为三个阶段,分别是分词前对于字符串的处理,分词,以及分词后对于每个索引的处理,对应的也是分析器的三个部分,分别是字符过滤器,分词器,分词过滤器(或者词条,或者令牌,叫法很多)。
ES中默认了内置了很多的分析器,内容如下:
- 标准分析器(standard):按语言规则进行分词,将所有词条转为小写,适合一般文本处理。
- 简单分析器(simple):按非字母字符分词,将词条转换为小写,适合不包含复杂字符的简单文本。
- 空格分析器(whitespace):按空格分词,不做大小写转换,适合对空格分隔的内容进行简单处理。
- 停止词分析器(stop):类似标准分析器,分词后去除停用词(如 “and”、“the” 等),适合英文内容的分词处理。
- 关键词分析器(keyword):将整个文本视为一个词条,不进行分词,适合精确匹配(如邮箱地址或用户ID)。
- 语言分析器(language,如 english、french、german):针对特定语言进行分词和处理,去掉停用词并进行词干提取,适用于多语言文本。
- 模式分析器(pattern):基于正则表达式分词,按自定义分隔模式拆分,适合按特定字符或模式分隔的内容。
- UAX_URL_Email 分析器(uax_url_email):识别 URL 和电子邮件地址作为单独词条,适用于带 URL 或电子邮件的内容。
- 自定义分析器(custom):用户自定义的分词方式,灵活组合字符过滤、分词和过滤规则,用于满足特殊的文本处理需求。
这些内置的分析器,都是可以直接配置给text类型属性使用的。
4.1 字符过滤器
字符过滤器用于在分词前对文本进行处理,比如说html_strip分词器可以在分词前,先将文本进行去除html标签处理。内置分析器中没有使用过字符过滤器,不过我们可以使用字符过滤器在我们的自定义分析器中,ES提供的字符过滤器如下:
html_strip
:去除 HTML 标签。mapping
:将字符或字符串替换为其他内容,支持自定义映射。pattern_replace
:使用正则表达式对字符或字符串进行替换。
4.2 分词器
无需多言,没有分词器怎么分词,ES提供分词器如下:
standard
:默认的分词器,基于语言规则进行分词。whitespace
:按空格分词,不做进一步处理。keyword
:将整个输入文本视为一个词条,不进行分词,适用于精确匹配。pattern
:基于正则表达式分词,可以自定义分隔模式。uax_url_email
:适用于带有 URL 和电子邮件地址的文本的分词器。ngram
:基于字符的n-gram
分词器,适合模糊匹配。edge_ngram
:基于字符的边界n-gram
分词器,生成从开头的n-gram
,适合自动完成。classic
:类似standard
分词器的旧版本,适用于英文文本。letter
:按字母字符分词,遇到非字母字符断开。lowercase
:转换为小写的keyword
分词器,整个文本视为一个词条并小写化。path_hierarchy
:基于路径层级的分词器,适用于文件路径、URL 分级等。char_group
:按字符组分词,可以指定多个字符作为分隔符。
4.3 分词过滤器
分词过滤器是对分词后的索引再次进行处理,比如说当我们使用内置的standard分析器时,rm可以搜索到分词后的Rm索引,原因就是standard分析器使用了lowercase分词过滤器,ES提供的分词过滤器如下:
lowercase
:将词条转换为小写。uppercase
:将词条转换为大写。stop
:去除停用词(例如and
、the
),支持多种语言的停用词。stemmer
:词干提取器,将词条转换为词干(如running
转为run
),支持多种语言。asciifolding
:将带重音符号的字符转换为 ASCII 字符(如é
转为e
)。synonym
:替换同义词(如quick
和fast
可以替换为彼此)。shingle
:生成包含多个词的组合(例如New York
生成New
,York
,New York
),用于短语匹配。ngram
:基于词条的n-gram
,生成较小的词条,适合模糊匹配。edge_ngram
:基于词条的边界n-gram
,从词条开头生成较小的词条,适合自动完成。unique
:去除重复的词条。reverse
:将词条的字符顺序反转。length
:过滤指定长度范围外的词条。truncate
:将词条截断为指定长度。porter_stem
:使用 Porter 词干算法,提取英文词条的词干。kstem
:另一种英文词干算法,提取英文词条的词干。snowball
:使用 Snowball 词干算法,支持多种语言的词干提取。fingerprint
:为多词条生成指纹标识,去重并排序,适用于重复检测。limit
:限制词条数目,截断超出指定数量的词条。pattern_capture
:使用正则表达式捕获匹配模式的子部分。pattern_replace
:使用正则表达式替换词条内容。
ES并没有内置中文分词器,对于中文的分词我们可以使用IK分词器来实现。
我们可以进入IK的github地址,在下面的readme中提供了安装命令
bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/8.4.1
4.4 自定义分析器
了解了分析器的三个部分后,我们就可以自由组合这三个部分,实现自己的自定义分析器,实例如下:
PUT my_index
{"settings": {"analysis": {"char_filter": {"mapping_filter": {"type": "mapping","mappings": ["$=>USD", "&=>and", "%=> percent"]}},"tokenizer": {"whitespace_tokenizer": {"type": "whitespace"}},"filter": {"lowercase_filter": {"type": "lowercase"},"english_stop_filter": {"type": "stop","stopwords": "_english_"}},"analyzer": {"my_custom_analyzer": {"type": "custom","char_filter": ["html_strip_filter","html_strip"],"tokenizer": "whitespace_tokenizer","filter": ["lowercase_filter", "english_stop_filter"]}}}}
}
实例中,我们在setting.analysis属性中,定义了字符过滤器mapping_filter,分词器whitespace_tokenizer,分词过滤器lowercase_filter和english_stop_filter。并将三者在analyzer中共同组合成了一个新的自定义分析器my_custom_analyzer。
像html_strip这种功能固定,无需配置的,可以直接放入自定义分析器中。
5. 词库
分析器功能虽然强大,不过其也是根据人类的语言习惯来进行分词,那么对于一些网络新型的热词,ES该如何分辨呢。这就涉及到更改词库的操作。
词库种类很多,不用的解析器能够使用的词库种类也不同(分词规则不同,词库当然也不同)。
比如说ES本身支持六种词库,包括:
- 停用词库:用于过滤无意义的常见词。
- 同义词库:用于归类同义词,增强召回率。
- 词干提取词库:用于词态还原,统一不同词态。
- 关键词映射词库:用于标准化关键术语。
- 拼写纠正词库:用于修正拼写错误,提高搜索容错。
- 停用词变体词库:适合特定领域的停用词。
而这六种词库,IK分析器能够识别的只有停用词库,同一词库,以及停用词变体词库。与其说IK只识别,倒不如说只有这三种词库对中文分词有帮助,所以不同的分析器,需要的词库也不同,完全根据分析器本身的性质来(ES内置的分析器也对这些词库的使用不完全兼容)。
具体操作
以停用词库为例,我们可以在自定义stop类型的分词过滤器时,给stopwords_path属性设置路径,示例如下:
PUT my_index
{"settings": {"analysis": {"filter": {"custom_stopwords": {"type": "stop","stopwords_path": "analysis/stopwords.txt" // 指定停用词库文件路径}},"analyzer": {"my_analyzer": {"type": "custom","tokenizer": "standard","filter": ["lowercase", "custom_stopwords"]}}}}
这样ES就会去寻找conf/analysis/stopwords.txt文件中,读取其中的停用词信息(一行为一词)。
而对于IK分析器,其可以在配置文件中配置远程词库链接(就是一个txt文件的链接)实现远程词库,以及基于mysql的远程词库,这里就不多说了。
6. 聚合查询
聚合查询就是将大量数据结合在一起查询,获取一些公共的数据。ES中聚合查询分为三种,分别为桶聚合(Bucket Aggregation),度量聚合(Metric Aggregation),管道聚合(Pipeline Aggregation)。
为了方便演示,我创建如下索引及数据
PUT products
{"mappings": {"properties": {"description": {"type": "text",},"category": {"type": "keyword"},"price": {"type": "float"},"rating": {"type": "integer"},"timestamp": {"type": "date"}}}
}
POST products/_bulk
{ "index": { "_id": 1 } }
{ "description": "高性能的Elasticsearch搜索引擎", "category": "电子产品", "price": 299.99, "rating": 4, "timestamp": "2023-01-10T12:00:00" }
{ "index": { "_id": 2 } }
{ "description": "Elasticsearch用于大数据分析", "category": "软件", "price": 199.99, "rating": 5, "timestamp": "2023-01-15T12:00:00" }
{ "index": { "_id": 3 } }
{ "description": "时尚服装,舒适体验", "category": "服装", "price": 49.99, "rating": 3, "timestamp": "2023-02-10T12:00:00" }
{ "index": { "_id": 4 } }
{ "description": "优质材料的服装", "category": "服装", "price": 79.99, "rating": 4, "timestamp": "2023-02-12T12:00:00" }
{ "index": { "_id": 5 } }
{ "description": "家用实木家具", "category": "家具", "price": 499.99, "rating": 5, "timestamp": "2023-03-01T12:00:00" }
{ "index": { "_id": 6 } }
{ "description": "现代风格的家具", "category": "家具", "price": 299.99, "rating": 3, "timestamp": "2023-03-05T12:00:00" }
{ "index": { "_id": 7 } }
{ "description": "智能家电,方便生活", "category": "家电", "price": 1200.00, "rating": 5, "timestamp": "2023-04-01T12:00:00" }
{ "index": { "_id": 8 } }
{ "description": "高效节能的家电", "category": "家电", "price": 850.00, "rating": 4, "timestamp": "2023-04-03T12:00:00" }
{ "index": { "_id": 9 } }
{ "description": "新鲜食品,健康选择", "category": "食品", "price": 15.00, "rating": 4, "timestamp": "2023-05-10T12:00:00" }
{ "index": { "_id": 10 } }
{ "description": "有机食品,绿色生活", "category": "食品", "price": 10.00, "rating": 3, "timestamp": "2023-05-12T12:00:00" }
6.1 桶聚合(Bucket Aggregation)
桶聚合就是将数据按照某个属性的的不同,进行分桶(其实就是简单的分类),实例如下:
查询方法就是通过/索引名/_search,然后请求体中的size:0是为了不展示查询的数据,只展示分桶数据。而aggs就是聚合查询的属性,在其中我们可以创建一个聚合类型的名称,这里我起的名称是bucket,其中terms代表的就是桶聚合,而field则是选择桶聚合的属性。
如图所示,结果是通过category属性将数据粉了六类,并标记了每个种类的数量。
对于text属性的桶聚合比较特殊。在创建索引时,映射中与属性类型同级的配置,还有一个fileddata,这个属性是用于对属性生成正排索引的,处text外,其他索引都支持精确查询,索引需要正排索引,所以这个值都是true。而text不支持精确搜索,其搜索功能依赖倒排索引,所以fileddata是false。但是分桶依赖的也是正排索引,所以text默认情况下并不支持分桶聚合,示例如下:
但是我们如果将fieldata设置为true呢,我们通过如下请求,将其设置为true:
POST products/_mapping
{"properties":{"description":{"type":"text",#类型必须在写一次,并且和原来一样"fielddata": true}}
}
可以看到,虽然分词成功了,但是结果很奇怪。实际上对text类型的分桶会根据分词后的结果进行分桶,而图中结果是因为ES内置的分词器不会对中文进行分词,所以结果就是一个字一个类。
而且数量也不够,这是因为与field属性平级的还有一个属性size,这个属性可以控制结果的数量,默认最多分为十个桶。
实际生产中如何设计text类型进行分桶的操作,通常是给其设置一个keyword类型的属性,然后通过对这个属性的分桶,来对text分桶,假设我们的description有一个tag属性,那么我们只需要在field属性中赋值description.tag即可。
6.2 度量聚合
度量聚合就是对数据进行计算,比如最大值,最小值,平均值等,具体如下
GET products/_search
{"size": 0,"aggs": {"average_price": {"avg": {"field": "price"}},"total_price": {"sum": {"field": "price"}},"max_price": {"max": {"field": "price"}},"min_price": {"min": {"field": "price"}},"price_count": {"value_count": {"field": "price"}}}
}
通过度量聚合可以对数据进行统计计算
6.3 管道聚合
管道聚合就是将一种聚合的结果,交给另一个聚合计算。
如图所示,可以在聚合的统计在写一个aggs,接受上一个聚合结果,也可以i通过buckets_path来指定路径。
6.4 将查询结果进行聚合查询
我们可以将query查询的结果,在使用aggs进行聚合查询,示例如下:
如图,我们将价格大于一百的产品拿出来,在进行分桶。
6.5 脚本查询
Elasticsearch 支持的脚本语言包括 Painless
(默认和推荐),并支持 expression
和 mustache
等语言。下面我们重点介绍如何使用 Painless
。我们可以在其script.source属性上编写查询脚本,示例如下:
图中第一个命令是以脚本作为条件,第二个是自己创建一个属性,用脚本计算值,并且支持传参。
7. 搜索推荐
在使用搜索引擎的搜索框时,当我们输入一部分文字,底部弹窗机会弹出我们可能想要搜索的东西,这个功能就是搜索推荐,搜索推荐的功能本质上也是通过输入的一部分,搜索全部部分,但是和搜索text类型文本不同的是,搜索推荐搜索的是分词后的倒排索引(也可以说是词库里的热词)返回给用户,方便用户进行搜索。
搜索推荐本质上也是搜索,是通过ES提供的suggest关键字实现的,其中包含一下四类实现
7.1 completion
Suggester
功能:completion
suggester 是一种用于实现自动完成(autocomplete)的建议器。它基于轻量级的索引结构(FST,有限状态转换器),可以实现快速的前缀匹配,非常适合用于实时的搜索推荐。
常用参数:
field
:指定用于自动完成的字段(通常是completion
类型的字段)。prefix
:用户输入的前缀,建议器将提供与该前缀匹配的推荐项。size
:控制返回的建议项数量。fuzzy
:用于启用模糊匹配(例如拼写错误时也能匹配),可以设置fuzziness
参数来调整模糊匹配的程度。
示例:
POST products/_search
{"suggest": {"product-suggest": {"prefix": "iph","completion": {"field": "suggest","size": 5,"fuzzy": {"fuzziness": 2}}}}
}
7.2 term
Suggester
功能:term
suggester 用于拼写纠错,可以在用户输入有错字时推荐正确的词。它基于倒排索引,适合单个词的拼写纠错。
常用参数:
-
field
:指定拼写建议的字段。 -
text
:用户输入的文本,建议器将对该文本进行拼写检查。 -
suggest_mode
:指定建议模式,可以是:
missing
:仅当词项缺失时才进行建议。popular
:仅对频率最高的词进行建议。always
:总是提供建议。
-
max_edits
:指定允许的编辑距离(即拼写错误的字符数),默认为 2。 -
prefix_length
:指定匹配前缀的最小长度,减少计算量。 -
size
:控制返回的建议项数量。
示例:
POST products/_search
{"suggest": {"text": "iphon","spellcheck-suggest": {"term": {"field": "name","suggest_mode": "always","max_edits": 1,"size": 3}}}
}
7.3 phrase
Suggester
功能:phrase
suggester 用于长查询的拼写纠错,特别适合基于上下文的拼写建议。例如,当用户输入多个单词时,phrase
suggester 可以利用上下文信息来提供更智能的拼写建议。
常用参数:
-
field
:指定用于建议的字段,一般设置为text
字段。 -
text
:用户输入的文本,建议器将检查其中的拼写和上下文。 -
size
:控制返回的建议项数量。 -
gram_size
:指定n-gram
的大小,通常设置为 2 或 3,用于基于上下文生成的建议。 -
direct_generator
:用于控制建议生成的方式,可以指定多个生成器,参数包括:
field
:指定字段。suggest_mode
:建议模式,类似于term
suggester。min_word_length
:建议生成器适用的最小单词长度。prefix_length
:最小前缀长度。size
:每个生成器返回的建议项数量。
-
highlight
:用于高亮拼写建议的部分,通常包含pre_tag
和post_tag
参数,用于包裹拼写建议的高亮标记。
示例:
POST products/_search
{"suggest": {"text": "iphon apple","phrase-suggest": {"phrase": {"field": "name.trigram","size": 1,"gram_size": 3,"direct_generator": [{"field": "name","suggest_mode": "always","min_word_length": 3}],"highlight": {"pre_tag": "<em>","post_tag": "</em>"}}}}
}
7.4 context
Suggester
功能:context
suggester 是 completion
suggester 的扩展,用于在自动完成推荐中增加上下文信息的过滤。例如,可以根据地理位置、类别、时间等上下文条件来过滤建议项。
常用参数:
-
field
:指定completion
类型的字段。 -
prefix
:用户输入的前缀。 -
contexts
:用于指定上下文条件,可以是地理位置、类别等。
location
:可以指定一个地理位置范围,例如{ "lat": 40.71, "lon": -74.01 }
。category
:可以指定一个分类标签,例如"electronics"
。
示例:
POST products/_search
{"suggest": {"product-suggest": {"prefix": "iph","completion": {"field": "suggest","size": 5,"contexts": {"category": ["electronics"]}}}}
}
解释
在这个示例中,context
被设置为 category
,只有符合 "electronics"
分类的建议项会被返回。
我们没办法讲所有命令全部记在脑子里,对于ES的学习,我们的目的也只是知道ES能干什么,大致有什么东西,对于实际的需求,还是需要在实际解决时搜索对应命令。除了文中给的指令外,ES的指令还有非常多,这里也不多介绍了。