什么是elasticsearch?
elasticsearch是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容
elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK),被广泛应用在日志数据分析、实时监控等领域
数据可视化
存储、搜索、分析数据
数据抓取
ElasticSearch的底层是Lucene技术(apache组织提供)
Elastic Search的发展
2004年Shay Banon基于Lucene开发了Compass
2010年Shay Banon重写了Compass,取名为Elasticsearch
官网地址:https://www.elastic.co/cn/
相比与Lucene,elasticsearch具备下列优势
较热门的搜索引擎技术:
正向索引和倒排索引
正向索引(根据文档找到词)
传统数据库(如mysql)采用正向索引,例如给下表(tb_goods)中的id创建索引:
id | title | price |
---|---|---|
1 | 小米手机 | 3499 |
2 | 华为手机 | 4999 |
3 | 华为小米充电器 | 49 |
4 | 小米手环 | 49 |
... | ... | ... |
搜索 手机
select * from tb_goods where title like '%手机%'
逐条数据扫描,判断是否包含手机
,如果包含则存入结果集中
倒排索引(根据词找文档)
词条(term) | 文档id |
---|---|
小米 | 1,3,4 |
手机 | 1,2 |
华为 | 2,3 |
充电器 | 3 |
手环 | 4 |
elasticsearch采用倒排索引
使用倒排索引搜索华为手机
分词,得到华为
,手机
两个词条
根据词条去词条列表查询文档id
得到每个词条所在文档id:
华为:2,3
手机:1,2
根据文档id查询文档,得到id为1,2,3的文档,存入结果集中(2的关联度较高会放在前面)
elasticsearch是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息
文档数据会被序列化为JSON格式后存储在elasticsearch中
相同类型的文档的集合,如商品索引,用户索引,订单索引(类似数据库表的概念)
映射(mapping):索引中文档的字段约束信息,类似表的结构约束
MySQL | Elasticsearch | 说明 |
---|---|---|
Table | Index | 索引(index),就是文档的集合,类似数据库的表(table) |
Row | Document | 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 |
Column | Field | 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column) |
Schema | Mapping | Mapping(映射)是索引中文档的约束,例如字段类型约束,类似数据库的表结构(Schema) |
SQL | DSL | DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD |
架构对比
ES承担了搜索操作,Mysql承担了写操作,mysql可以将数据同步到ES中(起到了互补的效果)
部署单点es
1、创建网络
因为我们还要部署kibana容器,因此需要让es和kibana容器互联,这里先创建一个网络:
11docker network create es-net
2、加载镜像
这里我们采用elasticsearch的1.12.1版本的镜像,这个镜像体积非常大,接近1G,不建议自己pull
使用镜像tar包,上传到虚拟机使用命令加载即可
x
1docker load -i es.tar
同理kibana的tar包也是如此
3、运行
运行docker命令,部署单点es:
-e参数说明
-v参数说明
--network
: 加入网络
--privileged
:授予逻辑卷访问权
-p参数说明
9200端口:http端口,提供用户访问
9300端口:容器之间各个节点互联端口
x
111docker run -d \
2 --name es \
3 -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
4 -e "discovery.type=single-node" \
5 -v es-data:/usr/share/elasticsearch/data \
6 -v es-plugins:/usr/share/elasticsearch/plugins \
7 --privileged \
8 --network es-net \
9 -p 9200:9200 \
10 -p 9300:9300 \
11elasticsearch:7.12.1
输入192.168.119.88:9200
查看访问结果
x
1docker run -d \
2--name kibana \
3-e ELASTICSEARCH_HOSTS=http://es:9200 \
4--network=es-net \
5-p 5601:5601 \
6kibana:7.12.1
--network es-net
:加入一个名为es-net的网络中,与elasticsearch在同一个网络中-e ELASTICSEARCH_HOSTS=http://es:9200"
:设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch-p 5601:5601
:端口映射配置kibana版本需要和es版本保持一致
浏览器访问192.168.119.88:5601
查看图形化界面
使用Kibana提供的Dev Tools来发送DSL语句(本质上就是发送一个RESTFUL的请求到es当中)
测试es是否连接
xxxxxxxxxx
1GET /
es在创建倒排索引时需要对文档分词,在搜索时,需要对用户输入内容分词,但默认的分词规则对中文处理并不友好,我们在kibana的DevTools中测试
xxxxxxxxxx
1POST /_analyze
2{
3 "analyzer":"standard",
4 "text":"学习分布式搜索引擎ElasticSearch"
5}
语法说明:
POST:请求方式
/_analyze:请求路径,这里省略了http://192.168.119.88:9200
,由kibana帮我们补充
请求参数,json风格:
处理中文分词,一般会使用IK分词器
网址:https://github.com/medcl/elasticsearch-analysis-ik
安装IK分词器
在线拉取
101# 进入容器内部
2docker exec -it elasticsearch /bin/bash
3
4# 在线下载并安装
5./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
6
7#退出
8exit
9#重启容器
10docker restart elasticsearch
离线安装
安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看
11docker volume inspect es-plugins
显示结果:
xxxxxxxxxx
111[
2 {
3 "CreatedAt": "2022-05-06T10:06:34+08:00",
4 "Driver": "local",
5 "Labels": null,
6 "Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
7 "Name": "es-plugins",
8 "Options": null,
9 "Scope": "local"
10 }
11]
说明plugins目录被挂载到了:/var/lib/docker/volumes/es-plugins/_data
这个目录中
重启容器
xxxxxxxxxx
1#重启容器
2docker restart es
3#查看es日志
4docker logs -f es
测试IK分词器
IK分词器包含两种模式:
ik_smart
:最少切分(粗粒度切分,分词较少)ik_max_word
:最细粒度切分(分词较多,带来的内存消耗大)
分词器都会依赖于一个字典来进行分词,我们可以为字典添加拓展词汇和停用词汇
ik分词器-拓展词库
要拓展ik分词器的词库,需要修改ik分词器目录下config目录中的IkAnalyzer.cfg.xml文件
x
1
2
3<properties>
4 <comment>IK Analyzer 扩展配置</comment>
5
6 <!--用户可以在这里配置自己的扩展字典 -->
7 <entry key="ext_dict"></entry>
8
9 <!--用户可以在这里配置自己的扩展停止词字典-->
10 <entry key="ext_stopwords"></entry>
11
12 <!--用户可以在这里配置远程扩展字典 -->
13 <!-- <entry key="remote_ext_dict">words_location</entry> -->
14
15 <!--用户可以在这里配置远程扩展停止词字典-->
16 <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
17
18</properties>
19
指定拓展词所在文件名(与当前目录同级)
41<!--用户可以在这里配置自己的扩展字典 -->
2<entry key="ext_dict">ext.dic</entry>
3 <!--用户可以在这里配置自己的扩展停止词字典-->
4<entry key="ext_stopwords">stopword.dic</entry>
创建ext.dic,添加扩展词即可,一行一个词语,注意必须都是UTF-8编码格式
分词器的作用
mapping是对索引库中文档的约束,常见的mapping属性包括:
type 字段数据类型,常见类型有
在es中没有数组,但是允许某个字段有多个值
index:是否创建倒排索引,默认为true(是否参与倒排索引)
analyzer:使用哪种分词器(结合text类型使用)
properties:该字段的子字段
x
1{
2 "age":21,
3 "weight":53.2,
4 "isMarried":false,
5 "info":"简介内容",
6 "email":"os467@qq.com",
7 "score":[99.1,99.5,98.9],
8 "name":{
9 "firstName":"三",
10 "lastName":"张"
11 }
12}
ES中通过Restful请求操作索引库、文档,请求内容用DSL语句来表示
创建索引库和mapping的DSL语法如下:
x
1PUT /索引库名称
2{
3 "mappings":{
4 "properties":{
5 "字段名1":{
6 "type":"text",
7 "analyzer":"ik_smart"
8 },
9 "字段名2":{
10 "type":"keyword",
11 "index":"false"
12 },
13 "字段名3":{
14 "properties":{
15 "子字段":{
16 "type":"keyword"
17 }
18 }
19 },
20 //...略
21 }
22 }
23}
创建索引库
271#创建索引库
2PUT /os467
3{
4 "mappings": {
5 "properties": {
6 "info":{
7 "type": "text",
8 "analyzer": "ik_smart"
9 },
10 "email":{
11 "type": "keyword",
12 "index": false
13 },
14 "name":{
15 "type": "object",
16 "properties": {
17 "firstName":{
18 "type": "keyword"
19 },
20 "lastName":{
21 "type": "keyword"
22 }
23 }
24 }
25 }
26 }
27}
xxxxxxxxxx
1#查询索引库
2GET /索引库名称
3#删除索引库
4DELETE /索引库名称
禁止修改原有字段
禁止修改索引库原有字段,因为mapping约束定义好后,es就会创建倒排索引库,如果修改原字段会对倒排索引库产生影响
添加新字段
索引库和mapping一旦创建无法修改,但可以添加新字段(新字段名不能和旧字段名重复)
xxxxxxxxxx
1PUT /索引库名称/_mapping
2{
3 "properties":{
4 "新字段名":{
5 "type":"integer"
6 }
7 }
8}
测试
91#修改索引库,添加新字段
2PUT /os467/_mapping
3{
4 "properties":{
5 "age":{
6 "type":"integer"
7 }
8 }
9}
每次写操作都会导致文档版本增加
DSL语法:
如果不加id,会由ES随机生成id
xxxxxxxxxx
101POST /索引库名称/_doc/文档id
2{
3 "字段1":"值1",
4 "字段2":"值2",
5 "字段3":{
6 "子字段1":"值3",
7 "子字段2":"值4"
8 },
9 //...略
10}
测试
101#插入文档
2POST /os467/_doc/1
3{
4 "info":"os467的个人简介内容",
5 "email":"os467@qq.com",
6 "name":{
7 "firstName":"三",
8 "lastName":"张"
9 }
10}
11GET /索引库名称/_doc/文档id
测试
21#查询文档
2GET /os467/_doc/1
11DELETE /索引库名称/_doc/文档id
测试
21#删除文档
2DELETE /os467/_doc/1
方式一:全量修改,会删除旧文档,添加新文档(如果id不存在则新增)
xxxxxxxxxx
1PUT /索引库名称/_doc/文档id
2{
3 "字段1":"值1",
4 "字段2":"值2"
5 //...略
6}
测试
101#全量修改文档
2PUT /os467/_doc/1
3{
4 "info":"os467的个人简介内容",
5 "email":"zhangsan@qq.com",
6 "name":{
7 "firstName":"三",
8 "lastName":"张"
9 }
10}
方式二:增量修改(局部修改),修改指定字段
xxxxxxxxxx
1POST /索引库名/_update/文档id
2{
3 "doc":{
4 "字段名":"新的值"
5 }
6}
测试
x
1#局部修改文档字段
2POST /os467/_update/1
3{
4 "doc":{
5 "email":"os467@qq.com"
6 }
7}
以后在java开发中,我们会使用java代码来实现操作es引擎,因此我们使用es官方提供的RestClient
什么是RestClient?
ES官方提供了各种不同语言的客户端,用来操作ES,这些客户端的本质就是组装DSL语句,通过http请求发送给ES
官方文档:https://www.elastic.co/guide/en/elasticsearch/client/index.html
利用JavaRestClient实现创建、删除索引库,判断索引库是否存在
mapping要考虑的问题:
字段名、数据类型、是否参与搜索、是否分词、如果分词,分词器是什么
ES中支持两种地理坐标数据类型:
geo_point:由维度(latitude)和经度(longitude)确定的一个点,例如
"116.397128, 39.916527"
geo_shape:有多个geo_point组成的复杂几何图形,例如一条直线
"LINESTRING(-74.19231902148438, -56.6090593036236)"
字段拷贝
当我们需要对几个相关字段进行查询的时候,我们可以使用copy_to工具来高效的创建一个新的字段来存储我们需要查询的词
字段拷贝可以使用copy_to属性将当前字段拷贝到指定字段,实例:
x
1"all":{
2 "type": "text",
3 "analyzer": "ik_max_word"
4},
5"brand":{
6 "type": "keyword",
7 "copy_to": "all"
8}
示例
x
1# 酒店的mapping
2PUT /hotel
3{
4 "mappings": {
5 "properties": {
6 "id":{
7 "type": "keyword"
8 },
9 "name":{
10 "type": "text",
11 "analyzer": "ik_max_word",
12 "copy_to": "all"
13 },
14 "address":{
15 "type": "keyword",
16 "index": false
17 },
18 "price":{
19 "type": "integer"
20 },
21 "score":{
22 "type": "integer"
23 },
24 "brand":{
25 "type": "keyword",
26 "copy_to": "all"
27 },
28 "city":{
29 "type": "keyword"
30 },
31 "starName":{
32 "type": "keyword"
33 },
34 "business":{
35 "type": "keyword",
36 "copy_to": "all"
37 },
38 "location":{
39 "type": "geo_point"
40 },
41 "pic":{
42 "type": "keyword",
43 "index": false
44 },
45 "all":{
46 "type": "text",
47 "analyzer": "ik_max_word"
48 }
49 }
50 }
51}
引入es的RestHighLevelClient依赖坐标
1<dependency>
2 <groupId>org.elasticsearch.client</groupId>
3 <artifactId>elasticsearch-rest-high-level-client</artifactId>
4</dependency>
因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本
x
1<properties>
2 <java.version>1.8</java.version>
3 <elasticsearch.version>7.12.1</elasticsearch.version>
4</properties>
初始化RestHighLevelClient
31RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
2 HttpHost.create("http://192.168.119.88:9200")
3 ));
测试
x
1package cn.itcast.hotel;
2
3
4import org.apache.http.HttpHost;
5import org.elasticsearch.client.RestClient;
6import org.elasticsearch.client.RestHighLevelClient;
7import org.junit.jupiter.api.AfterEach;
8import org.junit.jupiter.api.BeforeEach;
9
10import java.io.IOException;
11
12
13public class HotelIndexTest {
14
15 private RestHighLevelClient client;
16
17
18 void setUp(){
19 this.client = new RestHighLevelClient(RestClient.builder(
20 HttpHost.create("http://192.168.119.88:9200")
21 ));
22 }
23
24
25
26 void tearDown() throws IOException {
27 this.client.close();
28 }
29
30}
创建索引库
步骤:
1、准备request对象,指定索引库名称
2、指定DSL,以及格式为JSON
3、获取到索引库操作对象,调用创建索引库的方法
测试
x
1
2void testCreateHotelIndex(){
3
4 //1、创建Request对象,需要传入索引库名称
5 CreateIndexRequest request = new CreateIndexRequest("hotel");
6
7 //2、请求参数:DSL语句,MAPPING_TEMPLATE是静态常量字符串,内容是创建索引库的DSL语句
8 request.source(MAPPING_TEMPLATE, XContentType.JSON);
9
10 try {
11 //发起请求
12 //indices返回的对象中包含索引库操作的所有方法,索引库操作对象
13 client.indices().create(request, RequestOptions.DEFAULT);
14 } catch (IOException e) {
15 e.printStackTrace();
16 }
17
18}
删除索引库
141
2 void testDeleteHotelIndex(){
3
4 //1、创建Request对象
5 DeleteIndexRequest request = new DeleteIndexRequest("hotel");
6
7 try {
8 //2、发送请求
9 client.indices().delete(request,RequestOptions.DEFAULT);
10 } catch (IOException e) {
11 e.printStackTrace();
12 }
13
14 }
判断索引库是否存在
xxxxxxxxxx
1
2 void testExistsHotelIndex(){
3
4 //1、创建Request对象
5 GetIndexRequest request = new GetIndexRequest("hotel");
6
7 try {
8 //2、发送请求
9 boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
10 //3、输出
11 System.out.println(exists ? "索引库存在":"索引库不存在");
12
13 } catch (IOException e) {
14 e.printStackTrace();
15 }
16
17 }