前言
本篇内容为学习ElasticSearch 的课后总结,本文大致包括以下内容:
- ElasticSearch的介绍
- ElasticSearch与Kibana的单机环境搭建
- Kibana中操作ES
- 在集成环境中操作ES
整个过程记录详细,每个步骤亲历亲为,实测可用。
一、引言
-
什么是elasticsearch?
:elasticsearch(以下简称es)是一款强大的开源搜索引擎,可以帮助我们从海量的数据中快速找到我们需要的内容,可以用来实现搜索、日志统计、分析、系统监控等功能。例如:百度的搜索引擎就是基于ES 开发的。 -
ES的好搭档:ELK。
ELK代指ElasticSearch
、LogStash
、Kibana
。其中ES作为整个的核心,负责数据的存储;LogStash负责数据的收集和抓取;Kibana负责数据的可视化。ES为核心不可替代,LogStash和Kibana 可以使用其他工具进行替换。
-
ES是基于Lucene进行开发封装的。Lucene是一个Java语言编写的搜索引擎类库(可以理解为一个jar包),提供了搜索引擎得核心API,是Apache公司的顶级项目。其作者是大名鼎鼎的Doug Cutting,也就是Hadoop的创始人。(orz)
Lucene的优点: 易扩展 、高性能(倒排索引)
Lucene的缺点: 只限于Java语言开发 、 学习曲线陡峭、不支持水平扩展。
-
由于Lucene 提供的接口实在是太不友好,直接使用难度较大。于是我们的Shay Banon 就封装了这些接口,开发了ElasticSearch 。据了解,该款框架最初名为Compass,是作者专门开发用于其妻子搜索食谱的。所以,要成为一个优秀的coder,我们得先…,new 一个对象。(qaq)
相较于Lucene,ES具备下列优势:
- 支持分布式,可水平扩展。
- 提供Restful 接口,可被任意语言调用。(只要能发Http 请求即可)
以上内容大致讲解了ES的作用以及一些与其相关小知识。下一节我们将学习关于ElasticSearch的理论知识。
二、学习ElasticSearch
-
ES 中的一些概念与我们传统的关系型数据库有所不同。我们首先将两者进行对比比较:
MySql ElasticSearch 说明 Table Index 索引(index),就是文档的集合,类似数据库中的表。 Row Document 文档(Document),一条一条的数据,文档都是JSON格式的。 Column Field 字段(Field),JSON文档中的字段 Schema Mapping 映射(Mapping),对索引中文案当的约束,例如字段类型的约束。 SQL DSL DSL是ElasticSearch提供的JSON风格的请求语句,用来操作ES,实现CRUD -
ElasticSearch 是面向文档存储的,可以是数据库中的一条商品数据。文档数据先被序列化转为JSON格式后存储在es中。
3.引入ES后的系统架构:
当然我们不会因为引入ES后而完全抛弃MySql,它们两者之间还是各有利弊的。- MySql擅长事务类型的操作,可以确保数据的安全和一致性。(ACID: 原子性、一致性、隔离性、持久性)
- ElasticSearch 擅长海量数据的搜索、分析、计算。
此时,当用户执行写操作时,我们将数据写入MySql中;当用户执行搜索相关大的操作时,我们则通过ES来进行查询。
此时,出现了一个问题:如何保证MySql与ES中的数据一致性呢?
呃,目前这个问题笔者也不太清楚。大概就是借助了某个工具实现了两者之间的数据同步。
上一小节,我们对ES的相关概念进行了讲解分析。接下来,我们先完成es与Kibana 的环境搭建。
三、ES与Kibana 的安装配置
-
单点部署ES
我们还需要部署Kibana 容器,因此需要让ES 和Kibana 互连。这里先利用docker 创建一个网络。
docker network create es-net
创建成功后,可通过
docker network ls
进行查看:
-
加载镜像。这里我们采用elasticsearch的7.12.1版本的镜像,这个镜像体积非常大,接近1G。不建议大家自己pull。建议大家通过jar 包的形式传给虚拟机,然后再通过docker 进行
load
一下。# 导入数据 docker load -i es.tar
同样的操作导入
Kibana
的jar 包,并load
一下。此时,我们就准备好了ES 和Kibana 的镜像了!(两者的版本需保持一致)
-
运行ES
运行docker,单点部署ESdocker 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 "ES_JAVA_OPTS=-Xms512m -Xmx512m"
:内存大小;-e "discovery.type=single-node"
:非集群模式;-v es-data:/usr/share/elasticsearch/data
:挂载逻辑卷,绑定es的数据目录;-v es-plugins:/usr/share/elasticsearch/plugins
:挂载逻辑卷,绑定es的插件目录;--privileged
:授予逻辑卷访问权;--network es-net
:加入一个名为es-net的网络中-p 9200:9200
:端口映射配置。此为接收Http请求的端口。-p 9200:9200
:端口映射配置。此为ES内部与Kibana 或集群环境交换数据的端口。
运行成功后,此时,我们即可通过浏览器查看ES的启动效果啦!
(对了,ES的启动通常来说速度比较慢,所以大火们需要耐心等待,也可以通过
docker logs -f es
查看ES 的启动日志)
成功访问啦!记住此处的访问地址以及响应的内容,在Kibana 中做一个对比。 -
运行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 的控制台。
启动成功后,我们通过浏览器访问
Kibana
5. DevTools: Kibana 提供了一个devtools 页面。在此页面可以编写DSL来操作ES,并且具备良好的代码补全功能。
在此页面,我们可以直接编写DSL 语句来操作ES啦! 例如:我们之前可以直接通过浏览器向ES发起请求,在地址栏,我们输入了IP地址:9200
。在此处用斜杠代替,此外,请求为Restful 风格,所以我们可以得到:
细心的友友们就会注意到:此内容和我们直接直接通过浏览器访问页面的内容相同!
在上一节的内容,我们已经完成了ES 和Kibana 的环境搭建,接下来我们介绍ES 倒排索引中的分词器。
四、分词器
1. 分词器,es在创建倒排索引时需要对文档进行分词,在搜索时也需要对用户输入的内容进行分词。但是默认的分词规则对中文处理并不友好。我们先尝试默认的分词规则:
POST /_analyze
{
"text": "今晚月色真美!",
"analyzer": "standard"
}
语法说明:
-
POST:请求方式;
-
/_analyze:请求路径。这里忽略了
IP:端口
,由Kibana 补充。 -
请求参数,json 风格:
- analyzer:分词器类型,这里选择默认的standard 分词器;
- text:分词的内容;
发起请求后,我们可以发现:默认的分词器对于中文无法很好的识别。
此时,我们需要借助另外一个分词器:ik 。处理中文分词,一般会使用ik 分词器。
-
ik 分词器的安装:
这里提供两种安装ik 插件方式:离线和在线。在线安装的话,主机不能使用校园网。这里推荐使用离线安装,方便后续的扩展词典以及停用词典。a) 在线安装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
b) 离线安装ik插件(推荐)
① 查看数据卷目录:安装插件需要知道elasticsearch的plugins目录位置,这里使用了数据卷挂载。docker volume inspect es-plugins
② 将
ik
目录上传到es 的插件数据卷中:③ 重启容器
# 重启容器 docker restart es # 查看es日志 docker logs -f es
-
ik 分词器的使用:
ik 分词器包含两种模式:ik_smart
:最小切分;ik_max_word
:最细切分。
将上面的内容再次通过
ik
分词:POST /_analyze { "text": "今晚月色真美!", "analyzer": "ik_smart" }
结果如下,是不是清晰多了!
-
扩展词词典与禁用词典
不难分析,ik 的底层就像一本新华字典,将中文进行比对,然后再进行分词。随着时代的发展,很多新颖词汇ik 可不认识,因此,我们可以手动为ik 词典进行扩容。此外,俗话说得好:饭可以乱吃,话可不能乱说哦!
,有些敏感词汇,我们肯定不能让它存在,ik 同时也能够配置敏感词过滤。在此目录
/var/lib/docker/volumes/es-plugins/_data/ik/config
(具体位置与你的数据卷相关,且哥们这里采用离线下载的方式,在线下载好像不是在这里!!!)
下的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> <!--用户可以在这里配置远程扩展字典 --> <!-- <entry key="remote_ext_dict">words_location</entry> --> <!--用户可以在这里配置远程扩展停止词字典--> <!-- <entry key="remote_ext_stopwords">words_location</entry> --> </properties>
扩展词词典与禁用词典就是一个文件,文件所在位置与该配置文件的位置相同。(没有文件,则自己创建文件。
touch ext.dic
)配置后,按理来说,ik 就能识别这些词啦!
大家可以亲自去试试,笔者这里很遗憾的告诉大家,我我我…失败了!
具体发生了甚么问题,俺也不清楚,就是没成功!实在是太悲伤了…
到此为止,我们已经完成了单一架构的ES、Kibana 的全部环境搭建啦!接下来,我们将在Kibana 中的DevTools 学习DSL 的使用。
五、ES 的相关操作
-
ES 中的索引相当于MySql 中的Table。 我们理所应当的开始介绍ES 索引库的CRUD。在此之前,我们先介绍ES 中的mapping 属性:
mapping 是对索引库中文档的约束(简单来说就是属性的一系列规定),常见的mapping 属性包括:
- type:字段的数据类型,常见的简单类型 有:(当然还有其他复杂类型啦)
- 字符串:text(可分词的文本)、keyword(精确值,不进行分词,例如:地区名称)
- 数值:byte、short、integer、long、float、double
- 布尔:boolean
- 日期:date
- 对象:object
- index:是否创建索引(一般该字段不参与搜索时,可置为false)。默认为true。
- analyzer:使用哪种分词器。
- properties:该字段的子字段。
- type:字段的数据类型,常见的简单类型 有:(当然还有其他复杂类型啦)
-
索引库的操作:
a) 创建索引库:# 创建索引库 PUT /heima { "mappings": { "properties": { "info": { "type": "text", "analyzer": "ik_smart" }, "email": { "type": "keyword", "index": false }, "name": { "type": "object", "properties": { "firstName": { "type": "keyword" }, "lastName": { "type": "keyword" } } } } } }
b) 查询索引库:
# 查询索引库信息 GET /heima
c) 删除索引库:
# 删除索引库 DELETE /heima
d) 修改索引库: 索引库和mapping 一旦创建无法修改,但是可以添加新的字段。
PUT /heima/_mapping { "properties":{ "新字段名":{ "type":"integer" } } }
-
文档操作:
a) 创建文档:
POST /索引库/_doc/文档ID
# 新增文档: POST /heima/_doc/1 { "info":"重庆脚痛大学大学牲", "age":20, "email":"1522@qq.com", "name":{ "firstName":"张", "lastName":"三" } }
b) 查询文档:
# 查询文档 GET /heima/_doc/1
c) 删除文档:
# 删除指定文档 DELETE /heima/_doc/1
d) 修改文档:
修改文档包括两种形式:全量修改和局部修改。# 方式一:全量修改,会根据文档id 先删除旧的文档,再添加新文档。因此,全量修改也可以用作添加文档! PUT /索引库名/_doc/文档id { "info":"重庆脚痛大学大学牲(update)", "age":20, "email":"1522@qq.com", "name":{ "firstName":"张", "lastName":"三" } } # 方式二:局部修改,修改指定字段 POST /索引库名/_update/文档id { "doc": { "字段名":"新值" } }
在上一节的内容,我们大致学会了如何在DevTools 来操作ES,在下一节的内容中,我们将学习在IDEA中使用RestClient 操作ES。
六、RestClient
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。
其中的Java Rest Client又包括两种:
- Java Low Level Rest Client
- Java High Level Rest Client
此处,我们使用 Java High Level Rest Client
的API。
-
导入依赖
由于Spring-boot 的父工程的启动依赖中,已经设置好了ES 的安装版本了。
因此,我们需要在项目中手动添加与自己适应版本的ES 版本属性信息。<properties> <elasticsearch.version>7.12.1</elasticsearch.version> </properties> <dependencies> <!-- 引入ES RestClient依赖--> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> </dependency> <!--FastJson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.71</version> </dependency> </dependencies>
-
初始化RestHighLevelClient
private RestHighLevelClient client; @BeforeEach void setUp(){ this.client = new RestHighLevelClient(RestClient.builder( HttpHost.create("http://192.168.220.137:9200") )); } @AfterEach void tearDown() throws IOException { this.client.close(); }
-
索引库相关的操作
a) 创建索引库@Test void createHotelIndex() throws IOException { // 1. 创建Request 对象。指定索引库的名称 CreateIndexRequest request = new CreateIndexRequest("hotel"); // 2. 准备请求的参数:DSL语句 // MAPPING_TEMPLATE 为JSON 风格的语句 request.source(HotelConstans.MAPPING_TEMPLATE, XContentType.JSON); // 3. 发送请求 // indices() 方法:获取对索引库操作的方法,包含:create()、get()、delete()、update()方法 client.indices().create(request, RequestOptions.DEFAULT); }
代码分为三步:
- 1)创建Request对象。 因为是创建索引库的操作,因此Request是CreateIndexRequest。
- 2)添加请求参数, 其实就是DSL的JSON参数部分。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。
- 3)发送请求,client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。
HotelConstans.MAPPING_TEMPLATE的完整示例:
public class HotelConstans { 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" + " \"star_name\":{\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"bussiness\":{\n" + " \"type\": \"keyword\",\n" + " \"copy_to\": \"all\"\n" + " },\n" + " \"location\":{\n" + " \"type\": \"geo_point\"\n" + " },\n" + " \"pic\":{\n" + " \"type\": \"keyword\",\n" + " \"index\": false\n" + " },\n" + " \"all\":{\n" + " \"type\": \"text\",\n" + " \"analyzer\": \"ik_max_word\"\n" + " }\n" + " }\n" + " }\n" + "}"; }
b) 查看索引库
@Test void getHotelIndex() throws IOException { GetIndexRequest request = new GetIndexRequest("hotel"); GetIndexResponse response = client.indices().get(request, RequestOptions.DEFAULT); System.out.println(response); }
c) 删除索引库
@Test void createHotelIndex() throws IOException { // 1. 创建Request 对象。指定索引库的名称 CreateIndexRequest request = new CreateIndexRequest("hotel"); // 2. 准备请求的参数:DSL语句 // MAPPING_TEMPLATE 为JSON 风格的语句 request.source(HotelConstans.MAPPING_TEMPLATE, XContentType.JSON); // 3. 发送请求 // indices() 方法:获取对索引库操作的方法,包含:create()、get()、delete()、update()方法 client.indices().create(request, RequestOptions.DEFAULT); }
d) 修改索引库:一般不对索引库进行修改。
-
文档相关的操作
a) 添加文档@Test void testAddDocument() throws IOException { // 根据id 查询(先通过数据库准备数据) Hotel hotel = iHotelService.getById(61083L); HotelDoc hotelDoc = new HotelDoc(hotel); // 1. 准备Request 对象 IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString()); // 2. 准备JSON文档 request.source(JSON.toJSONString(hotelDoc),XContentType.JSON); // 3. 发送请求 client.index(request,RequestOptions.DEFAULT); }
b) 查看文档
@Test void testGetByIdDocument() throws IOException { GetRequest request = new GetRequest("hotel","61083"); GetResponse response = client.get(request, RequestOptions.DEFAULT); String json = response.getSourceAsString(); HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class); System.out.println(hotelDoc); }
c) 删除文档
@Test void testDeleteDocument() throws IOException { DeleteRequest request = new DeleteRequest("hotel","61083"); client.delete(request, RequestOptions.DEFAULT); }
d) 修改文档:此处为局部修改。
@Test void testUpdateDocument() throws IOException { UpdateRequest request = new UpdateRequest("hotel","61083"); request.doc( "price","952", "starName","四钻" ); client.update(request, RequestOptions.DEFAULT); }
e) 批量添加文档
@Test void testBulkRequest() throws IOException { // 批量查询酒店数据(先从数据库中查询数据) List<Hotel> hotelList = iHotelService.list(); // 1. 创建Request BulkRequest request = new BulkRequest(); // 2. 添加多个Index hotelList.forEach(hotel -> { HotelDoc hotelDoc = new HotelDoc(hotel); request.add(new IndexRequest("hotel") .id(hotelDoc.getId().toString()) .source(JSON.toJSONString(hotelDoc),XContentType.JSON)); }); // 3. 发送请求 client.bulk(request,RequestOptions.DEFAULT); }
-
总结
索引库操作的基本步骤:
- 初始化RestHighLevelClient
- 创建xxxIndexRequest。xxx是Create、Get、Delete
- 准备DSL( Create时需要,其它是无参)
- 发送请求。调用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete
文档操作的基本步骤:
- 初始化RestHighLevelClient
- 创建xxxRequest。xxx是Index、Get、Update、Delete、Bulk
- 准备参数(Index、Update、Bulk时需要)
- 发送请求。调用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
- 解析结果(Get时需要)
以上就为本篇文章的全部内容啦!
多多点赞支持一下呗!