Elasticsearch 详解:从入门到上手

本文面向 ES 初学者,从安装配置讲起,通过 RESTful API 演示索引管理、文档 CRUD、简单查询、DSL 查询与过滤、映射配置等核心操作,帮助读者快速上手 Elasticsearch。


目录

  1. ES 是什么
  2. 安装与启动
  3. RESTful API 回顾
  4. ES 核心概念
  5. 文档 CRUD 操作
  6. 简单查询
  7. DSL 查询与过滤
  8. 映射 Mapping

一、ES 是什么

1.1 基本概念

Elasticsearch(ES)是一个分布式、RESTful 风格的全文搜索引擎,基于 Apache Lucene 构建。

1.2 ES 的八大特点

① 基于 Lucene     → 底层是高性能的全文搜索库
② 使用简单        → 全部通过 HTTP JSON 操作
③ 分布式          → 天然支持集群、分片、复制
④ RESTful API     → 使用 HTTP 方法(GET/POST/PUT/DELETE)
⑤ 多语言客户端     → Java、PHP、Python、Go 等
⑥ PB 级数据       → 擅长处理海量数据
⑦ 高性能近实时     → 写入后 1 秒可搜索
⑧ JSON 格式       → 所有数据以 JSON 存储和传输

1.3 ES 的应用场景

┌─ 全文搜索:电商商品搜索、博客内容搜索
├─ 日志分析:ELK 栈(ES + Logstash + Kibana)
├─ 指标监控:应用性能监控、业务指标聚合
├─ 推荐系统:基于搜索的相关推荐
└─ 安全分析:入侵检测、异常行为分析

二、安装与启动

2.1 安装 ES

# ===== 前置条件 =====
# 需要 JDK 8 或更高版本(ES 7.x 自带 JDK)

# ===== 下载 ES =====
# 方式 1:官网下载(推荐)
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.0-linux-x86_64.tar.gz

# 方式 2:Docker 安装(最方便)
docker pull elasticsearch:7.17.0
docker run -d --name es -p 9200:9200 -p 9300:9300 \
  -e "discovery.type=single-node" \
  elasticsearch:7.17.0

# ===== 解压安装 =====
tar -zxvf elasticsearch-7.17.0-linux-x86_64.tar.gz
cd elasticsearch-7.17.0

# ===== 启动 ES =====
# Linux/Mac
./bin/elasticsearch
# Windows
bin\elasticsearch.bat

# ===== 验证启动 =====
curl http://localhost:9200

# 返回示例
{
  "name" : "node-1",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "...",
  "version" : {
    "number" : "7.17.0",
    "build_flavor" : "default",
    "build_type" : "tar",
    "lucene_version" : "8.11.1"
  },
  "tagline" : "You Know, for Search"
}

2.2 安装 Kibana(可视化工具)

# ===== Docker 安装 Kibana(推荐) =====
docker pull kibana:7.17.0
docker run -d --name kibana --link es:elasticsearch \
  -p 5601:5601 kibana:7.17.0

# 访问:http://localhost:5601
# Kibana 的 Dev Tools 是 ES 学习的最佳工具!

# ===== 或者安装 elasticsearch-head =====
# Chrome 插件:直接搜索安装 Elasticsearch Head
# 或 Docker 安装:
docker run -d -p 9100:9100 mobz/elasticsearch-head:5
# 访问:http://localhost:9100

2.3 解决跨域问题

# 如果使用 Head 插件或前端直接访问,需要配置跨域
# elasticsearch.yml 中添加:
http.cors.enabled: true
http.cors.allow-origin: "*"

三、RESTful API 回顾

3.1 RESTful 风格

RESTful 是一种 API 设计风格,使用名词表示资源,使用HTTP 动词表示操作

# ===== HTTP 动词在 ES 中的映射 =====
GET     → 查询资源(查询文档/索引)
POST    → 创建资源 / 更新局部
PUT     → 创建/替换资源
DELETE  → 删除资源
HEAD    → 检查资源是否存在

3.2 ES 的 RESTful 路径格式

HTTP 方法 /索引名称/文档类型/文档ID

示例:
  PUT    /crm/employee/1     → 创建文档 ID=1
  GET    /crm/employee/1     → 查询文档 ID=1
  POST   /crm/employee       → 创建文档(自动生成 ID)
  POST   /crm/employee/1/_update → 更新文档 ID=1
  DELETE /crm/employee/1     → 删除文档 ID=1

四、ES 核心概念

4.1 ES vs 关系型数据库

ES              MySQL          说明
──────────      ───────────    ──────────────────────
Index(索引)      Database       数据库
Type(类型)       Table          表(ES 7.x 废弃,8.x 移除)
Document(文档)   Row            行
Field(字段)      Column         列
Mapping(映射)    Schema         表结构
DSL              SQL            查询语言

ES 7.x 开始,一个索引只有一种类型 _doc

4.2 核心术语详解

# ===== ① Index(索引) =====
# 类似数据库,存储具有相似特征的文档集合
# 命名规则:全部小写,不能包含特殊字符

# ===== ② Type(文档类型)=====
# ES 6.x 之前一个索引可以有多个类型
# ES 7.x 之后 type 被弱化,固定为 _doc
# ES 8.x 完全移除 type

# ===== ③ Document(文档) =====
# 最小数据单元,JSON 格式
# 每个文档有唯一的 _id

# ===== ④ Field(字段) =====
# 文档中的一个键值对,相当于列

# ===== ⑤ Cluster(集群) =====
# 包含多个节点,默认名称 "elasticsearch"
# 同一集群的节点具有相同的 cluster.name

# ===== ⑥ Node(节点) =====
# 一个 ES 实例(一个进程、一台服务器)
# 加入集群后自动分配角色

# ===== ⑦ Shard(分片) =====
# 一个索引分成多个分片,分散在不同节点
# 类似于数据库的分库分表
# 主分片数创建时指定,不可修改

# ===== ⑧ Replica(副本) =====
# 主分片的复制品,提供高可用
# 主分片和它的副本不能在同一节点

4.3 分片与副本的关系

索引:employee(3 个主分片,1 个副本)

              node-1          node-2          node-3
              ──────          ──────          ──────
主分片:      P1              P2              P3
副本:        R2              R3              R1

特点:
  ├─ 主分片(P)和它的副本(R)不在同一节点
  ├─ 主分片数量创建后不能修改
  ├─ 副本数量可以动态调整
  └─ 节点越多,数据分布越均衡

五、文档 CRUD 操作

5.1 创建索引

# ===== 创建索引 =====
PUT /crm
{
  "settings": {
    "number_of_shards": 3,      # 主分片数
    "number_of_replicas": 1     # 副本数
  }
}

# 返回
{
  "acknowledged": true,
  "shards_acknowledged": true,
  "index": "crm"
}

# ===== 查看索引 =====
GET /crm

# ===== 查看所有索引 =====
GET _cat/indices?v

# 返回
# health status index uuid                   pri rep docs.count ...
# yellow open   crm   ...                     3   1          0

# ===== 删除索引 =====
DELETE /crm

5.2 创建文档(PUT)

# ===== 创建文档(指定 ID)=====
PUT /crm/employee/1
{
  "id": 1,
  "name": "张三",
  "age": 25,
  "city": "北京",
  "salary": 15000,
  "skills": ["Java", "Spring", "MySQL"]
}

# 返回
{
  "_index": "crm",
  "_type": "employee",
  "_id": "1",
  "_version": 1,
  "result": "created",
  "_shards": { "total": 2, "successful": 1, "failed": 0 }
}

# 注意:PUT 必须指定 ID,如果 ID 已存在则更新(全量替换)

5.3 创建文档(POST — 自动生成 ID)

# ===== 创建文档(不指定 ID,自动生成) =====
POST /crm/employee/
{
  "id": 2,
  "name": "李四",
  "age": 30,
  "city": "上海",
  "salary": 20000
}

# 返回:自动生成了 _id(如:WQxAbXMB3d5x8WfUj2a_)

5.4 获取文档

# ===== 查询文档(根据 ID)=====
GET /crm/employee/1

# 返回
{
  "_index": "crm",
  "_type": "employee",
  "_id": "1",
  "_version": 2,
  "_seq_no": 1,
  "_primary_term": 1,
  "found": true,          # true=找到,false=不存在
  "_source": {            # 文档原始 JSON 内容
    "id": 1,
    "name": "张三",
    "age": 25,
    "city": "北京",
    "salary": 15000,
    "skills": ["Java", "Spring", "MySQL"]
  }
}

# ===== 只返回 _source 字段 =====
GET /crm/employee/1/_source
# 返回:{"id":1,"name":"张三","age":25,"city":"北京","salary":15000}

5.5 更新文档

# ===== 方式 1:PUT 全量替换 =====
# 整个文档被替换(丢失未提供的字段)
PUT /crm/employee/1
{
  "id": 1,
  "name": "张三",
  "salary": 18000    # 只传了这些字段,其他字段会丢失
}

# ===== 方式 2:POST 局部更新(推荐)=====
POST /crm/employee/1/_update
{
  "doc": {
    "salary": 18000,      # 只更新 salary 字段
    "age": 26             # 其他字段保持不变
  }
}

# 返回
{
  "_index": "crm",
  "_type": "employee",
  "_id": "1",
  "_version": 3,
  "result": "updated"
}

5.6 删除文档

# ===== 删除指定文档 =====
DELETE /crm/employee/1

# 返回
{
  "_index": "crm",
  "_type": "employee",
  "_id": "1",
  "_version": 4,
  "result": "deleted"
}

# ===== 批量获取 =====
GET _mget
{
  "docs": [
    { "_index": "crm", "_type": "employee", "_id": "1" },
    { "_index": "crm", "_type": "employee", "_id": "2" },
    { "_index": "crm", "_type": "employee", "_id": "3" }
  ]
}

5.7 CRUD 速查表

操作      方法  路径                                    说明
─────    ───── ────────────────────────────────────    ────────────────
创建索引  PUT   /index                                 settings + mappings
删除索引  DELETE /index                                 
创建文档  PUT   /index/type/id                        指定 ID
创建文档  POST  /index/type                           自动生成 ID
查询文档  GET   /index/type/id                         
局部更新  POST  /index/type/id/_update                 {"doc": {...}}
全量更新  PUT   /index/type/id                        替换整个文档
删除文档  DELETE /index/type/id                         
批量获取  GET   _mget                                  {"docs": [...]}

六、简单查询

6.1 准备数据

# 先准备测试数据
PUT /crm/employee/1
{"id":1,"name":"张三","age":25,"city":"北京","salary":15000,"skills":["Java","Spring"]}

PUT /crm/employee/2
{"id":2,"name":"李四","age":30,"city":"上海","salary":20000,"skills":["Python","Django"]}

PUT /crm/employee/3
{"id":3,"name":"王五","age":28,"city":"北京","salary":18000,"skills":["Java","Elasticsearch"]}

PUT /crm/employee/4
{"id":4,"name":"赵六","age":35,"city":"深圳","salary":25000,"skills":["Go","Docker"]}

PUT /crm/employee/5
{"id":5,"name":"陈七","age":22,"city":"广州","salary":12000,"skills":["Java","Vue"]}

6.2 查询所有

# ===== 空查询(查询所有文档)=====
GET _search

6.3 字符串查询(URI Search)

# ===== 基本语法 =====
# GET _search?q=字段:值

# 按名称查询
GET /crm/employee/_search?q=name:张三

# 按条件查询 + 分页
GET /crm/employee/_search?q=city:北京&from=0&size=2

# 按范围查询
GET _search?q=age:[25 TO 35]

# 多条件组合
GET _search?q=city:北京 AND salary>=15000

# 缺点:复杂查询不适合用 URI 方式

6.4 分页查询

# ===== 分页查询 =====
# size:每页条数
# from:偏移量(从第几条开始)

# 第 1 页(每页 2 条)
GET /crm/employee/_search?size=2&from=0

# 第 2 页
GET /crm/employee/_search?size=2&from=2

# 第 3 页
GET /crm/employee/_search?size=2&from=4

七、DSL 查询与过滤

7.1 什么是 DSL

DSL(Domain Specific Language)是 ES 的基于 JSON 的查询语言,比 URI 方式更强大、更灵活。

# DSL 的基本语法结构
GET /索引/类型/_search
{
  "query": {
    "查询类型": {
      "字段名": "条件"
    }
  },
  "from": 0,
  "size": 10,
  "_source": ["字段1", "字段2"],
  "sort": [...]
}

7.2 match_all(查询所有)

# ===== match_all =====
GET /crm/employee/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 10,
  "_source": ["name", "age", "salary"],
  "sort": [
    { "salary": { "order": "desc" } },
    { "age": { "order": "asc" } }
  ]
}

7.3 match(标准查询 — 分词匹配)

# ===== match 查询 =====
# 会对查询值进行分词,任何词匹配都算匹配
# 类似 MySQL 的 LIKE,但 ES 的精确度更高

GET /crm/employee/_search
{
  "query": {
    "match": {
      "name": "张三"
    }
  }
}

# 多词匹配(只要匹配一个词就返回)
GET /crm/employee/_search
{
  "query": {
    "match": {
      "skills": "Java Python"
    }
  }
}
# 匹配"Java"或"Python"的文档都会返回

7.4 term(单词查询 — 精确匹配)

# ===== term 查询 =====
# 不会分词,精确匹配整个词
# 适合 keyword 类型、数字、日期、布尔值

GET /crm/employee/_search
{
  "query": {
    "term": {
      "city": {
        "value": "北京"
      }
    }
  }
}

# ===== terms(多值精确匹配)=====
GET /crm/employee/_search
{
  "query": {
    "terms": {
      "city": ["北京", "上海"]
    }
  }
}

7.5 range(范围查询)

# ===== range 范围查询 =====
# gt  >    gte  >=
# lt  <    lte  <=

GET /crm/employee/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 25,
        "lte": 35
      }
    }
  }
}

# 也可以用于日期范围
GET /logs/_search
{
  "query": {
    "range": {
      "@timestamp": {
        "gte": "2024-01-01",
        "lte": "2024-12-31"
      }
    }
  }
}

7.6 bool(组合查询)

# ===== bool 查询 =====
# bool 是最强大的组合查询,包含:
#   must     → 必须满足(类似 AND),参与得分
#   must_not → 必须不满足(类似 NOT)
#   should   → 满足更好(类似 OR),提高得分
#   filter   → 必须满足(类似 AND),不参与得分(可缓存,性能好)

GET /crm/employee/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "张三" } }
      ],
      "must_not": [
        { "term": { "age": 30 } }
      ],
      "should": [
        { "match": { "skills": "Java" } }
      ],
      "filter": [
        { "term": { "city": "北京" } },
        { "range": { "salary": { "gte": 10000, "lte": 20000 } } }
      ]
    }
  }
}

7.7 常见的 DSL 查询类型

# ===== ① 标准查询:match =====
{ "match": { "name": "张三" } }

# ===== ② 单词查询:term(精确匹配)=====
{ "term": { "city": "北京" } }

# ===== ③ 组合查询:bool =====
{ "bool": { "must": [...], "filter": [...], "should": [...] } }

# ===== ④ 范围查询:range =====
{ "range": { "age": { "gte": 20, "lte": 30 } } }

# ===== ⑤ 是否存在:exists =====
{ "exists": { "field": "email" } }

# ===== ⑥ 前缀匹配:prefix =====
{ "prefix": { "name": "张" } }

# ===== ⑦ 通配符:wildcard =====
{ "wildcard": { "name": "张*" } }
# * 匹配多个字符,? 匹配一个字符

# ===== ⑧ 模糊查询:fuzzy =====
{ "fuzzy": { "name": "张" } }

7.8 DSL 查询与过滤的区别

Query(查询上下文)vs Filter(过滤上下文)

            Query                            Filter
  ┌──────────────────────┐        ┌──────────────────────┐
  │  计算 _score         │        │  不计算 _score       │
  │  决定"有多匹配"       │        │  判断"匹配/不匹配"    │
  │  结果不被缓存         │        │  结果可被缓存         │
  │  性能相对较低         │        │  性能更好             │
  │  适用:全文搜索       │        │  适用:精确匹配       │
  │       match           │        │       term           │
  │       match_phrase    │        │       terms          │
  │       query_string    │        │       range          │
  └──────────────────────┘        └──────────────────────┘

建议:精确条件放在 filter 中(缓存 + 高性能)
      全文搜索放在 must/should 中(计算相关性)

八、映射 Mapping

8.1 什么是 Mapping

Mapping 定义了索引中字段的名称、类型、分词器等,相当于 MySQL 的 CREATE TABLE。

# ===== 查看索引的 mapping =====
GET /crm/_mapping

# 返回
{
  "crm": {
    "mappings": {
      "properties": {
        "id": { "type": "long" },
        "name": {
          "type": "text",
          "fields": {
            "keyword": { "type": "keyword", "ignore_above": 256 }
          }
        },
        "age": { "type": "integer" },
        "city": { "type": "text" },
        "salary": { "type": "float" },
        "skills": { "type": "text" }
      }
    }
  }
}

# 注意:不指定 mapping 时,ES 会自动推断字段类型(动态映射)

8.2 基本字段类型

# ===== 字符串类型 =====
text      → 分词,支持全文搜索、模糊匹配
keyword   → 不分词,支持精确匹配、排序、聚合

# ===== 数值类型 =====
byte      → 8 位整数(-128 ~ 127)
short     → 16 位整数
integer   → 32 位整数
long      → 64 位整数
float     → 32 位浮点数
double    → 64 位浮点数

# ===== 日期类型 =====
date      → 支持多种格式,如:"2024-01-01"
            "2024-01-01 12:00:00""2024/01/01"

# ===== 布尔类型 =====
boolean   → true / false

# ===== 特殊类型 =====
ip        → IP 地址
geo_point → 地理位置(经纬度)
binary    → Base64 编码的二进制

8.3 自定义 Mapping

# ===== 创建索引时指定 Mapping =====
PUT /employee
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "ik_max_word",       # 中文分词
        "fields": {
          "keyword": {                    # 多字段:精确匹配用
            "type": "keyword"
          }
        }
      },
      "age": {
        "type": "integer"
      },
      "email": {
        "type": "keyword"                # 邮箱:精确匹配
      },
      "city": {
        "type": "keyword"                # 城市:精确匹配
      },
      "salary": {
        "type": "float"
      },
      "birthday": {
        "type": "date",
        "format": "yyyy-MM-dd"           # 日期格式
      },
      "is_married": {
        "type": "boolean"                # 布尔值
      },
      "description": {
        "type": "text",
        "analyzer": "ik_max_word"        # 全文搜索
      }
    }
  }
}

8.4 添加新字段

# ===== 为已有索引添加字段 =====
PUT /employee/_mapping
{
  "properties": {
    "phone": {
      "type": "keyword"
    },
    "address": {
      "type": "text",
      "analyzer": "ik_max_word"
    }
  }
}

8.5 text vs keyword 的选择

场景                        text     keyword
──────────────────────────  ──────   ──────
全文搜索(模糊匹配)          ✓        
精确匹配(等于)                      ✓
排序                                        ✓
聚合(Group By)                          ✓
分词                                    ✓(不分词)
性能(精确查询)                          ✓(更好)

实战经验:
  ├─ 标题、描述、内容 → text(需要分词搜索)
  ├─ 邮箱、手机号、ID → keyword(精确匹配)
  ├─ 状态、分类、标签 → keyword(聚合统计)
  ├─ 需要排序的字段   → keyword
  └─ 既需要搜索又需要精确匹配
     → "多字段"方案:text 用于搜索 + keyword 子字段用于精确匹配
     
     示例:
     "name": {
       "type": "text",
       "fields": {
         "keyword": { "type": "keyword" }
       }
     }
     # name → match 查询(分词搜索)
     # name.keyword → term 查询(精确匹配)

总结

学习路线

第 1 步:掌握核心概念
  Index / Type / Document / Field
  Shard / Replica / Cluster / Node

第 2 步:学会文档 CRUD
  PUT / POST / GET / DELETE / _update / _mget

第 3 步:掌握简单查询
  _search 空查询 → URI 字符串查询 → 分页排序

第 4 步:精通 DSL 查询
  match → term → range → bool(must/filter/should)
  Query vs Filter 的选择

第 5 步:理解 Mapping
  字段类型 → text vs keyword → 自定义 mapping

常用指令速查

# 索引操作
PUT /index_name             创建索引
DELETE /index_name          删除索引
GET /index_name/_mapping    查看映射
GET _cat/indices?v          查看所有索引

# 文档操作
PUT /index/_doc/id          创建/替换文档
POST /index/_doc/id/_update 更新文档
GET /index/_doc/id          查询文档
DELETE /index/_doc/id       删除文档
GET /index/_doc/id/_source  仅获取内容

# 查询
GET _search                 查询所有
GET /index/_search          查询索引
GET /index/_search?q=...    URI 搜索

# DSL 查询
{ "query": { "match_all": {} } }                    # 全部
{ "query": { "match": { "field": "value" } } }      # 分词匹配
{ "query": { "term": { "field": "value" } } }       # 精确匹配
{ "query": { "range": { "field": { "gte": 1 } } } } # 范围
{ "query": { "bool": { "must": [...], "filter": [...] } } } # 组合

一句话总结

Elasticsearch 是一个用 RESTful JSON 操作的分布式搜索引擎:Index=数据库、Document=行、Field=列、Mapping=表结构、DSL=SQL。简单查询用 URI,复杂查询用 DSL,精确条件放 filter,全文搜索放 query。掌握这几点就可以上手了。

Logo

更多推荐