前言
Elasticsearch是一个基于Apache Lucene构建的分布式搜索和分析引擎,为全文搜索、结构化搜索、分析提供了强大的解决方案。本文是Elasticsearch 8.x系列教程的入门篇,将帮助初学者快速理解核心概念并搭建开发环境。
系列文章导航
一、核心概念与架构
(一)基础概念
1. 核心组件
集群(Cluster)
- 一个或多个节点的集合
- 共同提供索引和搜索功能
- 具有唯一的集群名称
节点(Node)
- 集群中的单个服务器
- 存储数据并参与索引和搜索
- 每个节点都有唯一的名称
索引(Index)
- 具有相似特征的文档集合
- 类似于关系数据库中的数据库
- 索引名称必须是小写字母
文档(Document)
- 可被索引的基础信息单元
- 以JSON格式表示
- 类似于关系数据库中的行
分片(Shard)
- 索引的水平分割
- 提供分布式存储和并行处理能力
- 分为主分片和副本分片
2. 应用场景
主要应用场景:
- 网站搜索 - 电商商品搜索、内容搜索
- 日志分析 - 系统日志、应用日志分析
- 实时分析 - 业务指标监控、用户行为分析
- 全文搜索 - 文档检索、知识库搜索
(二)Elasticsearch 8.x 新特性
1. 主要改进
- 新的Java客户端:Elasticsearch Java API Client替代已废弃的RestHighLevelClient
- 增强的安全性:默认启用安全功能,改进的身份验证和授权
- 向量搜索:原生支持kNN搜索,适用于AI和机器学习场景
- 性能优化:改进的索引和查询性能,更好的内存管理
2. 客户端变化
1 2 3 4 5
| ElasticsearchClient client = new ElasticsearchClient(transport);
|
二、环境搭建与配置
(一)Docker安装(推荐)
1. 安装Elasticsearch
1 2 3 4 5 6 7 8 9 10 11 12
| docker pull docker.elastic.co/elasticsearch/elasticsearch:8.12.0
docker run -d \ --name elasticsearch \ -p 9200:9200 \ -p 9300:9300 \ -e "discovery.type=single-node" \ -e "xpack.security.enabled=false" \ -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ docker.elastic.co/elasticsearch/elasticsearch:8.12.0
|
2. 验证安装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| curl http://localhost:9200
{ "name" : "node-1", "cluster_name" : "docker-cluster", "cluster_uuid" : "...", "version" : { "number" : "8.12.0", "build_flavor" : "default", "build_type" : "docker", "build_hash" : "...", "build_date" : "...", "build_snapshot" : false, "lucene_version" : "9.8.0", "minimum_wire_compatibility_version" : "7.17.0", "minimum_index_compatibility_version" : "7.0.0" }, "tagline" : "You Know, for Search" }
|
3. 基础配置
1 2 3 4 5 6 7 8 9 10 11
| cluster.name: my-elasticsearch-cluster node.name: node-1 path.data: /var/lib/elasticsearch path.logs: /var/log/elasticsearch network.host: 0.0.0.0 http.port: 9200 discovery.type: single-node
bootstrap.memory_lock: true
|
(二)Spring Boot 3 集成
1. 项目依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>co.elastic.clients</groupId> <artifactId>elasticsearch-java</artifactId> <version>8.12.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> </dependency> </dependencies>
|
2. 客户端配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Configuration public class ElasticsearchConfig { @Value("${elasticsearch.host:localhost}") private String host; @Value("${elasticsearch.port:9200}") private int port;
@Bean public ElasticsearchClient elasticsearchClient() { RestClient restClient = RestClient.builder( new HttpHost(host, port, "http") ).build(); ElasticsearchTransport transport = new RestClientTransport( restClient, new JacksonJsonpMapper() ); return new ElasticsearchClient(transport); } }
|
3. 应用配置
1 2 3 4 5 6 7 8 9 10 11 12
| elasticsearch: host: localhost port: 9200
spring: application: name: elasticsearch-demo logging: level: co.elastic.clients: DEBUG
|
三、基础索引操作
(一)索引管理
1. 创建简单索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| @Service public class IndexService { @Autowired private ElasticsearchClient client;
public void createSimpleIndex(String indexName) throws IOException { if (indexExists(indexName)) { System.out.println("索引 " + indexName + " 已存在"); return; } CreateIndexRequest request = CreateIndexRequest.of(i -> i .index(indexName) .settings(s -> s .numberOfShards("1") .numberOfReplicas("0") ) ); CreateIndexResponse response = client.indices().create(request); System.out.println("索引创建结果: " + response.acknowledged()); }
public boolean indexExists(String indexName) throws IOException { ExistsRequest request = ExistsRequest.of(e -> e.index(indexName)); return client.indices().exists(request).value(); }
public void deleteIndex(String indexName) throws IOException { DeleteIndexRequest request = DeleteIndexRequest.of(d -> d.index(indexName)); DeleteIndexResponse response = client.indices().delete(request); System.out.println("索引删除结果: " + response.acknowledged()); } }
|
2. 带映射的索引创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
public void createIndexWithMapping(String indexName) throws IOException { TypeMapping mapping = TypeMapping.of(m -> m .properties("id", Property.of(p -> p.keyword(k -> k))) .properties("title", Property.of(p -> p.text(t -> t .analyzer("standard") ))) .properties("content", Property.of(p -> p.text(t -> t .analyzer("standard") ))) .properties("createTime", Property.of(p -> p.date(d -> d .format("yyyy-MM-dd HH:mm:ss") ))) .properties("tags", Property.of(p -> p.keyword(k -> k))) ); CreateIndexRequest request = CreateIndexRequest.of(i -> i .index(indexName) .mappings(mapping) .settings(s -> s .numberOfShards("1") .numberOfReplicas("0") ) ); CreateIndexResponse response = client.indices().create(request); System.out.println("带映射的索引创建结果: " + response.acknowledged()); }
|
(二)文档操作
1. 文档实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
public class SimpleDocument { private String id; private String title; private String content; private String createTime; private List<String> tags; public SimpleDocument() {} public SimpleDocument(String id, String title, String content) { this.id = id; this.title = title; this.content = content; this.createTime = LocalDateTime.now().format( DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") ); this.tags = new ArrayList<>(); } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } public List<String> getTags() { return tags; } public void setTags(List<String> tags) { this.tags = tags; } }
|
2. 基础文档操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| @Service public class DocumentService { @Autowired private ElasticsearchClient client;
public void addDocument(String indexName, SimpleDocument document) throws IOException { IndexRequest<SimpleDocument> request = IndexRequest.of(i -> i .index(indexName) .id(document.getId()) .document(document) ); IndexResponse response = client.index(request); System.out.println("文档添加结果: " + response.result()); }
public SimpleDocument getDocument(String indexName, String id) throws IOException { GetRequest request = GetRequest.of(g -> g .index(indexName) .id(id) ); GetResponse<SimpleDocument> response = client.get(request, SimpleDocument.class); if (response.found()) { return response.source(); } return null; }
public void updateDocument(String indexName, String id, SimpleDocument document) throws IOException { UpdateRequest<SimpleDocument, SimpleDocument> request = UpdateRequest.of(u -> u .index(indexName) .id(id) .doc(document) ); UpdateResponse<SimpleDocument> response = client.update(request, SimpleDocument.class); System.out.println("文档更新结果: " + response.result()); }
public void deleteDocument(String indexName, String id) throws IOException { DeleteRequest request = DeleteRequest.of(d -> d .index(indexName) .id(id) ); DeleteResponse response = client.delete(request); System.out.println("文档删除结果: " + response.result()); } }
|
四、基础查询操作
(一)简单查询
1. 查询所有文档
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| @Service public class SimpleSearchService { @Autowired private ElasticsearchClient client;
public List<SimpleDocument> searchAll(String indexName) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(indexName) .query(q -> q.matchAll(m -> m)) .size(10) ); SearchResponse<SimpleDocument> response = client.search(request, SimpleDocument.class); return response.hits().hits().stream() .map(Hit::source) .collect(Collectors.toList()); }
public List<SimpleDocument> searchByTitle(String indexName, String title) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(indexName) .query(q -> q .match(m -> m .field("title") .query(title) ) ) ); SearchResponse<SimpleDocument> response = client.search(request, SimpleDocument.class); return response.hits().hits().stream() .map(Hit::source) .collect(Collectors.toList()); }
public List<SimpleDocument> searchByTag(String indexName, String tag) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(indexName) .query(q -> q .term(t -> t .field("tags") .value(tag) ) ) ); SearchResponse<SimpleDocument> response = client.search(request, SimpleDocument.class); return response.hits().hits().stream() .map(Hit::source) .collect(Collectors.toList()); } }
|
(二)实践示例
1. 完整的CRUD示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| @RestController @RequestMapping("/api/documents") public class DocumentController { @Autowired private IndexService indexService; @Autowired private DocumentService documentService; @Autowired private SimpleSearchService searchService; private static final String INDEX_NAME = "my_documents";
@PostMapping("/init") public ResponseEntity<String> initIndex() { try { indexService.createIndexWithMapping(INDEX_NAME); return ResponseEntity.ok("索引创建成功"); } catch (IOException e) { return ResponseEntity.status(500).body("索引创建失败: " + e.getMessage()); } }
@PostMapping public ResponseEntity<String> addDocument(@RequestBody SimpleDocument document) { try { documentService.addDocument(INDEX_NAME, document); return ResponseEntity.ok("文档添加成功"); } catch (IOException e) { return ResponseEntity.status(500).body("文档添加失败: " + e.getMessage()); } }
@GetMapping("/{id}") public ResponseEntity<SimpleDocument> getDocument(@PathVariable String id) { try { SimpleDocument document = documentService.getDocument(INDEX_NAME, id); if (document != null) { return ResponseEntity.ok(document); } else { return ResponseEntity.notFound().build(); } } catch (IOException e) { return ResponseEntity.status(500).build(); } }
@GetMapping("/search") public ResponseEntity<List<SimpleDocument>> searchDocuments(@RequestParam String title) { try { List<SimpleDocument> documents = searchService.searchByTitle(INDEX_NAME, title); return ResponseEntity.ok(documents); } catch (IOException e) { return ResponseEntity.status(500).build(); } } }
|
五、总结与下一步
(一)本文总结
通过本文的学习,您已经掌握了:
- 核心概念:集群、节点、索引、文档、分片等基础概念
- 环境搭建:使用Docker快速搭建Elasticsearch开发环境
- Java集成:在Spring Boot 3中集成Elasticsearch 8.x客户端
- 基础操作:索引的创建、文档的CRUD操作、简单查询
(二)学习建议
入门阶段重点:
- 熟练掌握基础概念和术语
- 能够独立搭建开发环境
- 掌握基本的索引和文档操作
- 理解简单查询的使用方法
实践建议:
- 动手搭建本地环境并运行示例代码
- 尝试创建不同类型的索引和文档
- 练习各种基础查询操作
- 观察Elasticsearch的日志输出
(三)下一步学习
完成入门学习后,建议继续学习:
🟡 进阶级教程:
- 复杂查询(布尔查询、范围查询、模糊查询)
- 聚合分析(桶聚合、指标聚合)
- 性能优化基础
- 实战案例开发
🔴 高级教程:
- 集群管理与运维
- 深度性能优化
- 企业级应用架构
- 安全配置与监控
(四)参考资料
系列文章导航