【学习笔记】ElasticSearch7入门教程

it2025-05-23  8

教程链接: https://www.bilibili.com/video/BV17a4y1x7zq

序言

最近看了一篇关于全文搜索效率与安全性的paper, 因为对这块不是很熟悉, 一个字一个字的抠出来写了一篇几乎是全文翻译的大纲https://caoyang.blog.csdn.net/article/details/109139934, 结果发现依然不能搞得很明白, 在百度很多专业术语, 如倒排索引(inverted index), 最小桶聚合, 跨度查询等时, 搜索结果无一例外指向了ElasticSearch, 这事情说起来很讽刺, 半个月前学SpringBoot时恰好也学到了SpringBoot整合ElasticSearch(https://caoyang.blog.csdn.net/article/details/108740232), 结果自己的笔记里写了这么一段P话👇 ​​​​​​ 笔者出于加强对论文理解的角度, 决定把ElasticSearch入个门, 有幸观看了狂神说的B站教学视频, 他的主页是https://blog.csdn.net/qq_33369905/

其实大概之前也看过几个他的教程视频,今天才在他写自己的生日时知道他是97年的,只比我大了一岁,但是他的话里行间表现出的完全不像是只比我大一岁的见识,可以很清晰地从他的话里听出那种桀骜不驯,就和他的网名"狂神"一般,确实是个很有实力,而且履历很丰富的大佬,看他的博客似乎正准备自主创业,这种性格加上这种实力让我艳羡不已,狂神那种人生即便不能最终走向辉煌,也注定是毫无遗憾的精彩。

非常感觉狂神的教学视频, 很清晰易懂, 下文是笔者对其教学视频的摘要, 以备自查, 仅供分享;

PS:实战部分略过(20201021截至Lesson14), 以后真要用到再补, 感觉前面讲得很详细差不多够用了, 而且笔者的目的是以了解为主;


目录

序言Lesson1 ElasticSearch课程简介Lesson2 Lucene创始人 Doug CuttingLesson3 Elastic概述Lesson4 Elastic v.s. solrLesson5 ES安装及head插件安装Lesson6 kibana安装Lesson7 ES核心概念理解Lesson8 IK分词器详解Lesson9 Rest风格操作Lesson10&11 基本操作回顾 & 花式查询详解Lesson12 SpringBoot集成ES详解Lesson13 关于索引的API操作详解Lesson14 关于文档的API操作详解Lesson15 京东搜索: 项目搭建Lesson16 京东搜索: 爬取数据Lesson17 京东搜索: 业务编写Lesson18 京东搜索: 前后端交互Lesson19 京东搜索: 关键字高亮实现Lesson20 狂神聊ES小结

Lesson1 ElasticSearch课程简介

本教程基于ElasticSearch7.6.1, 注意ES7的语法与ES6的API调用差别很大, 教程发布时最新版本为ES7.6.2(20200401更新);

ES是用于全文搜索的工具:

SQL: 使用like %关键词%来进行模糊搜索在大数据情况下是非常慢的, 即便设置索引提升也有限;ElasticSearch: 搜索引擎(baidu, github, taobao)一些ES涉及的概念: 分词器 ikRestful操作ESCRUDSpringBoot集成ES

Lesson2 Lucene创始人 Doug Cutting

Lucene: java写成的为各种中小型应用软件加入全文检索功能;Nutch: 一个建立在Lucene核心之上的网页搜索应用程序, Nutch的应用比Lucene要更加广泛大数据解决存储与计算(MapReduce)两个问题: 2004年Doug Cutting基于GFS系统开发了分布式文件存储系统;2005年Doug Cutting基于MapReduce在Nutch搜索引擎实现了这种算法;加入Yahoo后, Doug Cutting将MapReduce和NDFS结合创建了Hadoop, 成为了Hadoop之父;Doug Cutting将BigTable集成到Hadoop中 回到主题: Lucene是一套信息检索工具包, jar包, 不包含搜索引擎系统;Lucene包含索引结构, 读写索引的工具, 排序, 搜索规则, 工具类;Lucene和ES的关系: ES是基于Lucene做了一些封装和增强, 上手是比较简单的, 比Redis要简单

Lesson3 Elastic概述

分布式的全文搜索引擎, 高扩展性;接近实时更新的查询搜索;ES是基于Restful的(即用get, post, delete, put来访问);ES进行复杂的数据分析, ELK技术(elastic+logstash+kibana)

Lesson4 Elastic v.s. solr

当使用索引时, solr会发生io阻塞, 查询性较差, elastic则在索引情况下的优势明显;elastic的效率在传统项目下一般有50倍的提升;elastic解压即可用, solr需要配置solr用zookeeper进行分布式管理, elastic自带分布式solr支持更多格式的数据, json, xml, csv, elastic只支持jsonsolr比elastic的功能更强大solr查询快, 但是更新索引时慢(如插入和删除慢), elastic查询慢, 但是实时性查询快, 用于facebook新浪等搜索solr是传统搜索应用的解决方案, elastic适用于新兴的实时搜索应用solr比较成熟, elastic目前更新换代快;

Lesson5 ES安装及head插件安装

要求jdk1.8以上, 这是最低要求;

elastic客户端, 界面工具;

springboot添加依赖时默认没有到ES7, 需要自己修改

下载链接: https://www.elastic.co/cn/downloads/elasticsearch

可以直接在windows或linux上学习, 而无需使用虚拟机或者其他配置;

elastic 7.x.x的解压目录:

bin: 启动文件config: 配置文件 log4j2: 日志配置jvm.options: java虚拟机相关配置elasticsearch.yml: ES配置文件, 默认端口是9200 lib: 相关jar包logs: 日志modules: 功能模块plugins: 插件(如ik) 启动, 访问9200端口 双击: bin/elasticsearch.bat访问: localhost:9200 可以看到如下json字符串{ "name" : "CAOYANG", "cluster_name" : "elasticsearch", "cluster_uuid" : "xEDZ4q2JSxq54mJM2UiTxQ", "version" : { "number" : "7.9.2", "build_flavor" : "default", "build_type" : "zip", "build_hash" : "d34da0ea4a966c4e49417f2da2f244e3e97b4e6e", "build_date" : "2020-09-23T00:45:33.626720Z", "build_snapshot" : false, "lucene_version" : "8.6.2", "minimum_wire_compatibility_version" : "6.8.0", "minimum_index_compatibility_version" : "6.0.0-beta1" }, "tagline" : "You Know, for Search" } 安装可视化插件 ES head: 下载地址: https://github.com/mobz/elasticsearch-head 安装步骤: git clone git://github.com/mobz/elasticsearch-head.git cd elasticsearch-head npm install npm run start open http://localhost:9100/ 连接测试: 访问http://localhost:9100/ 会发生跨域问题, 需要在ES解压目录下的bin/elasticsearch.yml中配置跨域信息, 在配置文件末尾添加: http.cors.enable: truehttp.cors.allow-origin: "*" 重新连接测试: 访问http://localhost:9100/ 出现可视化界面, 一些健康状况为显示在页面上 初学时, 可以把ES当作一个数据库, 可以建立索引(库), 文档(库中的数据)这个head插件可以当作一个数据展示工具, 后面所有的查询在kibana上运行

Lesson6 kibana安装

kibana是一个针对ES的开源分析及可视化界面, 用来搜索查看和交互存储在ES索引中的数据下载地址: https://www.elastic.co/cn/kibana下载完毕后, 解压需要一段时间, 是一个标准的工程 好处: ELK基本上都是拆箱即用;启动bin/kibana.bat, 然后访问localhost:5601即可在页面上可以直接在console里写查询语句执行, 查询结果直接显示在页面上 汉化: 直接在kibana.yml末尾添加: i18n.locale: "zh-CN", 即SpringBoot中的国际化

Lesson7 ES核心概念理解

elasticsearch是面向文档的, 索引和搜索数据的最小单位是文档, 与关系型数据库比较: 数据库(database) v.s. 索引(indices, 理解为数据库)表(tables) v.s. 类型(types, 7.x弃用, 8.x会完全丢掉)行(rows) v.s. 文档(documents)字段(columns) v.s. 字段(fields)ES所有数据都是json ES在后台把索引划分成多个切片, 每份分片可以在集群中不同服务器间迁移ES一个人就是一个集群, 默认的集群名称就是elasticsearchES文档(可以理解为json或是yml中的数据格式)的重要属性: 自我包含, 一篇文档同时包含字段和对应的值, 也就是同时包含keyvalue可以是层次型的, 一个文档中包含子文档, 复杂的逻辑实体, 就是一个json对象灵活的结构, 文档不依赖预先定义的模式, 我们知道关系型数据库中, 要提前定义字段才能使用, elasticsearch中, 对于字段是灵活的, 有时可以忽略该字段, 或是动态添加一个新的字段 ES索引: 就是数据库 ES索引是一个非常大的文档集合, 索引存储了映射类型的字段和其他设置, 然后存储到各个分片上, 一般是存在集群的不同节点上, 一旦某个节点挂了, 数据不会丢失;分片是一个Lucene索引, 一个包含==倒排索引(inverted index)==的文件目录, 倒排索引的结构使得elasticsearch在不扫描全部文档的情况下, 就可以告诉你哪些文档包含特定的关键字; 通过各个关键字的权重(可以理解为score)之和来对查询结果进行排序使用倒排索引可以过滤掉完全无关的数据 总结: ES中, 索引(库)这个词被频繁使用, 就是数据库, 索引被分为多个分片, 每份分片是一个Lucene的索引, 所以一个ES索引是由多个Lucene索引组成, Lucene索引是一种倒排索引;

Lesson8 IK分词器详解

分词: 类似python的jieba和nltkIK分词器下载地址: https://github.com/medcl/elasticsearch-analysis-ik-releases 注意与ES版本对应 下载完毕后直接解压即可, 然后移动到elasticsearch7.x.x里的plugins/的ik目录下 IK项目的README中写道:

拷贝和解压release下的文件: #{project_path}/elasticsearch-analysis-ik/target/releases/elasticsearch-analysis-ik-*.zip 到你的 elasticsearch 插件目录, 如: plugins/ik

注意别下载错了, 解压或里面应该是一个config文件夹, 1个properties文件, 1个policy文件, 还有5个jar包 配置好后重启ES, 可以看到ik分词器被加载了; 可以通过elasticsearch-plugin来查看加载的插件(把ES目录的bin文件夹添加到Path里) 开始使用IK, 在kibana的界面控制台里写: 查看的不同的分词效果: GET _analyze { "analyzer": "ik_smart", "text": "中国人民军队" } GET _analyze { "analyzer": "ik_max_word", "text": "中国人民军队" } + ```ik_smart```为最少切分, 返回结果只有包含中国人民军队的结果, 就是尽可能少的切分; + ```ik_max_word```为最细粒度划分, 穷尽词库的可能, 如会划分中国, 人民, 军队, 及这三个分词; + 如果搜索```超级喜欢狂神说```, 会发现即使用最少切分, 狂神说三个字都被分开了; + 所以需要配置用户字典: 配置文件路径在ik/config/IKAnalyzer.cfg.xml * 可以新建一个kuang.dic文件, 然后在IKAnalyzer.cfg.xml中添加配置 - ```<entry key="ext_dict">kuang.dic</entry>``` - ```<entry key="ext_stopwords">kuang_stop.dic</entry>```

Lesson9 Rest风格操作

PUT: 创建文档(指定文档id), localhost:9200/索引名称/类型名称/文档ID 示例:PUT /test1/type1/1 { "name": "狂神说", "age": 3 } 命令执行返回结果: 完成了自动增加索引, 数据也成功添加了{ _index: test1, _type: type1, _id: 1, _score: 1, name: "狂神说", age: 3 } 数据类型: 字符串类型: text和keyword数值类型: long, integer, short, byte, double, float, half float, scaled float日期类型: datete布尔值类型: boolean二进制类型: binary 指定字段类型: 添加一个库并添加字段规则(类似mysql建表)PUT /test2 { "mappings": { "properties": { "name": { "type": "text" }, "age": { "type": "long" }, "birthday": { "type": "date" } } } } 返回结果发现acknowledged是true, 说明规则创建成功 POST: 创建文档(随机文档id), localhost:9200/索引名称/类型名称POST: 修改文档, localhost:9200/索引名称/文档id/_update 示例:POST /test3/_doc/1/_update { "doc": { "name": "法外狂徒张三" } } 则索引test3中的_doc类型里的name值被更新 注意如果不创建文档则默认是_doc DELETE: 删除文档, localhost:9200/索引名称/类型名称/文档id DELETE test1: 删除索引通过请求来判断是删除索引还是删除文档记录 GET: 查询文档(通过文档id), localhost:9200/索引名称/类型名称/文档id 获得索引表的规则: GET test2 GET 表就是拿到表的信息, GET 索引就是拿到索引的信息 GET _cat/health: 查看健康信息GET _cat/indices?v: 索引状态ge _cat可以获得es的当前的很多信息 POST: 查询所有数据, localhost:9200/索引名称/类型名称/_search

Lesson10&11 基本操作回顾 & 花式查询详解

关于文档的基本操作(重点!)

基本操作

PUT /kuangshen/user/1 { "name": "xxx", "age": 12, "desc": "xxx", "tags": {"a","b","c"} } kuangshen就是_index, user就是_type, 1就是_id简单搜索GET kuangshen/user/1: 取出kuangshen库中的user表里的id为1的文档结果; 有个_version字段, 表明被更新了几次可以添加条件来搜索: GET kuangshen/user/_search?q=name:狂神说Java, 注意这个是精确搜索, 少一些(比如搜索"狂神说")就找不到"狂神说Java"之前提到字符串有keyword和text的区别, keyword是不会被分词处理, text会被分词处理 POST kuangshen/user/1/_update {"doc":{"name": "xxxx"}}: 更新数据, 注意更新的数据放在doc键下, 是一个字典格式的, 一次可以同时更新多个字段

复杂操作: select(排序, 分页, 高亮, 模糊查询, 精准查询)

hits字段下是所有查询结果, 如果存在多条查询出来的结果, 则每个结果有_score值返回, 指匹配度, 会降序列出;

例1: 查询参数体(一个json对象), 把name字段包含"狂神"的结果都搜索出来

GET kuangshen/user/_search { "query": { "match": { "name": "狂神" } }, "_source": ["name","desc"], // 结果过滤, 只返回name和desc字段 "sort": [ //排序 { "age": { order: "asc" } } ], // 分页参数: 总第from个数据开始, 返回多少条数据(当前页面) "from": 0, "size": 2 }

例2: 多条件查询, 通过bool值字段

bool下的must命令, 下面的所有条件都要符合(and)bool下的should命令, 下面的所有条件都要符合(or)bool下的must_not命令, 下面的所有条件都不能符合bool下的filter字段, 过滤条件, 包括range下的lte, lt, gt, gte字段为大于小于等等match字段是包含这个字符串的结果都会被搜索出来 GET kuangshen/user/_search { "query": { "bool": { "must": [ { "match": { "name": "狂神说" } }, { "match": { "age": 23 } } ], "should": [ { "match": 13 }, { "match": { "age": 12 } } ], "filter": { // 过滤条件 "range": { "age": { "gte": 10, "lt": 25 } } } } } }

例3: 匹配多个条件

GET kuangshen/user/_search { "query": { "match": { "tags": "a b" // 会把tag字段包含(指match)"a"和"b"的都拿出来 } } } 多个条件用空格隔开, 只要满足一个结果就被查出, 会有得分结果返回, 得分越高匹配度越高

精确查询!

term查询就是直接通过倒排索引指定的词条进程精确查找的

关于分词:

term, 直接查询精确的, 把上面例子中的match换成term就是精确而非包含的查询match, 会使用分词器解析(先分析文档, 然后再通过分析的文档进行查询)text字符串会被分词解析, keyword则不会被分词解析

多个值匹配的精确查询: bool.should + term

高亮:

GET kuangshen/user/_search { "query": { "match": { "tags": "a b" // 会把tag字段包含(指match)"a"和"b"的都拿出来 } }, "highlight": { // 搜索结果高亮name "pre_tags": "<p class='key' style='color:red'>", "post_tags": "</p>", // 自定义高亮的tag "fields": { "name": {} } } }

Lesson12 SpringBoot集成ES详解

官方文档: https://www.elastic.co/guide/index.html 找到 Java Rest Client 里的高级客户端① 引入原生依赖: sts4直接选nosql里的elasticsearch就行了, 但是有可能默认是ES6, 要改成ES7的依赖才行;<dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-level-client</artifactId> <version>7.6.2</version> </dependency> ② 初始化 创建: 可以参数里是多个new(表示多个集群, 一般本地测试一个就行了) RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost("localhost",9200,"http"), new HttpHost("localhost",9201,"http"))); 关闭: client.close(); ③ ES的配置文件:@Configuration public class ElasticSearchConfig { @Bean public RestHighLevelClient restHighLevelClient() { RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost("127.0.0.1",9200,"http"), )); return client; } } ④ 测试文件:@SpringBootTest class KuangshenEsApiApplicationTests { @Autowired private RestHighLevelClient restHighLevelClient; @Test void contextLoads() { } }

Lesson13 关于索引的API操作详解

测试文件(续) 例1: 直接运行西面的测试代码, ES中创建的一个新的空索引kuang_index, 然后判断存在. 最后删除@SpringBootTest class KuangshenEsApiApplicationTests { @Autowired @Qualifier("restHighLevelClient") private RestHighLevelClient client; @Test void testCreateIndex() { // 测试索引创建 // 1. 创建索引请求 CreateIndexRequest request = new CreateIndexRequest("kuang_index"); // 2. 客户端执行请求 CreateIndexResponse createIndexResponse = client.indices().create(request,RequestOptions.DEFAULT); System.out.println(createIndexResponse); } @Test void testExistIndex() { // 测试获取索引 GetIndexRequest request = new GetIndexRequest("kuang_index"); boolean exists = client.indices.exist(request,RequestOptions.DEFAULT); System.out.println(exists); } @Test void testDeleteIndex() { // 测试删除索引 DeleteIndexRequest request = new DeleteIndexRequest("kuang_index"); AcknowledgedResponse delete = client.indices.delete(request,RequestOptions.DEFAULT); System.out.println(delete.isAcknowledged()); } }

Lesson14 关于文档的API操作详解

先创一个用户BEAN类, name和age两个字段

继续编写测试文件(汗)

例1: 测试添加文档

@SpringBootTest class KuangshenEsApiApplicationTests { @Autowired @Qualifier("restHighLevelClient") private RestHighLevelClient client; // 测试添加文档 @Test void testAddDocument() { // 创建对象 User user = new User("狂神说",3); // 创建请求 IndexRequest request = new IndexRequest("kuang_index"); // 规则 put /kuang_index/_doc/1 request.id("1"); request.timeout(TimeValue.timeValueSeconds(1)); request.timeout("1s"); // 将我们的数据放入请求 json (核心本质!!!) request.source(JSON.toJSONString(user),XContentType.JSON); // 客户端发送请求, 获取响应的结果 IndexResponse indexResponse = client.index(request,RequestOptions.DEFAULT); System.out.pringln(indexResponse.toString()); System.out.pringln(indexResponse.status()); // 对应命令返回的状态 CREATED } }

例2: 测试获取文档, 判断是否存在

@Test void testExistsDocument() throws IOException { GetRequest getRequest = new GetRequest("kuang_index","1"); // 不获取返回的_source的上下文: 不必要 getRequest.fetchSourceContext(new FetchSourceContext(false)); getRequest.storedFields("_none_"); boolean exists = client.exists(getRequest,RequestOptions.DEFAULT); System.out.pringln(exists); }

例3: 测试获取文档信息

@Test void testGetDocument() throws IOException { GetRequest getRequest = new GetRequest("kuang_index","1"); GetResponse getResponse = client.get("getRequest",RequestOptions.DEFAULT); System.out.pringln(getResponse.getSourceAsString()); System.out.pringln(getResponse); // 返回的全部内容与命令式一样 }

例4: 测试更新文档信息

@Test void testUpdateDocument() throws IOException { UpdateRequest updateRequest = new UpdateRequest("kuang_index","1"); updateRequest.timeout("1s"); User user = new User("狂神说Java",18) updateRequest.doc(JSON.toJSONString(user),XContentType.JSON); UpdateResponse update = client.update(updateRequest,RequestOptions.DEFAULT); System.out.pringln(updateRequest.status()); }

例5: 测试删除文档信息

@Test void testDeleteDocument() throws IOException { DeleteRequest request = new DeleteRequest("kuang_index",3); deleteRequest.timeout("1s"); DeleteResponse deleteResponse = client.delete(request,RequestOptions.DEFAULT); System.out.pringln(deleteResponse.status()); }

例6: 特殊的, 真的项目一般会批量插入数据

@Test void testBulkRequest() throws IOException { BulkRequest bulkRequest = new BulkRequest(); bulkRequest.timeout("10s"); ArrayList<User> userList = new ArrayList<>(); userList.add(new User("kuangshen1",3)); userList.add(new User("kuangshen2",3)); userList.add(new User("kuangshen3",3)); userList.add(new User("kuangshen4",3)); userList.add(new User("kuangshen5",3)); userList.add(new User("kuangshen6",3)); // 批处理请求 for (int i = 0; i<userList.size(); i++) { bulkRequest.add( new IndexRequest("kuang_index").id(""+(i+1)).source(JSON.toJSONString(userList.get(i),XContentType.JSON)); ); } BulkResponse bulkResponse = client.bulk(bulkRequest,RequestOptions.DEFAULT); System.out.pringln(bulkResponse.hasFailures()); // 是否失败, 返回false表示成功 }

例7: 查询

SearchRequest 搜索请求SearchSourceBuilder 条件构造HighlightBuilder 构造高亮TermQueryBuilder 构造精确查询MatchQueryBuilderxxxQueryBuilder 对应上面非SpringBoot部分看到的那些控制台的命令 @Test void testSearch() throws IOException { SearchRequest searchRequest = new SearchRequest(kuang_index); // 构建搜索条件 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //sourceBuilder.highligher(); 高亮 // 查询条件, 可以用QueryBuilders工具实现 // QueryBuilders.termQuery 精确 // QueryBuilders.matchAllQuery() 匹配所有 TermQueryBuilder termQueryBuilder = QueryBuilder.termQuery("name","kuangshen1") // MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); sourceBuilder.query(termQueryBuilder); sourceBuilder.timeout(new TimeValue(60,TimeUnit.SECONDS)); SearchResponse searchResponse = searchRequest.source(sourceBuilder,RequestOptions.DEFAULT); System.out.pringln(JSON.toJSONString(searchRequest.getHits())); // 记得那个hit键了吗? for (SearchHit documentFields : (searchRequest.getHits())) { System.out.pringln(documentFields.getSourceAsMap()); } }

Lesson15 京东搜索: 项目搭建

NOTIMPLEMENT ERROR


Lesson16 京东搜索: 爬取数据

NOTIMPLEMENT ERROR


Lesson17 京东搜索: 业务编写

NOTIMPLEMENT ERROR


Lesson18 京东搜索: 前后端交互

NOTIMPLEMENT ERROR


Lesson19 京东搜索: 关键字高亮实现

NOTIMPLEMENT ERROR


Lesson20 狂神聊ES小结

NOTIMPLEMENT ERROR


END

最新回复(0)