Elasticsearch(一)
Elasticsearch搜索引擎的黑马学习笔记
原文:day08-Elasticsearch - 飞书云文档 (feishu.cn)
一、引入
Elasticsearch是由elastic公司开发的一套搜索引擎技术,它是elastic技术栈中的一部分。
整套技术栈的核心就是用来存储、搜索、计算的Elasticsearch,因此我们接下来学习的核心也是Elasticsearch。
Kibana提供了一个开发控制台(DevTools),在其中对Elasticsearch的Restful的API接口提供了语法提示
所以我们需要安装Elasticsearch以及Kibana
1、安装Elasticsearch
1 2 3 4 5 6 7 8 9 10 11
| 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 hm-net \ -p 9200:9200 \ -p 9300:9300 \ elasticsearch:7.12.1
|
打开控制台,查看Elasticsearch基本信息
![1]()
2、安装Kibana
1 2 3 4 5 6
| docker run -d \ --name kibana \ -e ELASTICSEARCH_HOSTS=http://es:9200 \ --network=hm-net \ -p 5601:5601 \ kibana:7.12.1
|
进入Kibana的DevTools工具, 执行**Get /**指令,也可以查看Elasticsearch基本信息
![2]()
3、倒排索引
Elasticsearch之所以有如此高性能的搜索表现,正是得益于底层的倒排索引技术。
倒排索引中有两个非常重要的概念:
创建倒排索引:
-
将文档数据利用分词算法分词一个个的词条
-
创建表,每行数据包含词条、包含此词条的文档id、位置等等信息
-
由于词条唯一,可以创建正向索引
| 词条(索引) |
文档id |
| 小米 |
1,3,4 |
| 手机 |
1,2 |
| 华为 |
2,3 |
| 充电器 |
3 |
| 手环 |
4 |
![3]()
正向索引
优点:
-
可以给多个字段创建索引
-
有索引的字段搜索特别快
缺点:
-
非索引字段搜索特别慢
倒排索引
优点:
-
由于是根据词条搜索,进行模糊搜索的时候,特别快
缺点
-
只能根据词条创建索引,字段不行
-
无法根据字段排序
4、基础概念
文档:原本数据库数据表中的一行数据,在Elasticsearch中就一个文档。
字段:数据表中某一列,就是某个文档的字段
索引:同类型的文档放在一起,就叫做索引(Index)
映射:作为索引表中字段约束信息。类似表的结构约束
![4]()
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 |
5、IK分词器
对文档内容的分词,需要优秀的分词算法,IK分词器就是一种高效,准确的中文分词算法。
-
创建倒排索引时,对文档分词
-
用户搜索时,对输入的内容分词
在线安装:
1
| docker exec -it es ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
|
离线安装:
之前将elasticsearch的插件挂载到了/var/lib/docker/volumes/es-plugins/_data。我们将下载下来的IK分词器上传到这个目录即可。
安装完后都需要重启ES
1、使用
可以知道官方的分词器对中文的分词支持不好。我们可以试试IK分词器
-
ik_smart:智能切分,粗粒度
-
ik_max_word:最细切分,细粒度
1 2 3 4 5
| POST /_analyze { "analyzer": "ik_smart", "text": "黑马程序员学习java太棒了" }
|
结果非常好。不展示了。
2、拓展
再厉害的分词算法,也抵不过热梗的出现。蔡徐坤就没办法识别。会被分成3块。
IK分词器有拓展分词功能。
1、我们打开虚拟机目录
![5]()
2、点击IKAnalyzer.cfg.xml配置分词器词典
![6]()
3、在词典写下分好的词
![7]()
二、索引库操作
Mapping映射常见类型
-
type:字段数据类型,常见的简单类型有:
- 字符串:
text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
- 数值:
long、integer、short、byte、double、float、
- 布尔:
boolean
- 日期:
date
- 对象:
object
-
index:是否创建索引,默认为true
-
analyzer:使用哪种分词器
-
properties:该字段的子字段
1、创建索引库和映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| PUT /sy { "mappings": { "properties": { "info":{ "type": "text", "analyzer": "ik_smart" }, "email":{ "type": "keyword", "index": "false" }, "name":{ "properties": { "firstName": { "type": "keyword" } } } } } }
|
2、查询索引库
得到索引库的一些信息。
3、修改索引库
由于重新创建倒排索引非常麻烦。因此索引库一旦创建,无法修改mapping。但是可以增加新的字段到mapping中
1 2 3 4 5 6 7 8
| PUT /sy/_mapping { "properties": { "age":{ "type": "integer" } } }
|
4、删除索引库
三、文档操作
1、新增文档
向索引库中添加文档
1 2 3 4 5 6 7 8 9
| POST /sy/_doc/1 { "info": "黑马程序员Java讲师", "email": "zy@itcast.cn", "name": { "firstName": "云", "lastName": "赵" } }
|
这样的话就在sy索引中添加了一个id为1的文档。文档中的内容需要符合mapping格式
2、查询文档
3、删除文档
4、修改文档
1.全量修改 先删除再添加
1 2 3 4 5 6 7 8 9
| PUT /sy/_doc/1 { "info": "黑马程序员高级Java讲师", "email": "zy@itcast.cn", "name": { "firstName": "云", "lastName": "赵" } }
|
2.局部修改
1 2 3 4 5 6
| POST /sy/_update/1 { "doc": { "email": "ZhaoYun@itcast.cn" } }
|
只修改email字段
5、批处理
1 2 3 4 5 6 7 8
| POST _bulk { "index" : { "_index" : "test", "_id" : "1" } } { "field1" : "value1" } { "delete" : { "_index" : "test", "_id" : "2" } } { "create" : { "_index" : "test", "_id" : "3" } } { "field1" : "value3" } { "update" : {"_id" : "1", "_index" : "test"} } { "doc" : {"field2" : "value2"} }
|
_index:指定索引库名
_id:指定要操作的文档id
-
index代表新增操作
{ "field1" : "value1" }:则是要新增的文档内容
-
delete代表删除操作
-
update代表更新操作
{ "doc" : {"field2" : "value2"} }:要更新的文档字段
批量新增:
1 2 3 4 5
| POST /_bulk {"index": {"_index":"heima", "_id": "3"}} {"info": "黑马程序员C++讲师", "email": "ww@itcast.cn", "name":{"firstName": "五", "lastName":"王"}} {"index": {"_index":"heima", "_id": "4"}} {"info": "黑马程序员前端讲师", "email": "zhangsan@itcast.cn", "name":{"firstName": "三", "lastName":"张"}}
|
批量删除:
1 2 3
| POST /_bulk {"delete":{"_index":"heima", "_id": "3"}} {"delete":{"_index":"heima", "_id": "4"}}
|
四、RestAPI
1、客户端操作ES
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。https://www.elastic.co/guide/en/elasticsearch/client/index.html
可以用UI操作ES
2、Java代码操作ES
1 2 3 4
| <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> </dependency>
|
因为SpringBoot默认的ES版本是7.17.10,所以我们需要覆盖默认的ES版本:
1 2 3 4 5
| <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <elasticsearch.version>7.12.1</elasticsearch.version> </properties>
|
初始化RestHighLevelClient
1 2 3
| RestHighLevelClient client = new RestHighLevelClient(RestClient.builder( HttpHost.create("http://192.168.150.101:9200") ));
|
创建一个测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class IndexTest {
private RestHighLevelClient client;
@BeforeEach void setUp() { this.client = new RestHighLevelClient(RestClient.builder( HttpHost.create("http://192.168.150.101:9200") )); }
@Test void testConnect() { System.out.println(client); }
@AfterEach void tearDown() throws IOException { this.client.close(); } }
|
3、创建索引库
不是一股脑将Mysql的字段变成Mapping映射。我们先应该分析哪些需要搜索。
创建索引API
-
创建Request参数
因为是创建索引库的操作,因此Request是CreateIndexRequest
-
添加请求参数
- 其实就是Json格式的Mapping映射参数。因为json字符串很长,这里是定义了静态字符串常量
MAPPING_TEMPLATE,让代码看起来更加优雅。
-
发送请求
client.``indices``()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。例如创建索引、删除索引、判断索引是否存在等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| @Test void testCreateIndex() throws IOException { CreateIndexRequest request = new CreateIndexRequest("items"); request.source(MAPPING_TEMPLATE, XContentType.JSON); client.indices().create(request, RequestOptions.DEFAULT); }
private static final String MAPPING_TEMPLATE = "{\n" + " \"mappings\": {\n" + " \"properties\": {\n" + " \"id\":{\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"name\":{\n" + " \"type\": \"text\"\n" + " , \"analyzer\": \"ik_smart\"\n" + " },\n" + " \"price\":{\n" + " \"type\":\"integer\"\n" + " },\n" + " \"image\":{\n" + " \"type\": \"keyword\"\n" + " ,\"index\": false\n" + " },\n" + " \"category\":{\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"brand\":{\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"sold\":{\n" + " \"type\": \"integer\"\n" + " },\n" + " \"commentCount\":{\n" + " \"type\":\"integer\"\n" + " , \"index\": false\n" + " \n" + " },\n" + " \"isAD\":{\n" + " \"type\": \"boolean\"\n" + " },\n" + " \"updateTime\":{\n" + " \"type\":\"date\"\n" + " }\n" + " }\n" + " }\n" + "}";
|
4、删除索引库
1 2 3 4 5 6 7
| @Test void testDeleteIndex() throws IOException { DeleteIndexRequest request = new DeleteIndexRequest("items"); client.indices().delete(request, RequestOptions.DEFAULT); }
|
5、判断索引库是否存在
1 2 3 4 5 6 7 8 9
| @Test void testExistsIndex() throws IOException { GetIndexRequest request = new GetIndexRequest("items"); boolean exists = client.indices().exists(request, RequestOptions.DEFAULT); System.err.println(exists ? "索引库已经存在!" : "索引库不存在!"); }
|
索引库操作的基本步骤:
-
初始化RestHighLevelClient
-
创建XxxIndexRequest。XXX是Create、Get、Delete
-
准备请求参数( Create时需要,其它是无参,可以省略)
-
发送请求。调用**RestHighLevelClient#indices().xxx()**方法,xxx是create、exists、delete
五、RestClient
1、新增文档
-
首先要创建文档doc对应的实体类
-
查询数据库得到数据库实体类,将实体类转为doc对应的实体类
-
将doc对应的实体类转为JSON,插入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Test void testIndexDoc() throws IOException { Item item = itemService.getById(1533902L); ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
IndexRequest request = new IndexRequest("items").id(itemDoc.getId()); request.source(JSONUtil.toJsonStr(itemDoc),XContentType.JSON);
client.index(request,RequestOptions.DEFAULT); }
|
2、查询文档
1 2 3 4 5 6 7 8 9 10 11 12
| @Test void testGetDocumentById() throws IOException { GetRequest request = new GetRequest("items").id("100002644680"); GetResponse response = client.get(request, RequestOptions.DEFAULT); String json = response.getSourceAsString(); ItemDoc itemDoc = JSONUtil.toBean(json, ItemDoc.class); System.out.println("itemDoc= " + ItemDoc); }
|
3、删除文档
1 2 3 4 5 6 7 8
| @Test void testDeleteDoc() throws IOException {
DeleteRequest request = new DeleteRequest("items", "1533902"); client.delete(request,RequestOptions.DEFAULT); }
|
4、修改文档
1 2 3 4 5 6 7 8 9 10 11 12
| @Test void testUpdateDoc() throws IOException {
UpdateRequest request = new UpdateRequest("items", "1533902"); request.doc( "price",25600 ); client.update(request,RequestOptions.DEFAULT); }
|
5、批量导入
-
创建Request,用BulkRequest
-
准备请求参数
-
发送请求,用client.bulk()
BulkRequest本身没有请求参数,本质是将多个普通的CRUD请求组合在一起发送。因此BulkRequest中有add方法可以添加文档的CRUD请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Test void testBulkDoc() throws IOException {
int pageNo = 1,pageSize=500; while (true){ Page<Item> page = itemService.lambdaQuery() .eq(Item::getStatus, 1) .page(Page.of(pageNo, pageSize));
List<Item> records = page.getRecords(); if(records==null || records.isEmpty()){ return; } BulkRequest request = new BulkRequest();
for (Item item : records) {
ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
request.add(new IndexRequest("items") .id(item.getId().toString()) .source(JSONUtil.toJsonStr(itemDoc),XContentType.JSON)); }
client.bulk(request,RequestOptions.DEFAULT);
pageNo++;
} }
|
文档操作的基本步骤: