什么是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是否连接
xxxxxxxxxx1GET /
es在创建倒排索引时需要对文档分词,在搜索时,需要对用户输入内容分词,但默认的分词规则对中文处理并不友好,我们在kibana的DevTools中测试
xxxxxxxxxx1POST /_analyze2{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/bash3
4# 在线下载并安装5./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip6
7#退出8exit9#重启容器10docker restart elasticsearch
离线安装
安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看
11docker volume inspect es-plugins显示结果:
xxxxxxxxxx111[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这个目录中
重启容器
xxxxxxxxxx1#重启容器2docker restart es3#查看es日志4docker logs -f es
测试IK分词器
IK分词器包含两种模式:
ik_smart:最少切分(粗粒度切分,分词较少)ik_max_word:最细粒度切分(分词较多,带来的内存消耗大)
分词器都会依赖于一个字典来进行分词,我们可以为字典添加拓展词汇和停用词汇
ik分词器-拓展词库
要拓展ik分词器的词库,需要修改ik分词器目录下config目录中的IkAnalyzer.cfg.xml文件
x
1 23<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 /os4673{4 "mappings": {5 "properties": {6 "info":{7 "type": "text",8 "analyzer": "ik_smart"9 },10 "email":{11 "type": "keyword",12 "index": false13 },14 "name":{15 "type": "object",16 "properties": {17 "firstName":{18 "type": "keyword"19 },20 "lastName":{21 "type": "keyword"22 }23 }24 }25 }26 }27}
xxxxxxxxxx1#查询索引库2GET /索引库名称3#删除索引库4DELETE /索引库名称
禁止修改原有字段
禁止修改索引库原有字段,因为mapping约束定义好后,es就会创建倒排索引库,如果修改原字段会对倒排索引库产生影响
添加新字段
索引库和mapping一旦创建无法修改,但可以添加新字段(新字段名不能和旧字段名重复)
xxxxxxxxxx1PUT /索引库名称/_mapping2{3 "properties":{4 "新字段名":{5 "type":"integer"6 }7 }8}测试
91#修改索引库,添加新字段2PUT /os467/_mapping3{4 "properties":{5 "age":{6 "type":"integer"7 }8 }9}
每次写操作都会导致文档版本增加
DSL语法:
如果不加id,会由ES随机生成id
xxxxxxxxxx101POST /索引库名称/_doc/文档id2{3 "字段1":"值1",4 "字段2":"值2",5 "字段3":{6 "子字段1":"值3",7 "子字段2":"值4"8 },9 //...略10}测试
101#插入文档2POST /os467/_doc/13{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不存在则新增)
xxxxxxxxxx1PUT /索引库名称/_doc/文档id2{3 "字段1":"值1",4 "字段2":"值2"5 //...略6}测试
101#全量修改文档2PUT /os467/_doc/13{4 "info":"os467的个人简介内容",5 "email":"zhangsan@qq.com",6 "name":{7 "firstName":"三",8 "lastName":"张"9 }10}
方式二:增量修改(局部修改),修改指定字段
xxxxxxxxxx1POST /索引库名/_update/文档id2{3 "doc":{4 "字段名":"新的值"5 }6}测试
x
1#局部修改文档字段2POST /os467/_update/13{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# 酒店的mapping2PUT /hotel3{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": false17 },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": false44 },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
12void 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 }
判断索引库是否存在
xxxxxxxxxx12 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 }