ElasticSearch

一、 ElasticSearch介绍

Elasticsearch 是一个分布式、可扩展、实时的搜索和分析引擎,基于 Apache Lucene 构建。它能够快速存储、搜索和分析大量数据,广泛应用于全文搜索、日志分析、业务指标监控等场景。

Lucene 介绍

Lucene是开源、免费、高性能、纯Java编写的全文检索工具包

他是一个全文检索的工具包,是一个全文检索框架,并不是一个全文检索引擎

它非常复杂,并且需要Java集成使用

Lucene 和 ElasticSearch

  • ElasticSearch和solr是基于lucene的开源项目
  • ElasticSearch通过简单易用的restful api接口,隐藏了lucene的复杂性
  • ElasticSearch自带分布式管理,并且可以跨语言使用

Lucene 和 Solr

  • ElasticSearch自带分布式管理而Solr需要借助Zookpeeper实现分布式管理
  • Solr支持多格式的数据,在传统搜索中表现好于ES,但是它的更新效率比较低
  • ElasticSearch只支持json格式的数据,在处理实时索引搜索时明显好于Solr

二、. 核心概念

与关系型数据库的类比

关系型数据库 (MySQL)Elasticsearch
Database (数据库)Index (索引)
Table (表)Type (类型,已弃用)
Row (行)Document (文档)
Column (列)Field (字段)
Schema (模式)Mapping (映射)

注意:Elasticsearch 7.x 开始移除了 Type 概念,现在一个索引直接包含文档。

1. 文档(Document)

  • Elasticsearch 中的基本数据单元,类似于关系数据库中的一行记录。
  • 文档以 JSON 格式存储,包含多个字段。
  • 示例:
{
  "id": 1,
  "title": "Elasticsearch Guide",
  "content": "Elasticsearch is a distributed search engine.",
  "tags": ["search", "distributed"]
}

2.索引(Index)

  • 索引是文档的集合,类似于关系数据库中的表。
  • 每个索引有一个唯一的名称,用于标识和操作数据。
  • 示例:books 索引存储所有书籍相关的文档。

3.类型(Type)(已弃用)

在早期版本中,索引可以包含多个类型(类似于表结构),但在 Elasticsearch 7.x 及更高版本中已被弃用。

4.分片(Shard)

  • 索引可以被分成多个分片,每个分片是一个独立的 Lucene 索引。
  • 分片允许数据水平拆分,支持分布式存储和并行处理。
  • 分片分为主分片(Primary Shard)和副本分片(Replica Shard)。

5.节点(Node)

  • 节点是 Elasticsearch 集群中的一个服务器实例,负责存储数据和执行操作。
  • 节点可以扮演不同的角色(如主节点、数据节点、协调节点)。

6.集群(Cluster)

  • 集群由一个或多个节点组成,共同存储数据并提供搜索服务。
  • 集群通过唯一的名称标识。

3.架构与工作原理

1.分布式架构

  • Elasticsearch 采用分布式设计,数据存储在多个节点上。
  • 数据被分成多个分片,每个分片可以有多个副本,确保高可用性和容错性。

2.数据写入流程

  1. 客户端发送写入请求到协调节点。
  2. 协调节点根据文档 ID 计算目标分片,并将请求转发到主分片所在的节点。
  3. 主分片写入数据后,同步到副本分片。
  4. 写入成功后,返回响应给客户端。

3.数据搜索流程

  1. 客户端发送搜索请求到协调节点。
  2. 协调节点将请求广播到所有相关分片(主分片或副本分片)。
  3. 每个分片执行搜索操作,返回结果给协调节点。
  4. 协调节点合并结果,排序后返回给客户端。

4.倒排索引

  • Elasticsearch 使用倒排索引(Inverted Index)实现快速全文搜索。
  • 倒排索引将文档中的每个词映射到包含该词的文档列表。
  • 示例:
    • 文档 1:{“content”: “Elasticsearch is fast”}
    • 文档 2:{“content”: “Elasticsearch is distributed”}
    • 倒排索引:
    "Elasticsearch" -> [文档1, 文档2]
    "fast" -> [文档1]
    "distributed" -> [文档2]
    

三、主要特性

1.高性能

  • 支持实时搜索和分析,响应时间通常在毫秒级。
  • 通过分布式架构和倒排索引,能够快速处理大规模数据。

2.可扩展性

  • 支持水平扩展,可以通过增加节点来提升存储和计算能力。
  • 自动分片和副本机制,确保数据分布均衡。

3.高可用性

  • 通过副本分片实现数据冗余,确保节点故障时数据不丢失。
  • 主节点选举机制,确保集群的高可用性。

4.丰富的查询功能

  • 支持全文搜索、结构化搜索、模糊搜索、范围搜索等多种查询方式。
  • 提供聚合(Aggregation)功能,支持数据统计和分析。

5.插件生态

  • 支持丰富的插件,扩展 Elasticsearch 的功能。
  • 例如:IK 分词插件(中文分词)、Elasticsearch-Hadoop(与 Hadoop 集成)。
    • ik分词器 一个标准的中文分词器。可以根据定义的字典对域进行粉刺,并且支持用户配置自己的字典,所以它除了可以按通用的习惯分词外,还可以定制化分词。 可以使用插件的方式将他接入到ES。

      ik分词器有两种分词方式:ik_smart最粗粒度的拆分和ik_max_word最细粒度的拆分。

四、使用场景

1.全文搜索与信息检索

  • 适用于搜索引擎、电商网站的商品搜索、内容管理系统的文档搜索等场景。
  • 示例:一个电商网站有100万件商品,用户搜索“手机 2000元以内 拍照好”,传统MySQL的LIKE查询可能需要数秒甚至超时,而Elasticsearch通过倒排索引和组合查询,能在毫秒级返回结果。

2.日志分析与可观测性

  • 与 Logstash 和 Kibana 结合,构建日志管理和分析系统(ELK Stack)。
  • 示例:分析服务器日志,监控系统状态,排查故障。

3.向量检索与AI应用

  • Elasticsearch 内置向量数据库能力,支持将文本、图像等转换为向量嵌入,进行语义相似度搜索。这是当前 RAG(检索增强生成)和 AI 助手的核心基础设施。

4. 数据分析与商业智能

电子商务、广告业务等需要多维度统计分析。Elasticsearch 支持复杂聚合查询,可在数亿条数据中实时完成按分类统计、趋势分析等操作。

5. 数据库查询加速

传统关系型数据库在复杂查询场景下性能不足,可通过数据同步将数据导入Elasticsearch,利用其高并发低延迟的查询能力加速检索。

五、优缺点

1.优点

  • 高性能:支持实时搜索和分析。
  • 可扩展性:支持水平扩展,适应大规模数据。
  • 高可用性:通过副本机制确保数据安全。
  • 易用性:提供 RESTful API,易于集成和使用。

2.缺点

  • 资源消耗:对内存和 CPU 要求较高。
  • 复杂性:分布式系统的部署和维护较为复杂。
  • 数据一致性:在极端情况下可能出现数据不一致问题。

六、PHP 开发实践

使用官方推荐的 elasticsearch-php 客户端:

composer require elasticsearch/elasticsearch

客户端版本需与 Elasticsearch 服务器大版本匹配。例如 Elasticsearch 8.x 使用 elasticsearch-php 8.x。语言客户端向前兼容,但新功能需要新版本客户端支持。

连接:

<?php
require 'vendor/autoload.php';

use Elasticsearch\ClientBuilder;

// 无安全认证模式
$client = ClientBuilder::create()
    ->setHosts(['http://localhost:9200'])
    ->build();

// 带用户名密码认证
$client = ClientBuilder::create()
    ->setHosts(['http://localhost:9200'])
    ->setBasicAuthentication('username', 'password')
    ->build();

// HTTPS + 证书验证
$client = ClientBuilder::create()
    ->setHosts(['https://localhost:9200'])
    ->setBasicAuthentication('username', 'password')
    ->setSSLVerification('/path/to/cert.pem')  // 或 true/false
    ->build();

// 检查集群健康状态
$health = $client->cluster()->health();
print_r($health);
// 返回 'status' => 'green' 表示集群正常

连接配置的关键参数:

  • 超时设置:setConnectionParams([‘client’ => [’timeout’ => 10, ‘connect_timeout’ => 5]])

  • 重试次数:setRetries(3)

  • HTTPS安全验证:生产环境建议开启证书验证,测试环境可临时关闭setSSLVerification(false)

示例:使用 Elasticsearch 构建商品搜索系统

  1. 数据准备

    1. 创建 products 索引,定义字段映射:
    PUT /products
     {
       "mappings": {
         "properties": {
           "name": { "type": "text" },
           "price": { "type": "float" },
           "category": { "type": "keyword" }
         }
       }
     }
    
    $params = [
        'index' => 'products',
        'body' => [
            'settings' => [
                'number_of_shards' => 2,      // 分片数
                'number_of_replicas' => 1     // 副本数
            ],
            'mappings' => [
                'properties' => [
                    'name' => [
                        'type' => 'text',      // 全文搜索字段,使用分析器
                        'analyzer' => 'ik_max_word'  // 中文分词(需安装IK插件)
                    ],
                    'price' => [
                        'type' => 'double'     // 数值字段,用于范围查询和排序
                    ],
                    'category' => [
                        'type' => 'keyword'    // 精确匹配字段,不进行分词
                    ],
                    'description' => [
                        'type' => 'text'
                    ],
                    'created_at' => [
                        'type' => 'date'
                    ]
                ]
            ]
        ]
    ];
    
    $response = $client->indices()->create($params);
    
  2. 写入数据

    1. 插入商品数据:
    POST /products/_doc/1
     {
       "name": "Smartphone X",
       "price": 599.99,
       "category": "Electronics"
     }
    
  3. 搜索数据

    1. 搜索名称包含 “Smartphone” 的商品:
    GET /products/_search
     {
       "query": {
         "match": {
           "name": "Smartphone"
         }
       }
     }
    
  4. 聚合分析

    1. 按类别统计商品数量:
    GET /products/_search
     {
       "size": 0,
       "aggs": {
         "categories": {
           "terms": {
             "field": "category"
           }
         }
       }
     }
    
  5. 更新

    1. 完全替换文档

      通过指定文档 ID,直接覆盖旧文档。若文档不存在,则会创建新文档。

      示例:更新商品价格

      PUT /products/_doc/1
      {
        "name": "Smartphone X",
        "price": 549.99,  // 价格从 599.99 更新为 549.99
        "category": "Electronics"
      }
      

      操作说明:

      • Elasticsearch 会先删除旧文档(ID=1),再写入新文档。
      • 即使只修改部分字段,也需要传递完整的文档内容。
    2. 部分更新(Partial Update)

      使用 _update API,仅更新文档的指定字段,无需传递完整文档。

      语法:

      POST /<index>/_update/<doc_id>
      {
        "doc": {
          "field1": "new_value1",
          "field2": "new_value2"
        }
      }
      

      示例:仅更新商品价格

      POST /products/_update/1
      {
        "doc": {
          "price": 499.99
        }
      }
      

      操作说明:

      • Elasticsearch 内部会执行以下操作:

        1. 获取旧文档。

        2. 合并新旧文档的字段。

        3. 删除旧文档,写入新文档。

      • 优点:减少网络传输数据量,适合仅更新少量字段的场景。

    3. 使用脚本更新(Scripted Update)

      通过 Painless 脚本动态更新文档字段,支持复杂的逻辑(如条件更新、计算字段值等)。

      语法:

      POST /<index>/_update/<doc_id>
      {
        "script": {
          "source": "ctx._source.<field> = <value>",
          "lang": "painless"
        }
      }
      

      示例:将商品价格打 9 折

      POST /products/_update/1
      {
        "script": {
          "source": "ctx._source.price *= 0.9",
          "lang": "painless"
        }
      }
      

      示例:条件更新(仅当价格高于 500 时打折)

      POST /products/_update/1
      {
        "script": {
          "source": """
            if (ctx._source.price > 500) {
              ctx._source.price *= 0.8;
            }
          """,
          "lang": "painless"
        }
      }
      

    4.批量更新

    使用 _bulk API 批量更新文档,提升效率。

    POST /_bulk
    { "update": { "_index": "products", "_id": "1" } }
    { "doc": { "price": 499.99 } }
    { "update": { "_index": "products", "_id": "2" } }
    { "doc": { "price": 299.99 } }