目录
4.安装es(部署单点es)、kibana(可视化客户端)、IK(分词器)、部署es集群
1.1 引入es的RestHighLevelClient依赖:
1.2 因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:
3.5 利用JavaRestClient批量导入酒店数据到ES
初识elasticsearch:
1.了解ES
1 elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域。
2 elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。
2.正向索引与倒排索引
1 传统数据库(如MySQL)采用正向索引
是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程。
- 优点:
- 可以给多个字段创建索引
- 根据索引字段搜索、排序速度非常快
- 缺点:
- 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。
id | title | price |
1 | 小米手机 | 3999 |
2 | 华为手机 | 4999 |
3 | 华为,小米充电器 | 109 |
4 | 小米手环 | 299 |
搜索手机(select * from tb_goods where title like '%手机%')--->逐条扫描判断是否包含‘手机’ ---->是(存入结果集) 否(舍弃)
2 elasticsearch采用倒排索引:(先找文档id再准确的找文档)
文档(document):每条数据就是一个文档
词条(term):文档按照语义分成的词语
是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。
- 优点:
- 根据词条搜索、模糊搜索时,速度非常快
- 缺点:
- 只能给词条创建索引,而不是字段
- 无法根据字段做排序
id | title | price |
1 | 小米手机 | 3999 |
2 | 华为手机 | 4999 |
3 | 华为,小米充电器 | 109 |
4 | 小米手环 | 299 |
词条(stem) | 文档id |
小米 | 1,3,4 |
手机 | 1,2 |
华为 | 2,3 |
充电器 | 3 |
手环 | 4 |
搜索“华为手机” ---->分词(得到 华为 手机 两个词条)---->去词条列表查询文档id(得到每个词条所在文档id 华为:2,3 手机:1,2)----->根据文档id查询文档(得到id为1,,2,3的文档)----->存入结果集
3.es的一些概念
mysql与elasticsearch概念对比
MySQL | Elasticsearch | 说明 |
Table | Index | 索引(index),就是文档的集合,类似数据库的表(table) |
Row | Document | 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 |
Column | Field | 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column) |
Schema | Mapping | Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema) |
SQL | DSL | DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD |
Mysql:擅长事务类型操作,可以确保数据的安全和一致性
Elasticsearch:擅长海量数据的搜索、分析、计算
4.安装es(部署单点es)、kibana(可视化客户端)、IK(分词器)、部署es集群
1.1.创建网络
因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络:
docker network create es-net
1.2.加载镜像
这里我们采用elasticsearch的7.12.1版本的镜像,这个镜像体积非常大,接近1G。不建议大家自己pull。
可以去官网提前下载
将其上传到虚拟机中,然后运行命令加载即可:
# 导入数据
docker load -i es.tar
1.3.运行
运行docker命令,部署单点es:
docker run -d \ 后台运行
--name es \ 容器名字
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ 环境变量 堆内存大小 默认1G 最低512M
-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 \ 暴露的http协议端口 用户访问的端口
-p 9300:9300 \ es各个容器各个节点相连的端口(单点用不到 可以不用暴露)
elasticsearch:7.12.1 镜像名称
访问: 服务ip:9200
启动成功
2.部署kibana
2.1.部署
运行docker命令,部署kibana
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \ 加入一个名为es-net的网络中,与elasticsearch在同一个网络中
-p 5601:5601 \ 端口映射配置
kibana:7.12.1
-e ELASTICSEARCH_HOSTS=http://es:9200 \ 设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch
kibana启动一般比较慢,需要多等待一会,可以通过命令:
docker logs -f kibana
查看运行日志,当查看到下面的日志,说明成功:
此时,在浏览器输入地址访问:http://ip:5601,即可看到结果
2.2.DevTools
kibana中提供了一个DevTools界面:
这个界面中可以编写DSL来操作elasticsearch。并且对DSL语句有自动补全功能。
3 扩展插件 IK(分词器)
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
3.2.离线安装ik插件(推荐)
3.2.1 查看数据卷目录
安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看:
docker volume inspect es-plugins
显示结果:
[
{
"CreatedAt": "2022-05-06T10:06:34+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
"Name": "es-plugins",
"Options": null,
"Scope": "local"
}
]
说明plugins目录被挂载到了:/var/lib/docker/volumes/es-plugins/_data
这个目录中。
3.2.2 解压缩分词器安装包
3.2.3 上传到es容器的插件数据卷中
也就是/var/lib/docker/volumes/es-plugins/_data 中
3.2.4 重启容器
docker restart es
# 查看es日志
docker logs -f es
3.2.5 测试
IK分词器包含两种模式:
ik_smart
:最少切分 (切分到词语后就不会在此词语的基础上再切分了)
ik_max_word
:最细切分
POST /_analyze
{
"analyzer": "ik_max_word",
"text": "小明"
}
结果:
{
"tokens" : [{
"token" : "小",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "明",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "太棒了",
"start_offset" : 11,
"end_offset" : 14,
"type" : "CN_WORD",
"position" : 6
},
{
"token" : "太棒",
"start_offset" : 11,
"end_offset" : 13,
"type" : "CN_WORD",
"position" : 7
},
{
"token" : "了",
"start_offset" : 13,
"end_offset" : 14,
"type" : "CN_CHAR",
"position" : 8
}
]
}
3.3 扩展词词典
3.3.1 打开IK分词器config目录:
3.3.2 在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>
</properties>
3.3.3 新建一个 ext.dic,可以参考config目录下复制一个配置文件进行修改
然后在名为ext.dic的文件中,添加想要拓展的词语即可 例如添加:小明 (小明就会被当成一个词)
小明
注意当前文件(ext.dic)的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑
3.3.4 重启elasticsearch
docker restart es
# 查看 日志
docker logs -f elasticsearch
日志中已经成功加载ext.dic配置文件
3.4 停用词词典
3.4.1 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>
</properties>
3.4.2 在 stopword.dic 添加停用词
了
了 就会被忽略
注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑
3.4.4 重启elasticsearch
# 重启服务
docker restart elasticsearch
docker restart kibana# 查看 日志
docker logs -f elasticsearch
日志中已经成功加载stopword.dic配置文件
4 部署es集群(详解见3/3章)
部署es集群可以直接使用docker-compose来完成,不过要求你的Linux虚拟机至少有4G的内存空间
首先编写一个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
运行docker compose以启动集群:
docker-compose up
索引库操作(index)
1 mapping属性
mapping是对索引库中文档的约束,常见的mapping属性包括:
type:字段数据类型,常见的简单类型有:
字符串:text(可分词的文本)、
keyword(精确值,例如:品牌、国家、ip地址)
数值:long、integer、short、byte、double、float、
布尔:boolean
日期:date
对象:object
geo_point (地理坐标数据类型 由纬度和经度确定的一个点)
geo_shape (地理坐标数据类型 有多个geo_point组成的复杂几何图形)
index:是否创建索引,默认为true
analyzer:使用哪种分词器
properties:该字段的子字段
2 创建索引库
{
"age": 21,
"weight": 52.1,
"isMarried": false,
"info": "测试字符串test类型",
"email": "zy@itcast.cn",
"score": [99.1, 99.5, 98.9],
"name": {
"firstName": "云",
"lastName": "赵"
}
}
按照上面json数据创建索引库
PUT /testIndexName
{
"mappings": {
"properties": {
"info":{
"type": "text",
"analyzer": "ik_smart"
},
"email":{
"type": "keyword",
"index": "false"
},
"name":{
"properties": {
"firstName": {
"type": "keyword"
}
}
},
// ... 略
}
}
}
2.1 _all 字段
_all字段是把所有其它字段中的值,以空格为分隔符组成一个大字符串,然后被分析和索引,但是不存储,也就是说它能被查询,但不能被取回显示。
_all能让你在不知道要查找的内容是属于哪个具体字段的情况下进行搜索,例如:
PUT my_index/user/1
{
"first_name": "John",
"last_name": "Smith",
"date_of_birth": "1970-10-24"
}GET my_index/_search
{
"query": { //查询 章节2详讲
"match": {
"_all": "john smith 1970"
}
}
}_all字段的内容为:”john smith 1970 10 24”
使用_all字段查询
query_string 和 simple_query_string 查询默认都是查询 _all 字段,除非指定了其它默认字段。
_all字段在查询时占用更多的CPU和占用更多的磁盘空间,如果确实不需要它可以完全的关闭它或者基于每字段定制。
2.2 copy_to属性定制合并字段
字段拷贝可以使用copy_to属性将当前字段拷贝到指定字段
实例:
"name":{
"type": "keyword",“copy_to”:“full_name”
},"email":{
"type": "keyword",“copy_to”:“full_name”
},"info":{
"type": "text",“copy_to”:“full_name”
},"full_name":{
"type": "text",
"analyzer": "ik_max_word"
}
copy_to
设置对multi-field无效。如果尝试这样配置映射,Elasticsearch 会抛异常
例:
PUT /my_index { "mappings": { "person": { "properties": { "first_name": { "type": "string", "copy_to": "full_name", "fields": { "raw": { "type": "string", "index": "not_analyzed" } } }, "full_name": { "type": "string" } } } } }copy_to是针对“主”字段,而不是多字段的
3 索引库其它语法:
查看索引库语法:
GET /索引库名
删除索引库的语法:
DELETE /索引库名
修改索引库(只能添加新字段)
索引库和mapping一旦创建无法修改,但是可以添加新的字段,语法如下:
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
文档操作:
1 新增文档
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
// 略...
}
2 查询文档
GET /索引库名/_doc/文档id
3 删除文档
DELETE /索引库名/_doc/文档id
4 修改文档
4.1 全量修改,会删除旧文档,添加新文档
PUT /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
4.2 增量修改,修改指定字段值
POST /索引库名/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
5 动态映射
当我们向ES中插入文档时,如果文档中字段没有对应的mapping,ES会帮助我们字段设置mapping,规则如下:
JSON类型 | Elasticsearch类型 |
字符串 | 日期格式字符串:mapping为date类型 普通字符串:mapping为text类型,并添加keyword类型子字段 |
布尔值 | boolean |
浮点数 | float |
整数 | long |
对象嵌套 | object,并添加properties |
数组 | 由数组中的第一个非空类型决定 |
空值 | 忽略 |
插入文档时,es会检查文档中的字段是否有mapping,如果没有则按照默认mapping规则来创建索引。
如果默认mapping规则不符合你的需求,一定要自己设置字段mapping
RestAPI操作索引库:
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址
1初始化JavaRestClient
1.1 引入es的RestHighLevelClient依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
1.2 因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
1.3 初始化RestHighLevelClient:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
2 利用JavaRestClient操作索引库:
索引库操作的基本步骤:
初始化RestHighLevelClient
创建XxxIndexRequest。XXX是Create、Get、Delete
准备DSL( Create时需要)
发送请求。调用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete
2.1创建索引库
@Test
void testCreateHotelIndex() throws IOException {
// 1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.请求参数,MAPPING_TEMPLATE是静态常量字符串,内容是创建索引库的DSL语句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.发起请求
client.indices().create(request, RequestOptions.DEFAULT);
}
2.3删除索引库
@Test
void testDeleteHotelIndex() throws IOException {
// 1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2.发起请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
2.4判断索引库是否存在
@Test
void testExistsHotelIndex() throws IOException {
// 1.创建Request对象
GetIndexRequest request = new GetIndexRequest("hotel");
// 2.发起请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
// 3.输出
System.out.println(exists);
}
3 利用JavaRestClient实现文档的CRUD
文档操作的基本步骤:
初始化RestHighLevelClient
创建XxxRequest。XXX是Index、Get、Update、Delete
准备参数(Index和Update时需要)
发送请求。调用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete
解析结果(Get时需要)
步骤1:初始化JavaRestClient
新建一个测试类,实现文档相关操作,并且完成JavaRestClient的初始化
public class ElasticsearchDocumentTest {
// 客户端
private RestHighLevelClient client;
@BeforeEach
void setUp() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
}
@AfterEach
void tearDown() throws IOException {
client.close();
}
}
3.1 新增文档
@Test
void testIndexDocument() throws IOException {
// 1.创建request对象
IndexRequest request = new IndexRequest("indexName").id("1");
// 2.准备JSON文档
request.source("{\"name\": \"Jack\", \"age\": 21}", XContentType.JSON);
// 3.发送请求
client.index(request, RequestOptions.DEFAULT);
}
3.2 查询文档
@Test
void testGetDocumentById() throws IOException {
// 1.创建request对象
GetRequest request = new GetRequest("indexName", "1");
// 2.发送请求,得到结果
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 3.解析结果
String json = response.getSourceAsString();
System.out.println(json);
}
根据id查询到的文档数据是json,可以用JSON工具栏反序列化为java对象
3.3修改文档
方式一:全量更新。再次新增id一样的文档,就会删除旧文档,添加新文档
方式二:局部更新。只更新部分字段,演示方式二
@Test
void testUpdateDocumentById() throws IOException {
// 1.创建request对象
UpdateRequest request = new UpdateRequest("indexName", "1");
// 2.准备参数,每2个参数为一对 key value
request.doc(
"age", 18,
"name", "Rose"
);
// 3.更新文档
client.update(request, RequestOptions.DEFAULT);
}
3.4 删除文档
@Test
void testDeleteDocumentById() throws IOException {
// 1.创建request对象
DeleteRequest request = new DeleteRequest("indexName", "1");
// 2.删除文档
client.delete(request, RequestOptions.DEFAULT);
}
3.5 利用JavaRestClient批量导入酒店数据到ES
利用JavaRestClient中的Bulk批处理,实现批量新增文档,示例代码如下
@Test
void testBulk() throws IOException {
// 1.创建Bulk请求
BulkRequest request = new BulkRequest();
// 2.添加要批量提交的请求:这里添加了两个新增文档的请求
request.add(new IndexRequest("hotel")
.id("101").source("json source", XContentType.JSON));
request.add(new IndexRequest("hotel")
.id("102").source("json source2", XContentType.JSON));
// 3.发起bulk请求
client.bulk(request, RequestOptions.DEFAULT);
}