Elasticsearch 核心概念与 Java 客户端实战
1. 为什么选择 Elasticsearch?
1.1 从数据库的痛苦说起
先看个 MySQL 做搜索的典型问题:
-- MySQL 模糊查询 SELECT * FROM products WHERE name LIKE '%手机%' OR description LIKE '%手机%' OR tags LIKE '%手机%' ORDER BY create_time DESC LIMIT 100 OFFSET 0;
代码清单 1:MySQL 模糊查询
MySQL 搜索的痛点:
- LIKE '%xxx%'导致全表扫描
- 多字段 OR 查询性能极差
- 无法支持复杂评分排序
- 分词、同义词、拼音搜索不支持
1.2 Elasticsearch 的优势
ES 的倒排索引(Inverted Index)是核心:
// 倒排索引结构示例 public class InvertedIndex { // 词项 -> 文档列表 Map<String, List<Posting>> index = new HashMap<>(); // 文档 1: "华为手机很好用" // 文档 2: "小米手机性价比高" // 倒排索引: // "华为" -> [文档 1] // "手机" -> [文档 1, 文档 2] // "小米" -> [文档 2] // "很好用" -> [文档 1] // "性价比" -> [文档 2] }
代码清单 2:倒排索引原理
性能对比测试(1000 万商品数据):
| 场景 | MySQL | Elasticsearch | 性能差距 |
|---|---|---|---|
| 单字段模糊查询 | 3200ms | 45ms | 71 倍 |
| 多字段 OR 查询 | 8500ms | 65ms | 130 倍 |
| 复杂条件 + 排序 | 12000ms | 85ms | 141 倍 |
| 内存占用 | 4.2GB | 1.8GB | 57% |
2. ES 核心架构解析
2.1 集群架构
节点类型:
- 主节点(Master):管理集群状态、分片分配
- 数据节点(Data):存储数据、执行 CRUD
- 协调节点(Coordinating):路由请求、聚合结果
- 摄取节点(Ingest):数据预处理
2.2 索引与分片
// 索引创建示例 @Configuration public class IndexConfig { public void createProductIndex(RestHighLevelClient client) throws IOException { CreateIndexRequest request = new CreateIndexRequest("products"); // 索引设置 request.settings(Settings.builder() .put("index.number_of_shards", 3) // 主分片数 .put("index.number_of_replicas", 1) // 副本数 .put("index.refresh_interval", "1s") // 刷新间隔 .put("analysis.analyzer.default.type", "ik_max_word")); // 分词器 // Mapping 定义 XContentBuilder mapping = JsonXContent.contentBuilder() .startObject() .startObject("properties") .startObject("id") .field("type", "keyword") .endObject() .startObject("name") .field("type", "text") .field("analyzer", "ik_max_word") .field("search_analyzer", "ik_smart") .field("fields", JsonXContent.contentBuilder() .startObject() .startObject("pinyin") .field("type", "text") .field("analyzer", "pinyin_analyzer") .endObject() .startObject("keyword") .field("type", "keyword") .field("ignore_above", 256) .endObject() .endObject()) .endObject() .startObject("price") .field("type", "double") .endObject() .startObject("category_id") .field("type", "integer") .endObject() .startObject("sales") .field("type", "integer") .endObject() .startObject("create_time") .field("type", "date") .field("format", "yyyy-MM-dd HH:mm:ss||epoch_millis") .endObject() .endObject() .endObject(); request.mapping(mapping); CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT); if (response.isAcknowledged()) { log.info("索引创建成功"); } } }


