Elasticsearch 简介
Elasticsearch(简称 ES)是一个开源的分布式搜索和分析引擎,基于 Apache Lucene 构建,专为处理大规模数据设计。它提供近实时的全文搜索、结构化搜索、分析及数据可视化能力,广泛应用于日志分析、监控系统、企业搜索等场景。
核心特性
分布式架构
Elasticsearch 采用分布式设计,数据自动分片(Sharding)并支持副本(Replica),确保高可用性和横向扩展能力。节点加入或退出集群时,系统自动重新分配数据。
近实时搜索
数据写入后通常在 1 秒内可被检索,适用于需要快速响应的场景,如日志分析或实时监控。
全文搜索
支持复杂的全文检索功能,包括模糊匹配、同义词处理、分词(支持多语言分词器)和高亮显示。
RESTful API
通过 HTTP 接口实现交互,支持 JSON 格式的请求和响应,便于集成到各类应用中。示例:
json
GET /_search
{
"query": { "match": { "message": "error" } }
}
多数据类型支持
- 结构化数据:如数字、日期、经纬度。
- 非结构化数据:如文本、JSON 文档。
- 地理空间数据:支持地理位置查询和聚合。
聚合分析
提供丰富的聚合功能(如平均值、百分位数、地理距离聚合),适用于数据分析和报表生成。
典型应用场景
- 日志管理与分析:与 Logstash、Kibana 组成 ELK 技术栈,用于集中式日志收集和分析。
- 电商搜索:支持商品的多条件筛选、排序和推荐。
- 企业搜索:整合内部文档、数据库等资源,提供统一搜索入口。
- 安全分析:通过异常检测和模式识别,辅助威胁狩猎(Threat Hunting)。
基本概念
- 索引(Index):类似数据库中的表,存储相关文档。
- 文档(Document):索引中的基本数据单元,以 JSON 格式存储。
- 分片(Shard):索引的子分区,支持水平扩展。
- 节点(Node):集群中的单个服务器实例,承担数据存储或协调角色。
性能优化建议
- 根据数据量合理设置分片数,避免过多分片导致开销。
- 使用批量 API(Bulk API)提升写入效率。
- 通过
_source
字段控制返回内容,减少网络传输。
Elasticsearch 的灵活性和扩展性使其成为大数据搜索和分析领域的核心工具之一。
添加依赖
js
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置信息
js
elasticsearch:
uris: http://xxxxxxx:9200
username: ${ELASTICSEARCH_USERNAME:elasticsearch}
password: ${ELASTICSEARCH_PASSWORD:123456}
connection-timeout: 1s
socket-timeout: 30s
ElasticsearchConfig配置类
js
@Configuration
@EnableElasticsearchRepositories(basePackages = "org.example.repository")
public class ElasticsearchConfig {
@Value("${spring.elasticsearch.uris}")
private String elasticsearchUri;
@Value("${spring.elasticsearch.connection-timeout:1s}")
private String connectionTimeout;
@Value("${spring.elasticsearch.socket-timeout:30s}")
private String socketTimeout;
/**
* 创建并配置ElasticsearchClient Bean
*
* @return 配置好的ElasticsearchClient实例
*/
@Bean
public ElasticsearchClient elasticsearchClient() {
// 解析URI获取主机和端口
String[] uriParts = elasticsearchUri.replace("http://", "").split(":");
String hostname = uriParts[0];
int port = Integer.parseInt(uriParts[1]);
RestClient restClient = RestClient.builder(new HttpHost(hostname, port, "http"))
.setRequestConfigCallback(builder -> builder
.setConnectTimeout(parseTimeValue(connectionTimeout))
.setSocketTimeout(parseTimeValue(socketTimeout))
)
.build();
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
/**
* 解析时间值字符串为毫秒数
*
* @param timeValue 时间值字符串,支持秒(s)和毫秒(ms)单位
* @return 对应的毫秒数
*/
private int parseTimeValue(String timeValue) {
if (timeValue.endsWith("s")) {
return Integer.parseInt(timeValue.replace("s", "")) * 1000;
} else if (timeValue.endsWith("ms")) {
return Integer.parseInt(timeValue.replace("ms", ""));
}
return 30000; // 默认30秒
}
}
原生,创建es索引、查询创建的索引、删除索引
js
@SpringBootTest
public class TestSkuIndexManage {
@Autowired
private ElasticsearchConfig elasticsearchConfig;
/**
* 创建es索引
*/
@Test
public void test() throws Exception {
CreateIndexRequest request = CreateIndexRequest.of(c->c
.index("user"));
CreateIndexResponse response = elasticsearchConfig.elasticsearchClient()
.indices()
.create(request);
System.out.println(response.acknowledged());
}
/**
* 检查索引是否存在
*/
@Test
public void test2() throws Exception {
// 检查索引是否存在
boolean exists = elasticsearchConfig.elasticsearchClient()
.indices()
.exists(e -> e.index("user"))
.value();
System.out.println("索引 'user' 是否存在: " + exists);
}
}
@Test
public void test3() throws Exception {
// 删除索引
boolean deleted = elasticsearchConfig.elasticsearchClient()
.indices()
.delete(d -> d.index("user"))
.acknowledged();
System.out.println("索引 'user' 是否被删除: " + deleted);
}
使用 Repository 查询方法
js
@Repository
public interface DiscussPostRepository extends ElasticsearchRepository<ClassStudentDTO, String> {
// 通过ID查询
Optional<ClassStudentDTO> findById(String id);
// 通过用户名查询
List<ClassStudentDTO> findByUsername(String username);
// 通过工号查询
List<ClassStudentDTO> findByWorkNumber(String workNumber);
// 通过状态查询
List<ClassStudentDTO> findByStatus(Integer status);
// 通过用户名分页查询
Page<ClassStudentDTO> findByUsername(String username, Pageable pageable);
// 根据ID删除
void deleteById(String id);
// 根据实体删除
void delete(ClassStudentDTO entity);
// 删除所有数据
void deleteAll();
// 根据用户名删除
List<ClassStudentDTO> deleteByUsername(String username);
}
插入数据,查询数据,分页,删除
js
@SpringBootTest
public class Test_Doc_Insert {
@Autowired
private DiscussPostRepository discussPostRepository;
/**
* 测试插入数据
*/
@Test
public void test1() throws Exception {
ClassStudentDTO classStudentDTO = new ClassStudentDTO();
classStudentDTO.setId(UuidUtils.generate());
classStudentDTO.setUserId("8snkpxocINqkFeGQ6fxnhAbyvErNASYkQMAd");
classStudentDTO.setUsername("胡桃");
classStudentDTO.setWorkNumber("2079943039");
classStudentDTO.setJoinTime(new Date());
classStudentDTO.setStatus(1);
classStudentDTO.setIdentity("学生");
ClassStudentDTO classStudentDTO1 = discussPostRepository.save(classStudentDTO);
System.out.println(classStudentDTO1);
}
/**
* 测试查询数据
*/
@Test
public void test2() throws Exception {
List<ClassStudentDTO> list = discussPostRepository.findByUsername("胡桃");
System.out.println(list);
}
/**
* 测试工号查询数据
*/
@Test
public void test3() throws Exception {
List<ClassStudentDTO> list = discussPostRepository.findByWorkNumber("2079943039");
System.out.println(list);
}
/**
* 测试名称查询数据,分页
*/
@Test
public void test4() throws Exception {
Pageable pageable = PageRequest.of(0,10);
Page<ClassStudentDTO> pageResult = discussPostRepository.findByUsername("胡桃", pageable);
System.out.println("总记录数: " + pageResult.getTotalElements());
System.out.println("总页数: " + pageResult.getTotalPages());
System.out.println("当前页数据: " + pageResult.getContent());
System.out.println("当前页码: " + pageResult.getNumber());
System.out.println("每页大小: " + pageResult.getSize());
}
/**
* 根据ID删除数据
*/
@Test
public void testDeleteById() throws Exception {
// 假设已知要删除的文档ID
String idToDelete = "your-document-id";
discussPostRepository.deleteById(idToDelete);
System.out.println("已删除ID为 " + idToDelete + " 的文档");
}
/**
* 根据实体删除数据
*/
@Test
public void testDeleteByEntity() throws Exception {
// 先查询出要删除的实体
List<ClassStudentDTO> list = discussPostRepository.findByUsername("胡桃");
if (!list.isEmpty()) {
ClassStudentDTO entityToDelete = list.get(0);
discussPostRepository.delete(entityToDelete);
System.out.println("已删除实体: " + entityToDelete);
}
}
/**
* 删除所有数据
*/
@Test
public void testDeleteAll() throws Exception {
discussPostRepository.deleteAll();
System.out.println("已删除所有数据");
}
/**
* 根据条件删除数据
*/
@Test
public void testDeleteByUsername() throws Exception {
// 需要在Repository中定义findByUsername然后遍历删除
List<ClassStudentDTO> list = discussPostRepository.findByUsername("胡桃");
discussPostRepository.deleteAll(list);
System.out.println("已删除用户名为'胡桃'的所有数据,共" + list.size() + "条");
}
}
kibana展示
js
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "user",
"_id": "92rZPN54meTZUfCPIqEswa8dukRWHK9fKMCe",
"_score": 1,
"_source": {
"_class": "org.example.baens.ClassStudentDTO",
"id": "92rZPN54meTZUfCPIqEswa8dukRWHK9fKMCe",
"userId": "3wZ5k5WeVRdWzSD8YADdTAS6i3Xyniyn",
"username": "叶子飞",
"workNumber": "2079943027",
"joinTime": "2025-08-14T06:56:10.662Z",
"status": 1,
"identity": "教师"
}
},
{
"_index": "user",
"_id": "iyoF4chHkH7GloIl8Y4dhQrykCDCNAalOOUw",
"_score": 1,
"_source": {
"_class": "org.example.baens.ClassStudentDTO",
"id": "iyoF4chHkH7GloIl8Y4dhQrykCDCNAalOOUw",
"userId": "8snkpxocINqkFeGQ6fxnhAbyvErNASYkQMAd",
"username": "胡桃",
"workNumber": "2079943039",
"joinTime": "2025-08-14T07:00:40.866Z",
"status": 1,
"identity": "学生"
}
}
]
}
}