ElasticSearch

文章目录

前提

开源的搜索引擎,从海量数据中快速找到需要的内容。(分词检索,类似百度查询、博客文章关键词搜索)

elasticsearch结合 Kibana、Logstas、Beats,也就是 elastic stack(简称ELK),广泛应用于日志分析、实时监控。

JDK兼容性:https://www.elastic.co/cn/support/matrix#matrix_jvm

操作系统兼容性:https://www.elastic.co/cn/support/matrix

自身兼容性:https://www.elastic.co/cn/support/matrix#matrix_compatibility

对于ES 8.1 及以上版本而言,支持 JDK 17、JDK 18

docker安装步骤(es、kibana、IK)

倒排索引

MySQL、ES的区别和关联

mysql擅长事务操作(ACID),确保数据安全和一致性

ES擅长海量数据搜索、分析、计算

文档

elasticsearch是面向文档存储的(JSON),每一条数据就是一个文档

索引
es中的索引是指相同类型的文档集合

映射:索引中文档字段的约束,比如名称、类型

IK分词器

作用:

  • 在创建倒排索引时对文档进行分词
  • 用户搜索时,对内容进行分词

ik分词器的两种模式

POST /_analyze

{

"text":"这是程序员的一次测试,包含English",

"analyzer":"ik_max_word"

}
ik_smart:最小切分(粗粒度),分出来的词不会再细分(程序员)
ik_max_word:最细切分(细粒度),分出来的词更多更细(程序员、程序)


拓展词条、停用词条

进入docker 创建的容器的插件目录,找到Ik分词器下的 IKAnalyzer.cfg.xml 文件,扩展词典在 中添加文件名称(例如ext.dic),停用词典在 中添加,(例如stopword.dic)。当然之后需要你手段创建词典文件,内容格式为一词一行。

索引库

mapping属性

mapping映射是对索引库中文档的约束。类似mysql对表单字段的约束

json 复制代码
{
    "id":[1, 2, 3, 4, 5],
    "name":{
        "firstname":"明",
        "lastname":"李"
    }
}
  • type:字段数据类型,常见的类型有:
    • 字符串:text(可分词的文本)、keyword(不可分词的文本,例如国家、品牌、IP地址)
    • 布尔:boolean
    • 日期:date
    • 数值:long、short、byte、integer、double、float
    • Object:对象
      es里面没有数组类型,json格式key、value,允许value有多个值。上图id字段
  • index:是否创建索引,默认为true。就是是否创建倒排索引,不创建之后就不能通过它搜索。
  • analyzer:使用那种分词器
  • properties:该字段的子字段,上面name

索引库的crud

json 复制代码
# 建立索引库
PUT /linsy
{
  "mappings": {
    "properties": {
      "info": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "email": {
        "type": "keyword",
        "index": false
      },
      "name": {
        "type": "object",
        "properties": {
          "firstname": {
            "type": "keyword"
          },
          "lastName": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

查询索引库 GET /索引库名 GET /linsy

删除索引库 DELETE /索引库名

ES 禁止修改索引库字段类型及属性,会影响整个索引的结构,但是允许在原有索引库中新增字段。

注意不能新增已有字段

json 复制代码
PUT /索引库名/_mapping
{
  "properties": {
    "新字段名": {
      "type": "integer"
    }
  }
}

文档的crud

新增操作

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

{

"字段1": "值1",

"字段2": "值2",

"字段3": "值3",

}

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

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

json 复制代码
# 文档操作
# 插入
POST /linsy/_doc/1
{
  "age": "11",
  "email": "linsy@linsy.work",
  "info": "this is a first test 文档",
  "name": {
    "firstname": "明",
    "lastName": "李"
  }
}


GET  /linsy/_doc/1

DELETE /linsy/_doc/1

POST /linsy/_update/1
{
  "doc":{
    "age":1111
  }
}

修改文档:

  • 全量修改:删除旧文档,添加新文档。就是将上面新增的 DSL 改为 PUT
json 复制代码
PUT /索引库名/_doc/文档id
{
  "字段1": "值1",
  "字段2": "值2",
  "字段3": "值3",
}
  • 增量修改,修改指定字段
json 复制代码
POST /索引库名/_update/文档id
{
  "doc":{
    "字段名":"新的值"
  }
}

RestClient

springboot 导入elasticsearch依赖需注意,它默认使用的版本和springboot的版本一致,你需要对应到安装在服务器上的版本。

xml 复制代码
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
        </dependency>
xml 复制代码
    <properties>
        <java.version>8</java.version>
        <elasticsearch.version>7.17.11</elasticsearch.version>
    </properties>


创建索引库的mapping映射

json 复制代码
PUT /hotel
{
  "mappings": {
    "properties": {
      "id":{
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "ik_max_word",
        "copy_to": "all"
      },
      "address":{
        "type": "keyword",
        "index": false
      },
      "price":{
        "type": "integer"
      },
      "score":{
        "type": "integer"
      },
      "brand":{
        "type": "keyword",
        "copy_to": "all"
      },
      "city":{
        "type": "keyword"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword",
        "copy_to": "all"
      },
      "location":{
        "type": "geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

RestHighLevelClient 的使用

java 复制代码
        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://http://192.168.52.150:9200")
        ));

        // index的增删查
        CreateIndexRequest createIndexRequest = new CreateIndexRequest("linsy");
        createIndexRequest.source("建立索引库语句(put)", XContentType.JSON);
        restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
        restHighLevelClient.indices().delete(new DeleteIndexRequest("要删除的索引库名"), RequestOptions.DEFAULT);
        // 判断是否存在
        boolean b = restHighLevelClient.indices().exists(new GetIndexRequest("索引库名"), RequestOptions.DEFAULT);

es8.x已经弃用了RestHighLevelClient
官方创建RestClient文档

文档的crud

查询文档

java 复制代码
    @Autowired
    private IHotelService iHotelService;
    
    @BeforeEach
    public void before() {
        restHighLevelClient = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.52.150:9200")
        ));
    }

    @AfterEach
    public void after() throws IOException {
        restHighLevelClient.close();
    }

    @Test
    public void addDocumentTest() throws IOException {
        Hotel hotel = iHotelService.getById(61075);
        HotelDoc hotelDoc = new HotelDoc(hotel);
        IndexRequest indexRequest = new IndexRequest("hotel").id(hotel.getId().toString());
        indexRequest.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
        restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
    }

    @Test
    public void queryDocumentTest() throws IOException {
        GetResponse getResponse = restHighLevelClient.get(new GetRequest("hotel", "61075"), RequestOptions.DEFAULT);
        String json = getResponse.getSourceAsString();
        System.out.println(json);
    }

    @Test
    public void updateDocumentTest() throws IOException {
        UpdateRequest updateRequest = new UpdateRequest("hotel", "61075");
        updateRequest.doc(
                "city", "北京",
                "score", "90"
        );
        restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
    }

    @Test
    public void deleteDocumentTest() throws IOException {
        restHighLevelClient.delete(new DeleteRequest("hotel", "61075"), RequestOptions.DEFAULT);
    }
    
    @Test
    public void batchAdd() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();

        List<Hotel> list = iHotelService.list();
        for (Hotel hotel : list) {
            HotelDoc hotelDoc = new HotelDoc(hotel);
            bulkRequest.add(new IndexRequest("hotel")
                    .id(hotel.getId().toString())
                    .source(JSON.toJSONString(hotelDoc), XContentType.JSON));

        }
        restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
    }

DSL

查询

DSL 查询种类

  • 查询所有:查询所有数据,一般在测试时使用。march_all,但是一般显示全部,有一个分页的功能
  • 全文检索(full text)查询:利用分词器对用户的输入内容进行分词,然后去倒排索引库匹配。例如:
    • match_query
    • mutil_match_query
  • 精确查询:根据精确词条值查询数据,一般查找的时keyword、数值、日期、boolean等字段。例如:
    • ids
    • term
    • range
  • 地理查询(geo):根据经纬度查询,例如:
    • geo_distance
    • geo_bounding_box
  • 复合(compound)查询:复合查询时将上面各种查询条件组合在一起,合并查询条件。例如:
    • bool
    • funcation_score

DSL query 基本语法

json 复制代码
# DSL查询
GET /indexName/_search
{
  "query":{
    "查询类型":{
      "查询条件":"条件值"
    }
  }
}

match 与 multi_match 的与别是前者根据单字段查,后者根据多字段查。

参与搜索的字段越多,查询效率越低,建议利用copy_to将多个检索字段放在一起,然后使用match---all字段查。

json 复制代码
GET /hotel/_search
{
  "query": {
    "match": {
      "city": "上海"
    }
  }
}

GET /hotel/_search
{
  "query": {
    "match": {
      "all": "如家"
    }
  }
}

 GET /hotel/_search
 {
   "query": {
     "multi_match": {
       "query": "如家",
       "fields": ["name","brand","business"]
     }
   }
 }

精确查询: term字段全值匹配,range字段范围匹配。

精确查询一般查找keyword、数值、boolean等不可分词的字段

json 复制代码
# term
GET /hotel/_search
{
  "query": {
    "term": {
      "city": {
        "value": "北京"
      }
    }
  }
}
# range
GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "gt": 1000,
        "lt": 2000
      }
    }
  }
}

地理查询:

json 复制代码
GET /hotel/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": {
          "lat": 31.1,
          "lon": 121.5
        },
        "bottom_right": {
          "lat": 30.9,
          "lon": 121.7
        }
      }
    }
  }
}

GET /hotel/_search
{
  "query": {
    "geo_distance": {
      "distance": "20km",
      "location": {
        "lat": 31.13,
        "lon": 121.8
      }
    }
  }
}

复合查询(compound ):将简单查询条件组合在一起,实现复杂搜索逻辑。

  • function score:算分函数查询,可以控制文档的相关性算分,控制排名。例如百度竞价

es在5.1及之后就弃用了 TF-IDF 算法,开始采用 BM25算法。BM25算法不会因为词的出现频率变大而导致算分无限增大,会逐渐趋近一个值

function score query :可以修改文档相关性算分,得到新的算分。

三要素

  • 过滤条件:决定哪些条件要加分
  • 算分函数:如何计算function score
  • 加权方式:function score 与 query score如何运算
json 复制代码
GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {
        "match": {
          "all": "如家酒店"
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "city": "上海"
            }
          },
          "weight": 10
        }
      ],
      "boost_mode": "sum"
    }
  }
}

boolean query:布尔查询是一个或多个子查询的组合。

  • must:必须匹配每个子查询,类似"and"
  • should:选择性匹配子查询,类似"or"
  • must_not:必须不匹配,不参与算分,类似"非"
  • filter:必须匹配,不参与算分
json 复制代码
GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "all": "上海"
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "gt": 500
            }
          }
        }
      ],
      "filter": [
        {
          "geo_distance": {
            "distance": "10km",
            "location": {
              "lat": 31.21,
              "lon": 121.5
            }
          }
        }
      ]
    }
  }
}

搜索结构处理

排序

es支持对搜索结构进行排序,默认是根据相关度算分(_score)进行排序。可以排序的字段有keyword,数值、地理坐标、日期类型等。

json 复制代码
GET /hotel/_search
{
  "query": {
    "match_all": {}
  },"sort": [
    {
      "id": {
        "order": "desc"
      }
    }
  ]
}
GET /hotel/_search
{
  "query": {
    "match_all": {}
  },"sort": [
    {
      "_geo_distance": {
        "location": {
          "lat": 31.2,
          "lon": 121.5
        },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}

这个排序的结果就是相聚的公里数。

分页

针对深度分页;ES给出了两种方案

  • search after:分页时需要排序,原理是从上次的排序值开始(末尾值),查询下一页的数据。官方推荐使用,不会太占内存。手机向下反动滚页。
  • scroll:原理是将排序数据形成快照,保存在内存。不推荐

高亮

ES默认搜索字段和高亮字段必须一致,否则不会高亮。或者使用 "require_field_match": "false" 也能高亮。

最后将查询结果中 highlight 与 指定高亮的字段进行替换返回给前端就行。

RestClient

普通查询

java 复制代码
    @Test
    public void  testMatchAll() throws IOException {
        SearchRequest searchRequest = new SearchRequest("hotel");
        searchRequest.source().query(
                QueryBuilders.matchAllQuery()
        );
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits searchHits = searchResponse.getHits();

        long value = searchHits.getTotalHits().value;
        System.out.println(value);
        SearchHit[] hits = searchHits.getHits();
        System.out.println(hits[0]);
        HotelDoc hotelDoc = JSON.parseObject(hits[0].getSourceAsString(), HotelDoc.class);
        System.out.println(hotelDoc);
    }





        QueryBuilders.matchAllQuery()
        QueryBuilders.matchQuery("all","如家")
        QueryBuilders.multiMatchQuery("如家","name","brand","business")
        QueryBuilders.termQuery("city","上海")
        QueryBuilders.rangeQuery("price").gt(100).lt(400)
        
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.termQuery("city","北京"));
        boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gt(100).lt(400));

分页和排序

java 复制代码
    public void testPageAndSort() throws IOException {
        int pageNum = 2, pageSize = 10;

        SearchRequest searchRequest = new SearchRequest("hotel");
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brand", "如家");
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("all", "北京");
        boolQueryBuilder.must(termQueryBuilder);
        boolQueryBuilder.must(matchQueryBuilder);

        searchRequest.source().query(boolQueryBuilder);
        searchRequest.source().from((pageNum - 1) * pageSize).size(pageSize);
        searchRequest.source().sort("price", SortOrder.ASC);

        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHit[] hits = searchResponse.getHits().getHits();
        for (SearchHit hit : hits) {
            String source = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(source, HotelDoc.class);
            System.out.println(hotelDoc);
        }
    }

高亮

java 复制代码
    public void testHighLight() throws IOException {
        SearchRequest searchRequest = new SearchRequest("hotel");

        searchRequest.source().query(QueryBuilders.matchQuery("all","如家"));
        searchRequest.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        SearchHit[] hits = searchResponse.getHits().getHits();
        for (SearchHit hit : hits) {
            String source = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(source, HotelDoc.class);

            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if(!highlightFields.isEmpty()){
                HighlightField highlightField = highlightFields.get("name");
                //一般value只有一个元素,取数组第一个
                String name = highlightField.getFragments()[0].string();
                hotelDoc.setName(name);
            }
            System.out.println(hotelDoc);
        }
    }

让指定酒店置顶 (function_score )

相关推荐
拓端研究室TRL2 小时前
【梯度提升专题】XGBoost、Adaboost、CatBoost预测合集:抗乳腺癌药物优化、信贷风控、比特币应用|附数据代码...
大数据
黄焖鸡能干四碗2 小时前
信息化运维方案,实施方案,开发方案,信息中心安全运维资料(软件资料word)
大数据·人工智能·软件需求·设计规范·规格说明书
编码小袁2 小时前
探索数据科学与大数据技术专业本科生的广阔就业前景
大数据
WeeJot嵌入式3 小时前
大数据治理:确保数据的可持续性和价值
大数据
晨欣3 小时前
Elasticsearch和Lucene之间是什么关系?(ChatGPT回答)
elasticsearch·chatgpt·lucene
zmd-zk4 小时前
kafka+zookeeper的搭建
大数据·分布式·zookeeper·中间件·kafka
激流丶4 小时前
【Kafka 实战】如何解决Kafka Topic数量过多带来的性能问题?
java·大数据·kafka·topic
测试界的酸菜鱼4 小时前
Python 大数据展示屏实例
大数据·开发语言·python
时差9534 小时前
【面试题】Hive 查询:如何查找用户连续三天登录的记录
大数据·数据库·hive·sql·面试·database
Mephisto.java4 小时前
【大数据学习 | kafka高级部分】kafka中的选举机制
大数据·学习·kafka