目 录CONTENT

文章目录

ElasticSearch

FatFish1
2026-02-26 / 0 评论 / 0 点赞 / 14 阅读 / 0 字 / 正在检测是否收录...

ES的优势

相比其他的存储引擎,ES的优势主要在于:

  • 全文搜索:ES使用倒排索引和实时索引更新,使得搜索性能非常的好,远比数据库的全表索引要好

  • 指标分析:ES的存储结构是json,天生就适合从数据中提取指标,进行指标搜索和聚合,擅长排序、分页、聚合等操作

ES一个非常经典的应用就是ELK(ES+logstash+kibana)日志框架:

  • ES:承担数据仓储和检索能力

  • logstash:承担日志搬运、分析能力

  • kibana:承担前端接口,使ES的数据结果具备可读性

ES的数据存储结构

逻辑结构

ES的逻辑存储结构是json,一切数据最终都被序列化为json对象,称之为“文档(Document)”

每个文档包括两部分:元数据、数据

  • 元数据(Metadata)描述文档自身的信息,通常以“_”开头,包括:

    • _index:文档所属的索引,类似于数据库的“库”或“表”。

    • _id:文档的唯一标识符,可以手动指定或由系统自动生成。

    • _version:文档的版本号,用于并发控制。

    • _source:存储原始的 JSON 数据本身。

  • 数据(Field)以键值对的形式存在,有不同的数据类型,被存储在"_source"字段的value中:

    • 文本Text:简单的字符串类型

    • 关键字Keyword:用于精确匹配、过滤、排序、聚合的字段,例如"tags": ["elasticsearch", "database"]

    • 数字Numeric:简单的数字类型

    • 日期Date:支持多种格式的日期字符串或时间戳,例如"join_date": "2024/05/01"

    • 布尔Boolean:true或false

    • 对象Object:JSON内嵌对象,也是以json形式组织

    • 地理位置Geo:用于存储地理位置或形状,例如"location": {"lat": 40.71, "lon": -74.01}

一个简单的文档如下:

{
  "_index": "my-first-index",
  "_id": "1",
  "_version": 1,
  "_source": { // _source 里存放的就是原始的 JSON 数据
    "email": "john@example.com",
    "first_name": "John",
    "last_name": "Smith",
    "info": {
      "bio": "Eco-warrior",
      "age": 25,
      "interests": ["dolphins", "whales"]
    },
    "join_date": "2024/05/01"
  }
}

物理结构

ES的物理存储结构主要是三个点:分片、倒排索引、段

  • 分片Shard:在物理上被划分为多个分片。每个分片本质上是一个完整的 Lucene 索引,它是 Elasticsearch 实现分布式和并行处理的基础。当你写入一个文档时,它会被路由到某个特定的主分片中存储

  • 倒排索引Inverted Index:这是 Elasticsearch 能够实现快速全文搜索的核心数据结构。它不是记录“文档包含哪些词”,而是记录“每个词出现在哪些文档中”

    • 词典:所有不重复的词项列表

    • 倒排列表:包含这些词项的文档ID列表。比如,为“Elasticsearch”这个词建立的倒排索引,会记录它存在于文档1、文档5、文档10中。这样,当你搜索“Elasticsearch”时,系统能立刻定位到这些文档,而不是逐个文档去扫描。

  • 段Segment:分片内部的数据还会进一步被组织成多个“段”。段本身是一个小型的、不可变的倒排索引。新文档首先写入内存,然后定期刷写到磁盘上,形成一个段。后台还有一个合并过程,会将多个小段合并成一个大段,以清理数据并优化查询性能。

分词

因为ES是基于文本构建倒排索引,就必须知道这个文本中有哪些词,因此ES存储时必须进行分词

分词仅对TEXT类型操作,用于全文匹配搜索,而keyword类型不会分词,将整个字符串作为词项存入索引,用于精确匹配

正常情况下,ES会对存入的数据进行自动映射,但是实际业务场景,可能一个字段既需要分词匹配,又需要精确查询,这时,我们可以主动映射,创建索引,再上传数据就可以了,参考后面http://www.chymfatfish.cn/archives/elasticsearch#%E4%B8%BB%E5%8A%A8%E6%98%A0%E5%B0%84%E7%B4%A2%E5%BC%95

ES的api

ES的查询方法

  • 全文查询:match, match_phrase, match_phrase_prefix, multi_match, query_string, simple_query_string

  • 精确查询:term, terms, range, exists, missing, wildcard, regexp, fuzzy, prefix, ids

  • 复合查询:bool, boosting, constant_score, dis_max, function_score

  • 地理查询:geo_distance, geo_bounding_box, geo_polygon, geo_shape

  • 连接查询:nested, has_child, has_parent

  • 特殊查询:more_like_this, script, percolate

match和term

在请求体中传入query参数,标识查询方法和查询内容,例如:

{
    "query": {
        "term": {
            "title": "Elasticsearch Guide"
        }
    }
}

即以term查询方法,查询title为Elasticsearch Guide的内容

term和match是最常用的两个查询方法

  • term:精确匹配

    • 机制:不会对查询词进行分析,直接将输入当作一个整体去倒排索引中查找完全相等的词项

    • 适用字段:通常用于 keyword 类型(或未经过分析的字段),例如枚举值、ID、状态标签等。

    • 特点

      • 区分大小写(必须完全一致)

      • 不进行分词,搜索词必须与索引中的词项完全匹配

      • 如果用于 text 字段,通常无法匹配到预期结果(因为 text 字段在索引时已被分词,存储的是多个词项,而 term 查询的是整个短语)

    • 案例:文档有text类型{"title": "Elasticsearch Guide"},且 title 是 text 类型,索引时被分词为 ["elasticsearch", "guide"],则以{ "query": { "term": { "title": "Elasticsearch Guide" } } } 查询时,是查不到的

  • match:全文搜索

    • 机制先对查询词进行分析(如同索引时的分析器),将输入拆分为多个词项,然后查找包含任意(或全部)这些词项的文档,并计算相关性得分

    • 适用字段:主要用于 text 类型,适合全文检索场景,如文章内容、商品描述等。

    • 特点

      • 不区分大小写(取决于分析器,通常转小写)。

      • 支持分词,可以匹配包含部分关键词的文档

      • 默认行为是 OR(任一词项匹配即可),可通过 operator 改为 AND

    • 案例:还是上面的text类型{"title": "Elasticsearch Guide"},因为这里会分词,记录成Elasticsearch和Guide两个,使用{ "query": { "match": { "title": "Elasticsearch Guide" } } } 查询时,会对查询内容页进行分词,分成Elasticsearch和guide,然后去匹配,发现or匹配是可以匹配上的,因此能查到结果。当然,使用AND匹配也能匹配上,即{"query":{"match":{"title":{"query":"elasticsearch guide","operator":"and"}}}}

全文查询

全文查询:match, match_phrase, match_phrase_prefix, multi_match, query_string, simple_query_string

对输入文本进行分词,然后去倒排索引中匹配,例如:

  • match_phrase:短语匹配,要求分词后的词项不仅出现,而且顺序一致,{"match_phrase": {"title": "Elasticsearch guide"}}

精确值查询

精确查询:term, terms, range, exists, missing, wildcard, regexp, fuzzy, prefix, ids

这类查询不会对输入进行分析,直接查找倒排索引中的精确词项,适用于 keyword、数值、日期等字段,例如:

  • range:范围查询,支持数值、日期、ip等,{"range": {"price": {"gte": 10, "lte": 100}}}

  • exists:查询存在性,{"exists": {"field": "tags"}}

  • prefix:前缀匹配,{"prefix": {"code": "abc"}}

复合查询

复合查询:bool, boosting, constant_score, dis_max, function_score

用于组合多个查询,控制逻辑关系。

地理查询

地理查询:geo_distance, geo_bounding_box, geo_polygon, geo_shape

针对地理坐标和形状的查询。

连接查询

连接查询:nested, has_child, has_parent

用于在父子文档或嵌套对象之间建立关联。

特殊查询

特殊查询:more_like_this, script, percolate

Python对接ES

查询

首先需要安装elasticsearch库

pip install elasticsearch

一个基础查询案例,使用match_all方法无差别匹配:

from elasticsearch import Elasticsearch

# 1. 连接到 Elasticsearch(默认地址:http://localhost:9200)
es = Elasticsearch("http://localhost:9200")

# 检查连接是否成功
if not es.ping():
    raise ValueError("连接失败,请确认 Elasticsearch 是否运行在 http://localhost:9200")

# 2. 定义索引名和查询
index_name = "my_index"          # 替换为你的索引名
query_body = {
    "query": {
        "match_all": {}          # 查询所有文档(可根据需要修改)
    },
    "size": 10                    # 返回前10条结果
}

# 3. 执行查询
try:
    response = es.search(index=index_name, body=query_body)
except Exception as e:
    print(f"查询出错: {e}")
    exit(1)

# 4. 处理结果
hits = response["hits"]["hits"]   # 获取匹配的文档列表
total = response["hits"]["total"]["value"]  # 总匹配数

print(f"总共找到 {total} 条文档,返回 {len(hits)} 条:")
for doc in hits:
    print(f"ID: {doc['_id']}, 得分: {doc['_score']}, 内容: {doc['_source']}")

主动映射索引

from elasticsearch import Elasticsearch

# 连接
es = Elasticsearch("http://localhost:9200")
if not es.ping():
    raise ConnectionError("无法连接到 Elasticsearch")

index_name = "products"

# mappings 定义
mapping = {
    "settings": {
        "number_of_shards": 3,
        "number_of_replicas": 1
    },
    "mappings": {
        "properties": {
            "product_id": {"type": "keyword"},
            "name": {
                "type": "text",
                # 多字段:name 用于搜索,name.keyword 用于排序/聚合
                # ignore_above 256指的是超过该长度的字符串不索引为 keyword
                "fields": {"keyword": {"type": "keyword", "ignore_above": 256}}
            },
            "price": {"type": "float"},
            "created_at": {"type": "date", "format": "yyyy-MM-dd HH:mm:ss"},
            "tags": {"type": "keyword"},
            "description": {"type": "text", "analyzer": "standard"}
        }
    }
}

# 创建索引(如果已存在则忽略)
if not es.indices.exists(index=index_name):
    response = es.indices.create(index=index_name, body=mapping)
    print("索引创建成功")
else:
    print("索引已存在,如需更新请先删除或使用别名迁移")

# 验证
print(es.indices.get_mapping(index=index_name))

可以通过python字典和es.indices.create 方法进行索引的主动映射,name字段指定type为text,即创建了一个name索引,然后加了一个fields,指的是额外创建了一个name.keyword索引,用于排序、聚合

然后可以使用es.index方法指定对应的索引进行数据上传

from elasticsearch import Elasticsearch

es = Elasticsearch("http://localhost:9200")

doc = {
    "product_id": "P1001",
    "name": "Elasticsearch 实战",
    "price": 59.9,
    "created_at": "2025-03-15 10:30:00",
    "tags": ["搜索引擎", "大数据"],
    "description": "这是一本关于Elasticsearch的权威指南,涵盖从入门到精通。"
}

# 上传文档,自动生成 ID
resp = es.index(index="products", body=doc)
print(resp)

# 或者指定文档 ID
# resp = es.index(index="products", id="1", body=doc)

也可以使用helpers.bulk批量上传

from elasticsearch import Elasticsearch, helpers

es = Elasticsearch("http://localhost:9200")

actions = [
    {
        "_index": "products",
        "_id": i,  # 可选,不指定则自动生成
        "_source": {
            "product_id": f"P{i:04d}",
            "name": f"产品 {i}",
            "price": 10.0 + i,
            "created_at": "2025-03-15 12:00:00",
            "tags": ["tag1", "tag2"],
            "description": f"这是第 {i} 个产品的描述。"
        }
    }
    for i in range(1, 101)  # 生成100条数据
]

success, failed = helpers.bulk(es, actions, stats_only=True)
print(f"成功: {success}, 失败: {failed}")

注意:

  • 字段名必须一致文档中的字段名必须与映射中定义的完全一致(大小写敏感),否则如果动态映射开启(默认),新字段会被自动添加映射,可能导致类型不符合预期。

  • 动态映射行为:默认情况下("dynamic": true),如果上传的文档包含映射中未定义的字段,ES 会自动推断类型并添加映射。如果你希望严格禁止未定义字段,可以在创建索引时设置 "dynamic": "strict",此时上传包含未知字段的文档会报错。

  • 日期格式日期字段必须符合映射中定义的格式,否则会引发解析错误。

  • 数组处理:任何字段都可以接受数组值,ES 会自动处理(数组中元素类型需一致)。

基于term和match匹配

基于上面创建的索引和存储的数据,使用match和term两种方法匹配

# 测试 text 字段分词搜索
resp = es.search(index="products", body={
    "query": {"match": {"name": "实战"}}
})
print(resp["hits"]["total"]["value"])  # 应返回包含"实战"的文档

# 测试 keyword 字段精确匹配
resp = es.search(index="products", body={
    "query": {"term": {"product_id": "P1001"}}
})
print(resp["hits"]["total"]["value"])  # 应返回1

组合查询使用示例

query = {
    "bool": {
        "must": [
            {"match": {"title": "python book"}}
        ],
        "filter": [
            {"range": {"price": {"lte": 50}}},
            {"term": {"status": "published"}}
        ],
        "should": [
            {"match": {"description": "beginner"}}
        ],
        "minimum_should_match": 1  # 至少满足一个 should 条件
    }
}

response = es.search(index="products", body={"query": query})

可以关注一下bool类型,即对不同的条件进行组合,must代表AND操作,should代表OR操作

复杂一些,一般不太用到

Kibana与ES的协作

在ELK架构中,Kibana承担了前端/用户界面的能力,他的职责是将KQL解析成ES请求体,然后传给ES获取结果

常见的KQL与ES查询DSL映射如下:

场景

KQL

ES查询

说明

精确匹配

response: 200

{ "term": { "response": "200" } }

适用于keyword,即必须查response值为200对应的数据

全文搜索

message: "user login"

{ "match": { "message": "user login" } }

加双引号,KQL会按match类型解析,即查message中包含user 或 login的

范围查询

bytes > 1000

{ "range": { "bytes": { "gt": 1000 } } }

逻辑与 (AND)

response: 200 AND bytes > 1000

{ "bool": { "must": [ { "term": {...} }, { "range": {...} } ] } }

KQL中的AND会被转化成bool-must查询方法,同时注意使用的是term匹配,即使用AND就不能分词了

逻辑或 (OR)

response: 404 OR response: 500

{ "bool": { "should": [ { "term": {...} }, { "term": {...} } ] } }

KQL中的OR会被转化成bool-should查询方法,同时注意使用的是term匹配,即使用OR就不能分词了

逻辑非 (NOT)

NOT response: 200

{ "bool": { "must_not": [ { "term": {...} } ] } }

KQL中的NOT会被转化成bool-must_not查询方法,同时注意使用的是term匹配,即使用NOT就不能分词了

存在性检查

response: *

{ "exists": { "field": "response" } }

嵌套字段查询

user.name: "john"

{ "term": { "user.name": "john" } }

组合复杂逻辑

(response: 200 AND bytes > 1000) OR extension: "php"

一个更复杂的 bool 查询,其中包含 should 子句,should 里又包含 must 子句

0

评论区