文章目录

  • 一 初识elasticsearch
    • 1.1 认识ES
    • 1.2 基知:倒排索引
    • 1.3 正向和倒排总结
    • 1.4 es基础概念
  • 二 安装elasticsearch
    • 2.1 部署部署单点es
      • 2.1.1 创建网络
      • 2.1.2 下载镜像
      • 2.1.3 加载镜像
      • 2.1.4 运行
      • 2.1.5 访问
    • 2.2 部署kibana
      • 2.2.1 下载镜像
      • 2.2.2 加载镜像
      • 2.2.3 运行kibana
      • 2.2.4 查看日志
      • 2.2.5 使用DevTools
    • 2.3 安装IK分词器
      • 2.3.1 在线安装ik插件(较慢)
      • 2.3.2 离线安装ik插件(推荐)
    • 2.4 拓展词典
    • 2.5 停用词词典
    • 2.6 部署es集群【补充】
  • 三 索引库操作
    • 3.1 mapping映射属性
    • 3.2 创建索引库和映射
    • 3.3 查询索引库
    • 3.4 修改索引库
    • 3.5 删除索引库
    • 3.6 总结
  • 四 文档操作
    • 4.1 新增文档
    • 4.2 查询文档
    • 4.3 删除文档
    • 4.4 修改文档
    • 4.5 总结
  • 五 RestAPI
    • 5.1 分析数据结构:apping映射分析
    • 5.2 初始化RestClient
    • 5.3 创建索引库
    • 5.4 删除索引库
    • 5.5 判断索引库存在
    • 5.6 总结
  • 六 RestClient操作文档
    • 6.1 新增文档
    • 6.2 查询文档
    • 6.3 修改文档
    • 6.4 删除文档
    • 6.5 批量导入文档
    • 6.6 小结

一 初识elasticsearch

1.1 认识ES

  • elasticsearch的作用
    • elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容

  • ELK技术栈
    • elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域:
    • 而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。

  • elasticsearch和lucene
    • elasticsearch底层是基于lucene来实现的。
    • Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。

  • elasticsearch的发展历史:
    • 2004年Shay Banon基于Lucene开发了Compass
    • 2010年Shay Banon 重写了Compass,取名为Elasticsearch。

1.2 基知:倒排索引

  • 倒排索引的概念是基于MySQL这样的正向索引而言的。
  • 正向索引:
    • 基于文档id创建索引。查询词条时必须先找到文档,而后判断是否包含词条

  • 逐行扫描,也就是全表扫描,随着数据量增加,其查询效率也会越来越低。当数据量达到数百万时,就是一场灾难。
  • elasticsearch采用倒排索引
    • 文档(document):用来搜索的数据,每条数据就是一个文档 例如:一个网页、一个商品信息
    • 词条(term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。 例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条
  • 创建倒排索引是对正向索引的一种特殊处理,流程如下:
    1. 将每一个文档的数据利用算法分词,得到一个个词条
    2. 创建表,每行数据包括词条、词条所在文档id、位置等信息
    3. 因为词条唯一性,可以给词条创建索引,例如hash表结构索引
  • 倒排索引的搜索流程如下(以搜索”华为手机”为例):
    • 1)用户输入条件"华为手机"进行搜索。
    • 2)对用户输入内容分词,得到词条:华为手机
    • 3)拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3。
    • 4)拿着文档id到正向索引中查找具体文档。
  • 虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。

  • posting list
  • 倒排索引中包含两部分内容:
    • 词条词典(Term Dictionary):记录所有词条,以及词条与倒排列表(Posting List)之间的关系,会给词条创建索引,提高查询和插入效率
    • 倒排列表(Posting List):记录词条所在的文档id、词条出现频率 、词条在文档中的位置等信息
      • 文档id:用于快速获取文档
      • 词条频率(TF):文档在词条出现的次数,用于评分

1.3 正向和倒排总结

  • 正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程
  • 倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程
  • 正向索引
    • 优点:
      • 可以给多个字段创建索引
      • 根据索引字段搜索、排序速度非常快
    • 缺点:
      • 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。
  • 倒排索引
    • 优点:
      • 根据词条搜索、模糊搜索时,速度非常快
    • 缺点:
      • 只能给词条创建索引,而不是字段
      • 无法根据字段做排序

1.4 es基础概念

  • 文档和字段
    • elasticsearch是面向文档(Document) 存储的,可以是数据库中的一条商品数据,一个订单信息。
    • 文档数据会被序列化为json格式后存储在elasticsearch中,而Json文档中往往包含很多的字段(Field)

  • 索引和映射
    • 索引(Index),就是相同类型的文档的集合。
    • 映射(mapping):索引中文档的字段约束信息,类似表的结构约束

  • **mysql与elasticsearch的概念对比:加粗样式
MySQLElasticsearch说明
TableIndex索引(index),就是文档的集合,类似数据库的表(table)
RowDocument文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式
ColumnField字段(Field),就是JSON文档中的字段,类似数据库中的列(Column)
SchemaMappingMapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)
SQLDSLDSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD

  • 架构
    • Mysql:擅长事务类型操作,可以确保数据的安全和一致性
    • Elasticsearch:擅长海量数据的搜索、分析、计算
  • 因此在企业中,往往是两者结合使用:
    • 对安全性要求较高的写操作,使用mysql实现
    • 对查询性能要求较高的搜索需求,使用elasticsearch实现
    • 两者再基于某种方式,实现数据的同步,保证一致性

二 安装elasticsearch

2.1 部署部署单点es

2.1.1 创建网络

  • 因为还需要部署kibana容器,因此需要让es和kibana容器互联。先创建一个网络:
    docker network create es-net

2.1.2 下载镜像

  • 这里采用elasticsearch的7.12.1版本的镜像,不建议pull。
  • Elasticsearch中文社区下载地址

    • 记得将压缩包的名称修改为es.tar

2.1.3 加载镜像

[root@kongyue ~]# systemctl start docker[root@kongyue ~]# cd /tmp/[root@kongyue tmp]# docker load -i es.tar2653d992f4ef: Loading layer [==================================================>]  216.5MB/216.5MB0ba8eff8aa04: Loading layer [==================================================>]  101.4MB/101.4MB2a944434ad00: Loading layer [==================================================>]  314.9kB/314.9kBade95a7611c0: Loading layer [==================================================>]  543.9MB/543.9MB09a575a6e776: Loading layer [==================================================>]  26.62kB/26.62kB498ae65924d7: Loading layer [==================================================>]   7.68kB/7.68kB36b3f8db7aaa: Loading layer [==================================================>]  490.5kB/490.5kBLoaded image: elasticsearch:7.12.1

2.1.4 运行

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

命令解释:

  • -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:端口映射配置
[root@kongyue tmp]# 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.1f1f899239f106e6f0718b632383ed0401cb4b8e96772a7cb1e0d3c890862cfb6

2.1.5 访问

  • 访问:http://xxx.xxx.xxx.xxx:9200 即可看到elasticsearch的响应结果:

2.2 部署kibana

2.2.1 下载镜像

  • 官方地址Download Kibana
  • Elasticsearch中文社区下载地址
    • 记得将压缩包的名称修改为kibana.tar

2.2.2 加载镜像

[root@kongyue tmp]# docker load -i kibana.tard797e87ed4ce: Loading layer [==================================================>]  112.9MB/112.9MB80ce41fc1f8a: Loading layer [==================================================>]  26.62kB/26.62kB3345a8ffd0ea: Loading layer [==================================================>]  3.584kB/3.584kBd736a1702974: Loading layer [==================================================>]  20.34MB/20.34MB570575469db2: Loading layer [==================================================>]  56.83kB/56.83kB459d502a3562: Loading layer [==================================================>]  770.7MB/770.7MBf22a9f0649d0: Loading layer [==================================================>]  2.048kB/2.048kB4b66f24ba0de: Loading layer [==================================================>]  4.096kB/4.096kB0a50faa06266: Loading layer [==================================================>]  15.36kB/15.36kB8a310ff91413: Loading layer [==================================================>]  4.096kB/4.096kB5997553ddc84: Loading layer [==================================================>]  479.2kB/479.2kBf87dadd7c340: Loading layer [==================================================>]  309.8kB/309.8kBLoaded image: kibana:7.12.1

2.2.3 运行kibana

  • kibana启动一般比较慢,需要多等待一会
[root@kongyue tmp]# docker run -d \> --name kibana \> -e ELASTICSEARCH_HOSTS=http://es:9200 \> --network=es-net \> -p 5601:5601  \> kibana:7.12.1d8ade4066ec86d48c10effe249b65c122b63bd6734b1708b941a221bc633be78
  • --network es-net :加入一个名为es-net的网络中,与elasticsearch在同一个网络中
  • -e ELASTICSEARCH_HOSTS=http://es:9200":设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch
  • -p 5601:5601:端口映射配置

2.2.4 查看日志

  • 可以通过命令,查看运行日志:
    docker logs -f kibana
  • 当查看到下面的日志,说明成功:
    {"type":"log","@timestamp":"2023-03-24T05:05:05+00:00","tags":["info","http","server","Kibana"],"pid":7,"message":"http server running at http://0.0.0.0:5601"}

2.2.5 使用DevTools

  • 访问http://xxx.xxx.xxx.xxx:5601,即可看到以下页面,选择Explore on my own
  • 选择开发工具
  • 然后进入以下界面
  • 界面中可以编写DSL来操作elasticsearch。并且对DSL语句有自动补全功能。

2.3 安装IK分词器

2.3.1 在线安装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

2.3.2 离线安装ik插件(推荐)

  1. 查看数据卷目录
  • 安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录
  • 通过下面命令查看:
    docker volume inspect es-plugins
  • 显示结果:
    [root@kongyue tmp]# docker volume inspect es-plugins[    {        "CreatedAt": "2023-03-24T12:51:40+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 这个目录中。
  1. 下载IK分词器
  • Elasticsearch中文社区下载地址
  1. 解压缩分词器安装包
  • ik分词器解压缩,重命名为ik
  1. 上传到es容器的插件数据卷中
  • ik文件夹上传到查询出来的挂载目录
  1. 重启容器
# 重启容器docker restart es# 查看es日志docker logs -f es
  1. 测试:
  • IK分词器包含两种模式:
    • ik_smart:最少切分
    • ik_max_word:最细切分
GET /_analyze{  "analyzer": "ik_max_word",  "text": "天生我才必有用,大可不必独叹息"}
{  "tokens" : [    {      "token" : "天生我才必有用",      "start_offset" : 0,      "end_offset" : 7,      "type" : "CN_WORD",      "position" : 0    },    {      "token" : "天生我才",      "start_offset" : 0,      "end_offset" : 4,      "type" : "CN_WORD",      "position" : 1    },    {      "token" : "天生",      "start_offset" : 0,      "end_offset" : 2,      "type" : "CN_WORD",      "position" : 2    },    {      "token" : "我",      "start_offset" : 2,      "end_offset" : 3,      "type" : "CN_CHAR",      "position" : 3    },    {      "token" : "才",      "start_offset" : 3,      "end_offset" : 4,      "type" : "CN_CHAR",      "position" : 4    },    {      "token" : "必有用",      "start_offset" : 4,      "end_offset" : 7,      "type" : "CN_WORD",      "position" : 5    },    {      "token" : "必有",      "start_offset" : 4,      "end_offset" : 6,      "type" : "CN_WORD",      "position" : 6    },    {      "token" : "有用",      "start_offset" : 5,      "end_offset" : 7,      "type" : "CN_WORD",      "position" : 7    },    {      "token" : "大可不必",      "start_offset" : 8,      "end_offset" : 12,      "type" : "CN_WORD",      "position" : 8    },    {      "token" : "大可",      "start_offset" : 8,      "end_offset" : 10,      "type" : "CN_WORD",      "position" : 9    },    {      "token" : "可不",      "start_offset" : 9,      "end_offset" : 11,      "type" : "CN_WORD",      "position" : 10    },    {      "token" : "不必",      "start_offset" : 10,      "end_offset" : 12,      "type" : "CN_WORD",      "position" : 11    },    {      "token" : "独",      "start_offset" : 12,      "end_offset" : 13,      "type" : "CN_CHAR",      "position" : 12    },    {      "token" : "叹息",      "start_offset" : 13,      "end_offset" : 15,      "type" : "CN_WORD",      "position" : 13    }  ]}

2.4 拓展词典

  • 随着互联网的发展,出现了很多新的词语,在原有的词汇列表中并不存在。所以我们的词汇也需要不断的更新,IK分词器提供了扩展词汇的功能。
  1. 打开IK分词器config目录:
  2. 在IKAnalyzer.cfg.xml配置文件内容添加:
    <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"><properties>        <comment>IK Analyzer 扩展配置</comment>                <entry key="ext_dict">ext.dic</entry></properties>
  3. 新建一个 ext.dic,可以参考config目录下复制一个配置文件进行修改
    • 注意当前文件的编码必须是 UTF-8 格式
emo芭比Q了
  1. 重启elasticsearch
docker restart es
  1. 测试效果:
GET /_analyze{  "analyzer": "ik_max_word",  "text": "竞争太激烈,这次面试又芭比Q了,我真的很emo"}
{  "token" : "emo",  "start_offset" : 20,  "end_offset" : 23,  "type" : "ENGLISH",  "position" : 13}

2.5 停用词词典

  • 在互联网项目中,在网络间传输的速度很快,所以很多语言是不允许在网络上传递的,如:关于宗教、政治等敏感词语,那么我们在搜索时也应该忽略当前词汇。
  • IK分词器也提供了强大的停用词功能,让我们在索引时就直接忽略当前的停用词汇表中的内容。
  1. IKAnalyzer.cfg.xml配置文件内容添加:
    <!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></properties>
  2. 在 stopword.dic 添加停用词
    • 注意当前文件的编码必须是 UTF-8 格式
    草泥马特么的
  3. 重启elasticsearch
    # 重启服务docker restart elasticsearchdocker restart kibana
  4. 测试效果
    GET /_analyze{  "analyzer": "ik_max_word",  "text": "草泥马,特么的,都是不文明网络用语"}
    {  "tokens" : [    {      "token" : "草",      "start_offset" : 0,      "end_offset" : 1,      "type" : "CN_CHAR",      "position" : 0    },    {      "token" : "泥",      "start_offset" : 1,      "end_offset" : 2,      "type" : "CN_CHAR",      "position" : 1    },    {      "token" : "马",      "start_offset" : 2,      "end_offset" : 3,      "type" : "CN_CHAR",      "position" : 2    },    {      "token" : "特",      "start_offset" : 4,      "end_offset" : 5,      "type" : "CN_CHAR",      "position" : 3    },    {      "token" : "么",      "start_offset" : 5,      "end_offset" : 6,      "type" : "CN_CHAR",      "position" : 4    },    {      "token" : "的",      "start_offset" : 6,      "end_offset" : 7,      "type" : "CN_CHAR",      "position" : 5    },    {      "token" : "都是",      "start_offset" : 8,      "end_offset" : 10,      "type" : "CN_WORD",      "position" : 6    },    {      "token" : "不文明",      "start_offset" : 10,      "end_offset" : 13,      "type" : "CN_WORD",      "position" : 7    },    {      "token" : "不文",      "start_offset" : 10,      "end_offset" : 12,      "type" : "CN_WORD",      "position" : 8    },    {      "token" : "文明",      "start_offset" : 11,      "end_offset" : 13,      "type" : "CN_WORD",      "position" : 9    },    {      "token" : "网络",      "start_offset" : 13,      "end_offset" : 15,      "type" : "CN_WORD",      "position" : 10    },    {      "token" : "用语",      "start_offset" : 15,      "end_offset" : 17,      "type" : "CN_WORD",      "position" : 11    }  ]}

2.6 部署es集群【补充】

  • 部署es集群可以直接使用docker-compose来完成,不过要求你的Linux虚拟机至少有4G的内存空间
  1. 首先编写一个docker-compose文件,内容如下:
    version: '2.2'services:  es01:    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1    container_name: es01    environment:      - node.name=es01      - cluster.name=es-docker-cluster      - discovery.seed_hosts=es02,es03      - cluster.initial_master_nodes=es01,es02,es03      - bootstrap.memory_lock=true      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"    ulimits:      memlock:        soft: -1        hard: -1    volumes:      - data01:/usr/share/elasticsearch/data    ports:      - 9200:9200    networks:      - elastic  es02:    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1    container_name: es02    environment:      - node.name=es02      - cluster.name=es-docker-cluster      - discovery.seed_hosts=es01,es03      - cluster.initial_master_nodes=es01,es02,es03      - bootstrap.memory_lock=true      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"    ulimits:      memlock:        soft: -1        hard: -1    volumes:      - data02:/usr/share/elasticsearch/data    networks:      - elastic  es03:    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1    container_name: es03    environment:      - node.name=es03      - cluster.name=es-docker-cluster      - discovery.seed_hosts=es01,es02      - cluster.initial_master_nodes=es01,es02,es03      - bootstrap.memory_lock=true      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"    ulimits:      memlock:        soft: -1        hard: -1    volumes:      - data03:/usr/share/elasticsearch/data    networks:      - elasticvolumes:  data01:    driver: local  data02:    driver: local  data03:    driver: localnetworks:  elastic:    driver: bridge
  2. Run docker-compose to bring up the cluster:
    docker-compose up

三 索引库操作

  • 索引库就类似数据库表,mapping映射就类似表的结构。要向es中存储数据,必须先创建“库”和“表”。
  • 使用统一使用Kibana编写DSL的方式来演示

3.1 mapping映射属性

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

  • 举例:
    {    "age": 21,    "weight": 52.1,    "isMarried": false,    "info": "武神",    "email": "zy@itcast.cn",    "score": [99.1, 99.5, 98.9],    "name": {        "firstName": "云",        "lastName": "赵"    }}
  • 对应的每个字段映射(mapping):
    • age:类型为 integer;参与搜索,因此需要index为true;无需分词器
    • weight:类型为float;参与搜索,因此需要index为true;无需分词器
    • isMarried:类型为boolean;参与搜索,因此需要index为true;无需分词器
    • info:类型为字符串,需要分词,因此是text;参与搜索,因此需要index为true;分词器可以用ik_smart
    • email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器
    • score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器
    • name:类型为object,需要定义多个子属性
      • name.firstName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
      • name.lastName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器

3.2 创建索引库和映射

  • 基本语法:
    • 请求方式:PUT
    • 请求路径:/索引库名,可以自定义
    • 请求参数:mapping映射
  • 格式:
    PUT /索引库名称{  "mappings": {    "properties": {      "字段名":{        "type": "text",        "analyzer": "ik_smart"      },      "字段名2":{        "type": "keyword",        "index": "false"      },      "字段名3":{        "properties": {          "子字段": {            "type": "keyword"          }        }      }    }  }}
  • 举例:
    PUT /test01{  "mappings": {    "properties": {      "info":{        "type": "text",        "analyzer": "ik_smart"      },      "email":{        "type": "keyword",        "index": "false"      },      "name":{        "properties": {          "firstName": {            "type": "keyword"          }        }      }    }  }}
  • 演示结果:
    {  "acknowledged" : true,  "shards_acknowledged" : true,  "index" : "test01"}

3.3 查询索引库

  • 基本语法

    • 请求方式:GET
    • 请求路径:/索引库名
    • 请求参数:无
  • 格式

    GET /索引库名
  • 演示:

3.4 修改索引库

  • 倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping
  • 虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。
  • 语法说明
    PUT /索引库名/_mapping{  "properties": {    "新字段名":{      "type": "integer"    }  }}
  • 演示:

3.5 删除索引库

  • 语法:
    • 请求方式:DELETE
    • 请求路径:/索引库名
    • 请求参数:无
  • 格式:
    DELETE /索引库名

3.6 总结

  • 索引库操作:
    • 创建索引库:PUT /索引库名
    • 查询索引库:GET /索引库名
    • 删除索引库:DELETE /索引库名
    • 添加字段:PUT /索引库名/_mapping

四 文档操作

4.1 新增文档

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

4.2 查询文档

  • 语法:
    GET /{索引库名称}/_doc/{id}
  • 通过kibana查看数据:
    GET /heima/_doc/1
  • 演示:

4.3 删除文档

  • 语法:
    DELETE /{索引库名}/_doc/id值
  • 演示:

4.4 修改文档

  • 修改有两种方式:
    • 全量修改:直接覆盖原来的文档
    • 增量修改:修改文档中的部分字段

  • 全量修改
  • 全量修改是覆盖原来的文档,其本质是:
    • 根据指定的id删除文档
    • 新增一个相同id的文档
  • 注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
  • 语法:
    PUT /{索引库名}/_doc/文档id{    "字段1": "值1",    "字段2": "值2",    // ... 略}
  • 演示:

  • 增量修改
  • 增量修改是只修改指定id匹配的文档中的部分字段。
  • 语法:
    POST /{索引库名}/_update/文档id{    "doc": {         "字段名": "新的值"    }}
  • 演示:

4.5 总结

  • 文档操作:
    • 创建文档:POST /{索引库名}/_doc/文档id { json文档 }
    • 查询文档:GET /{索引库名}/_doc/文档id
    • 删除文档:DELETE /{索引库名}/_doc/文档id
    • 修改文档:
      • 全量修改:PUT /{索引库名}/_doc/文档id { json文档 }
      • 增量修改:POST /{索引库名}/_update/文档id { "doc": {字段}}

五 RestAPI

  • ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址
  • 其中的Java Rest Client又包括两种:
    • Java Low Level Rest Client
    • Java High Level Rest Client
  • 这里使用Java HighLevel Rest Client客户端API

5.1 分析数据结构:apping映射分析

  • 创建索引库,最关键的是mapping映射,而mapping映射要考虑的信息包括:
    • 字段名
    • 字段数据类型
    • 是否参与搜索
    • 是否需要分词
    • 如果分词,分词器是什么?
  • 其中:
    • 字段名、字段数据类型,可以参考数据表结构的名称和类型
    • 是否参与搜索要分析业务来判断,例如图片地址,就无需参与搜索
    • 是否分词呢要看内容,内容如果是一个整体就无需分词,反之则要分词
    • 分词器,我们可以统一使用ik_max_word

  • 几个特殊字段说明:
    • location:地理坐标,里面包含精度、纬度
    • all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索
  • 地理坐标说明:
  • copy_to说明:

5.2 初始化RestClient

  • 在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。
  • 步骤分为三步:
    1. 引入es的RestHighLevelClient依赖:
    <dependency>    <groupId>org.elasticsearch.client</groupId>    <artifactId>elasticsearch-rest-high-level-client</artifactId></dependency>
    1. 因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:
    <properties>    <java.version>1.8</java.version>    <elasticsearch.version>7.12.1</elasticsearch.version></properties>
    1. 初始化RestHighLevelClient:
    RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(        HttpHost.create("http://192.168.150.101:9200")));
    1. 测试,创建一个测试类HotelIndexTest,然后将初始化的代码编写在@BeforeEach方法中:
    public class HotelIndexTest {    private RestHighLevelClient client;    @BeforeEach    void setUp() {        this.client = new RestHighLevelClient(RestClient.builder(        //xxx.xxx.xxx.xxx 虚拟主机的IP                HttpHost.create("http://xxx.xxx.xxx.xxx:9200")        ));    }    @AfterEach    void tearDown() throws IOException {        this.client.close();    }}

5.3 创建索引库

  • 代码分为三步:
    1. 创建Request对象。因为是创建索引库的操作,因此Request是CreateIndexRequest。
    2. 添加请求参数,其实就是DSL的JSON参数部分。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。
    3. 发送请求,client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。
  • 演示
//创建一个类,定义mapping映射的JSON字符串常量class HotelConstants {    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" +            "        \"copy_to\": \"all\"\n" +            "      },\n" +            "      \"starName\":{\n" +            "        \"type\": \"keyword\"\n" +            "      },\n" +            "      \"business\":{\n" +            "        \"type\": \"keyword\"\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" +            "}";}//在hotel-demo中的HotelIndexTest测试类中,编写单元测试,实现创建索引@Testvoid createHotelIndex() throws IOException {    // 1.创建Request对象    CreateIndexRequest request = new CreateIndexRequest("hotel");    // 2.准备请求的参数:DSL语句    request.source(MAPPING_TEMPLATE, XContentType.JSON);    // 3.发送请求    client.indices().create(request, RequestOptions.DEFAULT);}

5.4 删除索引库

  • 删除索引库的DSL语句非常简单:
    DELETE /hotel
  • 与创建索引库相比:
    • 请求方式从PUT变为DELTE
    • 请求路径不变
    • 无请求参数
  • 所以代码的差异,注意体现在Request对象上。依然是三步走:
    1. 创建Request对象。这次是DeleteIndexRequest对象
    2. 准备参数。这里是无参
    3. 发送请求。改用delete方法
  • 编写单元测试,实现删除索引:
    @Testvoid testDeleteHotelIndex() throws IOException {    // 1.创建Request对象    DeleteIndexRequest request = new DeleteIndexRequest("hotel");    // 2.发送请求    client.indices().delete(request, RequestOptions.DEFAULT);}

5.5 判断索引库存在

  • 判断索引库是否存在,本质就是查询,对应的DSL是:
    GET /hotel
  • 因此与删除的Java代码流程是类似的。依然是三步走:
    1. 创建Request对象。这次是GetIndexRequest对象
    2. 准备参数。这里是无参
    3. 发送请求。改用exists方法
    @Testvoid testExistsHotelIndex() throws IOException {    // 1.创建Request对象    GetIndexRequest request = new GetIndexRequest("hotel");    // 2.发送请求    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);    // 3.输出    System.err.println(exists " />

    • 导入酒店数据,基本流程一致,但是需要考虑几点变化:
      • 酒店数据来自于数据库,需要先查询出来,得到hotel对象
      • hotel对象需要转为HotelDoc对象
      • HotelDoc需要序列化为json格式
    • 因此,代码整体步骤如下:
      1. 根据id查询酒店数据Hotel
      2. 将Hotel封装为HotelDoc
      3. 将HotelDoc序列化为JSON
      4. 创建IndexRequest,指定索引库名和id
      5. 准备请求参数,也就是JSON文档
      6. 发送请求
      @Testvoid testAddDocument() throws IOException {    // 1.根据id查询酒店数据    Hotel hotel = hotelService.getById(61083L);    // 2.转换为文档类型    HotelDoc hotelDoc = new HotelDoc(hotel);    // 3.将HotelDoc转json    String json = JSON.toJSONString(hotelDoc);    // 1.准备Request对象    IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());    // 2.准备Json文档    request.source(json, XContentType.JSON);    // 3.发送请求    client.index(request, RequestOptions.DEFAULT);}
    • 测试运行结果:

    6.2 查询文档

    • 询的目的是得到结果,解析为HotelDoc,因此难点是结果的解析
    • 结果是一个JSON,其中文档放在一个_source属性中,因此解析就是拿到_source,反序列化为Java对象即可
    • 三步走:
      1. 准备Request对象。这次是查询,所以是GetRequest
      2. 发送请求,得到结果。因为是查询,这里调用client.get()方法
      3. 解析结果,就是对JSON做反序列化
    • 编写单元测试:
      @Testvoid testGetDocumentById() throws IOException {    // 1.准备Request    GetRequest request = new GetRequest("hotel", "61082");    // 2.发送请求,得到响应    GetResponse response = client.get(request, RequestOptions.DEFAULT);    // 3.解析响应结果    String json = response.getSourceAsString();    HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);    System.out.println(hotelDoc);}

    6.3 修改文档

    • 在RestClient的API中,全量修改与新增的API完全一致,判断依据是ID:

      • 如果新增时,ID已经存在,则修改
      • 如果新增时,ID不存在,则新增
    • Upsert:如果文档尚不存在,则可以使用以下upsert方法定义一些内容,这些内容将作为新文档插入

      String jsonString = "{\"created\":\"2017-01-01\"}";request.upsert(jsonString, XContentType.JSON);  
    • 全量修改:

      @Test    void testUpdateDocument() throws IOException {        // 1.准备Request        UpdateRequest request = new UpdateRequest("hotel", "61083");        // 2.准备请求参数        UpdateRequest doc = request.doc(                "price", "952",                "starName", "四钻"        );        request.upsert(doc, XContentType.JSON);        // 3.发送请求        client.update(request, RequestOptions.DEFAULT);    }


    • 增量修改
      /** * 增量修改 * @throws IOException */@Testvoid testUpdateDocument2() throws IOException {    // 1.准备Request    UpdateRequest request = new UpdateRequest("hotel", "61083");    // 2.准备请求参数    UpdateRequest doc = request.doc(            "price", "888",            "starName", "八钻"    );    // 3.发送请求    client.update(request, RequestOptions.DEFAULT);}

    6.4 删除文档

    • 三步走:
      1. 准备Request对象,因为是删除,这次是DeleteRequest对象。要指定索引库名和id
      2. 准备参数,无参
      3. 发送请求。因为是删除,所以是client.delete()方法
      @Testvoid testDeleteDocument() throws IOException {    // 1.准备Request    DeleteRequest request = new DeleteRequest("hotel", "61083");    // 2.发送请求    client.delete(request, RequestOptions.DEFAULT);}

    6.5 批量导入文档

    • 需求:利用BulkRequest批量将数据库数据导入到索引库中

    • 步骤如下:

      • 利用mybatis-plus查询酒店数据
      • 将查询到的酒店数据(Hotel)转换为文档类型数据(HotelDoc)
      • 利用JavaRestClient中的BulkRequest批处理,实现批量新增文档
    • 批量处理BulkRequest,其本质就是将多个普通的CRUD请求组合在一起发送。

      • 其中提供了一个add方法,用来添加其他请求:
    • 能添加的请求包括:

      • IndexRequest:新增
      • UpdateRequest:修改
      • DeleteRequest:删除
    • Bulk中添加了多个IndexRequest——批量新增功能

    • 编写单元测试

      @Testvoid testBulkRequest() throws IOException {    // 批量查询酒店数据    List<Hotel> hotels = hotelService.list();    // 1.创建Request    BulkRequest request = new BulkRequest();    // 2.准备参数,添加多个新增的Request    for (Hotel hotel : hotels) {        // 2.1.转换为文档类型HotelDoc        HotelDoc hotelDoc = new HotelDoc(hotel);        // 2.2.创建新增文档的Request对象        request.add(new IndexRequest("hotel")                    .id(hotelDoc.getId().toString())                    .source(JSON.toJSONString(hotelDoc), XContentType.JSON));    }    // 3.发送请求    client.bulk(request, RequestOptions.DEFAULT);}
    • 测试运行结果:

      1. 查询数据库的数据量
      2. 查询索引库的文档数量

    6.6 小结

    • 文档操作的基本步骤:
      • 初始化RestHighLevelClient
      • 创建XxxRequestXXXIndex、Get、Update、Delete、Bulk
      • 准备参数(Index、Update、Bulk时需要)
      • 发送请求。调用RestHighLevelClient#.xxx()方法,xxx:index、get、update、delete、bulk
      • 解析结果(Get时需要)