了解ElasticSearch之前先来了解一下什么事正向索引和倒排索引
总结:
复杂查询用Elasticsearch,简单查询和增删改用MySQL,当然,增删改MySQL数据也需要同步到Elasticsearch中。
docker run -d \
--name es \
-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 \ #加入一个名为es的网络中
-p 9200:9200 \ #端口映射配置
-p 9300:9300 \
elasticsearch:7.12.1
为什么安装kibana?
kibana可以给我们提供一个elasticsearch的可视化界面,便于我们观察。
docker run \
-d \
--name kibana \
-e ELASTICSEARCH_URL=http://es:9200 \ #指定ElasticSearch主机地址
--network=es \ #加入一个名为es的网络中,与elasticsearch在同一个网络中
-p 5601:5601 \
kibana:7.12.1
-e ELASTICSEARCH_URL=http://es:9200"
:设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch
1. 进入kibana容器:docker exec -it kibana /bin/bash
2. 修改/config/kibana.yml
3. 将端口号改成你的ElasticSearch端口号即可,docker同一网络中用可通过容器名访问
如:http:es:9200
默认分词器对中文分词不友好。
安装分词器版本必须与ES版本保持一致。
elasticsearch ik分词器的安装和使用_宫凯宁的博客-CSDN博客_elasticsearch ik分词器elasticsearch几种常用分词器如下:分词器分词方式StandardAnalyzer单字分词CJKAnalyzer二分法IKAnalyzer词库分词其中常用的是IKAnalyzer,但IK是第三方插件,需要安装。...https://blog.csdn.net/weixin_44723434/article/details/89888489
# 进入容器内部
docker exec -it elasticsearch /bin/bash# 在线下载并安装
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.1.2/elasticsearch-analysis-ik-8.1.2.zip#退出
exit
#重启容器
docker restart elasticsearch
安装插件需要知道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
也就是/var/lib/docker/volumes/es-plugins/_data
:
# 4、重启容器
docker restart es# 查看es日志
docker logs -f es
IK分词器包含两种模式:
ik_smart
:粗粒分词,分出的词相对较少,但占用内存空间也较少
ik_max_word
:最细切分,分出的词相对更细,但占用内存空间也更多
GET /_analyze
{
"analyzer": "ik_max_word", //分词解析方式
"text": "黑马程序员学习java太棒了"
}
结果:
{
"tokens": [
{
"token": "发文",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "助手",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 1
},
{
"token": "会",
"start_offset": 4,
"end_offset": 5,
"type": "CN_CHAR",
"position": 2
},
{
"token": "检测",
"start_offset": 5,
"end_offset": 7,
"type": "CN_WORD",
"position": 3
},
{
"token": "您",
"start_offset": 7,
"end_offset": 8,
"type": "CN_CHAR",
"position": 4
},
{
"token": "的",
"start_offset": 8,
"end_offset": 9,
"type": "CN_CHAR",
"position": 5
},
{
"token": "文章",
"start_offset": 9,
"end_offset": 11,
"type": "CN_WORD",
"position": 6
},
{
"token": "标题",
"start_offset": 11,
"end_offset": 13,
"type": "CN_WORD",
"position": 7
}
]
}
IKAnalyzer.cfg.xml:扩展和通用词典文件所在位置配置
ext.dic:IKAnalyzer.cfg.xml中配置的扩展词典
stopword.dic:IKAnalyzer.cfg.xml中配置的停用词典
为什么要配置扩展词典和停用词典?
扩展词典:可能会有一些全新词IK分词器之前并没有加入到词典中,这时候就需要我们配置一些新出现的词,IK分词器才能在分词的时候把他们当做一个词来看待。如:绝绝子,鲲鲲等
停用词典:敏感词屏蔽
index:是否创建倒排索引,是否参与搜索。
创建索引库(表)案例:
PUT /hotel
{
"mappings": {
"properties": {
"id":{ //id通常设置为不分词的字符串字段
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all" //联合索引到all字段
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword"
},
"star_name":{
"type": "keyword"
},
"business":{
"type": "keyword",
"copy_to": "all"
},
"location":{
"type": "geo_point" //坐标类型:经纬度类型-> 格式为:"经度,纬度"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{ //联合索引字段
"type":"text",
"analyzer": "ik_max_word"
}
}
}
}
1. 分析数据结构
ES-Mapping需要考虑到的问题:
字段名、数据类型、是否参与搜索、是否分词、分词器用什么.....
2. 导入RestClient依赖
7.12.1
org.elasticsearch.client
elasticsearch-rest-high-level-client
3. 初始化RestHighLevelClient对象(Java高级REST客户端对象),通过该对象连接ES,调用API完成对应数据操作
//创建RestHighLevelClient对象,连接ES客户端
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://59.110.233.175:9200")
));
//销毁RestHighLevelClient对象
client.close();
4. 创建索引库
/**
* 创建Hotel索引库
*/
@Test
public void testCreateHotelIndex() throws IOException {
//1. 初始化索引库请求对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
//2. 请求参数,mappings,可以是一段静态常量字符串,内容是创建索引库的DSL
request.source(MAPPING_TEMPLATE, XContentType.JSON);
//3. 发起请求
client.indices().create(request, RequestOptions.DEFAULT);
}
MAPPING_TEMPLATE = "{\n" +
" \"mappings\": {\n" +
" \"properties\": {\n" +
" \"id\":{ //id通常设置为不分词的字符串字段\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"name\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"copy_to\": \"all\" //联合索引到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" +
" \"business\":{\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" +
"}";
5. 查询和删除索引库
/**
* 查询索引库是否存在
*/
@Test
public void testExistsHotelIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest("hotel");
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists ? "索引库存在" : "索引库不存在");
}
/**
* 删除索引库
*/
@Test
public void testDeleteHotelIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
client.indices().delete(request,RequestOptions.DEFAULT);
}
1. 添加文档
/**
* 添加文档(数据)
*/
@Test
void testAddDocument() throws IOException {
//根据id查询
Hotel hotel = hotelService.getById(61083l);
//转换为文档类型
HotelDoc hotelDoc = new HotelDoc(hotel);
//1. 创建Request对象
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
//2. 准备JSON文档 将对象序列化为JSON格式
request.source(JSON.toJSONString(hotelDoc),XContentType.JSON);
//3. 发送请求
client.index(request,RequestOptions.DEFAULT);
}
批量导入
/**
* 批量操作(添加、更新、删除)文档
*/
@Test
void bulkAddDocument() throws IOException {
//批量查询酒店数据
List hotels = hotelService.list();
//1. 创建请求
BulkRequest request = new BulkRequest();
//2. 请求参数设置
for (Hotel hotel : hotels) {
//2.1 解析数据
HotelDoc hotelDoc = new HotelDoc(hotel);
//2.2 添加IndexRequest请求到bulk(为Bulk请求做准备)
request.add(new IndexRequest("hotel")
.id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc),XContentType.JSON));
}
//3. 发送请求
client.bulk(request,RequestOptions.DEFAULT);
}
2. 查询文档
/**
* 查询文档(数据)
*/
@Test
void testGetDocument() throws IOException {
//1. 创建请求
GetRequest request = new GetRequest("hotel","61083");
//2. 发送请求,返回响应结果
GetResponse response = client.get(request, RequestOptions.DEFAULT);
//3. 解析响应结果
String jsonString = response.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(jsonString, HotelDoc.class);
System.out.println(hotelDoc);
}
3. 更新文档
/**
* 更新文档(数据)
*/
@Test
void testUpdateDocument() throws IOException {
//1. 创建请求
UpdateRequest request = new UpdateRequest("hotel", "61083");
//2. 请求参数
Map map = new HashMap<>();
map.put("price","550");
map.put("starName","四钻");
request.doc(map);
//3. 发送请求
client.update(request,RequestOptions.DEFAULT);
}
全量更新:再次写入id一样的文档,会覆盖掉原文档,写入id不存在的文档会添加文档(不常用)
局部更新:只更新部分字段
4. 删除文档
/**
* 删除文档(数据)
*/
@Test
void testDeleteDocument() throws IOException {
//1. 创建请求
DeleteRequest request = new DeleteRequest("hotel","61083");
//2. 发送请求
client.delete(request,RequestOptions.DEFAULT);
}