• ElasticSearch


    学这个之前先来说一下ElasticSearch也是一个数据库,我们连上mysql,redis和ElasticSearch也就三个数据库了,接下来我们对比一下这三个数据库:

    Mysql:
        分类: 关系型数据库
        概述: mysql在存储数据时,数据和数据之间有一定的关联关系
        存储介质: 存放在硬盘上
        优点: 不会导致数据丢失
        缺点: 执行效率低
        硬盘 --->  内存  ---> CPU
        事务控制,这也是他没被替换掉的原因
    redis:
        分类: 非关系型数据库
        概述: 数据在存储时,数据和数据之间没有关联关系
        存储介质: 内存
        优点: 执行效率高
        缺点: 可能会导致数据丢失
    ElasticSearch: 灵活的搜索
        分类: 非关系型数据库
        概述: ES专门用于对海量数据的搜索
        存储介质: 内存
        优点: 搜索效率高

    数据库排行查询网址:https://db-engines.com/ 

    搜索引擎-ElasticSearch

    1、面临问题

        在模糊查询中,由于索引失效,mysql的查询效率太过低下。

    2、解决方案-ElasticSearch

    2.1、简介

    【elasticsearch】

        elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容.

    【ELK】

        elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域:而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。

    【Lucene】

        elasticsearch底层是基于lucene来实现的。
    ​
        Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。    官网:https://lucene.apache.org/ 

    2.2、优缺点

    【优点】

        检索数据非常快

    【缺点】

    2.3、运行原理-倒排索引

    【正向索引】

        正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程.
        优点:
            可以给多个字段创建索引
            根据索引字段搜索、排序速度非常快
        缺点:
            根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。

    【倒排索引】

        倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。
        优点:
            根据词条搜索、模糊搜索时,速度非常快
        缺点:
            只能给词条创建索引,而不是字段
            无法根据字段做排序

    2.4、相关名词

    【索引库】

        索引(Index),就是相同类型的文档的集合。类似于mysql中的表

    【mapping映射】

        映射(mapping),是索引中文档的字段约束信息,类似mysql表的结构约束。

    【文档】

        文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式

    【词条】

        词条(Term),对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。

    2.5、Kibana

        可视化工具,在浏览器输入DSL语句即可查看ES中的内容。

    3、使用步骤

    3.1、Kibana浏览器操作-索引库&文档

    【索引库操作】

    【创建索引库】

    PUT /索引库名称
    {
      "mappings": {
        "properties": {
          "字段名":{
            "type": "text",
            "analyzer": "ik_smart"
          },
          "字段名2":{
            "type": "keyword",
            "index": "false"
          },
          "字段名3":{
            "properties": {
              "子字段": {
                "type": "keyword"
              }
            }
          }
        }
      }
    }

    【查看索引库】

    GET /索引库名

    【删除索引库】

    DELETE /索引库名

    【修改索引库(追加)】

    PUT /索引库名/_mapping
    {
     "properties":{
       "新字段":{
           "type":"数据类型(integer)"
            }
      }
    }   

    【文档操作】

    【新增文档】

    POST /索引库名/_doc/文档id(不给会随机生成id)
    {
        "字段1": "值1",
        "字段2": "值2",
        "字段3": {
            "子属性1": "值3",
            "子属性2": "值4"
       }
    }

    【查看文档】

    GET /索引库名/_doc/文档id

    【删除文档】

    DELETE /索引库名/_doc/文档id

    【修改文档】

    【全量修改】

    PUT /索引库名/_doc/文档id
    {
        "字段1": "值1",
        "字段2": "值2"
    }

    【增量修改】

    POST /索引库名/_update/文档id
    {
        "doc": {
            "字段名": "新的值"
        }
    }       

    3.2、java代码操作-索引库&文档

    【导入依赖】

    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
    </dependency>

    【与版本对应】

    <properties>
        <java.version>1.8</java.version>
        <elasticsearch.version>7.12.1</elasticsearch.version>
    </properties>

    【初始化连接&关闭连接】

    private RestHighLevelClient client; 
    ​
    // 创建连接
    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.248.222:9200")
        ));
    }
    ​
    // 关闭连接
    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    【索引库操作】

    【创建索引库】

    // 创建索引库
    @Test
    public void createIndex() throws IOException {
        // 1.创建请求语义对象
        CreateIndexRequest request = new CreateIndexRequest("hotel");
        // 添加 source
        request.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);
        // 2.发送请求
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        // 3.解析响应
        boolean flag = response.isAcknowledged();
        // 打印结果
        System.out.println(flag);
    }

    【删除索引库】

    // 删除索引库
    @Test
    public void deleteIndex() throws IOException {
        // 1.创建请求语义对象
        DeleteIndexRequest request = new DeleteIndexRequest("hotel");
        // 2.发送请求
        AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
        // 3.解析响应
        boolean flag = response.isAcknowledged();
        // 打印结果
        System.out.println(flag);
    }

    【查看索引库是否存在】

    // 查看索引库是否存在
    @Test
    public void existsIndex() throws IOException {
        // 1.创建请求语义对象
        GetIndexRequest request = new GetIndexRequest("hotel");
        // 2.发送请求
        boolean flag = client.indices().exists(request, RequestOptions.DEFAULT);
        // 3.解析响应
        // 打印结果
        System.out.println(flag);
    }

    【文档操作】

    【添加文档数据】

    @Autowired
    private IHotelService hotelService; 
    ​
    // 添加文档数据
    @Test
    public void add() throws IOException {
        // 1.根据id获取数据
        Hotel hotel = hotelService.getById(36934L);
        // 2.转换成 ES 对应的数据
        HotelDoc hotelDoc = new HotelDoc(hotel);
        // 3.转换成JSON
        String hotelDocJson = JSON.toJSONString(hotelDoc);
        // 4.创建请求语义对象
        IndexRequest request = new IndexRequest("hotel");
        request.id(hotel.getId() + "");
        request.source(hotelDocJson, XContentType.JSON);
        // 5.发送请求
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        // 6.解析响应结果
        DocWriteResponse.Result result = response.getResult();
        System.out.println(result);
    }

    【查询文档数据】

    // 查询文档数据
    @Test
    public void select() throws IOException {
        // 1.创建请求语义对象
        GetRequest request = new GetRequest("hotel","36934");
        // 2.发送请求
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        // 3.解析响应结果
        String json = response.getSourceAsString();
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        System.out.println(hotelDoc);
    }

    【修改文档数据】

    // 修改文档数据
    @Test
    public void update() throws IOException {
        // 1.创建请求语义对象
        UpdateRequest request = new UpdateRequest("hotel","36934");
        request.doc(
                "name", "7天连锁酒店(上海宝山路地铁站店)"
        );
        // 2.发送请求
        UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
        // 3.解析响应结果
        DocWriteResponse.Result result = response.getResult();
        System.out.println(result);
    }

    【删除文档数据】

    // 删除文档数据
    @Test
    public void delete() throws IOException {
        // 1.创建请求语义对象
        DeleteRequest request = new DeleteRequest("hotel","36934");
        // 2.发送请求
        DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
        // 3.解析响应结果
        DocWriteResponse.Result result = response.getResult();
        System.out.println(result);
    }

    【批量添加】

    @Autowired
    private IHotelService hotelService;
    ​
    // 批量添加文档数据
    @Test
    public void add() throws IOException {
        // 1.批量查询酒店数据
        List<Hotel> hotels = hotelService.list();
        // 2.创建请求语义对象
        BulkRequest request = new BulkRequest();
        for (Hotel hotel : hotels) {
            // 转换格式
            HotelDoc hotelDoc = new HotelDoc(hotel);
            String hotelDocJson = JSON.toJSONString(hotelDoc);
            IndexRequest indexRequest = new IndexRequest("hotel");
            indexRequest.id(hotel.getId() + "");
            indexRequest.source(hotelDocJson, XContentType.JSON);
            request.add(indexRequest);
        }
        // 5.发送请求
        BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
        // 6.解析响应结果
        RestStatus status = response.status();
        System.out.println(status);
    }

    3.3、Kibana浏览器-查询&结果处理

    【查询所有】

    GET /索引库名/_search
    {
     "query": {
       "match_all": {
        }
      }
    }

    【全文检索查询】

    【多字段】

    GET /索引库名/_search
    {
    "query": {
      "multi_match": {
         "query": "内容",
         "fields": ["字段1", "字段2"]
      }
     }
    }

    【单字段】

    GET /hotel/_search
    {
      "query": {
        "match": {
          "字段": "值"
        }
      }
    }

    【精确查询】

    【term查询】

    GET /索引库名/_search
    {
      "query": {
        "term": {
          "字段": {
            "value": "值"
          }
        }
      }
    }

    【range查询】

    GET /索引库名/_search
    {
      "query": {
        "range": {
          "字段": {
            "gte": 10, // 这里的gte代表大于等于,gt则代表大于
            "lte": 20 // lte代表小于等于,lt则代表小于
          }
        }
      }
    }

    【地理坐标查询】

    【矩形范围查询】

    GET /索引库名/_search
    {
      "query": {
        "geo_bounding_box": {
          "字段": {
            "top_left": { // 左上点
              "lat": 31.1,
              "lon": 121.5
            },
            "bottom_right": { // 右下点
              "lat": 30.9,
              "lon": 121.7
            }
          }
        }
      }
    }

    【附近/距离查询】

    GET /索引库名/_search
    {
      "query": {
        "geo_distance": {
          "distance": "15km", // 半径
          "字段": "31.21,121.5" // 圆心
        }
      }
    }

    【复合查询】

    【算分函数查询】

    GET /hotel/_search
    {
      "query": {
        "function_score": {
          "query": {
    // 原始查询条件 根据BM25算法进行算分 
            "match": {
            "all": "外滩"
            }
          },
          "functions": [
            {
    // 过滤条件 符合条件重新算分
              "filter": {
                  "term": {
                       "brand": "如家"
                  }
            },
    //算分函数 weight是常量值
              "weight": 10
            }
          ],
    //加权模式  默认是multiply
          "boost_mode": "sum"
        }
      }
    }

    【布尔查询】

    GET /hotel/_search
    {
      "query": {
        "bool": {
    //必须匹配
          "must": [
            {
              "match": {
                "all": "上海"
              }
            }
          ],
    //选择匹配
          "should": [
            {
              "term": {
                "brand": {
                  "value": "皇冠假日"
                }
              }
            },
            {
              "term": {
                "brand": "华美达"
              }
            }
          ],
    //必须不匹配 不参与算分
          "must_not": [
            {
              "range": {
                "price": {
                  "lte": 400
                }
              }
            }
          ],
    //必须匹配 不参与算分
          "filter": [
            {
              "geo_distance": {
                "distance": "10km",
                "location": {
                  "lat": 31.21,
                  "lon": 121.5
                }
              }
            }
          ]
        }
      }
    }

    【排序】

    【普通字段】

    GET /索引库名/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": [
        {
          "字段": "desc"  // 排序字段、排序方式ASC、DESC
        }
      ]
    }

    【地理坐标】

    GET /hotel/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": [
        {
          "_geo_distance": {
            "地理坐标相关字段": {
              "lat": 31.21,
              "lon": 121.5
            },
          "order":"asc",
    //单位
          "unit":"km"
          }
        }
      ]
    }

    【分页】

    【基本分页】

    GET /hotel/_search
    {
      "query": {
        "match_all": {}
      },
      "from": 0, // 分页开始的位置,默认为0
      "size": 10, // 期望获取的文档总数
      "sort": [
        {"price": "asc"}
      ]
    }

    【深度分页】

    【高亮】

    GET /hotel/_search
    {
      "query": {
        "match": {
          "FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
        }
      },
      "highlight": {
        "fields": { // 指定要高亮的字段
          "FIELD": {
            "pre_tags": "<em>",  // 用来标记高亮字段的前置标签
            "post_tags": "</em>", // 用来标记高亮字段的后置标签
            "require_field_match": "false"
          }
        }
      }
    }

    3.4、java代码操作-查询&结果处理

    【查询所有】

    // 查询所有--match all
    @Test
    public void matchAll() throws IOException {
        // 1.创建 SearchRequest 对象
        SearchRequest request = new SearchRequest("hotel");
        // 2.组织 DSL 参数
        request.source()
                .query(QueryBuilders.matchAllQuery());
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析响应结果
        handleResponse(response);
    }

    【全文检索查询】

    // 全文检索查询--match
    @Test
    public void match() throws IOException {
        // 1.创建 SearchRequest 对象
        SearchRequest request = new SearchRequest("hotel");
        // 2.组织 DSL 参数
        request.source()
                .query(QueryBuilders.matchQuery("all","如家"));
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析响应结果
        handleResponse(response);
    }

    【精确查询】

    【term查询】

    // 精确查询--term
    @Test
    public void term() throws IOException {
        // 1.创建 SearchRequest 对象
        SearchRequest request = new SearchRequest("hotel");
        // 2.组织 DSL 参数
        request.source()
                .query(QueryBuilders.termQuery("city","杭州"));
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析响应结果
        handleResponse(response);
    }

    【range查询】

    // 范围查询--range
    @Test
    public void range() throws IOException {
        // 1.创建 SearchRequest 对象
        SearchRequest request = new SearchRequest("hotel");
        // 2.组织 DSL 参数
        request.source()
                .query(QueryBuilders.rangeQuery("price").gte(500).lte(1000));
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析响应结果
        handleResponse(response);
    }

    【复合查询】

    【布尔查询】

    // 布尔查询--bool
    @Test
    public void bool() throws IOException {
        // 1.创建 SearchRequest 对象
        SearchRequest request = new SearchRequest("hotel");
        // 2.组织 bool  查询参数
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(QueryBuilders.termQuery("city", "杭州"));
        boolQuery.filter(QueryBuilders.rangeQuery("price").lt(1000));
        // 3.组织 DSL 参数
        request.source()
                .query(boolQuery);
        // 4.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 5.解析响应结果
        handleResponse(response);
    }

    【排序分页】

    // 排序、分页--sort from size
    @Test
    public void sortAndPage() throws IOException {
        // 1.创建 SearchRequest 对象
        SearchRequest request = new SearchRequest("hotel");
        // 2.组织 DSL 参数
        // 查询所有
        request.source().query(QueryBuilders.matchAllQuery());
        // 前 20条
        request.source().from(0).size(20);
        // 按价格升序
        request.source().sort("price", SortOrder.ASC);
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析响应结果
        handleResponse(response);
    }

    【高亮】

    // 高亮--highlights
    @Test
    public void highlights() throws IOException {
        // 1.创建 SearchRequest 对象
        SearchRequest request = new SearchRequest("hotel");
        // 2.组织 DSL 参数
        // 查询所有带有如家
        request.source().query(QueryBuilders.matchQuery("all","如家"));
        // 高亮处理
        request.source().highlighter(new HighlightBuilder()
                .field("name")
                .requireFieldMatch(false));
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析响应结果 -- 高亮特殊处理
        handleResponse(response);
    }

    【响应结果解析】

    // 响应结果解析
    private void handleResponse(SearchResponse response) {
        // 4.解析响应结果
        SearchHits searchHits = response.getHits();
        // 查询总条数
        long total = searchHits.getTotalHits().value;
        System.out.println("查询总条数:" + total);
        // 查询结果数组
        SearchHit[] hits = searchHits.getHits();
        // 遍历
        for (SearchHit hit : hits) {
            // 得到json
            String hotelDocJson = hit.getSourceAsString();
            // 转换成Java对象
            HotelDoc hotelDoc = JSON.parseObject(hotelDocJson, HotelDoc.class);
            // 高亮特殊处理
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!CollectionUtils.isEmpty(highlightFields)) {
                // 高亮字段结果
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField!=null) {
                    // 不为空时的第一个就是酒店名称
                    String name = highlightField.getFragments()[0].string();
                    hotelDoc.setName(name);
                }
            }
            // 打印结果
            System.out.println(hotelDoc);
        }
    }

    3.5、Kibana浏览器-聚合

    GET /hotel/_search
    {
     "query": {//限定聚合条件
        "range": {
          "price": {
            "lte": 200 // 只对200元以下的文档聚合
          }
        }
      },
      "size": 0,  // 设置size为0,结果中不包含文档,只包含聚合结果
      "aggs": { // 定义聚合
        "brandAgg": { //给聚合起个名字
          "terms": { // 聚合的类型,按照品牌值聚合,所以选择term
            "field": "brand", // 参与聚合的字段
            "order": {
              "_count": "asc" // 按照_count升序排列
            },
            "size": 20 // 希望获取的聚合结果数量
          },
          "aggs": { // 是brands聚合的子聚合,也就是分组后对每组分别计算
            "score_stats": { // 聚合名称
              "stats": { // 聚合类型,这里stats可以计算min、max、avg等
                "field": "score" // 聚合字段,这里是score
              }
            }
          }
        }
      }
    }

    3.6、java代码操作-聚合

    【聚合查询】

    // 聚合查询--aggregation
    @Test
    public void aggregation() throws IOException {
        // 1.创建 SearchRequest 对象
        SearchRequest request = new SearchRequest("hotel");
        // 2.组织 DSL 参数
        request.source().size(0);
        request.source().aggregation(AggregationBuilders
                .terms("brand_agg")
                .field("brand")
                .order(BucketOrder.aggregation("_count",true))
                .size(20));
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析响应结果
        handleResponse(response);
    } 

    【响应结果解析】

    // 响应结果解析
    private void handleResponse(SearchResponse response) {
        // 4.解析聚合响应结果
        Aggregations aggregations = response.getAggregations();
        // 根据名称获得聚合结果
        Terms brandTerms = aggregations.get("brand_agg");
        // 获取桶
        List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
        // 遍历
        for (Terms.Bucket bucket : buckets) {
            // 获取品牌信息
            String brandName = bucket.getKeyAsString();
            long count = bucket.getDocCount();
            System.out.println(brandName+":"+count);
        }
    }
  • 相关阅读:
    2022 JuiceFS 社区用户调研结果出炉
    SpringBoot:转换器和格式化器及修改默认配置
    Microsoft Office for Mac最新版本安装教程,亲测可用!!!
    Vue中组件化编码 完成任务的添加、删除、统计、勾选需求(实战练习三完结)
    使用Flink完成流数据统计
    rust字符串
    精解四大集合框架:List 核心知识总结
    pandas(四十三)Pandas实现复杂Excel的转置合并
    Nacos的服务手动注册与发现
    【Vue】基础系列(三三)指令语法-事件及其修饰符,动态样式,v-model的用法,数据持久化存在本地localStorage
  • 原文地址:https://blog.csdn.net/hnhroot/article/details/125464618