谁悲失路之人
ElasticSearch全文搜索的使用

ElasticSearch

[TOC]

概述

ES是Lucene的一个封装工具,解决了Lucene配置麻烦,不支持分布式的缺点

ES也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单

安装

官方下载:https://www.elastic.co/downloads/elasticsearch

然后解压,运行bin路径下的elasticsearch.bat文件

1574244745968

1574244783437

这样就成功运行了,然后在浏览器输入localhost:9200

1574244817786

出现这个界面就代表成功启动

辅助工具Kibana5

在使用MySql的时候,可以用Navcation来进行可视化管理,Kibana5就是可以对ElasticSearch进行可视化管理的一款工具

官方下载:https://www.elastic.co/downloads/kibana

解压然后修改config/kibana.yml,设置elasticsearch.url的值为已启动的ES地址值(一般不需要修改,默认就是对的)

启动Kibana5 : bin\kibana.bat

默认访问地址:http://localhost:5601

成功访问

增删改查操作

首先明确一点,ES是完全遵从了Restful风格的,关于Restful风格,随便一搜一大堆文章,这里就不详细介绍了

在ES中存储数据的行为就叫做索引(indexing),文档归属于一种类型(type),而这些类型存在于索引(index)中,我们可以简单的对比传统数据库和ES的对应关系:

关系数据库(MYSQL) -> 数据库DB-> 表TABLE-> 行ROW-> 列Column

ES的基础语法

  • 增加:PUT PUT {index}/{type}/{id}
  • 修改:同PUT {index}/{type}/{id}
  • 删除:DELETE {index}/{type}/{id}
  • 查询:GET {index}/{type}/{id}

下面通过几个Demo来进行演示

IK分词器

ES默认对英文文本的分词器支持较好,但和lucene一样,如果需要对中文进行全文检索,那么需要使用中文分词器,同lucene一样,在使用中文全文检索前,需要集成IK分词器

GitHub下载:https://github.com/medcl/elasticsearch-analysis-ik

解压,并将其内容放置于ES根目录/plugins/ik

然后重启ES

测试分词

注意:IK分词器有两种类型,分别是ik_smart分词器和ik_max_word分词器。

ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。

ik_max_word:会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合

索引

ES索引的增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建索引库

PUT imp
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
}
}
# 查询索引库

GET _cat/indices

# 查看指定索引库
GET _cat/indices/imp

# 修改索引库

# 删除索引库
DELETE imp

DSL查询

DSL过滤语句和DSL查询语句非常相似,但是它们的使用目的却不同:DSL过滤查询文档的方式更像是对于我的条件”有”或者”没有”(等于 ;不等于),而DSL查询语句则像是”有多像”(模糊查询)

DSL过滤和DSL查询在性能上的区别:

  • 过滤结果可以缓存并应用到后续请求。-> 精确过滤后的结果拿去模糊查询性能高
  • 查询语句同时匹配文档,计算相关性,所以更耗时,且不缓存。
  • 过滤语句可有效地配合查询语句完成文档过滤

案例:

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
#DSL查询
GET fueen/person/_search
{
"query": {
"match": {
"speak": "高级动物"
}
}
}

# 创建测试数据
PUT fueen/user/5
{
"id":5,
"sex":"SAUMAG Note10 Pro",
"ceagtor":"手机",
"money":8000
}

GET fueen/user/_search?_source

# DSL过滤

GET fueen/user/_search
{
"query": {
"bool": {
"must": [
{"match": {
"ceagtor": "手机"
}}
],
"filter": {
"range": {
"money": {
"gte": 6000,
"lte": 8000
}
}
}
}
},
"from": 0,
"size": 10,
"_source": ["sex","ceagtor","money"],
"sort": [
{
"money": "desc"
}
]
}

文档映射

ES的文档映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型

就是规定了每个输入字段值的数据类型

案例

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
# 映射
PUT imp/life/_mapping
{
"life":{
"properties":{
"id":{
"type":"long"
},
"name":{
"type":"text",
"analyzer":"ik_smart",
"search_analyzer":"ik_smart"
}
}
}
}

# 查看文档映射
GET imp/_mapping/life

# 删除映射
DELETE imp

# 动态模板
PUT _template/kof_template
{
"template": "*",
"settings": { "number_of_shards": 1 },
"mappings": {
"_default_": {
"_all": {
"enabled": false
},
"dynamic_templates": [
{
"string_as_text": {
"match_mapping_type": "string",
"match": "*_text",
"mapping": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word",
"fields": {
"raw": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
{
"string_as_keyword": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
]
}
}}

集群

为什么需要ES集群

和Redis一样,集群能够做很多事情

  • 单节点故障
  • 支持高并发
  • 海量数据存储

ES的集群节点

主节点

node.master=true,代表该节点有成为主资格,主节点的主要职责是和集群操作相关的内容,如创建或删除索引,跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点。一般会把主节点和数据节点分开,node.master=true , node.data=false

数据节点

node.data=true,数据节点主要是存储索引数据的节点,主要对文档进行增删改查操作,聚合操作等,数据节点对CPU,IO,内存要求较高,优化节点的时候需要做状态监控,资源不够时要做节点扩充。配置:mode.master=false,mode.data=true

负载均衡节点

当主节点和数据节点配置都设置为false的时候,该节点只能处理路由请求,处理搜索,分发索引操作等,从本质上来说该客户节点表现为智能负载平衡器。配置:mode.master=false,mode.data=false

模拟搭建集群

准备三个ES服务

可以模拟出三个不同的文件,修改不同的端口

配置说明

  • Node1-配置
1
2
3
4
5
6
7
8
9
10
11
12
# 统一的集群名
cluster.name: my-ealsticsearch
# 当前节点名
node.name: node-1
# 对外暴露端口使外网访问
network.host: 127.0.0.1
# 对外暴露端口
http.port: 9201
#集群间通讯端口号
transport.tcp.port: 9301
#集群的ip集合,可指定端口,默认为9300
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
  • Node2-配置
1
2
3
4
5
6
7
8
9
10
11
12
# 统一的集群名
cluster.name: my-ealsticsearch
# 当前节点名
node.name: node-2
# 对外暴露端口使外网访问
network.host: 127.0.0.1
# 对外暴露端口
http.port: 9202
#集群间通讯端口号
transport.tcp.port: 9302
#集群的ip集合,可指定端口,默认为9300
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
  • Node3-配置
1
2
3
4
5
6
7
8
9
10
11
12
# 统一的集群名
cluster.name: my-ealsticsearch
# 当前节点名
node.name: node-3
# 对外暴露端口使外网访问
network.host: 127.0.0.1
# 对外暴露端口
http.port: 9203
#集群间通讯端口号
transport.tcp.port: 9303
#集群的ip集合,可指定端口,默认为9300
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]

分别启动三个ES节点 , 访问:http://127.0.0.1:9201/

然后再通过Kiban5去访问,修改默认配置路径为:elasticsearch.url: “http://localhost:9201

访问成功

Java操作ES

创建一个Maven项目,在pom.xml中加入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<dependencies>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>

写一个连接ES的工具类

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
package com.ifueen.es;

import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
* ES集群的工具类
*/
public class ESClientUtil {

public static TransportClient getClient(){
Settings settings = Settings.builder()
.put("cluster.name","my-ealsticsearch")
.put("client.transport.sniff", true).build();

TransportClient client = null;
try {
client = new PreBuiltTransportClient(settings)
.addTransportAddress(
new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9302));
} catch (UnknownHostException e) {
e.printStackTrace();
}
return client;
}
}

然后开始测试

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package com.ifueen.es;

import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequestBuilder;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHits;
import org.junit.Test;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
* 测试集群
*/
public class TestESCluster {
//获取客户端
TransportClient client = ESClientUtil.getClient();
/**
* 添加文档
*/
@Test
public void testadd(){

//创建索引
IndexRequestBuilder index = client.prepareIndex("fueen", "person", "1");
Map<String,Object> data = new HashMap<String, Object>();
data.put("id","1");
data.put("username","fueen");
data.put("speak","我们不能失去信仰");
//添加
IndexResponse indexResponse = index.setSource(data).get();
System.out.println(indexResponse);
client.close();

}

/**
* 获取文档
*/
@Test
public void testquery(){
GetResponse getFields = client.prepareGet("fueen", "person", "1").get();
System.out.println(getFields.getSource());
}

/**
* 更新
*/
@Test
public void testupdate(){
HashMap<String, Object> map = new HashMap<>();
map.put("id","1");
map.put("username","这个世界会好吗");
map.put("speak","李志");
UpdateRequestBuilder builder = client.prepareUpdate("fueen", "person", "1");
UpdateResponse updateResponse = builder.setDoc(map).get();
System.out.println(updateResponse);
client.close();
}

/**
* 删除文档
*/
@Test
public void testdel(){
DeleteRequestBuilder del = client.prepareDelete("fueen", "person", "1");
DeleteResponse deleteResponse = del.get();
System.out.println(deleteResponse);
client.close();
}

/**
* 批量添加
*/
@Test
public void testbulikadd(){
BulkRequestBuilder builder = client.prepareBulk();
Map<String, Object> map = new HashMap<>();
map.put("id","1");
map.put("username","会沉寂吗");
map.put("speak","我的金桔");
builder.add(client.prepareIndex("fueen","person","1")
.setSource(map));


Map<String, Object> map1 = new HashMap<>();
map1.put("id","2");
map1.put("username","会沉寂吗");
map1.put("speak","人民不需要自由");
builder.add(client.prepareIndex("fueen","person","2")
.setSource(map1));

BulkResponse bulkItemResponses = builder.get();
Iterator<BulkItemResponse> iterator = bulkItemResponses.iterator();
while (iterator.hasNext()){
BulkItemResponse next = iterator.next();
System.out.println(next.getResponse());
}
client.close();
}

/**
* 批量查询
*/
@Test
public void testbulikquery(){
SearchRequestBuilder fueen = client.prepareSearch("fueen");
fueen.setTypes("person");
fueen.setFrom(0);
fueen.setSize(10);

//查询条件
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
List<QueryBuilder> must = boolQueryBuilder.must();
must.add(QueryBuilders.matchQuery("username","会沉寂吗"));

SearchResponse searchResponse = fueen.setQuery(boolQueryBuilder).get();
SearchHits hits = searchResponse.getHits();
System.out.println("条数:"+hits.getTotalHits());
hits.forEach(h->{
System.out.println(h);
});
client.close();
}

}


记几次Java面经
永远的蝴蝶
© 2019-2024 Fueen
Powered by hexo