今天的主要学习任务是分布式搜索,首先了解elasticsearch,然后学习索引库的操作、文档的操作、RestAPI等。elasticsearch是非常强大的开源搜索引擎,可以帮助我们从海量数据中快速定位到我们需要的内容。这一篇主要学习ES的基本使用,包括安装ES,安装kibana,安装分词器等,另外也学习了在java客户端实现索引库的增删改查和文档的增删改查。
目录
一、初识elasticsearch
1.1、了解ES
1.2、 倒排索引
1.3、ES与MySQL概念对比
1.4、安装ES
1.5、安装kibana
1.6、安装IK分词器
1.7、IK分词器的扩展与停用
二、索引库操作
2.1、mapping映射属性
2.2、创建、查询、删除、修改索引库
2.3、文档的添加、查询、删除、修改
三、RestClient操作
3.1、RestClient操作索引库
3.2、RestClient操作文档
elasticsearch是非常强大的开源搜索引擎,可以帮助我们从海量数据中快速定位到我们需要的内容。elastic stack(ELK) 主要包含如下三部分,其中es是用于搜索和存储等,是该技术栈的核心,另外还包括数据抓取和数据可视化的技术栈。
下面对elasticsearch做个小总结吧,es是开源的分布式搜索引擎,用来搜索、日志统计、分析等,ELK是以ES为核心的技术栈,包括数据可视化的Kibana和数据抓取的Logstash和Beats。另外Lucene是开源搜索引擎类库,提供搜索引擎核心的API。es底层是基于Lucene实现的。
我们先看一下正向索引,比如MySQL就是典型的采用正向索引,例如根据内容进行搜索,比对成功,则存储,比对失败,则丢弃。
正向索引的搜索是一条一条的搜索,而倒排搜索是将数据划分成词条和文档 ,每次搜索前先分词,然后根据词条进行搜索,找到文档id,最后再根据文档id进行搜索,将搜索到的结果按照顺序存入结果集。在复杂的场景适合用倒排索引进行搜索,效率更高。
我们看一下MySQL和es的概念对比,表对应索引,行对应文档,列对应字段,schema对应映射Mapping,SQL对应DSL。
对于数据安全要求较高的写相关操作,一般使用MySQL数据库,对于性能要求较高的读相关操作,一般使用Elasticsearch完成。MySQL和ES之间也可以实现数据同步。
下面看一下部署单点ES,我们需要先创建网络,因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络:当然需要先启动docker,再创建网络:
docker network create es-net
接下来就是加载es镜像了,一般来说,可以直接从服务器拉取,但是由于es镜像比较大,我们先下载到本地,然后通过xftp上传到虚拟机,然后使用load命令加载压缩包为镜像即可。
docker load -i es.tar
把kibana也上传到虚拟机,然后用load命令加载镜像,如下:
docker load -i kibana.tar
接下来就可以运行docker命令,部署单点es,如下:
命令解释:
● -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 :端口映射配置
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
在浏览器输入ip地址+端口号检验es是否安装成功,如下所示出现如下json字符串则安装成功:
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
安装完成后可以在浏览器使用ip地址+端口号的方式打开可视化界面,如下:
有常见的两种方式安装IK分词器,分别在线安装和离线安装,在线安装的docker命令如下,不过该方法比较慢。
# 进入容器内部
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
另外一种安装IK分词器的方式是离线安装,具体如下,安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看:
docker volume inspect es-plugins
plugins目录被挂载到了: /var/lib/docker/volumes/es-plugins/_data 这个目录中,我们将解压的ik分词器上传到这个目录中,然后重启容器即可。
docker restart es
最后测试IK分词的两种模式:在es的可视化界面使用Dev Tools进行测试即可,如下:
● ik_smart :最少切分
● ik_max_word :最细切分
ik分词器的扩展是很有必要的,因为词库是有限的,对于新的词汇,我们需要对分词器进行拓展,对于没有必要的分词,或者敏感词汇,也可以设置不进行分词,即停用,防止浪费内存。
2)在IKAnalyzer.cfg.xml配置文件扩展词典和停用词典,如下:
IK Analyzer 扩展配置
ext.dic
stopword.dic
3)在config文件夹内创建扩展和停用的文件,并设置相应的词语。
4)重启elasticsearch
docker restart es
docker restart kibana
5)测试
mapping常见的属性包括数据类型、是否索引、分词器、子字段等,常见的数据类型包括:文本或键值对字符串,数字,布尔类型,日期和对象类型等。
Elasticsearch通过Restful请求操作索引库和文档,请求内容通过DSL语句来表示,创建索引库和mapping的DSL如下:
下面在es的可视化界面的使用Dev Tools创建索引库,如下:
#创建索引库
PUT /wang
{
"mappings": {
"properties": {
"info": {
"type": "text",
"analyzer": "ik_smart"
},
"email": {
"type": "keyword"
, "index": false
},
"name": {
"type": "object",
"properties": {
"firstname": {
"type": "keyword"
},
"lastname": {
"type": "keyword"
}
}
}
}
}
}
如下所示,表明创建索引成功。
查询索引库用GET,删除索引库用DELETE,修改索引库用PUT,只能添加新字段,不能修改之前的字段。
#查询索引库
GET /wang
#删除索引
DELETE /wang
#修改索引,添加新字段
PUT /wang/_mapping
{
"properties":{
"age":{
"type":"long"
}
}
}
创建文档使用POST,查询文档用GET、删除文档用DELETE、修改文档主要有两种,一种是全量修改,另一种是增量修改。
#查询文档
GET /wang/_doc/1
#删除文档
DELETE /wang/_doc/1
#全量修改,修改文档
PUT /wang/_doc/1
{
"info":"Java学习之路,慢慢来",
"email":"[email protected]",
"name":{
"firstname":"忠",
"lastname":"黄"
}
}
#局部修改文档
POST /wang/_update/1
{
"doc":{
"email":"[email protected]"
}
}
我们要通过Java的客户端操作实现索引库的增删改查相关操作,首先需要导入demo,然后定于mapping映射,通过RestClient完成索引库的相关操作。
初始化Java客户端,需要先引入依赖,并使用相应的IP地址何端口号进行初始化。
创建库索引,如下主要分为三步,创建request对象,准备请求发送,最后是发送请求。
import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import static cn.itcast.hotel.constants.HotelConstants.MAPPING;
public class HotelIndexTest {
private RestHighLevelClient client ;
@Test
void testInits(){
System.out.println(client);
}
@Test
void TestInit() throws IOException {
//1.创建request对象
CreateIndexRequest createIndexRequest = new CreateIndexRequest("hotel") ;
//2.准备请求的参数
createIndexRequest.source(MAPPING, XContentType.JSON) ;
//3.发送请求
client.indices().create(createIndexRequest, RequestOptions.DEFAULT) ;
}
@BeforeEach
void setUp(){
this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.132.128:9200"))) ;
}
@AfterEach
void tearDown() throws IOException {
this.client.close() ;
}
}
因为是通过DSL语句创建索引库,所以这里定义了一个MAPPING常量,通过静态导包的方式加入,如下:
public class HotelConstants {
public static final String MAPPING = "{\n" +
"\"mappings\": {\n" +
"\n" +
"\"properties\": {\n" +
"\n" +
"\"id\":{\n" +
"\n" +
"\"type\": \"keyword\"\n" +
"},\n" +
"\"name\":{\n" +
"\n" +
"\"type\": \"text\",\n" +
"\"analyzer\": \"ik_max_word\",\n" +
"\"copy_to\": \"all\"\n" +
"},\n" +
"\"address\":{\n" +
"\n" +
"\"type\": \"keyword\",\n" +
"\"index\": false\n" +
"},\n" +
"\"price\":{\n" +
"\n" +
"\"type\": \"integer\"\n" +
"},\n" +
"\"score\":{\n" +
"\n" +
"\"type\": \"integer\"\n" +
"},\n" +
"\"brand\":{\n" +
"\n" +
"\"type\":\"keyword\",\n" +
"\"copy_to\": \"all\"\n" +
"},\n" +
"\"city\":{\n" +
"\n" +
"\"type\": \"keyword\"\n" +
"},\n" +
"\"starName\":{\n" +
"\n" +
"\"type\": \"keyword\"\n" +
"},\n" +
"\"business\":{\n" +
"\n" +
"\"type\": \"keyword\",\n" +
"\"copy_to\": \"all\"\n" +
"},\n" +
"\"location\":{\n" +
"\n" +
"\"type\": \"geo_point\"\n" +
"},\n" +
"\"pic\":{\n" +
"\n" +
"\"type\": \"keyword\",\n" +
"\"index\": false\n" +
"},\n" +
"\"all\":{\n" +
"\n" +
"\"type\": \"text\",\n" +
"\"analyzer\": \"ik_max_word\"\n" +
"}\n" +
"}\n" +
"}\n" +
"}" ;
}
删除索引库何判断索引库是否存在的代码如下:
//判断索引库是否存在
@Test
void TestExists() throws IOException {
//1.创建request对象
GetIndexRequest getIndexRequest = new GetIndexRequest("hotel") ;
//2.发送请求
boolean exists = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT) ;
//3.输出
System.out.println(exists ? "索引库存在" : "索引库不存在");
}
//删除索引库
@Test
void TestDelete() throws IOException {
//1.创建request对象
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("hotel") ;
//2.发送请求
client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT) ;
}
我们来看一下使用RestClient对文档进行增删改查的相关操作,具体如下:
首先需要初始化Java客户端,然后新增酒店数据到索引库,然后从索引库查询、删除、修改数据。
插入文档的代码如下,首先需要从数据库查询文档,这里面之前报了一个空指针异常,在测试类上加了@SpringBootTest注解后解决了,首先从数据库中查询,然后转换为文档类型,最后根据请求和Json对象发送新增文档请求即可。
@Test //文档新增
void addDocument() throws IOException {
//根据id查询酒店数据
Hotel hotel = iHotelService.getById(38665L) ;
//转换为文档类型
HotelDoc hotelDoc = new HotelDoc(hotel) ;
//1.创建request对象
IndexRequest indexRequest = new IndexRequest("hotel").id(hotel.getId().toString()) ;
//2.准备Json对象
indexRequest.source(JSON.toJSONString(hotelDoc), XContentType.JSON) ;
//3.发送请求
client.index(indexRequest, RequestOptions.DEFAULT) ;
}
可在es的可视化界面中使用指令查询索引库中添加的文档,如下:
根据id查询酒店数据,具体如下:
@Test //文档查询
void getDocumentById() throws IOException {
//1.创建request对象
GetRequest getRequest = new GetRequest("hotel", "38665") ;
//2.发送请求,得到响应
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT) ;
//3.解析响应的结果
String json = getResponse.getSourceAsString() ;
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class) ;
System.out.println(hotelDoc);
}
根据id进行文档的修改,也就是更新,就是全量更新和局部更新。
@Test //文档更新
void updateDocumentById() throws IOException {
//1.创建request对象
UpdateRequest updateRequest = new UpdateRequest("hotel", "38665") ;
//2.准备请求参数
updateRequest.doc("price","999","starName","3钻") ;
//3.发送请求
client.update(updateRequest,RequestOptions.DEFAULT) ;
}
接下来,我们进行删除文档,如下:
@Test //文档删除
void deleteDocumentById() throws IOException {
//1.创建request对象
DeleteRequest deleteRequest = new DeleteRequest("hotel", "38665") ;
//2.发送请求
client.delete(deleteRequest,RequestOptions.DEFAULT) ;
}
最后对文档操作做个总结:首先需要初始化客户端,然后创建请求对象,准备参数,发送请求,最后解析请求得到的结果即可。
最后我们在看一个批量导入数据到ES中,就是将文档数据批量导入到索引库中,具体如下:
下面是文档批量导入的代码,如下:
@Test //文档批量处理
void bulkDocumentById() throws IOException {
//批量出巡酒店数据
List list = iHotelService.list() ;
//1.创建request对象
BulkRequest bulkRequest = new BulkRequest() ;
//转换为文档类型
for(Hotel hotel : list){
HotelDoc hotelDoc = new HotelDoc(hotel) ;
//创建文档的request对象
bulkRequest.add(new IndexRequest("hotel")
.id(hotel.getId().toString())
.source(JSON.toJSONString(hotelDoc),XContentType.JSON)) ;
}
//2.发送请求
client.bulk(bulkRequest,RequestOptions.DEFAULT) ;
}