1.1 初识ElasticSearch | 《ElasticSearch入门到实战》电子书 (chaosopen.cn)
目录
第一章 入门
1.1 ElasticSearch需求背景
1.2 ElasticSearch 和关系型数据库的对比
1.3 基础概念
文档和字段
索引和映射
第二章 索引操作
2.0 Mapping映射属性
2.1 创建索引
DSL语法
Java API
2.2 删除索引
DSL语法
Java API
2.3 判断索引存在
DSL语法
Java API
2.4 开启/关闭索引
关闭索引
DSL语法
Java API
开启索引
DSL语法
Java API
2.5 索引别名
添加别名
DSL语法
Java API
删除别名
DSL语法
Java API
切换别名
DSL语法
Java API语法
查看别名
DSL语法
Java API语法
第三章 映射操作
3.1 查看映射
DSL语法
Java API
3.2 新增映射
DSL语法
Java API
第四章 文档数据操作(crud)
4.1 单条插入文档
4.2 批量插入文档
4.3 通过ID查询文档
4.4 条件查询文档
4.5 单条更新文档
4.6 批量更新文档
4.7 条件更新文档
4.8 单条删除文档
4.9 条件删除文档
第五章 字段类型介绍
5.1 索引方式
正向索引
倒排索引
第六章 分词操作
6.1 IK分词器
6.2 拓展词典
第一章 入门
ElasticSearch结合Kibana、Logstash、Beats,是一整套技术栈,被叫做ELK,因此下载时版本要对应一致。
1.1 ElasticSearch需求背景
假设一个电商项目,商品的搜索肯定是访问频率最高的页面之一。目前搜索功能是基于数据库的模糊搜索来实现的,存在很多问题。
首先,查询效率较低。
由于数据库模糊查询不走索引,在数据量较大的时候,查询性能很差。
需要注意的是,数据库模糊查询随着表数据量的增多,查询性能的下降会非常明显,而搜索引擎的性能则不会随着数据增多而下降太多。目前仅10万不到的数据量差距就如此明显,如果数据量达到百万、千万、甚至上亿级别,这个性能差距会非常夸张。
其次,功能单一
数据库的模糊搜索功能单一,匹配条件非常苛刻,必须恰好包含用户搜索的关键字。而在搜索引擎中,用户输入出现个别错字,或者用拼音搜索、同义词搜索都能正确匹配到数据。
综上,在面临海量数据的搜索,或者有一些复杂搜索需求的时候,推荐使用专门的搜索引擎来实现搜索功能。
1.2 ElasticSearch 和关系型数据库的对比
在Elasticsearch 7.x中,Type的概念已经被删除了,就是一个索引下面只能有一种类型。
1.3 基础概念
文档和字段
对标数据库,行为文档,列为字段 。
elasticsearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json
格式后存储在elasticsearch
中:
索引和映射
对标数据库,表为索引,索引中文档的字段约束为映射。
随着业务发展,需要在es中存储的文档也会越来越多,比如有商品的文档、用户的文档、订单文档等等:
所有文档都散乱存放显然非常混乱,也不方便管理。
因此,我们要将类型相同的文档集中在一起管理,称为索引(Index)。例如:
第二章 索引操作
ES索引是存储数据的容器,类似于数据库的表。
2.0 Mapping映射属性
Mapping是对索引库中文档的约束,常见的Mapping属性包括:
-
type
:字段数据类型,常见的简单类型有:-
字符串:
text
(可分词的文本)、keyword
(精确值,例如:品牌、国家、ip地址) -
数值:
long
、integer
、short
、byte
、double
、float
、 -
布尔:
boolean
-
日期:
date
-
对象:
object
-
-
index
:是否创建索引,默认为true
-
analyzer
:使用哪种分词器 -
properties
:该字段的子字段
2.1 创建索引
DSL语法
PUT indexname
{"settings": {"number_of_shards": 1,"number_of_replicas": 1},"mappings": {"properties": {"name1":{"type": "text"},"name2":{"type": "integer"}}}
}
参数说明:
settings:索引信息设置
number_of_shards:每个索引的主分片数,这个配置在索引创建后不能修改
number_of_replicas:每个主分片的副本数,这个配置可以随时修改。
mappings:索引映射定义
properties:字段定义 properties里是json配置,key为字段名称(自定义名称),value是个嵌套json,
type
是指定字段的类型。
Java API
//从Spring容器获取client对象@Autowiredprivate RestHighLevelClient client;@RequestMapping("/createIndex")public Boolean createIndex(String indexName) {//创建索引请求类,构造函数参数为索引名称CreateIndexRequest request = new CreateIndexRequest(indexName);//设置source映射字符串,直接把语句复制里面request.source("{\n" +" \"settings\": {\n" +" \"number_of_shards\": 1,\n" +" \"number_of_replicas\": 1\n" +" },\n" +" \"mappings\": {\n" +" \"properties\": {\n" +" \"name1\":{\n" +" \"type\": \"text\"\n" +" },\n" +" \"name2\":{\n" +" \"type\": \"integer\"\n" +" }\n" +" }\n" +" }\n" +"}",XContentType.JSON);try {//调用创建索引语法client.indices().create(request, RequestOptions.DEFAULT);return true;} catch (IOException e) {e.printStackTrace();}return false;}
2.2 删除索引
DSL语法
DELETE indexname
Java API
//从Spring容器获取client对象@Autowiredprivate RestHighLevelClient client;@RequestMapping("/deleteIndex")public Boolean deleteIndex(String indexName) {//删除索引请求类,构造函数参数为索引名称DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName);try {//调用删除索引语法client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);return true;} catch (IOException e) {e.printStackTrace();}return false;}
2.3 判断索引存在
DSL语法
HEAD indexname
如果索引存在,服务器将返回200状态码;如果索引不存在,服务器将返回404状态码。
200 - OK
Java API
@Autowiredprivate RestHighLevelClient client;@RequestMapping("/existsIndex")public Boolean existsIndex(String indexName) {GetIndexRequest request = new GetIndexRequest(indexName);try {return client.indices().exists(request, RequestOptions.DEFAULT);} catch (IOException e) {e.printStackTrace();}return false;}
2.4 开启/关闭索引
什么是 Elasticsearch 打开/关闭索引?
一旦索引被关闭,那么这个索引只能显示元数据信息,不能够进行读写操作。 再说说打开索引就好理解了。就是打开被关闭的索引,允许进行读写操作。
关闭索引
DSL语法
POST indexname/_close
调用执行,以下返回结果为成功
{ - "acknowledged": true,"shards_acknowledged": true,"indices": { - "indexname": { - "closed": true}}
}
Java API
@RequestMapping("/closeIndex")public Boolean closeIndex(String indexName) {CloseIndexRequest closeIndexRequest = new CloseIndexRequest(indexName);try {client.indices().close(closeIndexRequest, RequestOptions.DEFAULT);} catch (IOException e) {e.printStackTrace();}return true;}
开启索引
DSL语法
POST indexname/_open
调用执行,以下返回结果为成功
{ - "acknowledged": true,"shards_acknowledged": true
}
Java API
@RequestMapping("/openIndex")public Boolean openIndex(String indexName) {OpenIndexRequest openIndexRequest = new OpenIndexRequest(indexName);try {client.indices().open(openIndexRequest, RequestOptions.DEFAULT);} catch (IOException e) {e.printStackTrace();}return true;}
2.5 索引别名
索引别名概述:
给多个索引设置一个别名,可以使用这个别名同时查询多个索引的数据。
例如一个项目场景,每天要建立一个新的索引,程序要查询新的索引数据,程序必然要改变访问的索引名称,如果实现用户无感知切换,那么代码复杂度较高,很容易会对服务的使用者产生一定的影响,过程越复杂,BUG就越容易出现。
那么有了别名后,可以一开始就给索引加上这个别名,程序只关注访问这个别名,建立新索引后,把别名切换到新索引,程序不用变更,但是后续访问到了新的索引,并且无需停止应用的运行。当然这只是一个场景,在项目开发中,别名还有很多的用途,在后续项目讲解中我们会更多的介绍索引。
添加别名
创建索引别名,将别名
indexname_alias
与索引indexname
关联。
DSL语法
- 请求方式1
PUT indexname/_alias/indexname_alias
2.请求方式2
POST _aliases
{"actions": [{"add": {"index": "indexname","alias": "indexname_alias"}}]
}
Java API
@Autowiredprivate RestHighLevelClient client;@RequestMapping("/addAlias")public Boolean addAlias(String indexName, String aliasName) {IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest();IndicesAliasesRequest.AliasActions aliasActions = new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.ADD);aliasActions.index(indexName).alias(aliasName);indicesAliasesRequest.addAliasAction(aliasActions);try {client.indices().updateAliases(indicesAliasesRequest, RequestOptions.DEFAULT);} catch (IOException e) {e.printStackTrace();}return true;}
删除别名
删除索引别名:解除别名
indexname_alias
与索引indexname
的关联。
DSL语法
- 请求方式1
DELETE indexname/_alias/indexname_alias
2.请求方式2
POST _aliases
{"actions": [{"remove": {"index": "indexname","alias": "indexname_alias"}}]
}
Java API
@Autowiredprivate RestHighLevelClient client;@RequestMapping("/removeAlias")public Boolean removeAlias(String indexName, String aliasName) {IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest();IndicesAliasesRequest.AliasActions aliasActions = new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.REMOVE);aliasActions.index(indexName).alias(aliasName);indicesAliasesRequest.addAliasAction(aliasActions);try {client.indices().updateAliases(indicesAliasesRequest, RequestOptions.DEFAULT);} catch (IOException e) {e.printStackTrace();}return true;}
切换别名
切换一个别名是在同一个API中执行添加、删除操作。
DSL语法
POST _aliases
{"actions": [{"add": {"index": "indexname1","alias": "indexname_alias"}},{"remove": {"index": "indexname2","alias": "indexname_alias"}}]
}
Java API语法
@Autowiredprivate RestHighLevelClient client;@RequestMapping("/changeAlias")public Boolean changeAlias() {String aliasName = "indexname_alias";IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest();IndicesAliasesRequest.AliasActions addAliasActions = new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.ADD);addAliasActions.index("indexname1").alias(aliasName);IndicesAliasesRequest.AliasActions removeAliasActions = new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.REMOVE);removeAliasActions.index("indexname2").alias(aliasName);indicesAliasesRequest.addAliasAction(addAliasActions);indicesAliasesRequest.addAliasAction(removeAliasActions);try {client.indices().updateAliases(indicesAliasesRequest, RequestOptions.DEFAULT);} catch (IOException e) {e.printStackTrace();}return true;}
查看别名
在 more
里选择 aliases
看所有索引的别名情况,这里也可以修改,功能非常的全面。
DSL语法
- 通过别名查询索引
GET _alias/indexname_alias
根据返回结果所示,indexname
索引下有这个别名
{ - "indexname": { - "aliases": { - "indexname_alias": { - }}}
}
2.通过索引查询别名
GET indexname/_alias
3.查看别名是否存在索引中
GET indexname/_alias/indexname_alias
Java API语法
1.通过别名查询索引
@Autowiredprivate RestHighLevelClient client;@RequestMapping("/selectIndexByAlias")public Map selectIndexByAlias(String aliasName) {GetAliasesRequest getAliasesRequest = new GetAliasesRequest(aliasName);try {GetAliasesResponse response = client.indices().getAlias(getAliasesRequest,RequestOptions.DEFAULT);Map<String, Set<AliasMetadata>> aliases;aliases = response.getAliases();return aliases;} catch (IOException e) {e.printStackTrace();}return null;}
2.通过索引查询别名
@Autowiredprivate RestHighLevelClient client;@RequestMapping("/selectAliasByIndex")public Map selectAliasByIndex(String indexName) {GetAliasesRequest getAliasesRequest = new GetAliasesRequest();// 指定查看某一个索引的别名 不指定,则会搜索所有的别名getAliasesRequest.indices(indexName);try {GetAliasesResponse response = client.indices().getAlias(getAliasesRequest,RequestOptions.DEFAULT);Map<String, Set<AliasMetadata>> aliases;aliases = response.getAliases();return aliases;} catch (IOException e) {e.printStackTrace();}return null;}
3.查看别名是否存在索引中
@Autowiredprivate RestHighLevelClient client;@RequestMapping("/getAliasExist")public Boolean getAliasExist(String indexName, String aliasName) {GetAliasesRequest getAliasesRequest = new GetAliasesRequest(aliasName);getAliasesRequest.indices(indexName);try {return client.indices().existsAlias(getAliasesRequest, RequestOptions.DEFAULT);} catch (IOException e) {e.printStackTrace();}return false;}
第三章 映射操作
映射信息会有索引的字段信息。
3.1 查看映射
新建索引后,后续开发中会需要看下有哪些字段,那么我们可以在客户端工具查看,也可以用命令查看。
1.在客户端中,选择 overview
找到要查看的索引,点击索引名,出现下拉框,选择 show mappings
就可以看到索引映射的字段信息了
DSL语法
GET indexname/_mapping
请求返回结果如下:
{ - "indexname": { - "mappings": { - "properties": { - "name1": { - "type": "text"},"name2": { - "type": "integer"}}}}
}
返回的信息和建立该索引时的信息是一致的。
Java API
@Autowiredprivate RestHighLevelClient client;@RequestMapping("/getMapping")public Map getMapping(String indexName) throws IOException {GetMappingsRequest request = new GetMappingsRequest();request.indices(indexName);GetMappingsResponse mappingsResponse = client.indices().getMapping(request, RequestOptions.DEFAULT);Map<String, MappingMetadata> allMappings = mappingsResponse.mappings();MappingMetadata indexMapping = allMappings.get(indexName);Map<String, Object> mapping = indexMapping.sourceAsMap();return mapping;}
3.2 新增映射
映射一般情况下是创建索引的时候就已经指定好的,但是在实际开发情况下,我们需要新增字段,那么就需要新增一个字段映射,需要注意的是字段映射只能增加,不能更改删除。
DSL语法
POST indexname/_mapping
{"properties":{"name3":{"type":"keyword"}}
}
新增字段 name3
类型设置 keyword
返回以下结果说明成功:
{ - "acknowledged": true
}
查询索引映射,返回结果如下:
{ - "indexname": { - "mappings": { - "properties": { - "name1": { - "type": "text"},"name2": { - "type": "integer"},"name3": { - "type": "keyword"}}}}
}
从返回结果可见,新字段映射已经添加成功。
Java API
@Autowiredprivate RestHighLevelClient client;@RequestMapping("/addMapping")public Boolean addMapping(String indexName) {PutMappingRequest request = new PutMappingRequest(indexName);request.source("{\n" +" \"properties\":{\n" +" \"name3\":{\n" +" \"type\":\"keyword\"\n" +" }\n" +" }\n" +"}", XContentType.JSON);try {client.indices().putMapping(request, RequestOptions.DEFAULT);return true;} catch (IOException e) {e.printStackTrace();}return false;}
第四章 文档数据操作(crud)
本章节我们讲对于ES数据的基本操作。
先创建一个索引,便于后续演示
PUT index_operation
{"settings": {"number_of_shards": 1,"number_of_replicas": 1},"mappings": {"properties": {"name":{"type": "keyword"},"age":{"type": "integer"},"description":{"type": "text"}}}
}
4.1 单条插入文档
指定ID插入数据时,ES会先拿着指定的id去对比一遍所有数据,如果没有新增,有则覆盖。
DSL语法
PUT index_operation/_doc/1
{"name":"张三","age":18,"description":"一个学习ES的学生"
}
添加一条指定ID为1的数据
ID可以手动指定,也可以自动生成
随机ID插入数据如下所示:
POST index_operation/_doc
{"name":"李四","age":21,"description":"一个学习Java的学生"
}
返回以下结果说明成功
{ - "_index": "index_operation","_type": "_doc","_id": "TKc8a4sBNugPcqcoa9uX","_version": 1,"result": "created","_shards": { - "total": 2,"successful": 1,"failed": 0},"_seq_no": 1,"_primary_term": 1
}
我们发现ID变成了TKc8a4sBNugPcqcoa9uX
,这是ES自动生产的ID
Java API
@Autowiredprivate RestHighLevelClient client;@RequestMapping("/addDoc")public Boolean addDoc() throws IOException {IndexRequest request = new IndexRequest().index("index_operation");// 指定ID,也可以不指定随机生成request.id("21");// 这里用Map,也可以创建Java对象操作HashMap<String, Object> map = new HashMap<>();map.put("name", "张三");map.put("age", 18);map.put("description", "一个学习ES的学生");// map转换json字符串request.source(JSON.toJSONString(map), XContentType.JSON);// 请求esclient.index(request, RequestOptions.DEFAULT);return true;}
4.2 批量插入文档
一般情况是用程序读取数据库执行插入,手动插入情况比较少见。
DSL语法
POST _bulk
{"create":{"_index":"index_operation","_id":4}}
{"name":"张三4","age":18,"description":"测试4"}
{"create":{"_index":"index_operation","_id":5}}
{"name":"张三5","age":18,"description":"测试5"}
Java API
@Autowiredprivate RestHighLevelClient client;@RequestMapping("/addBatchDoc")public Boolean addBatchDoc() throws IOException {// 批量插入数据BulkRequest request = new BulkRequest();// 生成10条数据for (int i = 0; i < 10; i++) {// 创建对象HashMap<String, Object> map = new HashMap<>();map.put("name", "张三");map.put("age", 18 + i);map.put("description", "一个学习ES的学生");// 添加文档数据IndexRequest source = new IndexRequest().index("index_operation");source.source(JSON.toJSONString(map), XContentType.JSON);request.add(source);}// 请求esclient.bulk(request, RequestOptions.DEFAULT);return true;}
4.3 通过ID查询文档
4.4 条件查询文档
4.5 单条更新文档
4.6 批量更新文档
4.7 条件更新文档
4.8 单条删除文档
4.9 条件删除文档
第五章 字段类型介绍
5.1 索引方式
正向索引
正排索引是以文档的ID作为关键字,并且记录文档中每个字段的值信息,通过查询id来把整条文档拿出来。
但是在查询某一个keyword存在于哪些文档的时候, 需要对所有文档进行扫描匹配。这样检索效率比较低下。
倒排索引
第六章 分词操作
6.1 IK分词器
IK分词器包含两种模式:
-
ik_smart
:智能语义切分 -
ik_max_word
:最细粒度切分
我们在Kibana的DevTools上来测试分词器,首先测试Elasticsearch官方提供的标准分词器:
POST /_analyze
{"analyzer": "standard","text": "黑马程序员学习java太棒了"
}
结果如下:
{"tokens" : [{"token" : "黑","start_offset" : 0,"end_offset" : 1,"type" : "<IDEOGRAPHIC>","position" : 0},{"token" : "马","start_offset" : 1,"end_offset" : 2,"type" : "<IDEOGRAPHIC>","position" : 1},{"token" : "程","start_offset" : 2,"end_offset" : 3,"type" : "<IDEOGRAPHIC>","position" : 2},{"token" : "序","start_offset" : 3,"end_offset" : 4,"type" : "<IDEOGRAPHIC>","position" : 3},{"token" : "员","start_offset" : 4,"end_offset" : 5,"type" : "<IDEOGRAPHIC>","position" : 4},{"token" : "学","start_offset" : 5,"end_offset" : 6,"type" : "<IDEOGRAPHIC>","position" : 5},{"token" : "习","start_offset" : 6,"end_offset" : 7,"type" : "<IDEOGRAPHIC>","position" : 6},{"token" : "java","start_offset" : 7,"end_offset" : 11,"type" : "<ALPHANUM>","position" : 7},{"token" : "太","start_offset" : 11,"end_offset" : 12,"type" : "<IDEOGRAPHIC>","position" : 8},{"token" : "棒","start_offset" : 12,"end_offset" : 13,"type" : "<IDEOGRAPHIC>","position" : 9},{"token" : "了","start_offset" : 13,"end_offset" : 14,"type" : "<IDEOGRAPHIC>","position" : 10}]
}
可以看到,标准分词器智能1字1词条,无法正确对中文做分词。
我们再测试IK分词器:
POST /_analyze
{"analyzer": "ik_smart","text": "黑马程序员学习java太棒了"
}
执行结果如下:
{"tokens" : [{"token" : "黑马","start_offset" : 0,"end_offset" : 2,"type" : "CN_WORD","position" : 0},{"token" : "程序员","start_offset" : 2,"end_offset" : 5,"type" : "CN_WORD","position" : 1},{"token" : "学习","start_offset" : 5,"end_offset" : 7,"type" : "CN_WORD","position" : 2},{"token" : "java","start_offset" : 7,"end_offset" : 11,"type" : "ENGLISH","position" : 3},{"token" : "太棒了","start_offset" : 11,"end_offset" : 14,"type" : "CN_WORD","position" : 4}]
}
6.2 拓展词典
随着互联网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如:“泰裤辣”,“传智播客” 等。
IK分词器无法对这些词汇分词,测试一下:
POST /_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" : 3,"end_offset" : 4,"type" : "CN_CHAR","position" : 3},{"token" : "开设","start_offset" : 4,"end_offset" : 6,"type" : "CN_WORD","position" : 4},{"token" : "大学","start_offset" : 6,"end_offset" : 8,"type" : "CN_WORD","position" : 5},{"token" : "真的","start_offset" : 9,"end_offset" : 11,"type" : "CN_WORD","position" : 6},{"token" : "泰","start_offset" : 11,"end_offset" : 12,"type" : "CN_CHAR","position" : 7},{"token" : "裤","start_offset" : 12,"end_offset" : 13,"type" : "CN_CHAR","position" : 8},{"token" : "辣","start_offset" : 13,"end_offset" : 14,"type" : "CN_CHAR","position" : 9}]
}
可以看到,传智播客
和泰裤辣
都无法正确分词。
所以要想正确分词,IK分词器的词库也需要不断的更新,IK分词器提供了扩展词汇的功能。
1)打开IK分词器config目录:
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)在IK分词器的config目录新建一个 ext.dic
,可以参考config目录下复制一个配置文件进行修改
传智播客
泰裤辣
4)重启elasticsearch
docker restart es# 查看 日志
docker logs -f elasticsearch