Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。作为 Elastic Stack
的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。

What

Elasicsearch是什么?

Elasticsearch 是一个基于 Lucene 的实时的分布式搜索分析引擎,开箱即用,整合了全文检索、结构化搜索、分析三大功能。为什么不直接用 Lucene ?Lucene 只是一个全文检索引擎的架构,提供了大量可用的 API,但其并不是一个完整的全文检索引擎,使用 Lucene 时,你还需要自己写代码,自己去封装成全文检索引擎。

Elasticsearch基本概念

  • Field:字段。
  • Document :文档,一条数据,用 json 格式表示。一个Document 包含多个field,json 中的 key 即 field 。
  • Type:类型,一个 Document 分组,和 mysql 中的 table 类似,但又不完全相同。一个 Type 包含多个Document,同一个 Type 中的 Document 所拥有的 field 可以不同,但最好保持一致。
  • Index :索引,类似于 mysql 中的 database。一个 Index 包含多个 Type。默认情况下,Document 中的所有 field 都会被索引,这样这些 field 才会被搜索到。Elasticsearch 中有一个倒排索引(Inverted Index)的概念,可以实现 mysql 中 B+Tree索引加速检索的目的。
  • shard:分片。可以将一个 Index 中的数据切分为多个 shard,然后将之存储在多台服务器上,以增大一个 Index 可以存储的数据量,加速检索能力,提升系统性能。
  • replica :副本。replica 与 shard 存储的数据是相同的,replica 起到备份的作用。当 shard 发生故障时,可以从 replica 中读取数据,保证系统不受影响。
  • Node:节点,单个 Elasticsearch 实例。节点名称默认随机分配。
  • Cluster:集群,一组 Elasticsearch 实例。默认集群名称为 elasticsearch。

How

安装Elasticsearch

这里使用docker-compose部署

1
2
3
4
5
6
7
8
9
version: '3'
services:
elasticsearch:
image: elasticsearch:6.8.8
container_name: elasticsearch
environment:
discovery.type: single-node
ports:
- "9200:9200"

使用curl http://localhost:9200 可以测试安装结果,出现类似以下结果为正常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"name" : "Q8-z3Bf",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "H56Jga63RRWDwtu1D2OaEw",
"version" : {
"number" : "6.8.8",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "2f4c224",
"build_date" : "2020-03-18T23:22:18.622755Z",
"build_snapshot" : false,
"lucene_version" : "7.7.2",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}

为Elasticsearch安装ik分词器

在线安装

1
2
3
4
docker exec -it es容器id bash # 进入es容器
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.2/elasticsearch-analysis-ik-7.6.2.zip # 安装ik插件
exit # 退出
docker restart es容器id # 重启容器

集成springboot

pom文件

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>elasticsearch-springboot-test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>elasticsearch-springboot-test</name>
<description>elasticsearch-springboot-test</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<lombok.version>1.18.12</lombok.version>
</properties>

<dependencies>
<!--elasticsearch -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!--web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

添加配置文件application.yml

1
2
3
4
5
6
server:
port: 13000
spring:
elasticsearch:
rest:
uris: http://192.168.3.30:9200

定义domain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Document(indexName = UserIndexConstant.INDEX_NAME,shards = 1,replicas = 0)
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class UserCO {
@Id
private Long id;

@Field(type = FieldType.Text)
private String name;

@Field(type = FieldType.Integer)
private Integer age;
}

定义常量

1
2
3
4
public class UserIndexConstant {
public static final String INDEX_NAME = "test_index";
public static final String TYPE = "user";
}

定义操作接口

1
2
3
4
5
6
7
8
public interface IUserEsIndexService {
public void insert(List<UserCO> userCOs);
public void delete(UserCO userCO);
public void update(List<UserCO> userCOs);
public void deleteAll();
public <T> List<T> queryForList(SearchQuery query, Class<T> clazz);
public <T> Page<T> queryForPage(SearchQuery query, Class<T> clazz);
}

操作接口实现类

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

@Service
@AllArgsConstructor
public class UserEsIndexServiceImpl implements IUserEsIndexService {
private ElasticsearchOperations elasticsearchOperations;

@Override
public void insert(List<UserCO> userCOs) {
// index不存在则创建
if (!elasticsearchOperations.indexExists(UserCO.class)) {
elasticsearchOperations.createIndex(UserCO.class);
}

List<IndexQuery> collect = userCOs.stream().map(userCO -> {
IndexQuery indexQuery = new IndexQuery();
indexQuery.setId(userCO.getId().toString());
indexQuery.setObject(userCO);
return indexQuery;
}).collect(Collectors.toList());
elasticsearchOperations.bulkIndex(collect);
}

@Override
public void delete(UserCO userCO) {
elasticsearchOperations.delete(UserIndexConstant.INDEX_NAME, UserIndexConstant.TYPE, userCO.getId().toString());
}

@Override
public void update(UserCO userCO) {
this.delete(userCO);
List<UserCO> list = new ArrayList<UserCO>();
list.add(userCO);
this.insert(list);
}

@Override
public void deleteAll() {
elasticsearchOperations.deleteIndex(UserIndexConstant.INDEX_NAME);
}

@Override
public <T> List<T> queryForList(SearchQuery query, Class<T> clazz) {
return this.elasticsearchOperations.queryForList(query,clazz);
}

@Override
public <T> Page<T> queryForPage(SearchQuery query, Class<T> clazz) {
return this.elasticsearchOperations.queryForPage(query,clazz);
}
}

启动类

1
2
3
4
5
6
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}

测试类

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
@RunWith(SpringRunner.class)
@SpringBootTest(classes={Application.class})// 指定启动类
public class UserIndexTest {
@Autowired
private IUserEsIndexService userEsIndexService;

@Test
public void testInsert() {
String[] name1 = new String[]{"张", "王", "刘", "李", "白", "郝", "马"};
String[] name2 = new String[]{"小", "大", "海", "镇", "天", "时", "里"};
String[] name3 = new String[]{"渡", "嗄", "无", "图", "与", "可", "真"};
List<UserCO> userCOList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
int i1 = Math.abs(new Random().nextInt()) % 7;
int i2 = Math.abs(new Random().nextInt()) % 7;
int i3 = Math.abs(new Random().nextInt()) % 7;
int age = (Math.abs(new Random().nextInt()) % 50) + 20;
UserCO userCO = new UserCO((long) i, name1[i1] + name2[i2] + name3[i3], age);
System.out.println(userCO);
userCOList.add(userCO);
}
userEsIndexService.insert(userCOList);
}

@Test
public void testQuery(){
SearchQuery searchQuery = new NativeSearchQuery(new QueryStringQueryBuilder("1"));
List<UserCO> userCOS = userEsIndexService.queryForList(searchQuery, UserCO.class);
userCOS.forEach(System.out::println);
}

@Test
public void testDelete(){
UserCO userCO = new UserCO();
userCO.setId(1L);
userEsIndexService.delete(userCO);
}
@Test
public void testUpdate() throws InterruptedException {
SearchQuery searchQuery = new NativeSearchQuery(new QueryStringQueryBuilder("2"));
List<UserCO> userCOS = userEsIndexService.queryForList(searchQuery, UserCO.class);
userCOS.forEach(System.out::println);

UserCO userCO = new UserCO(2L,"测试变更"+System.currentTimeMillis(),100);
userEsIndexService.update(userCO);

Thread.sleep(500);
List<UserCO> userCOS2 = userEsIndexService.queryForList(searchQuery, UserCO.class);
userCOS2.forEach(System.out::println);
}
}

截止到2020年5月7日 SpringData最高支持到Elasticsearch 6.8.8版本,所以如果用最新版的es,将会一直报错。
SpringBoot data支持版本

备注