跳转至

第9章:搜索架构

搜索架构

9.1 搜索架构概述

搜索架构的作用

搜索架构用于提供高效的全文搜索功能,支持复杂的查询条件,快速检索大量数据。

搜索架构的特点

  1. 全文检索:支持全文搜索
  2. 高性能:快速检索大量数据
  3. 灵活查询:支持复杂的查询条件
  4. 可扩展:支持水平扩展

9.2 Elasticsearch

9.2.1 Elasticsearch概述

Elasticsearch是一个基于Lucene的分布式搜索引擎,提供全文搜索、分析、存储等功能。

特点: - 分布式架构 - 实时搜索 - 高性能 - 易于扩展

9.2.2 基础概念

索引(Index):类似于数据库的数据库

类型(Type):类似于数据库的表(ES 7.x已废弃)

文档(Document):类似于数据库的行

字段(Field):类似于数据库的列

分片(Shard):索引的水平拆分

副本(Replica):分片的复制

9.2.3 索引设计

Python
from elasticsearch import Elasticsearch

# 连接Elasticsearch实例(默认端口9200)
es = Elasticsearch(['http://localhost:9200'])

# 创建索引——定义字段映射(mappings)和索引设置(settings)
index_mapping = {
    "mappings": {
        "properties": {
            "title": {
                "type": "text",              # text类型:支持全文检索,会被分词器处理
                "analyzer": "ik_max_word"     # ik_max_word:最细粒度分词,召回率更高
            },
            "content": {
                "type": "text",
                "analyzer": "ik_max_word"
            },
            "author": {
                "type": "keyword"            # keyword类型:精确匹配,不会被分词
            },
            "publish_date": {
                "type": "date"               # date类型:支持日期范围查询和排序
            },
            "views": {
                "type": "integer"            # integer类型:支持数值范围查询和聚合
            }
        }
    },
    "settings": {
        "number_of_shards": 3,               # 主分片数:决定数据的水平拆分数量,创建后不可更改
        "number_of_replicas": 1              # 副本数:每个主分片的副本数量,提高可用性和读性能
    }
}

# 调用API创建名为'articles'的索引
es.indices.create(index='articles', mappings=index_mapping['mappings'], settings=index_mapping['settings'])

9.2.4 数据索引

Python
# 索引单个文档——将一条数据写入指定索引
article = {
    "title": "Elasticsearch入门教程",
    "content": "Elasticsearch是一个基于Lucene的分布式搜索引擎",
    "author": "张三",
    "publish_date": "2024-01-01",
    "views": 1000
}

# 指定索引名、文档ID和文档内容,写入单条数据
es.index(index='articles', id=1, document=article)

# 批量索引——使用bulk API一次性写入多条数据,比逐条写入性能更高
articles = [
    {
        "title": "Python编程入门",
        "content": "Python是一种简单易学的编程语言",
        "author": "李四",
        "publish_date": "2024-01-02",
        "views": 2000
    },
    {
        "title": "机器学习基础",
        "content": "机器学习是人工智能的一个分支",
        "author": "王五",
        "publish_date": "2024-01-03",
        "views": 3000
    }
]

from elasticsearch.helpers import bulk

# 构造批量操作列表:每个action包含目标索引、文档ID和文档内容
actions = [
    {
        "_index": "articles",        # 目标索引名
        "_id": i + 2,                # 文档ID(从2开始,避免与上面的id=1冲突)
        "_source": article           # 文档内容
    }
    for i, article in enumerate(articles)  # enumerate同时获取索引和值
]

# 执行批量写入,内部会自动分批发送请求
bulk(es, actions)

9.2.5 查询优化

基本查询

Python
# 匹配查询(match):对查询词进行分词后匹配,是最常用的全文检索查询
query = {
    "match": {
        "title": "Elasticsearch"    # 在title字段中搜索,会先对查询词分词再匹配
    }
}

result = es.search(index='articles', query=query)

# 多字段查询(multi_match):同时在多个字段中搜索,取最高评分
query = {
    "multi_match": {
        "query": "搜索引擎",           # 查询关键词
        "fields": ["title", "content"]  # 同时搜索title和content两个字段
    }
}

result = es.search(index='articles', query=query)

布尔查询

Python
# 布尔查询(bool):通过组合多个子查询实现复杂的搜索逻辑
query = {
    "bool": {
        "must": [                                        # must:所有条件必须满足(类似AND)
            {"match": {"title": "Elasticsearch"}},
            {"match": {"content": "搜索引擎"}}
        ],
        "should": [                                      # should:满足则加分,不满足也可以(类似OR加权)
            {"match": {"author": "张三"}}
        ],
        "must_not": [                                    # must_not:必须不满足(类似NOT),不影响评分
            {"match": {"content": "Python"}}
        ]
    }
}

result = es.search(index='articles', query=query)

范围查询

Python
# 范围查询(range):根据数值或日期范围筛选文档
query = {
    "range": {
        "publish_date": {
            "gte": "2024-01-01",    # gte = greater than or equal,大于等于
            "lte": "2024-01-31"     # lte = less than or equal,小于等于
        }
    }
}

# 也支持gt(大于)、lt(小于)等操作符
result = es.search(index='articles', query=query)

聚合查询

Python
# 聚合查询(aggs):类似SQL中的GROUP BY,用于数据统计和分析
query = {
    "match_all": {}                  # 匹配所有文档(不做过滤)
}
aggs = {
    "author_stats": {                # 自定义聚合名称
        "terms": {                   # terms聚合:按字段值分桶(类似GROUP BY)
            "field": "author"        # 按author字段分组
        },
        "aggs": {                    # 嵌套聚合:在每个分桶内再做统计
            "avg_views": {           # 计算每个作者的平均浏览量
                "avg": {
                    "field": "views"
                }
            }
        }
    }
}

# 执行查询,结果中aggregations字段包含聚合统计数据
result = es.search(index='articles', query=query, aggs=aggs)

9.2.6 性能优化

索引优化

Python
# 索引优化:选择合适的分析器以平衡搜索精度和性能
# ik_smart:粗粒度分词,速度更快,适合对性能要求高的场景
# ik_max_word:细粒度分词,召回率更高,但索引体积更大
index_mapping = {
    "mappings": {
        "properties": {
            "title": {
                "type": "text",
                "analyzer": "ik_smart"  # ik_smart比ik_max_word更快,分词粒度更粗
            }
        }
    }
}

查询优化

Python
# 性能优化:对不需要相关性评分的条件使用filter而非must
# filter不计算_score评分,且结果会被缓存,查询速度显著提升
query = {
    "bool": {
        "filter": [                                              # filter上下文:只做过滤,不影响评分
            {"term": {"author": "张三"}},                         # term精确匹配(keyword字段推荐用term)
            {"range": {"publish_date": {"gte": "2024-01-01"}}}   # 范围过滤
        ]
    }
}

result = es.search(index='articles', query=query)

分页优化

Python
# 深分页优化:from/size在深分页时性能急剧下降(需要跳过大量文档)
# search_after通过上一页最后一条记录的排序值定位下一页,性能恒定
result = es.search(
    index='articles',
    query={"match_all": {}},
    size=10,                                       # 每页返回10条
    sort=[
        {"publish_date": {"order": "desc"}},       # 主排序:按发布日期倒序
        {"_id": {"order": "asc"}}                  # 次排序:确保排序唯一性(避免同值文档顺序不确定)
    ]
)
# 提取最后一条文档的排序值,作为下一页的游标
last_sort = result['hits']['hits'][-1]['sort']  # 负索引:从末尾倒数访问元素

# 下一页:传入search_after参数,从上一页结尾处继续
result = es.search(
    index='articles',
    query={"match_all": {}},
    size=10,
    sort=[
        {"publish_date": {"order": "desc"}},
        {"_id": {"order": "asc"}}
    ],
    search_after=last_sort                         # 游标分页:基于排序值定位,无深分页性能问题
)

9.3 搜索架构设计

9.3.1 单节点架构

Text Only
应用 -> Elasticsearch

适用场景: - 数据量小 - 查询量小 - 测试环境

9.3.2 集群架构

Text Only
应用 -> Elasticsearch集群
       -> Node1
       -> Node2
       -> Node3

适用场景: - 数据量大 - 查询量大 - 生产环境

9.3.3 读写分离架构

Text Only
应用 -> 读节点(多个副本)
     -> 写节点(主节点)

适用场景: - 读多写少 - 需要高可用

9.3.4 多集群架构

Text Only
应用 -> 主集群(写)
     -> 从集群(读)

适用场景: - 大规模数据 - 高并发查询 - 需要灾备

9.4 搜索优化

9.4.1 索引优化

  1. 合理设置分片数
  2. 使用合适的分析器
  3. 优化字段映射
  4. 使用索引别名

9.4.2 查询优化

  1. 使用filter代替query
  2. 避免深分页
  3. 使用search_after
  4. 合理使用缓存

9.4.3 集群优化

  1. 合理分配分片
  2. 增加副本数
  3. 使用专用节点
  4. 监控集群状态

9.5 实战练习

练习1:设计一个文章搜索系统

设计一个文章搜索系统: 1. 设计索引结构 2. 实现数据索引 3. 实现搜索功能 4. 优化搜索性能

练习2:实现一个电商商品搜索

实现一个电商商品搜索: 1. 设计商品索引 2. 实现多条件搜索 3. 实现聚合统计 4. 优化搜索性能

练习3:设计一个搜索架构

设计一个大规模搜索架构: 1. 确定集群规模 2. 设计分片策略 3. 设计读写分离 4. 设计灾备方案

9.6 面试准备

常见面试题

  1. 什么是Elasticsearch?它有什么特点?
  2. 如何设计Elasticsearch索引?
  3. 如何优化Elasticsearch查询?
  4. Elasticsearch的分片和副本是什么?
  5. 如何设计一个搜索架构?

项目经验准备

准备一个搜索项目: - 使用的搜索技术 - 遇到的挑战 - 解决方案 - 项目成果

9.7 总结

本章介绍了搜索架构,包括Elasticsearch的基础使用、索引设计、查询优化和架构设计。搜索是现代应用的重要功能。

关键要点

  1. Elasticsearch是一个分布式搜索引擎
  2. 索引设计需要考虑字段类型和分析器
  3. 查询优化包括使用filter、避免深分页、使用search_after
  4. 搜索架构包括单节点、集群、读写分离、多集群
  5. 搜索优化需要综合考虑索引、查询、集群

下一步

下一章将深入学习大数据处理架构,包括批处理、流处理等内容。