Elasticsearch基础

Elasticsearch

一、概念

正向索引

基于文档id创建索引,查询词条时必须先找到文档,而后判断是否包含词条

请添加图片描述

倒排索引

对文档内容分词,对词条创建索引,并记录词条所在文档的信息,查询时先根据词条查询到文档id,而后获取到文档
在这里插入图片描述

文档

elasticsearch是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中

请添加图片描述

索引(index)

索引(index):相同类型文档的集合

映射(mapping):索引中文档字段约束信息,类似表的结构约束

在这里插入图片描述

概念对比

在这里插入图片描述

二、安装

安装es

1、部署单节点es

首先创建一个网络,让kibana与es使用同一个网络

docker network create es-net
2、拉取镜像
# 拉取es镜像,指定版本号
docker pull elasticsearch:7.17.18
# 拉取kibana镜像
docker pull kibana:7.17.18
3、运行命令
docker run -d --name es \
--network es-net \
--ulimit memlock=-1:-1 \
-v /home/es/data:/usr/share/elasticsearch/data \
-v /home/es/plugins:/usr/share/elasticsearch/plugins \
-v /home/es/config:/usr/share/elasticsearch/config \
-p 9200:9200 \
-p 9300:9300 \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "network.host=0.0.0.0" \
-e "discovery.type=single-node" \
-e "bootstrap.memory_lock=true" \
elasticsearch:7.17.18

命令解释:

  • -e “cluster.name=es-docker-cluster”:设置集群名称
  • -e “http.host=0.0.0.0”:监听的地址,可以外网访问
  • -e “discovery.type=single-node”:非集群模式
  • -e “ES_JAVA_OPTS=-Xms512m -Xmx512m”:内存大小
  • -v /home/es/data:/usr/share/elasticsearch/data:挂载数据卷,绑定es的数据目录
  • -v /home/es/plugins:/usr/share/elasticsearch/plugins:挂载数据卷,绑定es的插件目录
  • -v /home/es/logs:/usr/share/elasticsearch/logs:挂载数据卷,绑定es的日志目录
  • –privileged:授予数据卷访问权
  • –network es-net:加入一个名为es-net的网络中
  • -p 9200:9200:端口映射

通过命令查看是否运行成功

docker ps -a

问题:

由于挂载在宿主机上的目录,es执行写入时权限不足导致的无法启动,需要分配权限

# 直接给两个目录最高权限
chmod 777 /home/es/data
chmod 777 /home/es/plugins

在这里插入图片描述

重新启动,浏览器访问 ip+9200端口,出现json数据输出为成功启动

在这里插入图片描述

安装kibana

用于执行es命令

(1)执行命令
# 直接安装,不需要自定义yml
docker run -d --name kibana \
--network=es-net \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
-v /home/kibana/config:/usr/share/kibana/config \
-p 5601:5601 \
kibana:7.17.18

# 使用自定义的yml配置,挂载config配置目录在宿主机上
docker run -d --name kibana \
--network=es-net \
-v /home/kibana/config:/usr/share/kibana/config \
-p 5601:5601 \
kibana:7.17.18

命令解读:

  • –network es-net:设置网络
  • -e ELASTICSEARCH_HOST=http://es:9200:连接到es,容器名+端口号
(2)启动成功后

在这里插入图片描述

(3)报错

未连接上Elasticsearch,检查配置的es地址是否是正确的

在这里插入图片描述

安装IK分词器

(1)下载分词器

到github官网下载ik分词器对应版本,版本要跟es相同:https://github.com/infinilabs/analysis-ik

如果没有找到相同版本的,那就下载一个,解压后修改 plugin-descriptor.properties 文件中的版本号

在这里插入图片描述

(2)上传

在es容器的宿主机数据卷目录下,新建一个ik目录,将解压后的文件全部上传,上传完成后,重启es容器

docker restart es
(3)测试

ik分词器包含两种模式:

  • ik_smart:最少切分
  • ik_max_word:最细切分
(4)分词调整

ik中的config目录下可以使用自定义分词

修改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>

默认config目录下已经存在了 stopword.dic 这个文件,直接在xml文件中配置即可

新建ext.dic文件,并将自定义的词放进去,添加完成后,放在 config目录下即可

ext.dic文件配置如下图所示:

在这里插入图片描述

stopword.dic 文件配置同理

添加完成后,重启es容器,重新加载

docker restart es

测试结果:

在这里插入图片描述

三、索引库

官方手册:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/index.html

1、mapping属性(常见)

mapping是对索引库中文档的约束,常见的mapping属性包括:

  • type:字段数据类型,常见的简单类型有:
    • 字符串:text(可分词文本)、keyword(精确值:例如:品牌、国家、ip地址)
    • 数值:long、integer、short、byte、double、float
    • 布尔:boolean
    • 日期:date
    • 对象:object
  • index:是否创建索引,默认为true,是否参与搜索,不参与搜索的即可为false
  • analyzer:使用那种分词器
  • properties:该字段的子字段

以上为常见的属性,更多属性可在 https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping-params.html 查询

2、索引库操作

(1)新增

使用DSL语法进行创建,语法是json风格的

# 创建名为 /test1 的索引库
PUT test1
{
  "mappings": {
    "properties": {
      "info": {
        "type": "text", 
        "analyzer": "ik_smart"
      },
      "email": {
        "type": "keyword", 
        "index": false
      },
      "name": {
        "type": "object", 
        "properties": {
          "firstName": {
            "type": "keyword"
          },
          "lastName": {
            "type": "keyword"
          }
        }
      }
    }
  }
}
(2)查询
GET /索引库名称
# 示例
GET /test1
(3)删除
DELETE /索引库名称
# 示例
DELETE /test2
(4)修改

es是禁止修改索引库,因为一旦修改了索引库,就会导致原有的倒排索引就会失效

可以进行新增索引

PUT /索引库/_mapping
{
  "properties": {
    "属性名": {
      "type": "类型"
    }
  }
}

# 示例
PUT /test1/_mapping
{
  "properties": {
    "age": {
      "type": "integer"
    }
  }
}

3、文档操作

(1)新增

DSL语法

POST /索引库名/_doc/文档id
{
    "字段1":"值1",
    "字段2":"值2",
    "字段3": {
        "子属性1":"值3",
        "子属性2":"值4"
    }
}

# 示例
POST /test1/_doc/1
{
  "age":23,
  "email":"1066541346@qq.com",
  "info":"一个正在学习java的程序员",
  "name":{
    "firstName":"张",
    "lastName":"三"
  }
}

注意:如果不加文档id,那么es就会认为这个文档没有id,es就会默认生成一个id

(2)查询文档

DSL语法

GET /索引库名/_doc/文档id

# 示例
GET /test1/_doc/1

查询结果:

{
  "_index" : "test1", # 所在索引库
  "_type" : "_doc", # 类型为文档
  "_id" : "3", # 文档id
  "_version" : 1, # 版本控制,每做一次修改,版本都会更新
  "_seq_no" : 2,
  "_primary_term" : 1,
  "found" : true,
  "_source" : { # 插入的原始文档
    "age" : 23,
    "email" : "1066541346@qq.com",
    "info" : "一个正在学习python的程序员",
    "name" : {
      "firstName" : "王",
      "lastName" : "五"
    }
  }
}
(3)删除文档
DELETE /索引库名/_doc/文档id

# 示例
DELETE /test1/_doc/4
(4)修改文档

方式一:全量修改,会将旧文档删除,添加新文档,与新增文档差不多,只是POST请求变为PUT请求

如果传入的文档id在索引库中不存在,那么就不会执行旧文档删除的步骤,直接新增新文档

PUT /索引库名/_doc/文档id
{
    "字段1":"值1",
    "字段2":"值2",
    "字段3": {
        "子属性1":"值3",
        "子属性2":"值4"
    }
}

# 示例
PUT /test1/_doc/1
{
  "age":23,
  "email":"1066541346@qq.com",
  "info":"一个正在学习java的程序员",
  "name":{
    "firstName":"张",
    "lastName":"三"
  }
}

方式二:增量修改,修改指定的字段值

这个方式的文档只修改文档中的指定要修改的属性的值,并不会去修改其他的值

如果在索引库中根据文档id查询时,没有这个文档,则修改时会报404错误,找不到该文档

POST /索引库名/_update/文档id
{
    "doc": {
        "属性名":"属性值"
    }
}

# 示例
POST /test1/_update/1
{
  "doc": {
    "age": 100
  }

四、RestClient操作

ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端本质就是组装DSL语句,通过http请求发送给ES

官方文档地址:https://www.elastic.co/guide/en/elasticsearch/client/index.html

在使用RestClien之前先初始化一个springboot项目

首先准备一个数据库,表名为:tb_hotel

CREATE TABLE `tb_hotel` (
  `id` bigint NOT NULL COMMENT '酒店id',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店名称',
  `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店地址',
  `price` int NOT NULL COMMENT '酒店价格',
  `score` int NOT NULL COMMENT '酒店评分',
  `brand` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店品牌',
  `city` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所在城市',
  `star_name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '酒店星级,1星到5星,1钻到5钻',
  `business` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '商圈',
  `latitude` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '纬度',
  `longitude` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '经度',
  `pic` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '酒店图片',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT;

插入100条酒店数据

INSERT INTO `tb_hotel` VALUES (1, '阳光大酒店', '北京市朝阳区大街123号', 500, 9, '如家', '北京', '五星级', '朝阳区', '39.904202', '116.407394', 'pic1.jpg');
INSERT INTO `tb_hotel` VALUES (2, '海景度假村', '上海市浦东新区海滨大道456号', 600, 8, '锦江之星', '上海', '四星级', '浦东新区', '31.230391', '121.473701', 'pic2.jpg');
INSERT INTO `tb_hotel` VALUES (3, '华美达酒店', '广州市天河区东风东路789号', 450, 7, '华住', '广州', '三星级', '天河区', '23.129163', '113.264435', 'pic3.jpg');
INSERT INTO `tb_hotel` VALUES (4, '绿洲宾馆', '深圳市南山区科技园101号', 300, 8, '维也纳', '深圳', '四星级', '南山区', '22.543097', '114.057861', 'pic4.jpg');
INSERT INTO `tb_hotel` VALUES (5, '江南酒店', '杭州市西湖区文三路202号', 350, 9, '开元', '杭州', '五星级', '西湖区', '30.274085', '120.155070', 'pic5.jpg');
INSERT INTO `tb_hotel` VALUES (6, '云海酒店', '重庆市渝中区解放碑123号', 400, 7, '格林豪泰', '重庆', '四星级', '渝中区', '29.563761', '106.550464', 'pic6.jpg');
INSERT INTO `tb_hotel` VALUES (7, '山水酒店', '成都市锦江区春熙路456号', 320, 8, '全季', '成都', '三星级', '锦江区', '30.572815', '104.066803', 'pic7.jpg');
INSERT INTO `tb_hotel` VALUES (8, '君悦酒店', '武汉市武昌区中南路789号', 480, 9, '亚朵', '武汉', '五星级', '武昌区', '30.546498', '114.341745', 'pic8.jpg');
INSERT INTO `tb_hotel` VALUES (9, '湖滨酒店', '苏州市姑苏区观前街101号', 360, 7, '布丁', '苏州', '四星级', '姑苏区', '31.298886', '120.585316', 'pic9.jpg');
INSERT INTO `tb_hotel` VALUES (10, '紫荆花酒店', '西安市莲湖区大庆路202号', 380, 8, '希尔顿', '西安', '五星级', '莲湖区', '34.341574', '108.939770', 'pic10.jpg');
INSERT INTO `tb_hotel` VALUES (11, '银杏树酒店', '南京市玄武区中山路123号', 330, 7, '丽枫', '南京', '三星级', '玄武区', '32.060255', '118.796877', 'pic11.jpg');
INSERT INTO `tb_hotel` VALUES (12, '星空酒店', '天津市和平区南京路456号', 450, 9, '喜来登', '天津', '五星级', '和平区', '39.085100', '117.199370', 'pic12.jpg');
INSERT INTO `tb_hotel` VALUES (13, '海天大酒店', '青岛市市南区东海西路789号', 410, 8, '香格里拉', '青岛', '四星级', '市南区', '36.067235', '120.382606', 'pic13.jpg');
INSERT INTO `tb_hotel` VALUES (14, '彩虹酒店', '大连市中山区人民路101号', 300, 7, '万豪', '大连', '三星级', '中山区', '38.914003', '121.614682', 'pic14.jpg');
INSERT INTO `tb_hotel` VALUES (15, '天鹅湖酒店', '沈阳市和平区中山路202号', 380, 8, '锦江之星', '沈阳', '四星级', '和平区', '41.805699', '123.431472', 'pic15.jpg');
INSERT INTO `tb_hotel` VALUES (16, '凤凰酒店', '长春市朝阳区解放大路123号', 320, 7, '维也纳', '长春', '三星级', '朝阳区', '43.817072', '125.323544', 'pic16.jpg');
INSERT INTO `tb_hotel` VALUES (17, '兰花酒店', '哈尔滨市南岗区中山路456号', 390, 9, '华住', '哈尔滨', '五星级', '南岗区', '45.803775', '126.534967', 'pic17.jpg');
INSERT INTO `tb_hotel` VALUES (18, '牡丹江酒店', '郑州市金水区东风路789号', 360, 8, '开元', '郑州', '四星级', '金水区', '34.746303', '113.625350', 'pic18.jpg');
INSERT INTO `tb_hotel` VALUES (19, '玉龙雪山酒店', '昆明市盘龙区北京路101号', 330, 7, '如家', '昆明', '三星级', '盘龙区', '25.040609', '102.712251', 'pic19.jpg');
INSERT INTO `tb_hotel` VALUES (20, '海韵酒店', '厦门市思明区湖滨南路202号', 410, 8, '布丁', '厦门', '四星级', '思明区', '24.479834', '118.089425', 'pic20.jpg');
INSERT INTO `tb_hotel` VALUES (21, '金鹰酒店', '济南市历下区泉城路123号', 370, 9, '全季', '济南', '五星级', '历下区', '36.651216', '117.120095', 'pic21.jpg');
INSERT INTO `tb_hotel` VALUES (22, '天海酒店', '福州市鼓楼区五一路456号', 340, 8, '亚朵', '福州', '四星级', '鼓楼区', '26.075302', '119.306239', 'pic22.jpg');
INSERT INTO `tb_hotel` VALUES (23, '丽晶酒店', '南宁市青秀区民族大道789号', 350, 7, '格林豪泰', '南宁', '三星级', '青秀区', '22.817002', '108.366543', 'pic23.jpg');
INSERT INTO `tb_hotel` VALUES (24, '海角酒店', '海口市龙华区滨海大道101号', 420, 9, '丽枫', '海口', '五星级', '龙华区', '20.044002', '110.198293', 'pic24.jpg');
INSERT INTO `tb_hotel` VALUES (25, '紫金山酒店', '贵阳市云岩区中山西路202号', 310, 7, '希尔顿', '贵阳', '四星级', '云岩区', '26.647003', '106.630242', 'pic25.jpg');
INSERT INTO `tb_hotel` VALUES (26, '星辰酒店', '长沙市芙蓉区五一路123号', 380, 8, '喜来登', '长沙', '五星级', '芙蓉区', '28.228209', '112.938814', 'pic26.jpg');
INSERT INTO `tb_hotel` VALUES (27, '天际线酒店', '南昌市东湖区中山路456号', 370, 9, '香格里拉', '南昌', '四星级', '东湖区', '28.682892', '115.858197', 'pic27.jpg');
INSERT INTO `tb_hotel` VALUES (28, '星海湾酒店', '合肥市包河区金寨路789号', 340, 7, '万豪', '合肥', '三星级', '包河区', '31.820591', '117.227219', 'pic28.jpg');
INSERT INTO `tb_hotel` VALUES (29, '都市阳光酒店', '呼和浩特市新城区新华大街101号', 360, 8, '锦江之星', '呼和浩特', '四星级', '新城区', '40.841490', '111.751990', 'pic29.jpg');
INSERT INTO `tb_hotel` VALUES (30, '蓝海酒店', '兰州市城关区东方红广场202号', 330, 7, '维也纳', '兰州', '三星级', '城关区', '36.061089', '103.834304', 'pic30.jpg');
INSERT INTO `tb_hotel` VALUES (31, '西部酒店', '乌鲁木齐市天山区人民路123号', 400, 9, '华住', '乌鲁木齐', '五星级', '天山区', '43.825592', '87.616848', 'pic31.jpg');
INSERT INTO `tb_hotel` VALUES (32, '绿洲宾馆', '银川市兴庆区解放东路456号', 310, 7, '开元', '银川', '四星级', '兴庆区', '38.487194', '106.230909', 'pic32.jpg');
INSERT INTO `tb_hotel` VALUES (33, '江南酒店', '石家庄市长安区中山东路789号', 380, 8, '如家', '石家庄', '三星级', '长安区', '38.042759', '114.514297', 'pic33.jpg');
INSERT INTO `tb_hotel` VALUES (34, '云海酒店', '太原市小店区迎泽大街101号', 390, 9, '布丁', '太原', '五星级', '小店区', '37.870590', '112.548879', 'pic34.jpg');
INSERT INTO `tb_hotel` VALUES (35, '君悦酒店', '郑州市金水区花园路202号', 370, 8, '全季', '郑州', '四星级', '金水区', '34.746303', '113.625350', 'pic35.jpg');
INSERT INTO `tb_hotel` VALUES (36, '湖滨酒店', '长春市朝阳区解放大路123号', 350, 7, '亚朵', '长春', '三星级', '朝阳区', '43.817072', '125.323544', 'pic36.jpg');
INSERT INTO `tb_hotel` VALUES (37, '紫荆花酒店', '沈阳市和平区太原街456号', 400, 9, '格林豪泰', '沈阳', '五星级', '和平区', '41.805699', '123.431472', 'pic37.jpg');
INSERT INTO `tb_hotel` VALUES (38, '银杏树酒店', '合肥市庐阳区长江中路789号', 360, 8, '丽枫', '合肥', '四星级', '庐阳区', '31.820591', '117.227219', 'pic38.jpg');
INSERT INTO `tb_hotel` VALUES (39, '星空酒店', '福州市鼓楼区东街口101号', 340, 7, '希尔顿', '福州', '三星级', '鼓楼区', '26.075302', '119.306239', 'pic39.jpg');
INSERT INTO `tb_hotel` VALUES (40, '海天大酒店', '哈尔滨市南岗区学府路202号', 380, 9, '喜来登', '哈尔滨', '五星级', '南岗区', '45.803775', '126.534967', 'pic40.jpg');
INSERT INTO `tb_hotel` VALUES (41, '彩虹酒店', '杭州市西湖区湖滨路123号', 360, 8, '香格里拉', '杭州', '四星级', '西湖区', '30.274085', '120.155070', 'pic41.jpg');
INSERT INTO `tb_hotel` VALUES (42, '天鹅湖酒店', '贵阳市南明区花溪大道456号', 350, 7, '万豪', '贵阳', '三星级', '南明区', '26.647003', '106.630242', 'pic42.jpg');
INSERT INTO `tb_hotel` VALUES (43, '凤凰酒店', '成都市青羊区顺城大街789号', 370, 9, '锦江之星', '成都', '五星级', '青羊区', '30.572815', '104.066803', 'pic43.jpg');
INSERT INTO `tb_hotel` VALUES (44, '兰花酒店', '南宁市兴宁区朝阳路101号', 330, 8, '维也纳', '南宁', '四星级', '兴宁区', '22.817002', '108.366543', 'pic44.jpg');
INSERT INTO `tb_hotel` VALUES (45, '牡丹江酒店', '昆明市五华区滇池路202号', 340, 7, '华住', '昆明', '三星级', '五华区', '25.040609', '102.712251', 'pic45.jpg');
INSERT INTO `tb_hotel` VALUES (46, '玉龙雪山酒店', '苏州市吴中区东环路123号', 310, 9, '开元', '苏州', '五星级', '吴中区', '31.298886', '120.585316', 'pic46.jpg');
INSERT INTO `tb_hotel` VALUES (47, '海韵酒店', '南昌市青山湖区北京东路456号', 390, 8, '如家', '南昌', '四星级', '青山湖区', '28.682892', '115.858197', 'pic47.jpg');
INSERT INTO `tb_hotel` VALUES (48, '金鹰酒店', '厦门市湖里区仙岳路789号', 360, 7, '布丁', '厦门', '三星级', '湖里区', '24.479834', '118.089425', 'pic48.jpg');
INSERT INTO `tb_hotel` VALUES (49, '天海酒店', '广州市越秀区环市东路101号', 350, 9, '全季', '广州', '五星级', '越秀区', '23.129163', '113.264435', 'pic49.jpg');
INSERT INTO `tb_hotel` VALUES (50, '丽晶酒店', '西安市碑林区南大街202号', 400, 8, '亚朵', '西安', '四星级', '碑林区', '34.341574', '108.939770', 'pic50.jpg');
INSERT INTO `tb_hotel` VALUES (51, '海角酒店', '乌鲁木齐市沙依巴克区友好北路123号', 330, 7, '格林豪泰', '乌鲁木齐', '三星级', '沙依巴克区', '43.825592', '87.616848', 'pic51.jpg');
INSERT INTO `tb_hotel` VALUES (52, '紫金山酒店', '济南市市中区经十路456号', 370, 9, '丽枫', '济南', '五星级', '市中区', '36.651216', '117.120095', 'pic52.jpg');
INSERT INTO `tb_hotel` VALUES (53, '星辰酒店', '昆明市官渡区春城路789号', 310, 8, '希尔顿', '昆明', '四星级', '官渡区', '25.040609', '102.712251', 'pic53.jpg');
INSERT INTO `tb_hotel` VALUES (54, '天际线酒店', '长沙市天心区劳动西路101号', 340, 7, '喜来登', '长沙', '三星级', '天心区', '28.228209', '112.938814', 'pic54.jpg');
INSERT INTO `tb_hotel` VALUES (55, '星海湾酒店', '贵阳市花溪区贵黄路202号', 390, 9, '香格里拉', '贵阳', '五星级', '花溪区', '26.647003', '106.630242', 'pic55.jpg');
INSERT INTO `tb_hotel` VALUES (56, '都市阳光酒店', '福州市台江区五一路456号', 360, 8, '万豪', '福州', '四星级', '台江区', '26.075302', '119.306239', 'pic56.jpg');
INSERT INTO `tb_hotel` VALUES (57, '蓝海酒店', '银川市金凤区正源南街123号', 350, 7, '锦江之星', '银川', '三星级', '金凤区', '38.487194', '106.230909', 'pic57.jpg');
INSERT INTO `tb_hotel` VALUES (58, '西部酒店', '成都市武侯区科华南路789号', 320, 9, '维也纳', '成都', '五星级', '武侯区', '30.572815', '104.066803', 'pic58.jpg');
INSERT INTO `tb_hotel` VALUES (59, '绿洲宾馆', '呼和浩特市赛罕区新华东街101号', 390, 8, '华住', '呼和浩特', '四星级', '赛罕区', '40.841490', '111.751990', 'pic59.jpg');
INSERT INTO `tb_hotel` VALUES (60, '江南酒店', '兰州市七里河区西站东路202号', 340, 7, '开元', '兰州', '三星级', '七里河区', '36.061089', '103.834304', 'pic60.jpg');
INSERT INTO `tb_hotel` VALUES (61, '云海酒店', '南宁市西乡塘区明秀东路123号', 310, 9, '如家', '南宁', '五星级', '西乡塘区', '22.817002', '108.366543', 'pic61.jpg');
INSERT INTO `tb_hotel` VALUES (62, '君悦酒店', '厦门市集美区银江路456号', 370, 8, '布丁', '厦门', '四星级', '集美区', '24.479834', '118.089425', 'pic62.jpg');
INSERT INTO `tb_hotel` VALUES (63, '湖滨酒店', '西安市雁塔区小寨东路789号', 400, 7, '全季', '西安', '三星级', '雁塔区', '34.341574', '108.939770', 'pic63.jpg');
INSERT INTO `tb_hotel` VALUES (64, '紫荆花酒店', '贵阳市南明区解放路101号', 330, 9, '亚朵', '贵阳', '五星级', '南明区', '26.647003', '106.630242', 'pic64.jpg');
INSERT INTO `tb_hotel` VALUES (65, '银杏树酒店', '福州市晋安区鼓山大道202号', 380, 8, '格林豪泰', '福州', '四星级', '晋安区', '26.075302', '119.306239', 'pic65.jpg');
INSERT INTO `tb_hotel` VALUES (66, '星空酒店', '成都市高新区天府大道123号', 350, 7, '丽枫', '成都', '三星级', '高新区', '30.572815', '104.066803', 'pic66.jpg');
INSERT INTO `tb_hotel` VALUES (67, '海天大酒店', '杭州市拱墅区湖墅南路456号', 360, 9, '希尔顿', '杭州', '五星级', '拱墅区', '30.274085', '120.155070', 'pic67.jpg');
INSERT INTO `tb_hotel` VALUES (68, '彩虹酒店', '昆明市西山区滇池路789号', 310, 8, '喜来登', '昆明', '四星级', '西山区', '25.040609', '102.712251', 'pic68.jpg');
INSERT INTO `tb_hotel` VALUES (69, '天鹅湖酒店', '兰州市安宁区桃林路101号', 340, 7, '香格里拉', '兰州', '三星级', '安宁区', '36.061089', '103.834304', 'pic69.jpg');
INSERT INTO `tb_hotel` VALUES (70, '凤凰酒店', '呼和浩特市玉泉区大南街202号', 390, 9, '万豪', '呼和浩特', '五星级', '玉泉区', '40.841490', '111.751990', 'pic70.jpg');
INSERT INTO `tb_hotel` VALUES (71, '兰花酒店', '重庆市渝北区金开大道123号', 370, 8, '锦江之星', '重庆', '四星级', '渝北区', '29.563761', '106.550464', 'pic71.jpg');
INSERT INTO `tb_hotel` VALUES (72, '牡丹江酒店', '郑州市二七区大学路456号', 400, 7, '维也纳', '郑州', '三星级', '二七区', '34.746303', '113.625350', 'pic72.jpg');
INSERT INTO `tb_hotel` VALUES (73, '玉龙雪山酒店', '济南市槐荫区经十西路789号', 360, 9, '华住', '济南', '五星级', '槐荫区', '36.651216', '117.120095', 'pic73.jpg');
INSERT INTO `tb_hotel` VALUES (74, '海韵酒店', '南宁市江南区白沙大道101号', 350, 8, '开元', '南宁', '四星级', '江南区', '22.817002', '108.366543', 'pic74.jpg');
INSERT INTO `tb_hotel` VALUES (75, '金鹰酒店', '海口市美兰区海甸岛人民大道202号', 370, 7, '如家', '海口', '三星级', '美兰区', '20.044002', '110.198293', 'pic75.jpg');
INSERT INTO `tb_hotel` VALUES (76, '天海酒店', '长沙市雨花区万家丽中路123号', 330, 9, '布丁', '长沙', '五星级', '雨花区', '28.228209', '112.938814', 'pic76.jpg');
INSERT INTO `tb_hotel` VALUES (77, '丽晶酒店', '南京市秦淮区中华路456号', 380, 8, '全季', '南京', '四星级', '秦淮区', '32.060255', '118.796877', 'pic77.jpg');
INSERT INTO `tb_hotel` VALUES (78, '海角酒店', '杭州市滨江区江南大道789号', 340, 7, '亚朵', '杭州', '三星级', '滨江区', '30.274085', '120.155070', 'pic78.jpg');
INSERT INTO `tb_hotel` VALUES (79, '紫金山酒店', '福州市仓山区金山大道101号', 390, 9, '格林豪泰', '福州', '五星级', '仓山区', '26.075302', '119.306239', 'pic79.jpg');
INSERT INTO `tb_hotel` VALUES (80, '星辰酒店', '成都市金牛区人民北路456号', 360, 8, '丽枫', '成都', '四星级', '金牛区', '30.572815', '104.066803', 'pic80.jpg');
INSERT INTO `tb_hotel` VALUES (81, '天际线酒店', '南宁市青秀区长湖路123号', 340, 7, '希尔顿', '南宁', '三星级', '青秀区', '22.817002', '108.366543', 'pic81.jpg');
INSERT INTO `tb_hotel` VALUES (82, '星海湾酒店', '昆明市盘龙区金色大道202号', 330, 9, '喜来登', '昆明', '五星级', '盘龙区', '25.040609', '102.712251', 'pic82.jpg');
INSERT INTO `tb_hotel` VALUES (83, '都市阳光酒店', '呼和浩特市赛罕区腾飞路456号', 370, 8, '香格里拉', '呼和浩特', '四星级', '赛罕区', '40.841490', '111.751990', 'pic83.jpg');
INSERT INTO `tb_hotel` VALUES (84, '蓝海酒店', '兰州市红古区海淀路789号', 400, 7, '万豪', '兰州', '三星级', '红古区', '36.061089', '103.834304', 'pic84.jpg');
INSERT INTO `tb_hotel` VALUES (85, '西部酒店', '重庆市巴南区龙洲湾101号', 360, 9, '锦江之星', '重庆', '五星级', '巴南区', '29.563761', '106.550464', 'pic85.jpg');
INSERT INTO `tb_hotel` VALUES (86, '绿洲宾馆', '银川市金凤区正源南街456号', 340, 8, '维也纳', '银川', '四星级', '金凤区', '38.487194', '106.230909', 'pic86.jpg');
INSERT INTO `tb_hotel` VALUES (87, '江南酒店', '南宁市西乡塘区大学东路789号', 330, 7, '华住', '南宁', '三星级', '西乡塘区', '22.817002', '108.366543', 'pic87.jpg');
INSERT INTO `tb_hotel` VALUES (88, '云海酒店', '厦门市思明区莲花南路101号', 370, 9, '开元', '厦门', '五星级', '思明区', '24.479834', '118.089425', 'pic88.jpg');
INSERT INTO `tb_hotel` VALUES (89, '君悦酒店', '贵阳市云岩区北京东路456号', 360, 8, '如家', '贵阳', '四星级', '云岩区', '26.647003', '106.630242', 'pic89.jpg');
INSERT INTO `tb_hotel` VALUES (90, '湖滨酒店', '昆明市五华区人民中路789号', 310, 7, '布丁', '昆明', '三星级', '五华区', '25.040609', '102.712251', 'pic90.jpg');
INSERT INTO `tb_hotel` VALUES (91, '紫荆花酒店', '杭州市江干区秋涛北路123号', 390, 9, '全季', '杭州', '五星级', '江干区', '30.274085', '120.155070', 'pic91.jpg');
INSERT INTO `tb_hotel` VALUES (92, '银杏树酒店', '成都市双流区华阳大道456号', 350, 8, '亚朵', '成都', '四星级', '双流区', '30.572815', '104.066803', 'pic92.jpg');
INSERT INTO `tb_hotel` VALUES (93, '星空酒店', '福州市马尾区儒江西路789号', 400, 7, '格林豪泰', '福州', '三星级', '马尾区', '26.075302', '119.306239', 'pic93.jpg');
INSERT INTO `tb_hotel` VALUES (94, '海天大酒店', '南宁市青秀区厢竹大道101号', 360, 9, '丽枫', '南宁', '五星级', '青秀区', '22.817002', '108.366543', 'pic94.jpg');
INSERT INTO `tb_hotel` VALUES (95, '彩虹酒店', '杭州市余杭区良渚路456号', 310, 8, '希尔顿', '杭州', '四星级', '余杭区', '30.274085', '120.155070', 'pic95.jpg');
INSERT INTO `tb_hotel` VALUES (96, '天鹅湖酒店', '昆明市盘龙区白塔路789号', 340, 7, '喜来登', '昆明', '三星级', '盘龙区', '25.040609', '102.712251', 'pic96.jpg');
INSERT INTO `tb_hotel` VALUES (97, '凤凰酒店', '呼和浩特市赛罕区新城大道123号', 390, 9, '香格里拉', '呼和浩特', '五星级', '赛罕区', '40.841490', '111.751990', 'pic97.jpg');
INSERT INTO `tb_hotel` VALUES (98, '兰花酒店', '南宁市良庆区玉洞大道202号', 370, 8, '万豪', '南宁', '四星级', '良庆区', '22.817002', '108.366543', 'pic98.jpg');
INSERT INTO `tb_hotel` VALUES (99, '牡丹江酒店', '厦门市湖里区金山西路789号', 400, 7, '锦江之星', '厦门', '三星级', '湖里区', '24.479834', '118.089425', 'pic99.jpg');
INSERT INTO `tb_hotel` VALUES (100, '玉龙雪山酒店', '福州市鼓楼区五一路101号', 360, 9, '维也纳', '福州', '五星级', '鼓楼区', '26.075302', '119.306239', 'pic100.jpg');

项目准备好后,根据表结构,建立es的索引库

小提示:ES中支持两种地理坐标数据类型

  • geo_point:有纬度(latitude)和经度(longitude)确定的一个点。例如 “24.479834,119.306239”
  • geo_shape:有多个geo_point组成的复杂几何图形。例如一条直线,“LINESTRING(24.479834 119.306239,-119.306239 24.479834)”

问题:假如需求是根据多个字段进行搜索,而不是单个字段进行搜索,那么该如何实现

在es中,每个字段都提供了一个属性值,copy_to ,这个可以将其他字段的值拷贝到指定的字段,就可实现在一个字段中搜索到其他字段的值

这种方法做了优化,并不是真的把其他字段的值拷贝进去,只是基于它创建动态索引,查的时候看不到这个字段,但是可以根据这个公共字段搜索

示例:

# 提供一个公共字段,整合其他字段
"all": {
    "type": "text",
    "analyzer": "ik_max_word"
},
# 将需要拷贝到公共字段的字段添加上copy_to属性
"name": {
    "type": "text", 
    "analyzer": "ik_max_word",
    "copy_to": "all"
}

完整的创建索引库

PUT hotel
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name": {
        "type": "text", 
        "analyzer": "ik_max_word",
        "copy_to": "all"
      },
      "address": {
        "type": "keyword", 
        "index": false
      },
      "price": {
        "type": "integer"
      },
      "score": {
        "type": "integer"
      },
      "brand": {
        "type": "keyword",
        "copy_to": "all"
      },
      "city": {
        "type": "keyword"
      },
      "starName": {
        "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、初始化示例工程

(1)引入依赖,版本需要与现在使用的es版本一致

<!-- https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-high-level-client -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.17.21</version>
</dependency>

注意:此时的大部分依赖已经为 7.17.21,但是由于所有的依赖都是由springboot所进行管理的,还需要额外的在父工程规定依赖版本,在properties标签下添加,自此,所有关于es的依赖都以我们自己规定版本的为主

<properties>
    <elasticsearch.version>7.17.21</elasticsearch.version>
</properties>

(2)编写测试类

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;

/**
 * @author Patrick Star
 * @date 2024/6/14 0:51
 */
public class EsHotelIndexTest {
    private RestHighLevelClient client;

    /**
     * 在测试前运行
     */
    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://175.178.189.171:9200")
        ));
    }

    /**
     * 测试完后自动销毁
     */
    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }
}

2、RestHighLevelClient操作(已过时的API)

索引库
(1)新增索引库

可将dslStr封装为静态常量

    /**
     * 创建索引库
     */
    @Test
    void testCreateIndex() throws IOException {
        //dsl创建索引库语句
        String dslStr = "{\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" +
                "      \"starName\": {\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" +
                "}";
        //1、创建request对象
        CreateIndexRequest request = new CreateIndexRequest("hotel");
        //2、准备DSL语句
        request.source(dslStr, XContentType.JSON);
        //3、发起请求
        this.client.indices().create(request, RequestOptions.DEFAULT);
    }
(2)判断是否存在和删除索引库

判断索引库是否存在

/**
 * 判断索引库是否存在
 * @throws IOException
 */
@Test
void testExistIndex() throws IOException {
    GetIndexRequest request = new GetIndexRequest("hotel");
    boolean exists = this.client.indices().exists(request, RequestOptions.DEFAULT);
    System.out.println("索引库是否存在 ---------- > " + exists);
}

删除索引库

/**
 * 删除索引库
 * @throws IOException
 */
@Test
void testDeleteIndex() throws IOException {
    DeleteIndexRequest request = new DeleteIndexRequest("hotel");
    this.client.indices().delete(request, RequestOptions.DEFAULT);
}
文档
(1)新增文档

新增一个实体类,方便将hotel对象转为存在索引库的对象

@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + "," + hotel.getLongitude();
        this.pic = hotel.getPic();
    }
}

测试类

/**
 * 新增文档
 * @throws IOException
 */
@Test
void testAddDoc() throws IOException {
    //1、查询数据库,获取数据对象
    Hotel hotel = iHotelService.getById(1L);
    //2、将hotel对象转为文档对象
    HotelDoc hotelDoc = new HotelDoc(hotel);
    //3、准备request对象
    IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
    //4、准备json文档数据
    request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
    //5、发起请求
    this.client.index(request, RequestOptions.DEFAULT);
}
(2)查询文档
/**
 * 查询文档
 * @throws IOException
 */
@Test
void testGetDoc() throws IOException {
    //1、创建GetRequest对象,传入索引库名,文档id
    GetRequest request = new GetRequest("hotel", "1");
    //2、发起请求
    GetResponse response = client.get(request, RequestOptions.DEFAULT);
    String json = response.getSourceAsString();
    System.out.println("查询到的对象 ---------- > " + json);
}
(3)局部更新文档

注意这里的准备参数,参数与之用逗号隔开,一个属性一个值,每一对属性和值也用逗号隔开

/**
 * 根据文档id更新文档
 * @throws IOException
 */
@Test
void testUpdateDoc() throws IOException {
    //1、创建UpdateRequest对象,传入索引库名,文档id
    UpdateRequest request = new UpdateRequest("hotel", "1");
    //2、准备参数
    request.doc(
        "name","鲁光大道大酒店",
        "price","777"
    );
    //3、发起请求
    client.update(request, RequestOptions.DEFAULT);
}
(4)删除文档
/**
 * 根据文档id删除文档
 * @throws IOException
 */
@Test
void testDeleteDoc() throws IOException {
    DeleteRequest request = new DeleteRequest("hotel", "1");
    client.delete(request, RequestOptions.DEFAULT);
}
(5)批量新增
/**
 * 批量提交
 * @throws IOException
 */
@Test
void testBulkDoc() throws IOException {
    //获取所有的酒店数据
    List<Hotel> list = iHotelService.list();
    BulkRequest request = new BulkRequest();
    //for循环处理数据
    for (Hotel hotel : list) {
        HotelDoc hotelDoc = new HotelDoc(hotel);
        //构建IndexRequest对象
        IndexRequest indexRequest = new IndexRequest("hotel").id(hotelDoc.getId().toString());
        indexRequest.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
        request.add(indexRequest);
    }
    client.bulk(request, RequestOptions.DEFAULT);
}

3、RestClient基本查询 (已过时API)

(1)查询所有
/**
 * 查询所有
 * @throws IOException
 */
@Test
void testMatchAll() throws IOException {
    //1、准备request
    SearchRequest request = new SearchRequest("hotel");
    //2、准备查询条件
    request.source().query(QueryBuilders.matchAllQuery());
    //3、发起请求,获取响应结果
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //4、处理响应结果
    SearchHits hits = response.getHits();
    for (SearchHit hit : hits) {
        HotelDoc hotelDoc = JSON.parseObject(hit.getSourceAsString(), HotelDoc.class);
        System.out.println("结果 --------- > " + hotelDoc);
    }
}
(2)字段查询
/**
 * 字段查询
 * @throws IOException
 */
@Test
void testMatch() throws IOException {
    //1、准备request
    SearchRequest request = new SearchRequest("hotel");
    //2-1、单字段查询(字段名,查询的值)
    //request.source().query(QueryBuilders.matchQuery("all", "阳光"));
    //2-2、多字段查询
    request.source().query(QueryBuilders.multiMatchQuery("阳光南宁", "name", "city"));
    //3、发起请求,获取响应结果
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //4、处理响应结果
    SearchHits hits = response.getHits();
    long value = hits.getTotalHits().value;
    System.out.println("一共搜索数据 ---------  > "+value+" 条数据");
    for (SearchHit hit : hits) {
        HotelDoc hotelDoc = JSON.parseObject(hit.getSourceAsString(), HotelDoc.class);
    System.out.println("结果 --------- > " + hotelDoc);
    }
}
(3)其他查询api使用方式都差不多
(4)排序和分页
/**
 * 查询所有
 * @throws IOException
	*/
@Test
void testMatchAll() throws IOException {
    //1、准备request
    SearchRequest request = new SearchRequest("hotel");
    //2、准备查询条件
    request.source().query(QueryBuilders.matchAllQuery());
	//排序
    request.source().sort("price",SortOrder.ASC);
	//分页
    request.source().from(0).size(10);
    //3、发起请求,获取响应结果
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //4、处理响应结果
    SearchHits hits = response.getHits();
    long value = hits.getTotalHits().value;
    System.out.println("一共搜索数据 ---------  > "+value+" 条数据");
    for (SearchHit hit : hits) {
        HotelDoc hotelDoc = JSON.parseObject(hit.getSourceAsString(), HotelDoc.class);
    	System.out.println("结果 --------- > " + hotelDoc);
    }
}
(5)文本高亮
/**
 * 文本高亮
 * @throws IOException
 */
@Test
void testMatchAll() throws IOException {
    //1、准备request
    SearchRequest request = new SearchRequest("hotel");
    //2、准备查询条件
    request.source().query(QueryBuilders.matchQuery("all", "阳光"));
    request.source().highlighter(new HighlightBuilder()
                                 .field("name").requireFieldMatch(false)
                                 .field("city").requireFieldMatch(false));
    //3、发起请求,获取响应结果
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //4、处理响应结果
    SearchHits hits = response.getHits();
    long value = hits.getTotalHits().value;
    System.out.println("一共搜索数据 ---------  > "+value+" 条数据");
    for (SearchHit hit : hits) {
        HotelDoc hotelDoc = JSON.parseObject(hit.getSourceAsString(), HotelDoc.class);
        System.out.println("结果 --------- > " + hotelDoc);
        //解析高亮结果
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        HighlightField field = highlightFields.get("name");
        String resp = field.getFragments()[0].toString();
        System.out.println("高亮 ----------- > " + resp);
    }
}

五、DSL基本语法

官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-filter-context.html

DSL Query的分类

常见的查询类型包括:

  • 查询所有:查询出所有数据,一般测试用,例如 match_all ,这里的match_all一般只会查询出10条数据,避免一次性查询所有数据导致服务器内存消耗过大
  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
    • match_all
    • multi_match_query
  • 精确查询:根据精确词条值查询数据,一般是查询keyword、数值、日期、boolean等类型字段,例如:
    • ids 根据id查询
    • range 数值范围查询
    • term
  • 地理(geo)查询:根据经纬度查询,例如
    • geo_distance
    • geo_bounding_box
  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件,例如
    • bool
    • function_score

查询的基本语法:

GET /索引库名/_search
{
    "query": {
        "查询类型":{
            "查询条件":"条件值"
        }
    }
}

1、查询所有

GET /hotel/_search
{
  "query": {
    "match_all": {} # 查询所有
  }
}

2、全文检索

match查询

全文检索查询的一种,会对用户输入内容分词,然后去倒排索引检索,语法:

GET /indexName/_search
{
    "query": {
        "match": {
            "FIELD":"TEXT"
        }
    }
}

示例:

GET /hotel/_search
{
  "query": {
    "match": {
      "all": "如家西乡塘"
    }
  }
}
multi_match查询

与match查询类似,不过允许多字段查询,语法:

GET /indexName/_search
{
  "query": {
    "multi_match": {
      "query": "text",
      "fields": ["FIELD1","FIELD2"]
    }
  }
}

示例:

GET /hotel/_search
{
  "query": {
    "multi_match": {
      "query": "云海",
      "fields": ["brand", "business", "name"]
    }
  }
}

3、精确查询

根据精确词条值查询数据,一般是查询keyword、数值、日期、boolean等类型字段,一般是不会对词条进行分词查询

  • term:根据词条精确查询
  • range:根据值范围查询
term查询

语法:

GET /indexName/_search
{
  "query": {
    "term": {
      "FIELD": {
        "value": "VALUE"
      }
    }
  }
}

示例:

GET /hotel/_search
{
  "query": {
    "term": {
      "city": {
        "value": "南宁"
      }
    }
  }
}
range查询

语法:

GET /indexName/_search
{
  "query": {
    "range": {
      "FIELD": {
        "gte": 10,
        "lte": 20
      }
    }
  }
}

示例:

GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 500, # gt 大于 gte 大于等于
        "lte": 600 # lt 小于 lte 小于等于
      }
    }
  }
}

4、地理查询

geo_bounding_box查询

查询geo_point值落在某个矩形范围内的所有文档,这个FIELD字段必须是geo_point类型

语法:

GET /indexName/_search
{
  "query": {
    "geo_bounding_box": {
      "FIELD": {
       "top_left": {
          "lat": 40.73,
          "lon": -74.1
        },
        "bottom_right": {
          "lat": 40.01,
          "lon": -71.12
        }
      }
    }
  }
}
geo_distance查询

查询到指定中心点小于某个距离值的所有文档

语法:

GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance":"5km", # 距离
      "FIELD":"22.817002,108.366543" # 地理坐标字段
    }
  }
}

示例:

GET /hotel/_search
{
  "query": {
    "geo_distance": {
      "distance":"5km",
      "location":"22.817002,108.366543"
    }
  }
}

5、复合查询

复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件,例如

boolean_query查询

布尔查询时一个或多个查询子句的组合。子查询的组合方式有:

  • must:必须匹配每个子查询,类似 “与”
  • should:选择性匹配子查询,类似 “或”
  • must_not:必须不匹配,不参与算分,类似 “非”
  • filter:必须匹配,不参与算分

语法:

GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "阳光"
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "gt": 500
            }
          }
        }
      ],
      "filter": [
        {
          "geo_distance": {
            "distance": "100km",
            "location": {
              "lat": 26.07,
              "lon": 119.30
            }
          }
        }
      ]
    }
  }
}
function_score查询

算分函数查询,可以控制文档相关性算分,控制文档排名。例如百度搜索

相关性算分:当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列

规则:

在这里插入图片描述

es版本后的es默认采用的是BM25算法

TF-IDF算法中,词频越高,将来的得分就会无限的增加,会越来越高,BM25算法在词频的得分会趋于水平

语法:

GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {
        "match": {
          "city": "南宁"
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "id": "1"
            }
          },
          "weight": 10
        }
      ],
      "boost_mode": "multiply"
    }
  }
}

在这里插入图片描述

6、搜索结果处理

排序

es支持对搜索结果排序,默认是根据相关度算分(_score)来排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型

语法:

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "FIELD": {
        "order": "desc" //排序字段和排序方式 ASC、DESC
      }
    }
  ]
}

地理坐标排序

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance": {
        "FIELD": { //地理坐标字段
          "lat": 40,
          "lon": -70
        },
        "order": "asc", //排序方式
        "unit": "km" //排序单位
      }
    }
  ]
}

分页

es默认情况下只返回top10的数据,而如果要查询更多数据就需要修改分页参数

es通过修改from,size参数来控制要返回的分页结果

语法

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0, //分页开始的位置,默认是0
  "size": 30, //期望获取的文档总数
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}

深度分页问题

es是分布式的,所以会面临深度分页的问题。例如按price排序后,获取from=990,size=10的数据:

1、首先在每个数据分片上都有排序并查询前1000条文档

2、然后将所有节点的结果聚合,在内存中重新排序选出前1000条文档

3、最后从这1000条中,选取从990开始的10条文档

如果搜索页数过深,或者结果集(from+size)越大,对内存和CPU的消耗也越高。因此es设定结果集查询的上限是10000条

在这里插入图片描述

深度分页解决方案

  • search after:分页是需要排序,原理是从上一次的排序值开始,查询下一页数据,缺点是只能向后翻页,不可向前翻页,因为这种方式记录了这一次分页的最后一个文档的值,查询下一页的时候,是根据这个记录的值去获取下一页的文档,官方推荐使用的方式
  • scroll:原理将排序数据形成快照,保存在内存中,缺点是数据量大的时候,需要的内存也越大,官方不推荐。
高亮

在搜索结果中把搜索关键字高亮

  • 将搜索结果中的关键字用标签标记出来
  • 在页面中给标签添加css样式

语法:

注意:

  • 在默认情况下,es的搜索字段必须与高亮字段一致,否则不会高亮
  • 在搜索条件中必须要带关键字查询,不可使用match_all,否则不会显示高亮
GET /hotel/_search
{
  "query": {
    "match": { //必须指定搜索字段,并且不能使用match_all
      "all": "阳光酒店"
    }
  },
  "highlight": {
    "fields": { //指定要高亮的字段
      "name": {
        "pre_tags": "<em>", //用来标记高亮字段的前置标签
        "post_tags": "</em>",//用来标记高亮字段的后置标签
        "require_field_match": "false" //不需要字段匹配,作用是为了取消搜索字段与高亮字段不匹配,从而不高亮的问题
      },
      "city": {
        "pre_tags": "<em>",
        "post_tags": "</em>",
        "require_field_match": "false"
      }
    }
  }
}

六、数据聚合

1、聚合分类

聚合(aggregations)可以实现对文档数据的统计、分析、运算。聚合常见的有三大类:

  • 桶(Bucket)聚合:用来对文档做分组
    • termAggregation:按照文档字段值分组
    • Date Histogram:按照日期阶梯分组,例如一周为一组,一个月为一组
  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求max、min、avg、sum等
  • 管道(pipeline)聚合:其他聚合结果为基础做聚合

2、使用DSL实现聚合

(1)桶(Bucket)聚合

语法

GET /hotel/_search
{
  "size": 0, //设置size为0,结果不包含文档,只包含聚合结果
  "aggs": { //定义聚合
    "brandAgg": { //自定义聚合名称
      "terms": { //聚合类型,按照什么进行聚合就写什么,这里是按照词条进行聚合
        "field": "brand", //聚合的字段
        "size": 30 //期望获取的聚合结果数量
      }
    }
  }
}

示例:

根据品牌进行聚合

GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 10
      }
    }
  }
}

默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照 _count 降序排序。这个可自行修改规则,方式如下

GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 100,
        "order": { //添加自定义排序规则
          "_count": "asc" 
        }
      }
    }
  }
}

默认情况下,Bucket聚合是对索引库的所有文档做聚合,我们可以限定要聚合的文档范围,只要添加query条件即可:

GET /hotel/_search
{
  "query": { //添加聚合条件,不对索引库中的所有文档进行聚合
    "range": {
      "price": {
        "lte": 400
      }
    }
  }, 
  "size": 0,
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 100,
        "order": {
          "_count": "asc"
        }
      }
    }
  }
}

(2)度量(Metric)聚合

语法:

GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 100,
        "order": {
          "_count": "asc"
        }
      },
      "aggs": { //在桶聚合中添加新的聚合
        "scoreAggs": { //聚合名,自定义
          "stats": { //聚合类型
            "field": "score" //聚合的字段
          }
        }
      }
    }
  }
}

可自定义排序规则

GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 100,
        "order": {
          "scoreAggs.avg": "asc" //添加自定义的排序规则
        }
      },
      "aggs": {
        "scoreAggs": {
          "stats": {
            "field": "score"
          }
        }
      }
    }
  }
}

3、使用RestApi实现聚合

(1)桶聚合

语法:

/**
     * 查询所有
     * @throws IOException
     */
    @Test
    void testAggregation() throws IOException {
        //1、准备request
        SearchRequest request = new SearchRequest("hotel");
        //2、准备聚合条件
        request.source().size(0);
        request.source().aggregation(
                AggregationBuilders
                        .terms("brand_agg")
                        .field("brand")
                        .size(20)
        );
        //3、发起请求,获取响应结果
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //4、解析结果
        Aggregations aggregations = response.getAggregations();
        //根据名称获取聚合结果
        Terms brandAgg = aggregations.get("brand_agg");
        //获取桶
        List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
        //遍历桶
        for (Terms.Bucket bucket : buckets) {
            String keyAsString = bucket.getKeyAsString();
            long docCount = bucket.getDocCount();
            System.out.println("品牌名称 --- > " + keyAsString + " 品牌数量 --- > " + docCount);
        }
    }
(2)多条件聚合

例子:

1.在接口中声明一个filters接口

public interface IHotelService extends IService<Hotel> {
    Map<String, List<String>> filters();
}

2.实现类

@Service
public class HotelServiceImpl extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient client;

    @Override
    public Map<String, List<String>> filters() {
        try {
            //1、准备request
            SearchRequest request = new SearchRequest("hotel");
            //2、准备聚合条件
            request.source().size(0);
            //创造聚合条件
            aggregationBuild(request);
            //3、发起请求,获取响应结果
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            Map<String, List<String>> map = new HashMap<>();
            //4、解析结果
            Aggregations aggregations = response.getAggregations();
            List<String> brandAgg = getAggreResp(aggregations, "brand_agg");
            map.put("品牌", brandAgg);
            List<String> cityAgg = getAggreResp(aggregations, "city_agg");
            map.put("城市", cityAgg);
            List<String> starAgg = getAggreResp(aggregations, "star_agg");
            map.put("星级", starAgg);
            return map;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private List<String> getAggreResp(Aggregations aggregations, String aggName) {
        //根据名称获取聚合结果
        Terms brandAgg = aggregations.get(aggName);
        //获取桶
        List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
        List<String> keys = new ArrayList<>();
        //遍历桶
        for (Terms.Bucket bucket : buckets) {
            String key = bucket.getKeyAsString();
            keys.add(key);
        }
        return keys;
    }

    private void aggregationBuild(SearchRequest request) {
        request.source().aggregation(
                AggregationBuilders
                        .terms("brand_agg")
                        .field("brand")
                        .size(100)
        );
        request.source().aggregation(
                AggregationBuilders
                        .terms("city_agg")
                        .field("city")
                        .size(100)
        );
        request.source().aggregation(
                AggregationBuilders
                        .terms("star_agg")
                        .field("starName")
                        .size(100)
        );
    }
}

3.单元测试

@SpringBootTest
class EsHotelServiceApplicationTests {

    @Autowired
    private IHotelService iHotelService;

    @Test
    void contextLoads() {
        Map<String, List<String>> filters = iHotelService.filters();
        System.out.println("结果 ---------- > " + filters);
    }

}

七、自动补全

1、安装拼音分词器

下载地址:https://github.com/infinilabs/analysis-pinyin

下载对应版本后,上传到docker容器所挂载的es的插件数据卷下,重启容器,等待容器重启完成即可

docker restart es

测试:

分词器使用的是 pinyin

POST _analyze
{
  "text": ["如家酒店还不错"],
  "analyzer": "pinyin"
}

2、自定义分词器

es中分词器(analyzer)的组成包含三部分:

  • character filters:在tokenizer之前对文本进行处理,例如删除字符,替换字符
  • tokenizer:将文本按照一定的规则切割成词条(term),例如keyword,就是不分词,还有ik_smart
  • tokenizer filter:将tokenizer输出的词条进一步处理。例如大小写转换、同义词处理,拼音处理等

在这里插入图片描述

(1)创建自定义分词器

语法:

PUT /test
{
  "settings": { //索引库配置,只针对当前索引库的
    "analysis": { 
      "analyzer": { //自定义分词器
        "my_analyzer": { //自定义分词器的名称
          "tokenizer": "ik_max_word",
          "filter": "py" //下边过滤名称
        }
      },
      "filter": { //自定义的tokenizer filter
        "py": { //过滤器名称
          "type":"pinyin", //过滤器类型,这里是pinyin,一下配置可在pinyin分词器官网找到
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": { //建立mapping映射
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "my_analyzer" //使用自定义分词器
      }
    }
  }
}

用法:

//错误用法,因为定义的分词器只能针对当前索引库的,所以这里使用会报错
POST /_analyze
{
  "text": ["如家酒店还不错"],
  "analyzer": "my_analyzer" //使用自定义分词器
}

//正确用法,需要指定是哪个索引库
POST /test/_analyze
{
  "text": ["如家酒店还不错"],
  "analyzer": "my_analyzer" //使用自定义分词器
}
(2)自定义分词器存在的问题

问题复现:

首先插入两条数据

POST /test/_doc/1
{
  "id":1,
  "name":"狮子"
}

POST /test/_doc/2
{
  "id":2,
  "name":"虱子"
}

使用拼音查询:

//使用拼音查询,没有问题,可以搜索出两条结果
GET /test/_search
{
  "query": {
    "match": {
      "name": "shizi"
    }
  }
}

在这里插入图片描述

使用中文查询:

//使用中文查询,还是会出现两条结果,出现问题
GET /test/_search
{
  "query": {
    "match": {
      "name": "狮子"
    }
  }
}

在这里插入图片描述

总结:在创建索引库时,使用自定义分词器后,在插入数据创建倒排索引时,es会将进行拆分,如果词条的拼音一样,那么就不会再创建倒排索引,而是在之前一样的拼音的文档编号上将一样拼音的id放入,结果就是查询的时候出现了这样的问题

在这里插入图片描述

(3)自定义分词器问题解决方案

在创建索引库时使用拼音分词器,但是搜索时不使用拼音分词器

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "ik_max_word",
          "filter": "py"
        }
      },
      "filter": {
        "py": {
          "type":"pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "my_analyzer",
        "search_analyzer": "ik_smart" //指定搜索时的分词器
      }
    }
  }
}

3、DSL自动补全(其中一种,一共三种)

completion suggester查询

es提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全的效率,对于文档中字段的类型有一些约束:

  • 参与自动补全查询的字段必须是 completion 类型
  • 字段的内容一般是用来补全的多个词条形成的数组

创建索引库时指定自动补全字段:

PUT test2
{
  "mappings": {
    "properties": {
      "title": {
        "type": "completion" //指定索引库中属性的字段类型为 completion
      }
    }
  }
}

插入数据(词条以数组的形式):

POST test2/_doc
{
  "title": ["Sony", "WH-10000XM3"]
}

POST test2/_doc
{
  "title": ["SK-II", "PITERA"]
}

POST test2/_doc
{
  "title": ["NIntendo", "switch"]
}

自动补全查询:

GET /test2/_search
{
  "suggest": {
    "title_suggest": { //自定义自动补全查询名称
      "text": "so",  //输入的查询文本
      "completion": { //自动补全类型
        "field": "title", //自动补全字段
        "skip_duplicates": true, //跳过重复的值
        "size": 10 //获取前10条结果
      }
    }
  }
}

4、示例

1、修改hotel索引库,支持自动补全和拼音分词器

// 删除原有的hotel索引库
DELETE hotel
//创建hotel索引库
PUT hotel
{
  "settings": {
    "analysis": {
      "analyzer": {
        "text_analyzer": {
          "tokenizer": "ik_max_word",
          "filter": "py"
        },
        "completion_analyzer": {
          "tokenizer": "keyword",
          "filter": "py"
        }
      },
      "filter": {
        "py": {
          "type":"pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name": {
        "type": "text", 
        "analyzer": "text_analyzer",
        "search_analyzer": "ik_smart", 
        "copy_to": "all"
      },
      "address": {
        "type": "keyword", 
        "index": false
      },
      "price": {
        "type": "integer"
      },
      "score": {
        "type": "integer"
      },
      "brand": {
        "type": "keyword",
        "copy_to": "all"
      },
      "city": {
        "type": "keyword"
      },
      "starName": {
        "type": "keyword"
      },
      "business": {
        "type": "keyword",
        "copy_to": "all"
      },
      "location": {
        "type": "geo_point"
      },
      "pic": {
        "type": "keyword", 
        "index": false
      },
      "all": {
        "type": "text",
        "analyzer": "text_analyzer",
        "search_analyzer": "ik_smart"
      },
      "suggestion": {
        "type": "completion",
        "analyzer": "completion_analyzer"
      }
    }
  }
}

2、修改原有的java对象 HotelDoc.java,新增suggestion属性,用来做自动补全

@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;
    private List<String> suggestion;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + "," + hotel.getLongitude();
        this.pic = hotel.getPic();
        //将现有数据作为自动补全存入suggestion
        this.suggestion = Arrays.asList(this.brand, this.business, this.name);
    }
}

3、导入数据

运行之前的单元测试批量导入数据

4、DSL测试

//测试自动补全
GET /hotel/_search
{
  "suggest": {
    "hotel_suggestion": {
      "text": "江南",
      "completion": {
        "field": "suggestion",
        "skip_duplicates": true
      }
    }
  }
}
//测试拼音分词器
GET /hotel/_search
{
  "query": {
    "match": {
      "all": "buding"
    }
  }
}

4、RestApi实现

/**
     * 自动补全
     * @throws IOException
     */
    @Test
    void testSuggest() throws IOException {
        //1、准备request
        SearchRequest request = new SearchRequest("hotel");
        //2、查询条件
        request.source().suggest(new SuggestBuilder().addSuggestion(
                "hotel_suggestion",
                SuggestBuilders
                        .completionSuggestion("suggestion")
                        .prefix("sh")
                        .skipDuplicates(true)
                        .size(10)
        ));
        //3、发起请求,获取响应结果
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        System.out.println("相应结果 ---------- > " + response);
        //解析返回值,获取自动补全结果
        Suggest suggest = response.getSuggest();
        CompletionSuggestion suggestion = suggest.getSuggestion("hotel_suggestion");
        for (CompletionSuggestion.Entry.Option option : suggestion.getOptions()) {
            String text = option.getText().toString();
            System.out.println("自动补全词条值--------- > " + text);
        }
    }

八、数据同步

1、数据同步方式

方式一:同步调用

  • 优点:实现简单、粗暴
  • 缺点:业务耦合度高

在这里插入图片描述

方式二:异步通知

  • 优点:低耦合、实现难度一般
  • 缺点:依赖mq的可靠性

在这里插入图片描述

方式三:监听mysql的binlog

  • 完全解除服务间的耦合
  • 缺点:开启binlog增加数据库负担、实现复杂度高

在这里插入图片描述

2、基于mq实现数据同步

根据之前所学,进行练习

九、集群

1、结构

单机的es做数据存储,必然面临两个问题:海量数据存储问题、单点故障问题

  • 海量数据存储问题:将索引库从逻辑上拆分为N个分片(shard),存储到多个节点
  • 单点故障问题:将分片数据在不同节点备份(replica)

在这里插入图片描述

2、搭建集群

命令方式部署(不同的服务器上)

# 第一台服务器上的es容器
docker run -d \
	--name es01 \
	--network es-net \
	--ulimit memlock=-1:-1 \
	-p 9200:9200 \
	-p 9300:9300 \
	-e "network.host=0.0.0.0" \
	-e "network.publish_host=192.168.132.128" \
	-e "node.name=es01" \
	-e "cluster.name=es-docker-cluster" \
	-e "discovery.seed_hosts=192.168.132.129,192.168.132.131" \
	-e "cluster.initial_master_nodes=es01,es02,es03" \
	-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
	-e "bootstrap.memory_lock=true" \
	-v /home/es/data01:/usr/share/elasticsearch/data \
	-v /home/es/plugins01:/usr/share/elasticsearch/plugins \
	elasticsearch:7.17.18
	
# 第二台服务器上的es容器
docker run -d \
	--name es02 \
	--network es-net \
	--ulimit memlock=-1:-1 \
	-p 9200:9200 \
	-p 9300:9300 \
	-e "network.host=0.0.0.0" \
	-e "network.publish_host=192.168.132.129" \
	-e "node.name=es02" \
	-e "cluster.name=es-docker-cluster" \
	-e "discovery.seed_hosts=192.168.132.128,192.168.132.131" \
	-e "cluster.initial_master_nodes=es01,es02,es03" \
	-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
	-e "bootstrap.memory_lock=true" \
	-v /home/es/data02:/usr/share/elasticsearch/data \
	-v /home/es/plugins02:/usr/share/elasticsearch/plugins \
	elasticsearch:7.17.18
	
# 第三台服务器上的es容器
docker run -d \
	--name es03 \
	--network es-net \
	--ulimit memlock=-1:-1 \
	-p 9200:9200 \
	-p 9300:9300 \
	-e "network.host=0.0.0.0" \
	-e "network.publish_host=192.168.132.131" \
	-e "node.name=es03" \
	-e "cluster.name=es-docker-cluster" \
	-e "discovery.seed_hosts=192.168.132.128,192.168.132.129" \
	-e "cluster.initial_master_nodes=es01,es02,es03" \
	-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
	-e "bootstrap.memory_lock=true" \
	-v /home/es/data03:/usr/share/elasticsearch/data \
	-v /home/es/plugins03:/usr/share/elasticsearch/plugins \
	elasticsearch:7.17.18

运行命令后,集群部署成功,使用cerebro进行容器管理

问题:

(1)报错原因:
bootstrap check failure [2] of [2]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

解决方案:

需要修改linux权限

# 修改 /etc/sysctl.conf
vim /etc/sysctl.conf
# 在这个文件添加
vm.max_map_count=262144
# 让修改后的文件生效
sysctl -p

做完这些步骤之后,将es容器先删除,再重新run

(2)报错原因:

bootstrap check failure [1] of [1]: memory locking requested for elasticsearch process but memory is not locked

解决方法一:将bootstrap.memory_lock=true改成false,取消内存锁定

解决方法二:在环境变量中新增 –ulimit memlock=-1:-1

(3)报错原因:

每个节点都能启动,但是节点之间缺不能互相连接,形成不了集群

{"type": "server", "timestamp": "2024-06-21T08:29:34,750Z", "level": "WARN", "component": "o.e.d.HandshakingTransportAddressConnector", "cluster.name": "es-docker-cluster", "node.name": "es01", "message": "[connectToRemoteMasterNode[192.168.132.129:9300]] completed handshake with [{es02}{gDSdsTzfRC-4Y5_DyJlFfQ}{yYnnMJJsQQiJdTYqtgd-lQ}{172.18.0.2}{172.18.0.2:9300}{cdfhilmrstw}{ml.machine_memory=16637534208, ml.max_open_jobs=512, xpack.installed=true, ml.max_jvm_size=536870912, transform.node=true}] but followup connection failed", 
"stacktrace": ["org.elasticsearch.transport.ConnectTransportException: [es02][172.18.0.2:9300] handshake failed. unexpected remote node {es01}{8C6LL9h1QyKrdcAp-Rin9w}{QgVGe5pVT-uw9A88tQh--w}{172.18.0.2}{172.18.0.2:9300}{cdfhilmrstw}{ml.machine_memory=16637542400, ml.max_open_jobs=512, xpack.installed=true, ml.max_jvm_size=536870912, transform.node=true}",
"at org.elasticsearch.transport.TransportService.lambda$connectionValidator$6(TransportService.java:468) ~[elasticsearch-7.17.18.jar:7.17.18]",
"at org.elasticsearch.action.ActionListener$MappedActionListener.onResponse(ActionListener.java:95) [elasticsearch-7.17.18.jar:7.17.18]",
"at org.elasticsearch.transport.TransportService.lambda$handshake$9(TransportService.java:577) [elasticsearch-7.17.18.jar:7.17.18]",
"at org.elasticsearch.action.ActionListener$DelegatingFailureActionListener.onResponse(ActionListener.java:219) [elasticsearch-7.17.18.jar:7.17.18]",
"at org.elasticsearch.action.ActionListenerResponseHandler.handleResponse(ActionListenerResponseHandler.java:43) [elasticsearch-7.17.18.jar:7.17.18]",
"at org.elasticsearch.transport.TransportService$ContextRestoreResponseHandler.handleResponse(TransportService.java:1471) [elasticsearch-7.17.18.jar:7.17.18]",
"at org.elasticsearch.transport.TransportService$ContextRestoreResponseHandler.handleResponse(TransportService.java:1471) [elasticsearch-7.17.18.jar:7.17.18]",
"at org.elasticsearch.transport.InboundHandler.doHandleResponse(InboundHandler.java:352) [elasticsearch-7.17.18.jar:7.17.18]",
"at org.elasticsearch.transport.InboundHandler.lambda$handleResponse$1(InboundHandler.java:340) [elasticsearch-7.17.18.jar:7.17.18]",
"at org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingRunnable.run(ThreadContext.java:718) [elasticsearch-7.17.18.jar:7.17.18]",
"at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) [?:?]",
"at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) [?:?]",
"at java.lang.Thread.run(Thread.java:1583) [?:?]"] }

解决方法:

使用docker进行部署的时候,官方提供的方法是在相同的服务器使用docker进行容器隔离进行集群部署

在不同服务器上的节点,因为都是不同的ip,所以要指定每个es容器对外交互的ip

# 容器对外交互的ip,设置成为容器所在服务器的ip
network.publish_host=192.168.132.129

3、集群状态监控

监控工具下载地址:https://github.com/lmenezes/cerebro/releases

cerebro工具

下载对应的操作系统的压缩包,解压之后直接运行bin目录下的bat文件,运行完成后使用 ip+9000端口浏览器访问,输入其中一个es节点的地址即可

在这里插入图片描述

4、创建索引库

(1)使用kibana创建索引库并分片

(2)使用cerebro创建索引库分片

步骤如下图

在这里插入图片描述

创建完成后,即可在首页查看

在这里插入图片描述

第一个红框表示索引库的分片信息

  • 3*2:因为设置了分成3片,并且每个片区都进行了1个备份,所以每个节点上都是会有两份数据

  • 实线绿色框表示为该节点的主分片,虚线绿色框表示为该节点当前所备份的数据分片

  • 实线框与虚线框的数字不一样,原因是每个节点上虚线框为备份分片,es集群的备份分片不能放在与主分片相同的节点上,确保了如果有任意一个节点宕机,那么数据仍在另一节点上

5、集群职责与脑裂

5.1 集群职责

在这里插入图片描述

  • master eligible:备选主节点,为了做高可用,万一主节点宕机了,可以充当主节点使用
  • data:数据节点
  • ingest:数据预处理节点,可以对数据存储之前做一下预处理,比如删除一些字段,插入一下字段,或者对内容进行修改,如果说在准备文档时用java代码对文档进行处理,那么该节点就没什么用处
  • coordinating:协调节点,不做业务处理,做协调处理,路由+负载均衡

默认情况下,es的节点同时具备这四种角色,正常开发时,需要做配置,不推荐一个节点同时身兼数职

典型的es集群

在这里插入图片描述

5.2 脑裂问题

默认情况下,每个节点都是master eligible节点,因此一旦master节点宕机,其他候选节点会选举一个成为主节点。当主节点与其他节点网络故障时,可能发生脑裂问题。

为了避免脑裂,需要要求选票超过(eligible节点数量 + 1)/ 2 才能当选为主,因此eligible节点数量最好是奇数。对应配置项是discovery.zen.minimum_master_nodes,在es7.0以后,已经默认配置,因此一般不会出现脑裂问题

通俗的讲就是当主节点还是好的,但是突然因为网络等原因,与其他节点断开连接,那么其他节点就会重新选举一个新的主节点,但是原来的主节点仍然能处理用户的请求数据,那么这两个就会导致有两个主节点在同时处理数据,最后主节点连接上其他节点后,就会造成数据不一致的情况。

在这里插入图片描述

6、分布式新增与查询

当新增文档时,应该保存到不同分片,保证数据均衡,es会通过hash算法来计算文档应该存储到哪个分片:

在这里插入图片描述

说明:

  • _routing默认是文档的id
  • 算法与分片数量有关,索引库一旦建立,分片数量不可修改,如果中途分片数量修改,那么插入的数据可能丢失

新增流程:

在这里插入图片描述

查询流程:

分两个阶段:

  • scatter phase:分散阶段,coordinating node(协调节点)会把请求分发给每一个分片
  • gather phase:聚集阶段,coordinating node(协调节点)汇总data node的搜索结果,并处理为最终结果集返回给用户

在这里插入图片描述

十、安全认证

es的x-pack安全认证

1、单机版

(1)进入容器内部,将es的config目录挂载到宿主机上,修改elasticsearch.yml文件

# 前面是默认的配置
cluster.name: "docker-cluster"
network.host: 0.0.0.0
ingest.geoip.downloader.enabled: false
# 新增xpack配置
xpack.security.enabled: true # 开启安全认证
xpack.license.self_generated.type: basic # 指定许可类型
xpack.security.transport.ssl.enabled: true # 用于启用节点之间的传输层安全

xpack.license.self_generated.type类型:

  • basic:免费许可证,提供基本功能,包括基础监控、Canvas、SQL 和安全性(基本认证和 TLS/SSL)。

  • trial:试用许可证,有效期为 30 天,提供所有 X-Pack 功能,包括高级安全性、机器学习、Graph 和 Kibana 的高级功能。

  • gold:收费许可证,提供高级监控、Alerting、Canvas 和 SQL 等功能。

  • platinum:收费许可证,提供所有 X-Pack 功能,包括机器学习、Graph 和高级安全性。

  • enterprise:最全面的收费许可证,提供企业级功能和支持。

(2)修改完成elasticsearch.yml配置文件后,重启es容器,让es容器使用自定义的yml配置文件

docker restart es

启动成功后, 通过浏览器 ip+端口 访问es,出现需要输入用户名和密码的警告窗就说明已经开启了防护

在这里插入图片描述

(3)进入容器,设置密码

# 进入容器
docker exec -it es bash

# 进入bin目录下,运行命令 interactive为手动设置密码
./elasticsearch-setup-passwords interactive
# ./elasticsearch-setup-passwords auto 自动生成密码

在这里插入图片描述

es内置elastic、apm_system、logstash_system、kibana等内置用户设置密码,手动设置需要一个个输入,建议使用同样的密码

输入完成后即可在浏览器的弹出框使用elastic内置用户进行登录,用户名为elastic,密码为刚刚输入的密码

在这里插入图片描述

输入密码确定后即可访问成功

内置用户的权限与作用

角色用途权限描述
elastic超级用户拥有所有权限用于初始设置和管理所有 Elasticsearch 功能。这个用户通常用于配置和管理整个集群,是具有最高权限的用户
apm_system用于 Elastic APM(应用性能监控)具有对 APM 索引的读写权限,以及一些监控权限用于 Elastic APM 服务器与 Elasticsearch 的通信,确保 APM 数据能够正确存储和检索
kibana_system用于 Kibana 服务具有对 .kibana* 索引的读写权限,并可以访问一些监控 API用于 Kibana 应用程序与 Elasticsearch 交互,管理 Kibana 索引和保存的对象,如仪表板、可视化等
logstash_system用于 Logstash 服务具有对 logstash-* 索引的读写权限,以及对 .logstash 内部索引的管理权限用于 Logstash 服务与 Elasticsearch 交互,确保 Logstash 能够索引和管理数据流
beats_system用于 Beats(如 Filebeat、Metricbeat 等)具有对 beats-* 索引的读写权限,以及对 .monitoring-beats-* 索引的读写权限用于 Beats 客户端与 Elasticsearch 交互,确保 Beats 能够收集和索引数据
remote_monitoring_user用于集群的远程监控具有对监控相关的 API 和索引(如 .monitoring-*)的读写权限用于 X-Pack Monitoring 功能,允许远程监控集群的状态和性能数据

2、集群版

(1)挂载配置

将每个es容器中的config配置文件挂载到宿主机上,每个节点都需要拷贝,方便后续使用自定义配置启动容器

# 将es容器中的config目录拷贝出来,做挂载
docker cp es01:/usr/share/elasticsearch/config /home/es
(2)证书生成

证书的作用主要是用于让每个es节点都能认识对方,然后建立通信

选择任意es节点,进入容器容器内部,使用x-pack提供的工具生成证书,工具在容器内的bin目录下

# 生成第一个证书
./elasticsearch-certutil ca

在这里插入图片描述

执行第二个命令

# 生成第二个证书,同第一个证书一样,可配置也可不配置,看个人情况
./elasticsearch-certutil cert -- ca elastic-stack-ca.p12

执行完成后即可在容器的根目录下出现两个证书

在这里插入图片描述

注意:需要注意拷贝出来的证书权限,权限不足的话容器无法读取证书

(3)配置证书

将这两个证书拷贝到宿主机上,放入第一步的config配置目录下,所有节点也是如此

# 两个证书都复制出来
docker cp es01:/usr/share/elasticsearch/elastic-certificates.p12 /home/es/config01
docker cp es01:/usr/share/elasticsearch/elastic-stack-ca.p12 /home/es/config01
(4)修改配置

修改每个节点的elasticsearch.yml配置文件

# 前三个配置在单机部署已有解释
xpack.security.enabled: true
xpack.license.self_generated.type: basic
xpack.security.transport.ssl.enabled: true
# 集群需要配置
xpack.security.transport.ssl.verification_mode: certificate # 校验签名和主机名
# 证书位置,注意这个位置是容器内config目录的位置,不是宿主机挂载的位置
xpack.security.transport.ssl.keystore.path: /usr/share/elasticsearch/config/elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: /usr/share/elasticsearch/config/elastic-certificates.p12

xpack.security.transport.ssl.verification_mode解释:用于指定节点之间在传输层安全(Transport Layer Security, TLS/SSL)通信时的证书验证模式

  • none:不进行任何验证。这意味着节点之间的通信将是加密的,但不会验证证书。这种模式通常用于测试环境,不推荐用于生产环境。

  • certificate:验证证书的签名和主机名。这是默认和推荐的选项,确保通信双方的证书是由受信任的 CA 签名的。

  • full:除了验证证书外,还验证证书的完整性和主机名。这是最严格的模式。

(5)重启容器

删除所有节点的docker容器,然后重新部署,将容器的config目录挂载到自定义的config目录下

# 第一台服务器上的es容器
docker run -d \
	--name es01 \
	--network es-net \
	--ulimit memlock=-1:-1 \
	-p 9200:9200 \
	-p 9300:9300 \
	-e "network.host=0.0.0.0" \
	-e "network.publish_host=192.168.132.128" \
	-e "node.name=es01" \
	-e "cluster.name=es-docker-cluster" \
	-e "discovery.seed_hosts=192.168.132.129,192.168.132.131" \
	-e "cluster.initial_master_nodes=es01,es02,es03" \
	-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
	-e "bootstrap.memory_lock=true" \
	-v /home/es/data01:/usr/share/elasticsearch/data \
	-v /home/es/plugins01:/usr/share/elasticsearch/plugins \
	-v /home/es/config01:/usr/share/elasticsearch/config \
	elasticsearch:7.17.18
	
# 第二台服务器上的es容器
docker run -d \
	--name es02 \
	--network es-net \
	--ulimit memlock=-1:-1 \
	-p 9200:9200 \
	-p 9300:9300 \
	-e "network.host=0.0.0.0" \
	-e "network.publish_host=192.168.132.129" \
	-e "node.name=es02" \
	-e "cluster.name=es-docker-cluster" \
	-e "discovery.seed_hosts=192.168.132.128,192.168.132.131" \
	-e "cluster.initial_master_nodes=es01,es02,es03" \
	-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
	-e "bootstrap.memory_lock=true" \
	-v /home/es/data02:/usr/share/elasticsearch/data \
	-v /home/es/plugins02:/usr/share/elasticsearch/plugins \
	-v /home/es/plugins02:/usr/share/elasticsearch/plugins \
	-v /home/es/config02:/usr/share/elasticsearch/config \
	elasticsearch:7.17.18
	
# 第三台服务器上的es容器
docker run -d \
	--name es03 \
	--network es-net \
	--ulimit memlock=-1:-1 \
	-p 9200:9200 \
	-p 9300:9300 \
	-e "network.host=0.0.0.0" \
	-e "network.publish_host=192.168.132.131" \
	-e "node.name=es03" \
	-e "cluster.name=es-docker-cluster" \
	-e "discovery.seed_hosts=192.168.132.128,192.168.132.129" \
	-e "cluster.initial_master_nodes=es01,es02,es03" \
	-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
	-e "bootstrap.memory_lock=true" \
	-v /home/es/data03:/usr/share/elasticsearch/data \
	-v /home/es/plugins03:/usr/share/elasticsearch/plugins \
	-v /home/es/config03:/usr/share/elasticsearch/config \
	elasticsearch:7.17.18

随机访问当前的一个es节点,需要输入密码的就说明已经成功,使用cerebro集群监控也需要输入密码

在这里插入图片描述

(6)设置密码

设置密码的方式与单机版相同,随机选择一个es节点,进入容器内部,使用x-pack工具设置密码即可

只需要在随机的一个es节点设置即可,不需要全部节点都要设置

3、kibana设置

es开启安全认证后,kibana需要连接到es也需要认证

3.1明文配置

通过在kibana.yml配置文件中配置es的用户名和密码进行连接

(1)修改yml配置

将kibana在容器中的config配置放到宿主机上

# 将kibana容器内的config配置复制到宿主机上
docker cp kibana:/usr/share/kibana/config /home/kibana

修改kibana.yml配置文件

# 默认配置
server.host: "0.0.0.0"
server.shutdownTimeout: "5s"
elasticsearch.hosts: [ "http://elasticsearch:9200" ] # es节点ip地址
monitoring.ui.container.elasticsearch.enabled: true
# 开启es安全认证
kibana.index: ".kibana" # kibana在es中存储数据的索引名称
i18n.locale: "zh-CN" # 语言设置,这里设置成中文
elasticsearch.username: "elastic" # es的用户名
elasticsearch.password: "123456" # es的连接密码
xpack.reporting.encryptionKey: "a_random_string" # 用于设置用于加密和解密报表数据的密钥,这里是随机字符
xpack.security.encryptionKey: "something_at_least_32_characters" # 用于加密和解密存储在 Kibana 中的敏感数据,这里是随机字符
(2)重启容器

指定好config的挂载目录,重新运行容器,也可删除容器后重新运行

docker run -d --name kibana \
--network=es-net \
-v /home/kibana/config:/usr/share/kibana/config \
-p 5601:5601 \
kibana:7.17.18

在这里插入图片描述

ip+端口访问kibana管理,出现需要输入用户名和密码即可成功,输入在kibana.yml中设置的用户名和密码即可访问

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值