文章目录
- 一、ElasticSearch
- 1.1 概述
- 1.2 倒排索引
- 1.3 ES与MySQL的概念对比
- 二、 安装
- 2.1 部署单点ES
- 2.2 部署kibana
- 三、安装IK分词器
- 3.1 在线安装ik插件(较慢)
- 3.2 离线安装ik插件(推荐)
- 3.3 扩展词词典
- 3.4 停用词词典
- 四、索引库操作
- 4.1 mapping 约束
- 4.2 操作索引库
- 4.3 操作文档
- 五、RestClient操作索引库
- 5.1 准备工作
- 5.2 分析数据结构,编写DSL代码
- 5.3 初始化JavaRestClient
- 5.4 创建、判断存在、删除索引库
- 5.5 增、查、删、改、批量导入文档
一、ElasticSearch
1.1 概述
elasticsearch是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。
elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域。
2004年Shay Banon基于Lucene开发了Compass
2010年Shay Banon 重写了Compass,取名为Elasticsearch。
官网地址: https://www.elastic.co/cn/
目前最新的版本是:7.12.1
相比与lucene,elasticsearch具备下列优势:支持分布式,可水平扩展;提供Restful接口,可被任何语言调用
1.2 倒排索引
倒排索引中包含两部分内容:
- 词条词典(Term Dictionary):记录所有词条,以及词条与倒排列表(Posting List)之间的关系,会给词条创建索引,提高查询和插入效率
- 倒排列表(Posting List):记录词条所在的文档id、词条出现频率 、词条在文档中的位置等信息
文档id:用于快速获取文档
词条频率(TF):文档在词条出现的次数,用于评分
1.3 ES与MySQL的概念对比
elasticsearch是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中。
二、 安装
2.1 部署单点ES
- 创建网络
因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络:
docker network create es-net
- 加载镜像
这里我们采用elasticsearch的7.12.1版本的镜像,这个镜像体积非常大,接近1G。不建议大家自己pull。
课前资料提供了镜像的tar包:
大家将其上传到虚拟机中,然后运行命令加载即可:
# 导入数据
docker load -i es.tar
同理还有kibana
的tar包也需要这样做。
- 运行
运行docker命令,部署单点es:
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
命令解释:
-e "cluster.name=es-docker-cluster"
:设置集群名称-e "http.host=0.0.0.0"
:监听的地址,可以外网访问-e "ES_JAVA_OPTS=-Xms512m -Xmx512m"
:内存大小-e "discovery.type=single-node"
:非集群模式-v es-data:/usr/share/elasticsearch/data
:挂载逻辑卷,绑定es的数据目录-v es-logs:/usr/share/elasticsearch/logs
:挂载逻辑卷,绑定es的日志目录-v es-plugins:/usr/share/elasticsearch/plugins
:挂载逻辑卷,绑定es的插件目录--privileged
:授予逻辑卷访问权--network es-net
:加入一个名为es-net的网络中-p 9200:9200
:端口映射配置
在浏览器中输入:http://主机IP:9200 即可看到elasticsearch的响应结果:
2.2 部署kibana
kibana可以给我们提供一个elasticsearch的可视化界面,便于我们学习。
- 部署
运行docker命令,部署kibana
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601 \
kibana:7.12.1
--network es-net
:加入一个名为es-net的网络中,与elasticsearch在同一个网络中-e ELASTICSEARCH_HOSTS=http://es:9200"
:设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch-p 5601:5601
:端口映射配置
kibana启动一般比较慢,需要多等待一会,可以通过命令:
docker logs -f kibana
查看运行日志,当查看到下面的日志,说明成功:
此时,在浏览器输入地址访问:http://主机IP:5601,即可看到结果
选择Explore on my owen ,再选择Dev Tools 即可进入下图界面
- DevTools
kibana中提供了一个DevTools界面:
这个界面中可以编写DSL来操作elasticsearch。并且对DSL语句有自动补全功能。
三、安装IK分词器
es在创建倒排索引时需要对文档分词;在搜索时,需要对用户输入内容分词。但默认的分词规则对中文处理并不友好。
3.1 在线安装ik插件(较慢)
# 进入容器内部
docker exec -it elasticsearch /bin/bash
# 在线下载并安装
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
#退出
exit
#重启容器
docker restart elasticsearch
3.2 离线安装ik插件(推荐)
- 查看数据卷目录
安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看:
docker volume inspect es-plugins
显示结果:
[
{
"CreatedAt": "2022-05-06T10:06:34+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
"Name": "es-plugins",
"Options": null,
"Scope": "local"
}
]
说明plugins目录被挂载到了:/var/lib/docker/volumes/es-plugins/_data
这个目录中。
-
解压缩分词器安装包
下面我们需要把课前资料中的ik分词器解压缩,重命名为ik -
上传到es容器的插件数据卷中
也就是/var/lib/docker/volumes/es-plugins/_data
:
- 重启容器
# 4、重启容器
docker restart es
# 查看es日志
docker logs -f es
- 测试:
IK分词器包含两种模式:
-
ik_smart
:最少切分 -
ik_max_word
:最细切分
3.3 扩展词词典
随着互联网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如:“奥力给”,“传智播客” 等。
所以我们的词汇也需要不断的更新,IK分词器提供了扩展词汇的功能。
1)打开IK分词器config目录:
2)在IKAnalyzer.cfg.xml配置文件内容添加:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 *** 添加扩展词典-->
<entry key="ext_dict">ext.dic</entry>
</properties>
3)新建一个 ext.dic,可以参考config目录下复制一个配置文件进行修改
传智播客
奥力给
4)重启elasticsearch
docker restart es
# 查看 日志
docker logs -f elasticsearch
日志中已经成功加载ext.dic配置文件
5)测试效果:
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "传智播客Java就业超过90%,奥力给!"
}
注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑
3.4 停用词词典
在互联网项目中,在网络间传输的速度很快,所以很多语言是不允许在网络上传递的,如:关于宗教、政治等敏感词语,那么我们在搜索时也应该忽略当前词汇。
IK分词器也提供了强大的停用词功能,让我们在索引时就直接忽略当前的停用词汇表中的内容。
1)IKAnalyzer.cfg.xml配置文件内容添加:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典-->
<entry key="ext_dict">ext.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典 *** 添加停用词词典-->
<entry key="ext_stopwords">stopword.dic</entry>
</properties>
3)在 stopword.dic 添加停用词
的
哦
4)重启elasticsearch
# 重启服务
docker restart elasticsearch
docker restart kibana
# 查看 日志
docker logs -f elasticsearch
日志中已经成功加载stopword.dic配置文件
5)测试效果:
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "传智播客Java就业率超过95%的奥力给哦!"
}
注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑
四、索引库操作
4.1 mapping 约束
4.2 操作索引库
# 1.创建索引
PUT /heima
{
"mappings": {
"properties": {
"infomation": {
"type": "text",
"analyzer": "ik_smart"
},
"email": {
"type": "keyword",
"index": false
},
"name": {
"type": "object",
"properties": {
"firstName": {
"type": "keyword"
},
"lasttName": {
"type": "keyword"
}
}
}
}
}
}
# 2. 查询索引库
GET /heima
# 3. 删除索引库
DELETE /heima
# 4. 修改索引库【索引库一旦创建无法修改,除非添加全新的字段】
PUT /heima/_mapping
{
"properties": {
"age": {
"type": "integer"
}
}
}
4.3 操作文档
# 1. 插入文档 语法:POST /索引名/_doc/文档id
POST /heima/_doc/1
{
"information": "黑马",
"email": "acsnai@163.com",
"name": {
"firstName": "吕",
"lasttName": "步"
},
"age": 19
}
# 2. 查询文档
GET /heima/_doc/2
# 3. 删除文档
DELETE /heima/_doc/1
# 4. 修改文档
# 4.1 全量修改,会删除旧文档,添加新文档(如果id不存在,相当于新增)
PUT /heima/_doc/2
{
"information": "黑马",
"email": "acsnai@163.com",
"name": {
"firstName": "吕",
"lasttName": "步"
},
"age": 19
}
# 4.2 局部修改
# 语法:POST /索引名/_update/文档id { "doc": {} }
POST /heima/_update/2
{
"doc": {
"email": "eceee@qq.com"
}
}
五、RestClient操作索引库
5.1 准备工作
将tb_hotel.sql
导入mysql数据库中,将hotel-demo导入idea中
5.2 分析数据结构,编写DSL代码
# 酒店的mapping
# 1.当想要多条件查询时,使用copy to
# 如下:定义一个all字段,在想要当做查询条件的字段中使用copy to
# 2. 经纬度使用:geo_point类型
PUT /hotel
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address": {
"type": "keyword",
"index": false
},
"price": {
"type": "integer"
},
"score": {
"type": "integer"
},
"brand": {
"type": "keyword",
"copy_to": "all"
},
"city": {
"type": "keyword"
},
"starName": {
"type": "keyword"
},
"business": {
"type": "keyword",
"copy_to": "all"
},
"location": {
"type": "geo_point"
},
"pic": {
"type": "keyword",
"index": false
},
"all": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
5.3 初始化JavaRestClient
- 引入es的RestHighLevelClient依赖:
<!--1.引入elasticsearch依赖-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
- 因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:
<!--2.使用7.12.1版本的elasticsearch-->
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
- 初始化RestHighLevelClient:
@SpringBootTest
class HotelIndexTest {
private RestHighLevelClient client;
/**
* 开始前,初始化ES,绑定IP地址: 自己的虚拟机地址:9200
* 目的:不用每次开始测试都要加上这些代码
*/
@BeforeEach
void setUp() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.101:9200")
));
}
/**
* 结束后销毁资源
* 目的:不用每次开始测试都要加上这些代码
*/
@AfterEach
void tearDown() throws IOException {
client.close();
}
}
5.4 创建、判断存在、删除索引库
@SpringBootTest
class HotelIndexTest {
private RestHighLevelClient client;
/**
* 开始前,初始化ES,绑定IP地址: 自己的虚拟机地址:9200
* 目的:不用每次开始测试都要加上这些代码
*/
@BeforeEach
void setUp() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.101:9200")
));
}
/**
* 结束后销毁资源
* 目的:不用每次开始测试都要加上这些代码
*/
@AfterEach
void tearDown() throws IOException {
client.close();
}
/**
* 创建索引库hotel
* @throws IOException
*/
@Test
void testCreateIndex() throws IOException {
// 1.准备Request PUT /hotel
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.准备请求参数 MAPPING_TEMPLAT是静态常量字符串,内容是创建索引库的DSL语句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.发送请求 创建索引库
client.indices().create(request, RequestOptions.DEFAULT);
}
/**
* 判断索引库hotel是否存在
* @throws IOException
*/
@Test
void testExistsIndex() throws IOException {
// 1.准备Request
GetIndexRequest request = new GetIndexRequest("hotel");
// 3.发送请求
boolean isExists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(isExists ? "存在" : "不存在");
}
/**
* 删除索引库hotel
* @throws IOException
*/
@Test
void testDeleteIndex() throws IOException {
// 1.准备Request
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 3.发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
}
将上述DSL语句定义成静态变量MAPPING_TEMPLATE
public class HotelIndexConstants {
public static final String MAPPING_TEMPLATE = "{\n" +
" \"mappings\": {\n" +
" \"properties\": {\n" +
" \"id\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"name\": {\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"address\": {\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"price\": {\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"score\": {\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"brand\": {\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"city\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"starName\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"business\": {\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"pic\": {\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"location\": {\n" +
" \"type\": \"geo_point\"\n" +
" },\n" +
" \"all\": {\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
}
5.5 增、查、删、改、批量导入文档
@SpringBootTest
class HotelDocumentTest {
private RestHighLevelClient client;
@Autowired
private IHotelService hotelService;
@BeforeEach
void setUp() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.101:9200")
));
}
@AfterEach
void tearDown() throws IOException {
client.close();
}
/**
* 添加文档:将 [数据库] 中一条 [数据] 取出然后转换成 [文档] 加入 [索引库] 中
* @throws IOException
*/
@Test
void testAddDocument() throws IOException {
// 1.查询数据库hotel数据
Hotel hotel = hotelService.getById(61083L);
// 2.转换为HotelDoc
HotelDoc hotelDoc = new HotelDoc(hotel);
// 3.转JSON
String json = JSON.toJSONString(hotelDoc);
// 1.准备Request
IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
// 2.准备请求参数DSL,其实就是文档的JSON字符串
request.source(json, XContentType.JSON);
// 3.发送请求
client.index(request, RequestOptions.DEFAULT);
}
/**
* 查询文档
* @throws IOException
*/
@Test
void testGetDocumentById() throws IOException {
// 1.准备Request // GET /hotel/_doc/{id}
GetRequest request = new GetRequest("hotel", "61083");
// 2.发送请求
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 3.解析响应结果
String json = response.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println("hotelDoc = " + hotelDoc);
}
/**
* 删除文档
* @throws IOException
*/
@Test
void testDeleteDocumentById() throws IOException {
// 1.准备Request // DELETE /hotel/_doc/{id}
DeleteRequest request = new DeleteRequest("hotel", "61083");
// 2.发送请求
client.delete(request, RequestOptions.DEFAULT);
}
/**
* 更新文档
* @throws IOException
*/
@Test
void testUpdateById() throws IOException {
// 1.准备Request
UpdateRequest request = new UpdateRequest("hotel", "61083");
// 2.准备参数
request.doc(
"price", "870"
);
// 3.发送请求
client.update(request, RequestOptions.DEFAULT);
}
/**
* 批量导入文档
* @throws IOException
*/
@Test
void testBulkRequest() throws IOException {
// 查询所有的酒店数据
List<Hotel> list = hotelService.list();
// 1.准备Request
BulkRequest request = new BulkRequest();
// 2.准备参数
for (Hotel hotel : list) {
// 2.1.转为HotelDoc
HotelDoc hotelDoc = new HotelDoc(hotel);
// 2.2.转json
String json = JSON.toJSONString(hotelDoc);
// 2.3.添加请求
request.add(new IndexRequest("hotel").id(hotel.getId().toString())
.source(json, XContentType.JSON));
}
// 3.发送请求
client.bulk(request, RequestOptions.DEFAULT);
}
}
HotelDoc
对应的是ES的hotel的实体类、Hotel
对应的是mysql的hotel的实体
@Data
@NoArgsConstructor
public class HotelDoc {
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String location;
private String pic;
public HotelDoc(Hotel hotel) {
this.id = hotel.getId();
this.name = hotel.getName();
this.address = hotel.getAddress();
this.price = hotel.getPrice();
this.score = hotel.getScore();
this.brand = hotel.getBrand();
this.city = hotel.getCity();
this.starName = hotel.getStarName();
this.business = hotel.getBusiness();
this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
this.pic = hotel.getPic();
}
}
@Data
@TableName("tb_hotel")
public class Hotel {
@TableId(type = IdType.INPUT)
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String longitude;
private String latitude;
private String pic;
}
在kibana的DevTools界面,输入:GET /hotel/_search
可以看到数据都导入到了ES中