教程链接: 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), 以后真要用到再补, 感觉前面讲得很详细差不多够用了, 而且笔者的目的是以了解为主;
本教程基于ElasticSearch7.6.1, 注意ES7的语法与ES6的API调用差别很大, 教程发布时最新版本为ES7.6.2(20200401更新);
ES是用于全文搜索的工具:
SQL: 使用like %关键词%来进行模糊搜索在大数据情况下是非常慢的, 即便设置索引提升也有限;ElasticSearch: 搜索引擎(baidu, github, taobao)一些ES涉及的概念: 分词器 ikRestful操作ESCRUDSpringBoot集成ES要求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上运行拷贝和解压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>```基本操作
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": {} } } }先创一个用户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()); } }NOTIMPLEMENT ERROR
NOTIMPLEMENT ERROR
NOTIMPLEMENT ERROR
NOTIMPLEMENT ERROR
NOTIMPLEMENT ERROR
NOTIMPLEMENT ERROR
END