今天咱们聊聊Elasticsearch。它功能强大,搜索、分析、聚合样样精通;但又因为概念太多,配置复杂,一不小心就踩坑。今天就用最通俗的话给你讲透ES的核心知识点!

前言:为什么要学Elasticsearch?

哎,说起搜索功能,估计每个后端都有过这样的经历:

  • 用MySQL的LIKE查询,数据一多就慢得要死
  • 老板要个全文搜索功能,SQL写得比绕口令还复杂
  • 想做个商品推荐,传统数据库根本玩不转
  • 日志分析、数据统计,关系型数据库力不从心

这些问题的根源是什么?就是没有选对合适的工具

Elasticsearch就是专门解决搜索和分析问题的神器:

  • 搜索快如闪电:毫秒级全文搜索
  • 扩展性极强:轻松处理PB级数据
  • 功能丰富:搜索、聚合、分析一站式解决
  • 生态完善:ELK Stack(Elasticsearch + Logstash + Kibana)

今天我就用最接地气的方式,把ES的核心知识点给你讲透!

第一部分:核心概念 - 搞懂ES的"江湖规则"

1.1 ES vs 传统数据库

首先搞清楚ES和MySQL的区别,这很重要:

概念 MySQL Elasticsearch 说明
数据库 Database Index(索引) 数据存储的最大单位
Table Type(类型)* 数据的分类,7.x后废弃
Row Document(文档) 一条完整的数据记录
Column Field(字段) 数据的具体属性
主键 Primary Key _id 文档的唯一标识

注意:ES 7.x以后取消了Type概念,一个Index就相当于MySQL的一张表。

1.2 倒排索引 - ES的核心秘密

什么是倒排索引?

想象一下字典的索引页:

  • 正排索引:根据页码找内容(MySQL就是这样)
  • 倒排索引:根据内容找页码(ES的核心)
传统方式:
文档1: "Java是一门编程语言"
文档2: "Python也是编程语言"  
文档3: "编程需要学习Java"

倒排索引:
"Java" -> [文档1, 文档3]
"编程" -> [文档1, 文档2, 文档3]  
"语言" -> [文档1, 文档2]

这就是为什么ES搜索这么快的原因!

1.3 集群架构 - ES的"组织架构"

集群(Cluster):多个ES节点组成的集群 节点(Node):一个ES实例就是一个节点 分片(Shard):索引数据的水平分割 副本(Replica):分片的备份

集群架构示例:
Node1: [Primary Shard 0] [Replica Shard 1]
Node2: [Primary Shard 1] [Replica Shard 0]  
Node3: [Primary Shard 2] [Replica Shard 2]

为什么要分片?

  • 单机存不下大量数据
  • 分布式并行提高性能
  • 一个节点挂了,其他节点还能工作

第二部分:基础操作 - 从增删改查开始

2.1 索引管理

创建索引:

PUT /my_index
{
  "settings": {
    "number_of_shards": 3,      // 主分片数
    "number_of_replicas": 1     // 副本数
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ik_max_word"  // 中文分词器
      },
      "price": {
        "type": "double"
      },
      "create_time": {
        "type": "date"
      }
    }
  }
}

查看索引:

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

删除索引:

DELETE /my_index

2.2 文档操作

新增文档:

// 指定ID
PUT /my_index/_doc/1
{
  "title": "Elasticsearch实战教程",
  "price": 99.9,
  "create_time": "2024-03-15"
}

// 自动生成ID
POST /my_index/_doc
{
  "title": "Java编程指南",
  "price": 89.0,
  "create_time": "2024-03-16"
}

查询文档:

// 根据ID查询
GET /my_index/_doc/1

// 查询所有文档
GET /my_index/_search
{
  "query": {
    "match_all": {}
  }
}

更新文档:

// 全量更新
PUT /my_index/_doc/1
{
  "title": "ES高级教程",
  "price": 199.9,
  "create_time": "2024-03-15"
}

// 部分更新
POST /my_index/_update/1
{
  "doc": {
    "price": 159.9
  }
}

删除文档:

DELETE /my_index/_doc/1

2.3 批量操作

POST /_bulk
{"index": {"_index": "my_index", "_id": "1"}}
{"title": "商品1", "price": 100}
{"index": {"_index": "my_index", "_id": "2"}}
{"title": "商品2", "price": 200}
{"update": {"_index": "my_index", "_id": "1"}}
{"doc": {"price": 150}}
{"delete": {"_index": "my_index", "_id": "2"}}

第三部分:查询语法 - ES查询的"十八般武艺"

3.1 基础查询

匹配查询(最常用):

GET /my_index/_search
{
  "query": {
    "match": {
      "title": "Elasticsearch教程"
    }
  }
}

精确查询:

GET /my_index/_search
{
  "query": {
    "term": {
      "price": 99.9
    }
  }
}

范围查询:

GET /my_index/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 50,    // 大于等于
        "lte": 200    // 小于等于
      }
    }
  }
}

模糊查询:

GET /my_index/_search
{
  "query": {
    "fuzzy": {
      "title": {
        "value": "Elastcsearch",  // 故意写错
        "fuzziness": 2            // 允许2个字符差异
      }
    }
  }
}

3.2 复合查询

布尔查询(最强大):

GET /my_index/_search
{
  "query": {
    "bool": {
      "must": [        // 必须满足(AND)
        {"match": {"title": "Elasticsearch"}}
      ],
      "should": [      // 应该满足(OR)
        {"match": {"title": "教程"}},
        {"match": {"title": "实战"}}
      ],
      "must_not": [    // 必须不满足(NOT)
        {"term": {"price": 0}}
      ],
      "filter": [      // 过滤条件(不计算分数)
        {"range": {"price": {"gte": 50}}}
      ]
    }
  }
}

多字段查询:

GET /my_index/_search
{
  "query": {
    "multi_match": {
      "query": "Java编程",
      "fields": ["title^2", "description"],  // title权重x2
      "type": "best_fields"
    }
  }
}

3.3 高级查询功能

高亮显示:

GET /my_index/_search
{
  "query": {
    "match": {"title": "Elasticsearch"}
  },
  "highlight": {
    "fields": {
      "title": {
        "pre_tags": ["<mark>"],
        "post_tags": ["</mark>"]
      }
    }
  }
}

分页查询:

GET /my_index/_search
{
  "query": {"match_all": {}},
  "from": 0,      // 偏移量
  "size": 10      // 返回数量
}

排序:

GET /my_index/_search
{
  "query": {"match_all": {}},
  "sort": [
    {"price": {"order": "desc"}},
    {"_score": {"order": "desc"}}
  ]
}

第四部分:聚合分析 - ES的"数据分析神器"

4.1 指标聚合

GET /my_index/_search
{
  "size": 0,  // 不返回文档,只要聚合结果
  "aggs": {
    "avg_price": {
      "avg": {"field": "price"}
    },
    "max_price": {
      "max": {"field": "price"}
    },
    "min_price": {
      "min": {"field": "price"}
    },
    "total_sales": {
      "sum": {"field": "sales"}
    },
    "price_stats": {
      "stats": {"field": "price"}  // 一次性得到所有统计信息
    }
  }
}

4.2 桶聚合

分组统计:

GET /my_index/_search
{
  "size": 0,
  "aggs": {
    "group_by_category": {
      "terms": {
        "field": "category.keyword",
        "size": 10
      },
      "aggs": {
        "avg_price_per_category": {
          "avg": {"field": "price"}
        }
      }
    }
  }
}

时间直方图:

GET /my_index/_search
{
  "size": 0,
  "aggs": {
    "sales_over_time": {
      "date_histogram": {
        "field": "create_time",
        "calendar_interval": "1M",  // 按月统计
        "format": "yyyy-MM"
      },
      "aggs": {
        "monthly_sales": {
          "sum": {"field": "sales"}
        }
      }
    }
  }
}

范围聚合:

GET /my_index/_search
{
  "size": 0,
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          {"to": 100},
          {"from": 100, "to": 500},
          {"from": 500}
        ]
      }
    }
  }
}

第五部分:Mapping映射 - 告诉ES如何理解你的数据

5.1 数据类型

核心数据类型:

PUT /my_index
{
  "mappings": {
    "properties": {
      "title": {"type": "text"},           // 全文搜索
      "keyword": {"type": "keyword"},      // 精确匹配
      "price": {"type": "double"},         // 浮点数
      "count": {"type": "integer"},        // 整数
      "is_active": {"type": "boolean"},    // 布尔值
      "create_time": {"type": "date"},     // 日期
      "location": {"type": "geo_point"}    // 地理位置
    }
  }
}

复杂数据类型:

PUT /my_index
{
  "mappings": {
    "properties": {
      "user": {
        "type": "object",        // 对象类型
        "properties": {
          "name": {"type": "text"},
          "age": {"type": "integer"}
        }
      },
      "tags": {"type": "keyword"},  // 数组类型
      "content": {
        "type": "text",
        "fields": {                 // 多字段映射
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      }
    }
  }
}

5.2 分词器配置

中文分词器(IK):

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "type": "custom",
          "tokenizer": "ik_max_word",
          "filter": ["lowercase", "stop"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "my_analyzer",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

第六部分:性能优化 - 让ES跑得更快

6.1 索引优化

批量操作:

// Java客户端批量插入
BulkRequest bulkRequest = new BulkRequest();
for (int i = 0; i < 1000; i++) {
    IndexRequest indexRequest = new IndexRequest("my_index")
        .id(String.valueOf(i))
        .source("field1", "value" + i);
    bulkRequest.add(indexRequest);
}
BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);

索引设置优化:

PUT /my_index
{
  "settings": {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 0,        // 写入时先设为0
      "refresh_interval": "30s",      // 延长刷新间隔
      "translog": {
        "durability": "async",        // 异步事务日志
        "sync_interval": "30s"
      }
    }
  }
}

6.2 查询优化

使用过滤器代替查询:

// ❌ 慢:使用查询(计算分数)
{
  "query": {
    "bool": {
      "must": [
        {"range": {"price": {"gte": 100}}}
      ]
    }
  }
}

// ✅ 快:使用过滤器(不计算分数)
{
  "query": {
    "bool": {
      "filter": [
        {"range": {"price": {"gte": 100}}}
      ]
    }
  }
}

合理使用分页:

// ❌ 深分页性能差
SearchRequest request = new SearchRequest("my_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.from(10000).size(10);  // 跳过1万条

// ✅ 使用scroll或search_after
SearchRequest scrollRequest = new SearchRequest("my_index");
scrollRequest.scroll(TimeValue.timeValueMinutes(1L));
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.size(1000);
scrollRequest.source(sourceBuilder);

6.3 硬件优化

内存配置:

# elasticsearch.yml
# JVM堆内存设置为物理内存的一半,但不超过32GB
# -Xms16g -Xmx16g

# 系统配置
vm.max_map_count=262144
fs.file-max=65536

磁盘优化:

  • 使用SSD硬盘
  • 数据和日志分离存储
  • 定期清理不需要的索引

第七部分:实战案例 - 电商搜索系统

7.1 商品搜索实现

索引设计:

PUT /products
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "analysis": {
      "analyzer": {
        "product_analyzer": {
          "type": "custom",
          "tokenizer": "ik_max_word",
          "filter": ["lowercase", "synonym_filter"]
        }
      },
      "filter": {
        "synonym_filter": {
          "type": "synonym",
          "synonyms": [
            "手机,mobile,phone",
            "电脑,computer,PC"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "product_analyzer",
        "fields": {
          "keyword": {"type": "keyword"}
        }
      },
      "category": {"type": "keyword"},
      "brand": {"type": "keyword"},
      "price": {"type": "double"},
      "sales": {"type": "integer"},
      "rating": {"type": "float"},
      "tags": {"type": "keyword"},
      "description": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "create_time": {"type": "date"}
    }
  }
}

搜索接口实现:

@RestController
public class ProductSearchController {
    
    @Autowired
    private ElasticsearchRestTemplate elasticsearchTemplate;
    
    @GetMapping("/search")
    public SearchResult searchProducts(@RequestParam String keyword,
                                     @RequestParam(defaultValue = "0") int page,
                                     @RequestParam(defaultValue = "20") int size,
                                     @RequestParam(required = false) String category,
                                     @RequestParam(required = false) String priceRange) {
        
        // 构建查询条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        
        // 关键词搜索
        if (StringUtils.hasText(keyword)) {
            MultiMatchQueryBuilder multiMatch = QueryBuilders.multiMatchQuery(keyword)
                .field("name", 3.0f)        // 商品名权重3
                .field("description", 1.0f)  // 描述权重1
                .field("tags", 2.0f)        // 标签权重2
                .type(MultiMatchQueryBuilder.Type.BEST_FIELDS)
                .operator(Operator.AND);
            
            boolQuery.must(multiMatch);
        }
        
        // 分类过滤
        if (StringUtils.hasText(category)) {
            boolQuery.filter(QueryBuilders.termQuery("category", category));
        }
        
        // 价格范围过滤
        if (StringUtils.hasText(priceRange)) {
            String[] range = priceRange.split("-");
            RangeQueryBuilder priceQuery = QueryBuilders.rangeQuery("price");
            if (range.length == 2) {
                priceQuery.gte(Double.parseDouble(range[0]))
                         .lte(Double.parseDouble(range[1]));
            }
            boolQuery.filter(priceQuery);
        }
        
        // 构建搜索请求
        NativeSearchQueryBuilder searchQuery = new NativeSearchQueryBuilder()
            .withQuery(boolQuery)
            .withPageable(PageRequest.of(page, size))
            .withSort(SortBuilders.scoreSort().order(SortOrder.DESC))  // 按相关性排序
            .withSort(SortBuilders.fieldSort("sales").order(SortOrder.DESC)) // 按销量排序
            .withHighlightFields(
                new HighlightBuilder.Field("name").preTags("<mark>").postTags("</mark>")
            );
        
        // 添加聚合统计
        searchQuery.addAggregation(
            AggregationBuilders.terms("category_agg").field("category").size(10)
        );
        searchQuery.addAggregation(
            AggregationBuilders.terms("brand_agg").field("brand").size(10)
        );
        
        // 执行搜索
        SearchHits<Product> searchHits = elasticsearchTemplate.search(
            searchQuery.build(), Product.class);
        
        // 处理搜索结果
        List<ProductVO> products = searchHits.stream()
            .map(this::convertToVO)
            .collect(Collectors.toList());
        
        // 处理聚合结果
        Map<String, List<String>> facets = extractFacets(searchHits.getAggregations());
        
        return SearchResult.builder()
            .products(products)
            .total(searchHits.getTotalHits())
            .facets(facets)
            .build();
    }
    
    private ProductVO convertToVO(SearchHit<Product> hit) {
        Product product = hit.getContent();
        ProductVO vo = new ProductVO();
        
        // 基本信息
        BeanUtils.copyProperties(product, vo);
        
        // 处理高亮
        Map<String, List<String>> highlights = hit.getHighlightFields();
        if (highlights.containsKey("name")) {
            vo.setHighlightName(highlights.get("name").get(0));
        }
        
        // 设置相关性分数
        vo.setScore(hit.getScore());
        
        return vo;
    }
}

7.2 搜索建议实现

自动补全:

PUT /suggestions
{
  "mappings": {
    "properties": {
      "suggest": {
        "type": "completion",
        "analyzer": "ik_max_word"
      }
    }
  }
}

// 搜索建议
POST /suggestions/_search
{
  "suggest": {
    "product_suggest": {
      "prefix": "iphone",
      "completion": {
        "field": "suggest",
        "size": 10
      }
    }
  }
}

第八部分:运维监控 - 保证ES稳定运行

8.1 集群监控

关键指标:

# 集群健康状态
GET /_cluster/health

# 节点信息
GET /_nodes/stats

# 索引统计
GET /_stats

# 热点线程
GET /_nodes/hot_threads

Java监控代码:

@Component
public class ElasticsearchMonitor {
    
    @Autowired
    private RestHighLevelClient client;
    
    @Scheduled(fixedRate = 30000)  // 每30秒检查一次
    public void monitorClusterHealth() {
        try {
            ClusterHealthRequest request = new ClusterHealthRequest();
            ClusterHealthResponse response = client.cluster().health(request, RequestOptions.DEFAULT);
            
            ClusterHealthStatus status = response.getStatus();
            if (status == ClusterHealthStatus.RED) {
                // 发送告警
                alertService.sendAlert("ES集群状态异常: " + status);
            }
            
            // 记录监控指标
            monitoringService.recordMetric("es.cluster.nodes", response.getNumberOfNodes());
            monitoringService.recordMetric("es.cluster.active_shards", response.getActiveShards());
            monitoringService.recordMetric("es.cluster.relocating_shards", response.getRelocatingShards());
            
        } catch (Exception e) {
            log.error("ES集群监控失败", e);
        }
    }
}

8.2 性能调优

GC优化:

# jvm.options
-Xms16g
-Xmx16g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+UnlockExperimentalVMOptions
-XX:G1NewSizePercent=30
-XX:G1MaxNewSizePercent=40

索引生命周期管理:

PUT /_ilm/policy/log_policy
{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": {
            "max_size": "50GB",
            "max_age": "30d"
          }
        }
      },
      "warm": {
        "min_age": "30d",
        "actions": {
          "allocate": {
            "number_of_replicas": 0
          }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

总结

Elasticsearch虽然概念多、配置复杂,但掌握了核心知识点,用起来就得心应手了:

核心要点回顾:

  1. 倒排索引是基础 - 理解了它就理解了ES的快速搜索原理
  2. 分片和副本是关键 - 决定了ES的扩展性和可用性
  3. 查询语法要熟练 - bool查询是核心,聚合分析是亮点
  4. Mapping设计要合理 - 好的映射是高性能的前提
  5. 性能优化不能少 - 批量操作、合理分页、硬件配置都很重要

学习建议:

  • 先理解核心概念,再动手实践
  • 多写查询语句,熟能生巧
  • 关注性能监控,及时发现问题
  • 结合业务场景,选择合适的方案

应用场景:

  • 全文搜索:商品搜索、内容检索
  • 日志分析:ELK Stack日志处理
  • 数据分析:实时统计、业务指标分析
  • 推荐系统:基于用户行为的个性化推荐

Elasticsearch不仅仅是一个搜索引擎,更是一个强大的数据分析平台。掌握了它,你就拥有了处理海量数据的神器!


服务端技术精选
关注我,每周分享实用的后端技术干货!
有问题欢迎留言讨论~


本文示例代码已上传GitHub,公众号回复"Elasticsearch"获取完整项目代码