Work In Progress
初识 ElasticSearch
使用场景
搜索引擎:生产环境中使用最多,例如京东、淘宝、美团等使用 ES 实现高性能商品搜索,支持多条件筛选、排序和相关性排名等。
日志分析与监控:ELK 作为日志分析的行业标准,是 ES 经典的使用场景。
数据分析:大数据分析,通过 DSL 快速得到结果。
与类似组件对比
选 ES :复杂查询/分析场景
选 RDBMS :强事务需求
ES 处理搜索/日志
ClickHouse 处理深度分析
存储场景决策树
快速开始
⚠️ 注意 :自行下载 Docker 以及 docker-compose 工具
Kibana 快速开始
通过 localhost:5601
进入:
使用 Dev Tools:
Logstash 快速开始
运行 docker-compose up
命令后可以到日志中查看是否导入数据成功。
Cerebro 快速开始
通过 localhost:9000
进入:
可以看到刚导入的 movies 数据。
ElasticSearch 入门
Elasticsearch 中的最小数据单元,以 JSON 格式存储数据。
1、类比于关系型数据库中的一行数据。
2、包含实际数据字段(如 title
, content
等)。
描述文档自身属性的系统字段,用于唯一标识和管理文档。
包含以下核心元数据:
- _id
:文档唯一标识符(可自定义或自动生成)。
- _index
:文档所属的索引。
- _version
:文档版本号(支持乐观锁)。
一类结构相似文档的集合,类似于关系型数据库中的「表」。
通过 Mapping 定义字段类型和属性(如文本、数值、日期等)。支持倒排索引,优化全文搜索性能。
Elasticsearch 集群中的一个运行实例,本质是一个 Java 进程。
节点类型包括:
- 主节点 :管理集群状态。
- 数据节点 :存储数据。
- 协调节点 :处理请求路由。
索引的物理子集,用于分布式存储和计算。分片分为主分片(Primary Shard)和副本分片(Replica Shard)。
主分片 :数据存储和写入的基本单元,数量在索引创建时固定。
副本分片 :主分片的冗余拷贝,提供高可用和查询负载均衡。
与关系型数据库类比
健康状况
1、在 Kibana 中查询,使用命令 GET _cluster/health
2、在 Cerebro 中查询
测试节点下线
此时状态为 Yellow。
CRUD
仅当文档不存在时创建。需指定 ID(PUT
)或自动生成(POST
)。
若文档存在则替换(全量更新)。可指定 ID(PUT
)或自动生成(POST
)。
提供部分字段或脚本(支持 doc
或 script
)
部分更新。支持 upsert
(不存在时插入)。需启用 _source
字段。默认返回更新后的 Source。
Create
Demo1
复制 # Req
# create document 自动生成 _id
POST users/_doc
{
"user" : "LanLance"
}
# Resp
{
"_index" : "users",
"_type" : "_doc",
"_id" : "64IRqpYBLb6gKsOx04Rm",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
Demo2
复制 # Req
# create document 指定 ID
PUT users/_doc/1?op_type=create
{
"user" : "LanLance_2"
}
# Resp
{
"_index" : "users",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
Demo3
复制 # Req
# create document 指定 ID;如果已经存在就报错
PUT users/_create/1
{
"user" : "LanLance"
}
# Resp
{
"error" : {
"root_cause" : [
{
"type" : "version_conflict_engine_exception",
"reason" : "[1]: version conflict, document already exists (current version [1])",
"index_uuid" : "J6rYdyr6TY-H9asFHjyTwQ",
"shard" : "0",
"index" : "users"
}
],
"type" : "version_conflict_engine_exception",
"reason" : "[1]: version conflict, document already exists (current version [1])",
"index_uuid" : "J6rYdyr6TY-H9asFHjyTwQ",
"shard" : "0",
"index" : "users"
},
"status" : 409
}
Get
Demo1
复制 # Req
# Get 指定 ID
GET users/_doc/1
# Resp
{
"_index" : "users",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 1,
"_primary_term" : 1,
"found" : true,
"_source" : {
"user" : "LanLance_2"
}
}
Index & Update
Demo1
复制 # Req
# Update 指定 ID (先删除,在写入)
PUT users/_doc/1
{
"user" : "LanLance"
}
# Resp
{
"_index" : "users",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1
}
Demo2
复制 # Req
# Update 在原文档上增加字段
POST users/_update/1/
{
"doc":{
"message" : "trying out Elasticsearch"
}
}
# Resp
{
"_index" : "users",
"_type" : "_doc",
"_id" : "1",
"_version" : 3,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 3,
"_primary_term" : 1
}
Delete
Demo1
复制 # Req
# Delete 指定 ID
DELETE users/_doc/1
# Resp
{
"_index" : "users",
"_type" : "_doc",
"_id" : "1",
"_version" : 4,
"result" : "deleted",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 4,
"_primary_term" : 1
}
Bulk、MGet、MSearch
批量增删改(Create/Index/Update/Delete)
功能与场景对比
响应中标记每个操作的 error
和 status
性能与限制对比
使用
复制 # bulk
## 执行第1次
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test2", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
## 执行第2次
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test2", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
# mget
GET /_mget
{
"docs" : [
{
"_index" : "test",
"_id" : "1"
},
{
"_index" : "test",
"_id" : "2"
}
]
}
## URI 中指定 index
GET /test/_mget
{
"docs" : [
{
"_id" : "1"
},
{
"_id" : "2"
}
]
}
GET /_mget
{
"docs" : [
{
"_index" : "test",
"_id" : "1",
"_source" : false
},
{
"_index" : "test",
"_id" : "2",
"_source" : ["field3", "field4"]
},
{
"_index" : "test",
"_id" : "3",
"_source" : {
"include": ["user"],
"exclude": ["user.location"]
}
}
]
}
清除数据
复制 DELETE users
DELETE test
DELETE test2
倒排索引
倒排索引是搜索引擎和全文检索系统的核心数据结构,核心思想是通过建立「单词到文档」的映射关系从而实现 Keyword 快速定位包含该词的所有文档。例如当用户搜索「ElasticSearch」时,系统可直接通过倒排索引找到包含这一词汇的所有文档集合。
倒排索引的实现依赖于两个关键结构:单词词典 (Term Dictionary)和倒排列表 (Posting List)。
Analysis & Analyzer
Analysis 是通过 Analyzer 来实现的。
分词器
由三部分组成:
Character Filters:针对原始文本处理,例如去除 html。
Token Filter:将切分的的单词进行加工,例如小写、删除 stopwords、增加同义词等。
复制 Character Filters => Tokenizer => Token Filters
ElasticSearch 内置分词器
按 Unicode 标准分词,移除标点符号,转小写,支持多语言基础处理。
在非字母字符处分割文本,删除非字母字符,转小写(如 Hello-World
→ ["hello", "world"]
)。
按空格严格分割,保留原始格式(如代码、特定标识)。
仅按空格分割,保留大小写和标点(如 Quick-Brown
→ ["Quick-Brown"]
)。
需过滤常见停用词(如英文中的“the”、“is”)的文本。
按非字母字符分割出连续字母词条,转小写后移除停用词(如 The fox
→ ["fox"]
)。
将整个输入作为单一词条,不进行任何处理(如 Hello World
→ ["Hello World"]
)。
通过正则表达式(默认 \W+
)分割文本,转小写(可自定义正则)。
按语言规则分词,处理停用词、转小写、词干提取等(如 running
→ ["run"]
)。
analyzer API
通过 analyzer API 能够快速得到分词结果进行测试,以下提供了一些例子可以去到 Kibana 的 Dev Tools 进行使用。
复制 #standard
GET _analyze
{
"analyzer": "standard",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
#simple
GET _analyze
{
"analyzer": "simple",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
#stop
GET _analyze
{
"analyzer": "stop",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
#whitespace
GET _analyze
{
"analyzer": "whitespace",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
#keyword
GET _analyze
{
"analyzer": "keyword",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
#pattern
GET _analyze
{
"analyzer": "pattern",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
#english
GET _analyze
{
"analyzer": "english",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
示例:standard 分词器的分词结果
复制 {
"tokens" : [
{
"token" : "2",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<NUM>",
"position" : 0
},
{
"token" : "running",
"start_offset" : 2,
"end_offset" : 9,
"type" : "<ALPHANUM>",
"position" : 1
},
{
"token" : "quick",
"start_offset" : 10,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 2
},
{
"token" : "brown",
"start_offset" : 16,
"end_offset" : 21,
"type" : "<ALPHANUM>",
"position" : 3
},
{
"token" : "foxes",
"start_offset" : 22,
"end_offset" : 27,
"type" : "<ALPHANUM>",
"position" : 4
},
{
"token" : "leap",
"start_offset" : 28,
"end_offset" : 32,
"type" : "<ALPHANUM>",
"position" : 5
},
{
"token" : "over",
"start_offset" : 33,
"end_offset" : 37,
"type" : "<ALPHANUM>",
"position" : 6
},
{
"token" : "lazy",
"start_offset" : 38,
"end_offset" : 42,
"type" : "<ALPHANUM>",
"position" : 7
},
{
"token" : "dogs",
"start_offset" : 43,
"end_offset" : 47,
"type" : "<ALPHANUM>",
"position" : 8
},
{
"token" : "in",
"start_offset" : 48,
"end_offset" : 50,
"type" : "<ALPHANUM>",
"position" : 9
},
{
"token" : "the",
"start_offset" : 51,
"end_offset" : 54,
"type" : "<ALPHANUM>",
"position" : 10
},
{
"token" : "summer",
"start_offset" : 55,
"end_offset" : 61,
"type" : "<ALPHANUM>",
"position" : 11
},
{
"token" : "evening",
"start_offset" : 62,
"end_offset" : 69,
"type" : "<ALPHANUM>",
"position" : 12
}
]
}
Search API
示例
复制 # URI Search
GET kibana_sample_data_ecommerce/_search?q=customer_first_name:Eddie
GET kibana*/_search?q=customer_first_name:Eddie
GET /_all/_search?q=customer_first_name:Eddie
# Request Body Search
POST kibana_sample_data_ecommerce/_search
{
"profile": true,
"query": {
"match_all": {}
}
}
指定查询的索引
URI Search
示例
复制 GET /movies/_search?q=2012&df=title&sort=year:desc&from=0&size=10&timeout=1s
{
"profile":"true"
}
参数:
q 指定查询语句,使用 Query String Syntax。
df 指定默认字段,不指定时会对所有字段进行查询。
泛查询
复制 GET /movies/_search?q=2012
{
"profile":"true"
}
未指定 df
参数时 ES 会搜索所有字段,可能触发跨字段匹配,性能消耗较大。分析结果如下:
复制 {
"type": "DisjunctionMaxQuery",
"description": "(title.keyword:2012 | id.keyword:2012 | year:[2012 TO 2012] | genre:2012 | @version:2012 | @version.keyword:2012 | id:2012 | genre.keyword:2012 | title:2012)"
}
可以看到在所有字段进行了匹配。
显式字段查询
复制 GET /movies/_search?q=title:2012
{
"profile":"true"
}
通过 title:2012
显式指定字段,精准限定搜索范围,比泛查询更高效。分析结果如下:
复制 {
"type": "TermQuery",
"description": "title:2012"
}
短语分割问题
复制 GET /movies/_search?q=title:Beautiful Mind
{
"profile":"true"
}
实际执行 title:Beautiful OR Mind
,空格被识别为 OR 逻辑,返回包含任意词的文档。
精确短语匹配
复制 GET /movies/_search?q=title:"Beautiful Mind"
{
"profile":"true"
}
使用双引号包裹词组,强制进行短语搜索,要求词语按顺序完整出现。
分组查询
复制 GET /movies/_search?q=title:(Beautiful Mind)
{
"profile":"true"
}
括号实现逻辑分组,等效于 title:Beautiful OR title:Mind
,优先执行组内操作。
布尔运算符
复制 GET /movies/_search?q=title:(Beautiful AND Mind)
显式布尔查询,要求同时包含两个词(AND 逻辑)。
范围查询
复制 GET /movies/_search?q=title:beautiful AND year:[2002 TO 2018%7D
[2002 TO 2018}
表示闭区间包含 2002,开区间不包含 2018(%7D
为 URL 编码的 }
符号)。
通配符搜索
复制 GET /movies/_search?q=title:b*
{
"profile":"true"
}
b*
匹配以 b 开头的任意长度字符,支持 ?
匹配单个字符,注意通配符在前端影响性能。
模糊匹配
复制 GET /movies/_search?q=title:beautiful~1
{
"profile":"true"
}
GET /movies/_search?q=title:"Lord Rings"~2
{
"profile":"true"
}
"Lord Rings"~2
表示短语中允许间隔 2 个单词。
Request Body Search & Query DSL
通常生成环境都使用这种方法,更加强大、功能更丰富。
示例
复制 POST movies/_search
{
"from":0,
"size":10,
"query": {
"match": {
"title": {
"query": "last christmas",
"operator": "and"
}
}
}
}
基本 Match 查询
复制 POST movies/_search
{
"query": {
"match": {
"title": "last christmas"
}
}
}
精确 AND 匹配
复制 POST movies/_search
{
"query": {
"match": {
"title": {
"query": "last christmas",
"operator": "and"
}
}
}
}
通过 operator:"and"
强制要求所有分词必须同时存在。
短语搜索
复制 POST movies/_search
{
"query": {
"match_phrase": {
"title": {
"query": "one love"
}
}
}
}
模糊短语匹配
复制 POST movies/_search
{
"query": {
"match_phrase": {
"title": {
"query": "one love",
"slop": 1
}
}
}
}
slop
参数允许词语间隔位置数(此处允许间隔 1 个词)。
Query String 搜索
复制 GET /movies/_search
{
"query": {
"query_string": {
"default_field": "title",
"query": "Beafiful AND Mind"
}
}
}
多字段搜索
复制 GET /movies/_search
{
"query": {
"query_string": {
"fields": ["title","year"],
"query": "2012"
}
}
}
Simple Query 搜索
复制 GET /movies/_search
{
"query": {
"simple_query_string": {
"query": "Beautiful +mind",
"fields": ["title"]
}
}
}
跨索引查询
复制 POST /movies,404_idx/_search?ignore_unavailable=true
{
"query": {
"match_all": {}
}
}
ignore_unavailable=true
忽略不存在索引。
源过滤
复制 POST kibana_sample_data_ecommerce/_search
{
"_source":["order_date"],
"query": {
"match_all": {}
}
}
_source
过滤返回字段。
脚本字段
复制 GET kibana_sample_data_ecommerce/_search
{
"script_fields": {
"new_field": {
"script": {
"lang": "painless",
"source": "doc['order_date'].value+'hello'"
}
}
}
}
动态计算返回字段。
Mapping
Mapping 类似数据库中的 schema 定义,用于定义字段名称、类型、相关配置。
字段的数据类型
倒排索引(分词后存储),支持模糊匹配和相关性评分。
Lucene 的数值索引优化(如 integer
、long
)。
"U29tZSBoZWxsbyB3b3JsZA=="
Lucene 的数值/日期范围索引,支持 >=
、<=
等操作。
{"city": "Beijing", "zip": "100000"}
独立索引的子文档,需通过 nested
查询访问。
[{"name": "book", "price": 15}]
{"lat": 40.7128, "lon": -74.0060}
多值字段(无需显式声明),底层以扁平化多值形式存储。
存储为 32 位或 128 位整数,支持 CIDR 范围查询。
Dynamic Mapping
在写入文档时如果索引不存在会自动创建索引,该机制使得我们不用手动定义 Mappings,但是通常不用这个,因为容易推算错误,并且 ES 禁止对有数据写入的字段修改定义。
示例
复制 # 插入测试数据
PUT mapping_test/_doc/1
{
"uid" : "123",
"isVip" : false,
"isAdmin": "true",
"age":19,
"heigh":180
}
# 查看字段类型
GET mapping_test/_mapping
# Resp
{
"mapping_test" : {
"mappings" : {
"properties" : {
"age" : {
"type" : "long"
},
"heigh" : {
"type" : "long"
},
"isAdmin" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"isVip" : {
"type" : "boolean"
},
"uid" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
如上结果所示,Dynamic Mapping 机制会自动推断类型,同时 text 类型会新增一个 keyword 类型支持精确查找。
控制 Dynamic Mappings
dynamic
字段不同情况下的表现。
Index
index
用于控制字段是否被索引。
示例
复制 "mobile" : {
"type" : "text",
"index": false
}
Index Options
仅记录文档 ID,节省空间,适用于只需文档匹配的场景
文档编号(doc id)+ 词频(term frequency)
记录位置信息,支持短语查询(Phrase Query)和邻近查询(Proximity Query)
文档编号 + 词频 + 位置 + 偏移量(offset)
index_options
用于控制是否存储文档 ID、词频、位置和偏移量等,从而影响搜索效率和功能支持(如短语查询、高亮等)。同时记录的内容越多,占用存储空间越大。
null_value
只有 Keyword 类型支持设定 null_value。
示例
复制 "mobile" : {
"type" : "keyword",
"null_value": "NULL"
}
自定义 Analyzer
Character Filter
示例
复制 POST _analyze
{
"tokenizer":"keyword",
"char_filter":["html_strip"],
"text": "<b>hello world</b>"
}
能够将 html 的标签去除。
复制 POST _analyze
{
"tokenizer": "standard",
"char_filter": [
{
"type" : "mapping",
"mappings" : [ "- => _"]
}
],
"text": "123-456, I-test! test-990 650-555-1234"
}
能够将 text 中的 -
替换为 _
。
Tokenizer
示例
复制 POST _analyze
{
"tokenizer":"path_hierarchy",
"text":"/user/ymruan/a/b/c/d/e"
}
能够按照目录层级进行切分。
Token Filter
示例
复制 GET _analyze
{
"tokenizer": "whitespace",
"filter": ["lowercase","stop","snowball"],
"text": ["The girls in China are playing this game!"]
}
Index Template
用于自动设定 Mappings 和 Settings,并按照一定的规则自动匹配到新创建的索引中。
可以控制 order 的数值控制 merge 的过程。先应用 order 低的,后续高的会覆盖之前的设定。
示例
复制 PUT /_template/template_test
{
"index_patterns" : ["test*"],
"order" : 1,
"settings" : {
"number_of_shards": 1,
"number_of_replicas" : 2
},
"mappings" : {
"numeric_detection": true
}
}
表示当一个新索引以 test
开头时,会自动将索引的分片数设置为 2,同时会自动探测数字类型。
Dynamic Template
动态设定字段类型。例如:
示例
复制 PUT my_index
{
"mappings": {
"dynamic_templates": [
{
"strings_as_boolean": {
"match_mapping_type": "string",
"match": "is*",
"mapping": {
"type": "boolean"
}
}
},
{
"strings_as_keywords": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
]
}
}
表示当字符串类型为 is 开头时设置为 boolean 类型,其余设置为 keyword 类型。
聚合(Aggregation)
ElasticSearch 除了提供搜索以外,还提供了针对 ES 进行同喜分析的功能。
聚合是一个分析总结全套的数据,而不是寻找单个文档。
本节示例需要在 Kibana 中添加官方提供的 Sample flight data 样例数据。
Bucket
示例
复制 GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs":{
"flight_dest":{
"terms":{
"field":"DestCountry"
}
}
}
}
# Resp
"aggregations" : {
"flight_dest" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 3187,
"buckets" : [
{
"key" : "IT",
"doc_count" : 2371
},
{
"key" : "US",
"doc_count" : 1987
},
// ...
]
}
}
将国家分成了桶。
Metric
示例
复制 GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs":{
"flight_dest":{
"terms":{
"field":"DestCountry"
},
"aggs":{
"avg_price":{
"avg":{
"field":"AvgTicketPrice"
}
},
"max_price":{
"max":{
"field":"AvgTicketPrice"
}
},
"min_price":{
"min":{
"field":"AvgTicketPrice"
}
}
}
}
}
}
# Resp
// ...
{
"key" : "IT",
"doc_count" : 2371,
"max_price" : {
"value" : 1195.3363037109375
},
"min_price" : {
"value" : 100.57646942138672
},
"avg_price" : {
"value" : 586.9627099618385
}
},
// ...
进行了平均值、最大值、最小值的计算。
嵌套
示例
复制 GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs":{
"flight_dest":{
"terms":{
"field":"DestCountry"
},
"aggs":{
"stats_price":{
"stats":{
"field":"AvgTicketPrice"
}
},
"wather":{
"terms": {
"field": "DestWeather",
"size": 5
}
}
}
}
}
}
# Resp
// ...
{
"key" : "IT",
"doc_count" : 2371,
"wather" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 506,
"buckets" : [
{
"key" : "Clear",
"doc_count" : 428
},
{
"key" : "Sunny",
"doc_count" : 424
},
{
"key" : "Rain",
"doc_count" : 417
},
{
"key" : "Cloudy",
"doc_count" : 414
},
{
"key" : "Heavy Fog",
"doc_count" : 182
}
]
},
"stats_price" : {
"count" : 2371,
"min" : 100.57646942138672,
"max" : 1195.3363037109375,
"avg" : 586.9627099618385,
"sum" : 1391688.585319519
}
},
// ...
在使用国家分桶后再进行票价统计和 5 组最常见天气的分布。
深入搜索
Term 搜索与全文搜索
过滤(Filter)、聚合(Aggregation)
{"term": {"field": "value"}}
{"match": {"field": "user_input"}}
Term 搜索
示例
1、插入数据
复制 POST /products/_bulk
{ "index": { "_id": 1 }}
{ "productID" : "XHDK-A-1293-#fJ3","desc":"iPhone" }
{ "index": { "_id": 2 }}
{ "productID" : "KDKE-B-9947-#kL5","desc":"iPad" }
{ "index": { "_id": 3 }}
{ "productID" : "JODL-X-1937-#pV7","desc":"MBP" }
2、Term 查询
复制 POST /products/_search
{
"query": {
"term": {
"desc": {
// "value": "iPhone"
// "value": "iphone"
}
}
}
}
使用 iPhone
不能搜索出结果,而小写的 iphone
可以。因为是精确查询,而原始数据在分词后的结果为 iphone
。
复制 POST /products/_search
{
"query": {
"term": {
"desc.keyword": {
// "value": "iPhone"
// "value": "iphone"
}
}
}
}
此时使用 keyword 类型能成功获取数据。
3、Constant Score 转为 Filter
复制 POST /products/_search
{
"explain": true,
"query": {
"constant_score": {
"filter": {
"term": {
"productID.keyword": "XHDK-A-1293-#fJ3"
}
}
}
}
}
将 Query 转为 Filter,忽略 TF-IDF 计算,避免相关性算分的开销。
全文搜索
Match / Match Phrase / Query String
索引和搜索时都会进行分词,查询字符串先传递到一个合适的分词器,然后生成一个供查询的列表。
查询会对每个词项逐个查询再将结果进行合并,并为每个文档生成一个算分。
match
/ multi_match
,避免 query_string
结构化搜索
结构化数据是指具有固定格式和明确字段的数据,每个字段都有特定的类型(如字符串、数字、日期等),并且数据是可预测、易于解析的。结构化搜索即对结构化数据进行搜索。
示例
1、插入数据
复制 DELETE products
POST /products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10,"avaliable":true,"date":"2018-01-01", "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20,"avaliable":true,"date":"2019-01-01", "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30,"avaliable":true, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30,"avaliable":false, "productID" : "QQPX-R-3956-#aD8" }
2、Bool
复制 POST products/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"avaliable": true
}
}
}
}
}
3、数字
复制 POST products/_search
{
"query": {
"term": {
"price": 30
}
}
}
4、Range
复制 POST products/_search
{
"query" : {
"constant_score" : {
"filter" : {
"range" : {
"price" : {
"gte" : 20,
"lte" : 30
}
}
}
}
}
}
搜索相关性
搜索的相关性算分,描述了一个文档和查询语句匹配的程度。ES 会对每个匹配查询条件的结果进行算分 _score。
打分的本质是排序,需要把最符合用户需求的文档排在前面。ES5 之前,默认的相关性算分采用 TF-IDF,现在采用 BM 25。
词频 TF
度量一条查询和结果文档相关性的简单方法:将搜索中的每一个词 TF 相加。
Stop Word 不应考虑,类似 「the」、「的」。
逆文档频率 IDF
Inverse Document Frequency:$log(全部文档数/检索词出现过的文档总数)$
TF-IDF 的本质就是将 TF 求和变成了加权求和:$TF(X)*IDF(X)$
BM 25
与 TF-IDF 相比,当一个词的 TF 无限增加时,BM 25 算分会趋于一个稳定值。
Boosting
Boosting 是控制相关度的一种手段。
当 boost > 1 时打分的相关度相对性提升。
当 0 < boost < 1 时打分的权重相对性降低。
多字段多字符串查询
bool 查询
一个 bool 查询是一个或者多个查询子句的组合。
Filter Context 查询字句,必须不能匹配。
Filter Context 必须匹配,不贡献算分。
1、bool 查询
复制 POST /products/_search
{
"query": {
"bool" : {
"must" : {
"term" : { "price" : "30" }
},
"filter": {
"term" : { "avaliable" : "true" }
},
"must_not" : {
"range" : {
"price" : { "lte" : 10 }
}
},
"should" : [
{ "term" : { "productID.keyword" : "JODL-X-1937-#pV7" } },
{ "term" : { "productID.keyword" : "XHDK-A-1293-#fJ3" } }
],
"minimum_should_match" :1
}
}
}
如果 bool 查询中没有 must 条件,那么 should 中必须至少满足一条查询。
2、boost 控制查询分数
复制 POST /news/_bulk
{ "index": { "_id": 1 }}
{ "content":"Apple Mac" }
{ "index": { "_id": 2 }}
{ "content":"Apple iPad" }
{ "index": { "_id": 3 }}
{ "content":"Apple employee like Apple Pie and Apple Juice" }
POST news/_search
{
"query": {
"boosting": {
"positive": {
"match": {
"content": "apple"
}
},
"negative": {
"match": {
"content": "pie"
}
},
"negative_boost": 0.5
}
}
}
此时会将苹果产品放前边,而 id 为 3 的显示在最后。
多字段单字符串查询
Disjunction Max Query
将任何与任一查询匹配的文档作为结果返回。采用字段上最匹配的评分最终评分返回。
示例
1、插入数据
复制 PUT /blogs/_doc/1
{
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
PUT /blogs/_doc/2
{
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
2、bool 测试
复制 POST /blogs/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
结果可以看到 1 号文档在前,是因为 bool 会对两个进行加和平均,不符合直觉。
2、dis_max 测试
复制 POST blogs/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
此时 2 号文档在前,符合直觉。
复制 POST blogs/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
]
}
}
}
结果还是 1 号文档在前,同时分数相同。因为最高分数的 quick 都只出现一次。但 2 号文档中有 pet ,直觉上应该 2 号文档更高,但 dis_max 只取最大的一条。
复制 POST blogs/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
],
"tie_breaker": 0.2
}
}
}
加入 tie_breaker 后会把最匹配一条之外的分数与其值相乘,这样 2 号文档就能更高,符合直觉。
MultiMatch
multi_match
是 Elasticsearch 中一种用于在多个字段中执行全文搜索的查询方式。它扩展了 match
查询,允许你在多个字段上同时进行匹配。
姓名拆分(first_name + last_name)
示例
1、插入数据
复制 PUT /titles
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "english",
"fields": {"std": {"type": "text","analyzer": "standard"}}
}
}
}
}
POST titles/_bulk
{ "index": { "_id": 1 }}
{ "title": "My dog barks" }
{ "index": { "_id": 2 }}
{ "title": "I see a lot of barking dogs on the road " }
2、使用 most_fields
复制 GET /titles/_search
{
"query": {
"multi_match": {
"query": "barking dogs",
"type": "most_fields",
"fields": [ "title", "title.std" ]
}
}
}
结果显示 2 号文档在前,符合直觉。若不使用 MultiMatch 则会 1 号文档在前,因为 English 分词器会把 barking dogs 分为 bark 和 dog,1 号文档分数更高。
自然语言与查询
当处理人类自然语言时,有时尽管搜索和原文不完全匹配,但是希望搜到一些内容。
可以采取的措施:
混合多语言的挑战
不同的索引使用不同的语言;同一个索引中,不同的字段使用不同的语言;一个文档的一个字段内混合不同的语言。
词干提取:以色列文档,包含了希伯来语,阿拉伯语,俄语和英文。
不正确的文档频率:英文为主的文章中,德文算分高(稀有)。
中文分词(IK)
由于生产环境中最常见的中文分词器为 IK,因此本篇也以 IK 为主。
安装
示例
复制 POST _analyze
{
"analyzer": "ik_smart",
"text": ["剑桥分析公司多位高管对卧底记者说,他们确保了唐纳德·特朗普在总统大选中获胜"]
}
Search Template
用于解耦程序和搜索 DSL。
示例
复制 POST _scripts/tmdb
{
"script": {
"lang": "mustache",
"source": {
"_source": [
"title",
"overview"
],
"size": 20,
"query": {
"multi_match": {
"query": "{{q}}",
"fields": [
"title",
"overview"
]
}
}
}
}
}
POST tmdb/_search/template
{
"id":"tmdb",
"params": {
"q": "basketball with cartoon aliens"
}
}```
上游可以不感知模版的变化,避免耦合。
### Index Alias
实现零停机运维。比如在进行索引重建、版本升级、滚动更新等操作时,无需中断服务。
**示例**
1、插入数据
```json
PUT movies-2019/_doc/1
{
"name":"the matrix",
"rating":5
}
PUT movies-2019/_doc/2
{
"name":"Speed",
"rating":3
}
2、设置别名
复制 POST _aliases
{
"actions": [
{
"add": {
"index": "movies-2019",
"alias": "movies-latest"
}
}
]
}
POST movies-latest/_search
{
"query": {
"match_all": {}
}
}
Function Score Query
可以在查询结束后对每一个匹配的文档进行一系列的重新算分,根据新生成的分数进行排序。
Weight:为每一个文档设置一个简单而不被规范化的权重。
Field Value Factor:使用该数值来修改 \_score
,例如将「热度」和「点赞数」作为算分的参考因素。
Random Score: 为每一个用户使用一个不同的,随机算分结果。
衰减函数:以某个字段的值为标准,距离某个值越近,得分越高。
Script Score:自定义脚本完全控制所需逻辑。
示例
1、插入数据
复制 DELETE blogs
PUT /blogs/_doc/1
{
"title": "About popularity",
"content": "In this post we will talk about...",
"votes": 0
}
PUT /blogs/_doc/2
{
"title": "About popularity",
"content": "In this post we will talk about...",
"votes": 100
}
PUT /blogs/_doc/3
{
"title": "About popularity",
"content": "In this post we will talk about...",
"votes": 1000000
}
2、使用多个参数测试
复制 POST /blogs/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p" ,
"factor": 0.1
}
}
}
}
使用 modifier 平滑参数后:$新的算分 = 老的算分 * log(1 + 投票数)$
Factor 参数:$新的算分 = 老的算分 * log(1 + factor * 投票数)$
3、一致性随机函数
复制 POST /blogs/_search
{
"query": {
"function_score": {
"random_score": {
"seed": 911119
}
}
}
}
具体需求:让每个用户能看到不同的随机排名,但是也希望同一个用户访问时,结果的相对顺序保持一致。
4、Boost Mode 和 Max Boost
复制 POST /blogs/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p" ,
"factor": 0.1
},
"boost_mode": "sum",
"max_boost": 3
}
}
}
Max Boost 可以将算分控制在一个最大值。Boost Mode 用于进行算分的数学运算。
Suggester
实现搜索引擎中纠错的功能。原理是将文本分解为 Token 然后在索引的字典中查找相似的 Term 并返回。
四种 Suggester 的不同之处:
对输入文本的每个词条进行纠错或建议,基于索引中的词典查找相似 Term。
在 Term Suggester 基础上,考虑多个词条之间的关系(如是否共同出现、相邻程度等)。
多个词组成的短语级别的纠错和建议(如句子片段的修正)。
提供前缀匹配的自动补全功能,支持快速搜索建议(如用户输入时的提示)。
基于 Completion Suggester,增加了上下文信息的支持(如地理位置、类别等过滤条件)。
需要结合上下文信息的自动补全(如特定分类下的搜索提示)。
1、插入数据
复制 DELETE articles
POST articles/_bulk
{ "index" : { } }
{ "body": "lucene is very cool"}
{ "index" : { } }
{ "body": "Elasticsearch builds on top of lucene"}
{ "index" : { } }
{ "body": "Elasticsearch rocks"}
{ "index" : { } }
{ "body": "elastic is the company behind ELK stack"}
{ "index" : { } }
{ "body": "Elk stack rocks"}
{ "index" : {} }
{ "body": "elasticsearch is rock solid"}
2、Term Suggester
几种 Suggestion Mode
Missing:如果索引中已经存在,就不提供建议。
复制 POST /articles/_search
{
"size": 1,
"query": {
"match": {
"body": "lucen rock"
}
},
"suggest": {
"term-suggestion": {
"text": "lucen rock",
"term": {
"suggest_mode": "missing",
"field": "body"
}
}
}
}
3、Phrase Suggester
在 Term Suggester 的基础上增加了一些额外的逻辑。
Max Errors:最多可以拼错的 Terms 数。
Confidence:控制返回建议的置信度阈值。只有当建议短语的原始得分加上长度归一化后 ≥confidence 时才会被返回,默认为 1。
复制 POST /articles/_search
{
"suggest": {
"my-suggestion": {
"text": "lucne and elasticsear rock hello world ",
"phrase": {
"field": "body",
"max_errors":2,
"confidence":2,
"direct_generator":[{
"field":"body",
"suggest_mode":"always"
}],
"highlight": {
"pre_tag": "<em>",
"post_tag": "</em>"
}
}
}
}
}
4、Completion Suggester
提供了自动补全功能。对性能要求比较严苛,采用了非倒排索引的数据结构,将 Analyze 数据编码成 FST 和索引一起存放。FST 会整个加载进内存,速度很快。同时 FST 仅支持前缀查找。
复制 DELETE articles
PUT articles
{
"mappings": {
"properties": {
"title_completion":{
"type": "completion"
}
}
}
}
POST articles/_bulk
{ "index" : { } }
{ "title_completion": "lucene is very cool"}
{ "index" : { } }
{ "title_completion": "Elasticsearch builds on top of lucene"}
{ "index" : { } }
{ "title_completion": "Elasticsearch rocks"}
{ "index" : { } }
{ "title_completion": "elastic is the company behind ELK stack"}
{ "index" : { } }
{ "title_completion": "Elk stack rocks"}
{ "index" : {} }
复制 POST articles/_search?pretty
{
"size": 0,
"suggest": {
"article-suggester": {
"prefix": "elk ",
"completion": {
"field": "title_completion"
}
}
}
}
5、Context Suggester
是 Completion Suggester 的拓展,能够在搜索中加入更多的上下文信息。
可以定义两种类型的 Context:
实现 Context Suggester 的具体步骤:
索引数据,并且为每个文档加入 Context 信息。
结合 Context 进行 Suggestion 查询。
复制 DELETE comments
PUT comments
PUT comments/_mapping
{
"properties": {
"comment_autocomplete":{
"type": "completion",
"contexts":[{
"type":"category",
"name":"comment_category"
}]
}
}
}
POST comments/_doc
{
"comment":"I love the star war movies",
"comment_autocomplete":{
"input":["star wars"],
"contexts":{
"comment_category":"movies"
}
}
}
POST comments/_doc
{
"comment":"Where can I find a Starbucks",
"comment_autocomplete":{
"input":["starbucks"],
"contexts":{
"comment_category":"coffee"
}
}
}
复制 POST comments/_search
{
"suggest": {
"MY_SUGGESTION": {
"prefix": "sta",
"completion":{
"field":"comment_autocomplete",
"contexts":{
"comment_category":"coffee"
}
}
}
}
}
Demo
全文搜索
tmdb-search
是电影搜索项目,主要用于索引和搜索 TMDB(The Movie Database)的电影数据。
项目组成部分
ingest_tmdb_from_file.py
: 将 TMDB 数据导入 Elasticsearch
ingest_tmdb_to_appserarch.py
: 将数据导入 AppSearch(可选功能)
mapping/english_analyzer.json
: 默认英文分析器配置
mapping/english_english_3_shards.json
: 3 分片的配置版本
多个针对 "Space Jam" 电影的查询示例,展示不同的查询策略
快速开始
确保已安装所需 Python 包和 Python3 环境:
复制 pip install -r requirements.txt
ElasticSearch 的安装详见上方快速开始章节内容。
复制 python ingest_tmdb_from_file.py
复制 # 普通搜索
python query_tmdb.py
# 带高亮显示的搜索
python query_tmdb.py highlight
整体流程
参考
https://github.com/onebirdrocks/geektime-ELK/
https://www.elastic.co/elasticsearch