搜索引擎数据库—ElasticSearch

打印 上一主题 下一主题

主题 1058|帖子 1058|积分 3174

简介

ELK技术:elasticsearch + logstash + kibana
ELK基础

ELK 是什么

ELK 是 Elasticsearch、Logstash、 Kibana 三大开源框架首字母大写简称。其中 Elasticsearch 是一个基于 Lucene、分布式、通过 Restful方式进行交互的近及时搜索平台框架。 像雷同百度、谷歌这种大数据全文搜索引擎的场景都可以使用 Elasticsearch 作为底层支持框架。
Logstash 是 ELK 的中心数据流引擎,用于从差别目标(文件/数据存储/MQ )网络的差别格式数据,经过过滤后支持输出到差别目标地(文件/MQ/redis/elasticsearch/kafka等)。
Kibana可以将 elasticsearch 的数据通过友好的页面展示出来 ,提供及时分析的功能。
一般以为 ELK 是一个日记分析架构技术栈总称,但实际上 ELK 不但仅实用于日记分析,它还可以支持其它任何数据分析和网络的场景。
即 Logstash 网络数据,ES 过滤数据,Kibana 共同展示数据。
Elaticsearch

从前我们查数据查信息,是用的 SQL,然后用的是 like %搜索关键字%,但如果是大数据的场景下,就会很慢,然后我们就会去创建索引以提高查询速率,但是 %搜索关键字%,左侧的 百分号 会使得索引失效。这时候就需要搜索效率较高的分布式的全文搜索引擎 ElasticSearch。ES 也可以来当数据库,但是不建议。
Lucene 是 Doug Cutting 的开源的 Java 全文检索项目,是一个 jar包,引入就可以直接使用。Hadoop 也是 Doug Cutting 的。
Elaticsearch,简称为es,是一个开源的高扩展的分布式全文检索引擎,它可以近乎及时地存储、检索数据,本身扩展性很好,可以扩展到上百台服务器,处理 PB级别(大数据期间)的数据。
ElasticSearch 是基于 Lucene 做了封装和加强,es 也使用 Java 开发并使用 Lucene 作为其核心来实现全部索引和搜索的功能,它的目标是通过简单的 RESTful API 来隐藏 Lucene 的复杂性,让全文搜索变得简单。
Solr 和 ES 对比

简介

ES:全文搜索、布局化搜索、分析。
Solr 也是 Apache下的项目,也是使用 Java 开发的,也是基于 Lucene。
对比



  • 单纯对已有的数据进行搜索时,即已经存在于服务器内里了,Solr 更快。
  • 当及时创建索引时,ES 快,Solr 会产生 IO阻塞,查询性能较差。
  • 随着数据量的增加,Solr 的搜索效率会变低,而 ES 几乎没有显着变化。
总结



  • es 根本是开箱即用,非常简单。Solr安装略微复杂
  • Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能。
  • Solr 支持更多格式的数据,比如 JSON、XML、CSV,而 ES 仅支持 json 文件格式。
  • Solr 官方提供的功能更多,而 Elasticsearch 本身更注意于核心功能,高级功能多有第三方插件提供,比方图形化界面需要 kibana 友好支持
  • Solr 查询快,但更新索引时慢(即插入、删除慢),用于电商等查询多的应用

    • ES 创建索引快(即查询慢),即及时性查询快,用于 facebook、新浪等搜索。
    • Solr 是传统搜索应用的有力办理方案,但 ES 更实用于新兴的及时搜索应用。

  • Solr 比较成熟,有一个更大、更成熟的用户、开发和贡献者社区,而 ES 相对开发维护者较少,更新太快,学习使用成本较高。
3、ES 的下载安装和插件的下载

3.1、ES 下载

官网:Elastic — The Search AI Company | Elastic
进官网下载压缩包即可,
3.1.1、Windows 下安装和启动

解压到指定目录即可使用,
运行 bin/elasticsearch.bat 即可启动
在 bin目录下 cmd,输入 elasticsearch-plugin list,可以检察已加载的插件
3.1.1.1、目录布局

  1. bin    启动文件
  2. config    配置文件
  3.    log4j2.properties    日志配置文件
  4.    jvm.options    虚拟机需求配置,若内存较小一定要修改 -Xms,比如 -Xms256m
  5.    elasticsearch.yml    es 的配置文件,默认 9200端口,通信地址 9300
  6. lib    相关 jar包
  7. modules    功能模块
  8. plugins    插件,比如把 ik分词器放进去
复制代码
3.2、插件下载

3.2.1、head

地址:https://github.com/mobz/elasticsearch-head/
下载完之后如果下的是压缩文件就解压到指定目录,然后进入文件夹,进行 cmd。(首先需要 node 环境)
  1. npm install (或 cnpm install)
  2. npm run start
复制代码
3.2.1.1、使用

这个是 ES 的可视化图形界面工具,地址是 9100,但是由于端口不一样,以是访问 9200 会产生跨域问题。
以是需要在 elasticsearch.yml 中进行设置,办理跨域,加上以下这段:
  1. http.cors.enabled: true
  2. http.cors.allow-origin: "*"
复制代码
4、Kibana下载安装

4.1、简介

Kibana是一个针对Elasticsearch的开源分析及 可视化平台,用来搜索、检察交互存储在Elasticsearch索弓 |中的数据。使用Kibana ,
可以通过各种图表进行高级数据分析及展示。Kibana让海 量数据更容易理解。它操纵简单,基于欣赏器的用户界面可以快速创建仪
表板( dashboard )及时显示Elasticsearch查询动态。设置Kibana非常简单。无需编码或者额外的基础架构,几分钟内就可以完成
Kibana安装并启动Elasticsearch索引监测。
4.2、下载安装

官网:https://www.elasticsearch.co/cn/kibana
下载到压缩包之后解压到指定目录,运行 bin/kibana.bat 即可启动。端口在 5601。
汉化的设置文件在 x-pack\plugins\translations\translations 内里。
使用汉化需要在 config/kibana.yml 的末了加上汉化设置:
  1. i18n.locale: "zh-CN"
复制代码
5、ES核心概念

5.1、简介

ES 是面向文档的,齐备都是 json,
Relation DB(关系型数据库)ElasticSearch数据库(database)索引(index)表(table)types(已过时)行(row)document字段(column)field 5.2、设计



  • 物理设计:ES 在背景把每个索引划分成多个分片,每片分片可以在集群中的差别服务器间迁移。一个人就是一个集群,默认的集群名称就是 elasticsearch
  • 逻辑设计:一个索引类型中可以有多个文档,
5.3、索引、类型、文档

索引就是数据库,
类型就分类,比如食品类,工具类等,
文档就是一条具体数据,文档是 json,是 k:v 情势的,可以是层次型的,
一个集群至少一个节点,一个节点就是一个 es进程,如果创建索引,那么索引将会有 5个分片组成(primary shard,又称主分片)构成,每个主分片会有一个副本(replica shard,又称复制分片)。
主分片对应的复制分片都不会在同一个节点内,作用利于某个节点挂掉后数据不会丢失。
一个分片是一个 Lucene索引,一个包罗倒排索引的文件目录。倒排索引的布局使得 ES 在不扫描全部文档的环境下,就能告诉你哪些文档包罗特定的关键字。
即一个 ES索引实在底层是由多个 Lucene索引组成的,Lucene 使用倒排索引,就可以不使用 like关键字了,而是使用额外的空间存储倒排索引,即用空间换时间。
5.3.1、倒排索引

ES 使用的布局是倒排索引,接纳 Lucene倒排索引作为底层,这种布局实用于全文搜索,一个索引由文档中全部不重复的列表构成,对于每一个词,都有一个包罗它的文档列表。
倒排索引是实现“单词-文档矩阵”的一种具体存储情势,通过倒排索引,可以根据单词快速获取包罗这个单词的文档列表。倒排索引主要由两个部分组成:“单词词典”和“倒排文件”。
单词-文档矩阵就是一个矩阵,列是文档,行是单词,然后若文档包罗此单词,就进行标记。然后根据指定的单词数组进行匹配,匹配水平越高的代表权重越大,在 ES 中就是 _score。

5.4、数据类型



  • 字符串类型:text、keyword(text 支持分词,keyword 不支持)
  • 数值类型:long、integer、short、byte、double、float、half float、scaled float
  • 日期类型:date
  • 布尔值类型:boolean
  • 二进制类型:binary
6、IK分词器

6.1、简介

分词:即把一段文本划分成一个个关键字,然后进匹配操纵,中文的话会默认把每个字都划分成一个词。
IK分词器提供了两个分词算法:ik_smart 和 ik_max_word,其中 ik_smart 是最少划分,ik_max_word 是最细粒度划分。
6.2、下载

官网:https://github.com/medcl/elasticsearch-analysis-ik
下载完毕后解压到 ES文件夹的 plugins文件夹,然后把 ik 这个文件夹的名称改成 ik,名字可以不是 ik,需要本身去查询相应知识。
6.3、kibana 中简单使用分词器

ik_smart 是将一段话打断点划分掉,每个词不会有重复使用的地方,比如:中国共产党。
而 ik_max_word 是举例全部大概,比如:中国共产党,中国,国共,共产党,共产,党。
但偶然候大概有想要归并在一起进行查询的词被划分掉了,这时候就需要我们本身把词参加到分词器的字典中。可以创建一个 dic文件放到 elasticsearch-7.6.1\plugins\ik\config\ 目录下,然后再在 IKAnalyzer.cfg.xml 中的 <entry key="ext_stopwords"></entry> 内里将我们新建的文件全名放进去。如果有多个,就写多个 entry。
  1. GET _analyze
  2. {
  3.   "analyzer": "ik_smart",
  4.   "text": "中国共产党"
  5. }
  6. GET _analyze
  7. {
  8.  "analyzer": "ik_max_word",
  9.  "text": "中国共产党"
  10. }
复制代码
7、Rest风格操纵


7.1、PUT 创建、更新

  1. // 创建指定索引库指定类型指定文档id 的文档
  2. // 对同一个文档 PUT 不同数据也可以去修改,就是覆盖了,
  3. // 但是如果你少写了一个字段,就相当于传了 null 过去,所以推荐使用 _update
  4. PUT /index1/type1/1
  5. {
  6.   "name": "chw",
  7.   "age": 15
  8. }
  9. // 创建指定索引库,设置其规则
  10. PUT /index1
  11. {
  12.   "mappings": {
  13.     "properties": {
  14.       "name": {
  15.         "type": "text"
  16.       },
  17.       "age": {
  18.         "type": "long"
  19.       },
  20.       "birthday": {
  21.         "type": "date"
  22.       }
  23.     }
  24.   }
  25. }
  26. // 创建指定索引库指定类型指定文档id 的文档
  27. PUT /index2/type1/1
  28. {
  29.  "name": "chw",
  30.  "age": 15
  31. }
  32. // _doc 就是默认的类型,可以写出来,也可以不写,type 已经过时不用了
  33. // ES 会默认识别数据类型,然后使用最匹配的数据类型
  34. PUT /index3/_doc/1
  35. {
  36.  "name": "chw",
  37.  "age": 15,
  38.  "birthday": "1997-01-05",
  39.  "tags": ["二刺螈","直男"]
  40. }
复制代码
7.2、GET 查询

  1. // 查询对应索引库的基本信息
  2. GET index2(或者 /index2)
  3. // 查询 ES 的信息
  4. GET _cat/health
  5. GET _cat/indices?v
复制代码
7.3、POST 更新

  1. POST /index3/_doc/1/_update
  2. {
  3.  "doc": {
  4.    "name": "修改后的名字"
  5.   }
  6. }
复制代码
7.4、DELETE删除

  1. DELETE index1
复制代码
8、花式查询

  1. GET index3/type1/_search?q=name:chw
  2. GET /ringnex-dev*/container_log/_search
  3. {
  4.  "query": {
  5.     "bool": {
  6.       "must": [
  7.         {
  8.           "range": {
  9.               "log_time": {
  10.                 "gte": "now-14d/d",
  11.                 "lte": "now-13d/d"
  12.               }
  13.             }
  14.         },
  15.         
  16.         {
  17.           "match": {
  18.            "log_level": "INFO"
  19.           }
  20.         },
  21.         {
  22.           "query_string": {
  23.             "default_field": "log",
  24.             "query": "系统日志 AND 83c401c012e34892a73e13fd6bab88b1.436925.16517091000101945"
  25.           }
  26.         }
  27.       ]
  28.     }
  29.  },
  30.  "_source": ["log", "log_level", "log_time"],
  31.  "from": 0,
  32.  "size": 5,
  33.  "sort": [
  34.    {
  35.      "log_time": {
  36.        "order": "asc"
  37.      }
  38.    }
  39.  ]
  40. }
复制代码
8.1、模糊匹配

_score 就是匹配度,匹配度越高,则分值越高。hits是查询结果数组,就是索引和文档的信息,即每一个 {} 就是一个文档。
_source 就是根据 query 的结果来选择想要的字段,过滤结果,就相当于 SQL语句的 SELECT。
sort 就是排序,在内里指定想排序的字段,然后在字段中指定 order 是 desc/asc。
分页就通过 from和size 两个关键字指定,即开始和需要数量。下标从 0 开始。
匹配多个条件就通过空格分隔即可,比如:"tags": "男 技术 旅游",多个条件之间只要能匹配上 1个,就可以被查询出来,匹配水平越高,_score的分值越高。
  1. GET /ringnex-dev-202204*/container_log/_search
  2. {
  3.  "query": {
  4.    "match": {
  5.      "log": "系统日志"
  6.    }
  7.   },
  8.  "_source": ["name","age","desc"],
  9.  "sort": [
  10.    {
  11.      "age": {
  12.        "order": "asc"
  13.      }
  14.    }
  15.   ],
  16.  "from": 0,
  17.  "size": 2
  18. }
复制代码
bool就是布尔查询,must 可以进行多 match 操纵,即多条件查询,and 连接。 如果将 must 改成 should,那就变成了 or 连接。
not 就是 must_not。
可以在 bool 中加过滤器 filter,进行数据过滤。
  1. GET /ringnex-dev-202204*/container_log/_search
  2. {
  3.  "query": {
  4.    "bool": {
  5.      "must": [
  6.        {
  7.          "match": {
  8.            "name": "chw"
  9.          }
  10.        },
  11.        {
  12.          "match": {
  13.            "age": "18"
  14.          }
  15.        },
  16.      ],
  17.      "filter": {
  18.        "range": {
  19.          "age": {
  20.            "gte": 10,
  21.            "lte": 20
  22.          }
  23.        }
  24.      }
  25.    }
  26.   }
  27. }
复制代码
8.2、精确匹配

term 查询是直接通过 倒排索引 指定的词条进行精确查找的。效率比 match 高。
match 则会使用 分词器解析,先分析文档,再通过分析过的文档进行查询。
两个类型:text、keyword。text 可以被分词器解析,而 keyword 是不能被分词器解析的。
还可以通过 bool+should 来精确查询多个。
  1. // 先去设置 name 的分词器匹配类型是 keyword,才会变成精确查找,不会被分词器解析
  2. GET /ringnex-dev-202204*/container_log/_search
  3. {
  4.  "query": {
  5.    "term": {
  6.      "name": "ch"
  7.    }
  8.   }
  9. }
  10. GET /ringnex-dev-202204*/container_log/_search
  11. {
  12.  "query": {
  13.    "bool": {
  14.      "should": [
  15.        {
  16.          "term": {
  17.            "name": "c"
  18.          }
  19.        },
  20.        {
  21.          "term": {
  22.            "name": "ch"
  23.          }
  24.        },
  25.      ]
  26.    }
  27.   }
  28. }
复制代码
8.3、高亮查询

查询完之后,加上 highlight 关键字,然后指定字段。它会给你加上 <em>标签,当然可以自定义所加的标签,通过 pre_tags、post_tags 自定义加上的前后缀标签。
  1. GET /ringnex-dev-202204*/container_log/_search
  2. {
  3.  "query": {
  4.    "match": {
  5.      "name": "ch"
  6.    }
  7.   },
  8.  "highlight": {
  9.    "pre_tags": "<p class='key' style='color:red'>",
  10.    "post_tags": "</p>",
  11.    "fields": {
  12.      "name": {}
  13.    }
  14.   }
  15. }
复制代码
9、SpringBoot集成ES

9.1、依靠引入

记得要检察版本是否与要连接的 ES 版本一致,否则大概连接不上。
  1. <!-- 非 SpringBoot项目 -->
  2. <dependency>
  3.  <gtoupId>org.elasticsearch.client</groupId>
  4.  <artifactId>elasticsearch-rest-high-level-client</artifactId>
  5.  <version>7.6.2</version>
  6. </dependency>
  7. <properties>
  8.  <elasticsearch.version>7.6.1</elasticsearch.version>
  9. </properties>
  10. <dependency>
  11.  <gtoupId>org.springframework.boot</groupId>
  12.  <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  13. </dependency>
复制代码
9.2、写一个 config

  1. @Configuration
  2. public class ElasticSearchClientConfig {
  3.  @Bean
  4.  public RestHighLevelClient restHighLevelClient(){
  5.    return new RestHighLevelClient(RestClient.builder(
  6.      // 集群的话就 new 多个 HttpHost,用 , 分隔。"http" 这个参数可以不加,即允许使用 https
  7.      new HttpHost("127.0.0.1", 9200, "http")
  8.    ));
  9.   }
  10. }
复制代码
9.3、使用 Client

  1. @Autowired
  2. private RestHighLevelClient restHighLevelClient;
  3. @Override
  4. public Page<DocumentResponseVO> listPageMatch(SearchLogVo searchLogVo) {
  5.    List<DocumentResponseVO> list = new ArrayList<>();
  6.    // 构建查询条件
  7.    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  8.    // 过滤结果字段
  9.    String[] fields = {"log", "log_level", "log_time"};
  10.    FetchSourceContext sourceContext = new FetchSourceContext(true, fields, Strings.EMPTY_ARRAY);
  11.    searchSourceBuilder.fetchSource(sourceContext);
  12.    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
  13.    // 匹配查询
  14.    List<String> collect = new ArrayList<>();
  15.    if (ObjectUtil.isNotNull(searchLogVo.getKeyword())) {
  16.        collect = Lists.newArrayList(searchLogVo.getKeyword());
  17.    }
  18.    if (!collect.contains("系统日志")) {
  19.        collect.add("系统日志");
  20.    }
  21.    if (!StringUtils.isEmpty(searchLogVo.getTid()) && !collect.contains(searchLogVo.getTid())) {
  22.        collect.add(searchLogVo.getTid());
  23.    }
  24. //        collect = collect.stream()
  25. //                .map(it -> "log:"" + it + """)
  26. //                .collect(Collectors.toList());
  27.    String join = Joiner.on(" AND ").join(collect);
  28.    QueryStringQueryBuilder keywordQueryBuilder = QueryBuilders.queryStringQuery(join);
  29. //        keywordQueryBuilder.defaultField("*");
  30.    keywordQueryBuilder.field("log");
  31.    boolQueryBuilder.must(keywordQueryBuilder);
  32.    // 时间范围条件
  33.    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("@timestamp");
  34.    rangeQueryBuilder.gte(searchLogVo.getBeginDate().getTime());
  35.    rangeQueryBuilder.lte(searchLogVo.getEndDate().getTime());
  36.    /// 时间格式
  37.    rangeQueryBuilder.format("epoch_millis");
  38. //        rangeQueryBuilder.format("yyyy-MM-dd hh:mm:ss");
  39.    boolQueryBuilder.must(rangeQueryBuilder);
  40.    searchSourceBuilder.query(boolQueryBuilder);
  41.    // 分页
  42.    searchSourceBuilder.from(searchLogVo.getFrom());
  43.    searchSourceBuilder.size(searchLogVo.getSize());
  44.    // 请求响应时间
  45.    searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
  46.    // 排序
  47.    searchSourceBuilder.sort("@timestamp", SortOrder.DESC);
  48.    // 若以 * 结尾,则根据日期精确索引范围
  49.    List<String> dateIndex = new ArrayList<>();
  50.    String index = searchLogVo.getIndex();
  51.    if (index.endsWith("*")) {
  52.        DateTime beginDate = new DateTime(searchLogVo.getBeginDate());
  53.        beginDate = beginDate.withTime(0, 0, 0, 0);
  54.        DateTime endDate = new DateTime(searchLogVo.getEndDate());
  55.        while (beginDate.isBefore(endDate)) {
  56.            dateIndex.add(beginDate.toString("yyyyMMdd"));
  57.            beginDate = beginDate.plusDays(1);
  58.        }
  59.        String[] split = index.split("-");
  60.        if (split.length == 2) {
  61.            index = index.substring(0, index.length() - 1);
  62.        } else if (split.length == 3) {
  63.            index = index.substring(0, index.lastIndexOf("-"));
  64.        } else {
  65.            throw new RuntimeException("暂不知其他索引情况");
  66.        }
  67.        String finalIndex = index;
  68.        dateIndex = dateIndex.stream()
  69.                .map(it -> finalIndex + "-" + it + "*")
  70.                .collect(Collectors.toList());
  71.    }
  72.    SearchRequest searchRequest = new SearchRequest(dateIndex.toArray(new String[dateIndex.size()]));
  73.    searchRequest.source(searchSourceBuilder);
  74.    SearchResponse search = null;
  75.    try {
  76.        // ES查询
  77.        search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  78.    } catch (IOException e) {
  79.        e.printStackTrace();
  80.        log.error("ES查询出错:{}", e.getMessage(), e);
  81.    }
  82.    // 获取命中结果
  83.    SearchHits searchHits = search.getHits();
  84.    if (!ObjectUtils.isEmpty(searchHits.getHits())) {
  85.        SearchHit[] hits = searchHits.getHits();
  86.        list = Arrays.stream(hits)
  87.                .map(DocumentResponseVO::hitsMappingDocumentResponse)
  88.                .peek(DocumentResponseVO::initSource)
  89.                .collect(Collectors.toList());
  90.    }
  91.    long totalHits = searchHits.getTotalHits();
  92.    int ceil = (int) Math.ceil(totalHits / (searchLogVo.getSize() * 1.0));
  93.    Page<DocumentResponseVO> page = new Page<>();
  94.    if (searchLogVo.getFrom() == 0) {
  95.        page.setCurrent(1);
  96.    } else {
  97.        page.setCurrent(searchLogVo.getFrom() / searchLogVo.getSize() + 1);
  98.    }
  99.    page.setPages((long) ceil)
  100.            .setTotal(totalHits)
  101.            .setSize(searchLogVo.getSize())
  102.            .setRecords(list);
  103.    return page;
  104. }
复制代码
10、关于索引的API操纵

10.1、创建索引

  1. void createIndexTest(){
  2.  // 1、得到创建索引的请求
  3.  CreateIndexRequest createIndexRequest = new CreateIndexRequest("index1");
  4.  // 2、客户端执行请求,获得响应
  5.  CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
  6. }
复制代码
10.2、获取索引

  1. void getIndexTest(){
  2.  GetIndexRequest getIndexRequest = new GetIndexRequest("index1");
  3.  boolean exists = restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
  4.  if (exists) {
  5.      GetIndexResponse getIndexResponse = restHighLevelClient.indices().get(getIndexRequest, RequestOptions.DEFAULT);
  6.   }
  7. }
复制代码
10.3、删除索引

  1. void deleteIndexTest(){
  2.  DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("index1");
  3.  DeleteIndexResponse delete = restHighLevelClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
  4.  System.out.println(delete.isAcknowledged());
  5. }
复制代码
11、关于文档的API操纵

  1. public void initSource() {
  2.    String logString = (String) sourceAsMap.get("log");
  3.    String tid = logString.substring(logString.indexOf("TID") + 4, logString.indexOf("]"));
  4.    this.tid = tid;
  5.    String[] split = logString.split("c.p.health.aop.log.BaseAopLog -", 2);
  6.    if (split.length == 2) {
  7.        String subLog = split[1];
  8.        subLog = StringUtils.trim(subLog);
  9.        subLog = StringUtils.replace(subLog, "\n", "");
  10.        subLog = StringUtils.replace(subLog, "\t", "");
  11.        if (ObjectUtils.isEmpty(this.source)) {
  12.            this.source = JsonUtils.json2pojo(subLog, RespLogVo.class);
  13.        }
  14.    }
  15. }
  16. public static DocumentResponseVO hitsMappingDocumentResponse(SearchHit hit) {
  17.    DocumentResponseVO doc = new DocumentResponseVO();
  18.    doc.setDocId(hit.getId())
  19.            .setIndex(hit.getIndex())
  20.            .setType(hit.getType())
  21.            .setSourceAsMap(hit.getSourceAsMap())
  22.            .setSourceAsString(hit.getSourceAsString())
  23.            .setScore((double) hit.getScore())
  24.            .setLogLevel((String) hit.getSourceAsMap().get("log_level"));
  25.    return doc;
  26. }
  27. ==================================================
  28. @Override
  29.    public Page<DocumentResponseVO> listPageMatch(SearchLogVo searchLogVo) {
  30.        List<DocumentResponseVO> list = new ArrayList<>();
  31.        // 构建查询条件
  32.        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  33.        // 过滤字段
  34.        String[] fields = {"log", "log_level", "log_time"};
  35.        FetchSourceContext sourceContext = new FetchSourceContext(true, fields, Strings.EMPTY_ARRAY);
  36.        searchSourceBuilder.fetchSource(sourceContext);
  37.        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
  38.        // 时间范围条件
  39.        RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("log_time");
  40.        rangeQueryBuilder.gte(searchLogVo.getBeginDate());
  41.        rangeQueryBuilder.lte(searchLogVo.getEndDate());
  42.        /// 时间格式
  43. //        rangeQueryBuilder.format("yyyy-MM-dd hh:mm:ss");
  44.        boolQueryBuilder.must(rangeQueryBuilder);
  45.        // 日志级别
  46.        // 需将 log_level 的 type 设置成 keyword 后才可用 TermQueryBuilder
  47.        List<String> logLevelList = Lists.newArrayList(searchLogVo.getLogLevel());
  48.        String logLevelQueryString = Joiner.on(" OR ").join(logLevelList);
  49.        QueryStringQueryBuilder logLevelQueryBuilder = QueryBuilders.queryStringQuery(logLevelQueryString);
  50.        logLevelQueryBuilder.field("log_level");
  51.        boolQueryBuilder.must(logLevelQueryBuilder);
  52.        // 匹配查询
  53.        List<String> keywordList = new ArrayList<>();
  54.        if (ObjectUtil.isNotNull(searchLogVo.getKeyword())) {
  55.            keywordList = Lists.newArrayList(searchLogVo.getKeyword());
  56.        }
  57.        if (!keywordList.contains("系统日志")) {
  58.            keywordList.add("系统日志");
  59.        }
  60.        if (!StringUtils.isEmpty(searchLogVo.getTid())) {
  61.            keywordList.add(searchLogVo.getTid());
  62.        }
  63.        String keywordQueryString = Joiner.on(" AND ").join(keywordList);
  64.        QueryStringQueryBuilder keywordQueryBuilder = QueryBuilders.queryStringQuery(keywordQueryString);
  65.        keywordQueryBuilder.field("log");
  66.        boolQueryBuilder.must(keywordQueryBuilder);
  67.        searchSourceBuilder.query(boolQueryBuilder);
  68.        // 分页对象
  69.        searchSourceBuilder.from(searchLogVo.getFrom());
  70.        searchSourceBuilder.size(searchLogVo.getSize());
  71.        // 请求响应时间
  72.        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
  73.        // 排序
  74.        searchSourceBuilder.sort("@timestamp", SortOrder.ASC);
  75.        SearchRequest searchRequest = new SearchRequest(searchLogVo.getIndex());
  76.        searchRequest.source(searchSourceBuilder);
  77.        SearchResponse search = null;
  78.        try {
  79.            // ES查询
  80.            search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  81.        } catch (IOException e) {
  82.            e.printStackTrace();
  83.            log.error("ES查询出错:{}", e.getMessage(), e);
  84.        }
  85.        // 获取命中结果
  86.        SearchHits searchHits = search.getHits();
  87.        if (!ObjectUtils.isEmpty(searchHits.getHits())) {
  88.            SearchHit[] hits = searchHits.getHits();
  89.            list = Arrays.stream(hits)
  90.                    .map(DocumentResponseVO::hitsMappingDocumentResponse)
  91.                    .peek(DocumentResponseVO::initSource)
  92.                    .collect(Collectors.toList());
  93.        }
  94.        long totalHits = searchHits.getTotalHits();
  95.        int ceil = (int) Math.ceil(totalHits / (searchLogVo.getSize() * 1.0));
  96.        Page<DocumentResponseVO> page = new Page<>();
  97.        if (searchLogVo.getFrom() == 0) {
  98.            page.setCurrent(1);
  99.        } else {
  100.            page.setCurrent(searchLogVo.getFrom() / searchLogVo.getSize() + 1);
  101.        }
  102.        page.setPages((long) ceil)
  103.                .setTotal(totalHits)
  104.                .setSize(searchLogVo.getSize())
  105.                .setRecords(list);
  106.        return page;
  107.    }
复制代码
11.0、使用注意

11.0.1、使用 TermQueryBuilder 时需注意

使用精确查找时经常会失败,是由于你没将精确查询的字段的 type 设置成 keyword
  1. .startObject("cyzjdm").field("type", "keyword").field("index", false).endObject()
复制代码
term 做精确查询可以用它来处理数字,布尔值,日期以及文本。查询数字时问题不大,但是当查询字符串时会有问题。 term 查询的寄义是 termQuery 会去倒排索引中探求确切的 term,但是它并不知道分词器的存在。term 表示查询字段里含有某个关键词的文档,terms表示查询字段里含有多个关键词的文档。
也就是说直接对字段进行 term 本质上还是模糊查询,只不过不会对搜索的输入字符串进行分词处理罢了。如果想通过 term 查到数据,那么 term 查询的字段在索引库中就必须有与 term 查询条件相同的索引词,否则无法查询到结果。
即 elasticsearch 里默认的 IK分词器是会将每一个中文都进行了分词的切割,以是你直接想查一整个词,或者一整句话是无返回结果的。
11.0.1.1、关于 keyword

有的文章说设置该属性用于关键词搜索,不进行分词。对于字符串类型的字段,es 会默认天生一个 keyword字段用于精确搜索。也有的说实际上还是会分词,只不过keyword的设置增加了一个额外字段,该字段就是 filename.keyword。这个 keyword 才是不分词的索引字段,也就真正意义上实现了不分词处理字段。索引也是索引该字段才天生真正的精确匹配。至于分不分词实验一下就好了。感觉他们想表达的意思差不多是 filename.keyword 不分词,但是 filename 还是会分词。
11.1、添加/更新文档

  1. // 创建请求
  2. IndexRequest request = new IndexRequest("chw_index");
  3. // 规则 PUT /chw_index/_doc/1
  4. request.id("1");
  5. // 请求的超时时间
  6. request.timeout(TimeValue.timeValueSeconds(1));
  7. // 把传过来的数据放入请求
  8. request.source(JSON.toJSONString(传过来的对象), XContentType.JSON);
  9. // 客户端发送请求,获取响应的结果
  10. IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
  11. System.out.println(indexResponse.toString());
  12. System.out.println(indexResponse.status());
复制代码
11.2、获取文档

  1. GetRequest getRequest = new GetRequest("chw_index", "1");
  2. // 不获取返回的 _source 的上下文了
  3. getRequest.fetchSourceContext(new FetchSourceContext(false));
  4. getRequest.storedFields("_none_");
  5. boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
  6. // 获取文档信息
  7. GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
  8. // 文档的内容
  9. getResponse.getSourceAsString();getResponse.getSourceAsMap();
复制代码
11.3、更新文档

  1. UpdateRequest updateRequest =  new UpdateRequest("chw_index", "1");
  2. updateRequest.timeout("1s");
  3. // 把传过来的数据放入请求
  4. updateRequest.doc(JSON.toJSONString(传过来的对象), XContentType.JSON);
  5. // 执行请求命令
  6. UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
复制代码
11.4、删除文档

  1. DeleteRequest deleteRequest =  new DeleteRequest("chw_index", "1");
  2. deleteRequest.timeout("1s");
  3. DeleteResponse deleteResponse = client.delete(updateRequest, RequestOptions.DEFAULT);
复制代码
11.5、批量操纵

批量操纵时使用 BulkRequest 对象, .什么方法,就做什么操纵,末了 client.bulk,其他的操纵和参数都是雷同的。
  1. BulkRequest bulkRequest = new BulkRequest();
  2. bulkRequest.timeout("10s");
  3. for(int i = 0; i < xxList.size(); i++){
  4.  bulkRequest.add(
  5.    new IndexRequest("chw_index")
  6.    .id(""+(i+1))
  7.    .source(传入对象的JSOn字符串, XContentType.JSON);
  8.   )
  9. }
  10. BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
  11. bulkResponse.hasFailures();// 是否成功
复制代码
11.6、查询



  • SearchRequest:搜索请求
  • SearchSourceBuilder:条件构造
  • HighlightBuilder:构建高亮
  • TermQueryBuilder: 精确查询
  • XxxQueryBuilder...
  1. SearchRequest searchRequest = new SearchRequest ("chw_index");
  2. // 构建搜索条件构建器
  3. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  4. // 查询条件构建器
  5. // .termQuery:精确匹配,.matchAllQuery():匹配所有
  6. TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "chw");
  7. searchSourceBuilder.query(termQueryBuilder);
  8. // from和size 有默认值
  9. // searchSourceBuilder.from();
  10. // searchSourceBuilder.size();
  11. searchSourceBuilder.timeout("60s");
  12. searchRequest.source(searchSourceBuilder);
  13. SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
  14. // 命中的结果内容
  15. searchResponse.getHits();
复制代码
12、简单上手

12.1、京东搜索

12.1.1、爬虫

12.1.1.1、jsoup依靠

解析网页,如果想要解析视频需要用 tika
  1. <dependency>
  2.    <groupId>org.jsoup</groupId>
  3.    <artifactId>jsoup</artifactId>
  4.    <version>1.10.2</version>
  5. </dependency>
复制代码
12.1.1.2、HTML 解析工具类

  1. public class HtmlParseUtil {
  2.  public static List<Object> parseJD(String keyword) throws Exception {
  3.    // 请求地址,不能获取到 ajax
  4.    String url = "https://search.jd.com/Search?keyword=" + keyword;
  5.    // 解析网页,返回的 Document 就是页面的对象
  6.    Document document = Jsoup.parse(new URL(url), 30000);
  7.    // 获取 div元素
  8.    Element goodsElement = document.getElementById("J_goodsList");
  9.    // 获取 li 元素
  10.    Element liElement = goodsElement.getElementsByTag("li");
  11.    
  12.    List<自定义对象> 自定义对象List = new ArrayList<>();
  13.    for (Element el : liElement) {
  14.      // 图片一般都是懒加载的
  15.      String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
  16.      String price = el.getElementsByClass("p-price").eq(0).text();
  17.      String title = el.getElementsByClass("p-name").eq(0).text();
  18.      
  19.      自定义对象 对象 = new 自定义对象();
  20.      // 设置对象属性值
  21.      自定义对象List.add(对象);
  22.    }
  23.   }
  24. }
复制代码
12.1.2、ES 入库、查询业务

  1. public Boolean bulk(String keyword) {
  2.  List<自定义对象> 自定义对象List = HtmlParseUtil.parseJD(keyword);
  3.  BulkRequest bulkRequest = new BulkRequest();
  4.  bulkRequest.timeout("2m");
  5.  
  6.  for(自定义对象 对象 : 自定义对象List){
  7.    bulkRequest.add(new IndexRequest("jd_goods_index")
  8.      .source(JSON.toJSONString(对象), XContentType.JSON);
  9.    )
  10.   }
  11.  BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
  12.  return !bulkResponse.hasFailures();
  13. }
  14. public List<Map<String, Object>> pageByKeyword(String keyword, Integer pageNo, Integer pageSize) {
  15.  SearchRequest searchRequest = new SearchRequest ("jd_goods_index");
  16.  SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  17.  
  18.  // 查询条件构建器
  19.  // .termQuery:精确匹配,.matchAllQuery():匹配所有
  20.  TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
  21.  searchSourceBuilder.query(termQueryBuilder);
  22.  
  23.  // 高亮
  24.  HighlighterBuilder highlighterBuilder = new HighlighterBuilder();
  25.  highlighterBuilder.field("title");
  26.  highlighterBuilder.requireFieldMatch(false);
  27.  highlighterBuilder.preTags("<span style='color:red'>");
  28.  highlighterBuilder.postTags("</span>");
  29.  searchSourceBuilder.highlighter(highlighterBuilder);
  30.  
  31.  searchSourceBuilder.from((pageNo - 1) * pageSize);
  32.  searchSourceBuilder.size(pageSize);
  33.  searchSourceBuilder.timeout(new TimeValue(60, TimeUtil.SECONDS));
  34.  
  35.  searchRequest.source(searchSourceBuilder);
  36.  SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
  37.  // 命中的结果内容
  38.  List<Map<String, Object>> result = new ArrayList<>();
  39.  for (SearchHit document : searchResponse.getHits().getHits()) {
  40.    // 解析高亮字段
  41.    Map<String, HighlightField> highlightFieldMap = document.getHighlightFields();
  42.    HighlightField title = highlightFieldMap.get("title");
  43.    Map<String, Object> sorceAsMap = document.getSourceAsMap();
  44.    if(title != null) {
  45.      Text[] fragments = title.fragments();
  46.      String newTitle = "";
  47.      for (Text text : fragments) {
  48.        newTitle += text;
  49.      }
  50.      // 解析出高亮并设置进去
  51.      sorceAsMap.put("title", newTitle);
  52.    }
  53.    result.add(sorceAsMap);
  54.   }
  55.  return result;
  56. }
复制代码
















































免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

勿忘初心做自己

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表