ElasticSearch是一个搜索服务器
服务器,我们见过许多,如tomcat服务器
搜索功能我们也见过许多:百度、谷歌、电商购物等等
关系型数据库也能实现相似功能,但是由于字符串查询的like语句,导致效率下降
ElasticSearch学习记录 - 图1
如这样的一条sql语句,我们想要查询跟“华为手机”相关的,可以这样写,但其实我们的想法是查询带有“华为”,或者带有“手机”的titile记录。需要使用到一个很关键的东西:倒排索引
ElasticSearch学习记录 - 图2
如第一个问题,可能比较难以相关诗句或者比较少,但是如果是第二个问题,相信很多人都可以快速地回答上来,原因就是因为这是一种正向索引地思想,而第一个问题,则是倒排索引的提现。
需要将词语拆分成不同的词条
ElasticSearch学习记录 - 图3
如这一句诗被拆分成多个不同的词条。
与关系型数据库不同,ES数据库存储的是索引(index),而其内容是文档(document)
ElasticSearch学习记录 - 图4
如每一行的数据
{
id
title
price
}
而在存储中,则是以词条切分的数据作为索引,而value对用的是真实数据中的索引【在这个案例中就是id序号】
对于每一条数据的倒排索引,如果词条在原先已经存在【黑色的数据】那么就不加或者如果又新增的value,就在value那一栏的对应索引上新增数据,如果是之气那没有的索引,则将该此台哦加入索引中。
ElasticSearch学习记录 - 图5
当进行查询时,如查询词分词条后有一条为“手机”,那么就查询手机对应的词条好 1,2,3,因此根据词条去查数据,是很快的。
hr小姐姐你好,我已悉知offer内容,很高兴能够获得富途集团基于我的信任,因为个人原因,经个人仔细考虑,做出以下抉择:申请放弃此次2022届富途集团秋招-web后台开发工程师一职位的offer。非常感谢富途集团给我的认可,对于拒绝的原因,乃我个人所抉择,主要有两点:1、有其他公司为我提供了更好的发展平台,出具了更具有吸引力的薪资、福利等。2、可能贵公司使用的技术方向与我个人职业规划有所出入,与我当前预期的发展反向可能有所差异,经过慎重考虑,我个人还是希望能够选择我当前坚持的方向。最终我做出了如上的一个选择。真心非常感谢富途集团对我的支持和信任,十分抱歉占用了你们校招组的时间,给你们添麻烦了。
王先生你好,我已悉知offer内容,很高兴能够获得达达集团给予我的信任,经个人仔细考虑,做出以下抉择:愿意接受offer并加入达达集团。十分感谢达达集团对我个人的信任和支持,希望能够在后来的日子与达达集团一同成长,为构建更好的达达平台奉献自己的一份力量

初识ElasticSearch

  • ES是一个基于Lucene的搜索服务器。而大名鼎鼎的solr也是基于Lucene服务器的
  • 是一个分布式、高拓展、高实时的搜索与数据分析引擎
  • 基于RESTFulWeb接口
  • ElasticSearch是用Java语言开发的,并作为Apache许可条款下开放源码发布,是一种流行的企业级搜搜引擎

ES功能的体现,虽然能提高搜索效率,但是真正存储数据,还是需要用到关系型数据库,如mysql
ES和mysql的分工不一样,MYSQL存储管理数据,而ES负责数据搜索
ElasticSearch学习记录 - 图6
经过p6的教程【我忘记做笔记了】跟着做后可以成功启动,看到如下效果
ElasticSearch学习记录 - 图7

Kibana安装

是一个基于ES的开源分析及可视化平台
安装接下后
vim /opt/kibana-7.4.0-linux-x86_64/config/kibana.yml

添加
server.port: 5601
server.host: “0.0.0.0”
server.name: “kibana-itcast”
elasticsearch.hosts: [“http://127.0.0.1:9200“] # 这里是要监视的ES节点
elasticsearch.requestTimeout: 99999

进入bin目录
./kibana # 启动kibana
进入后点击
ElasticSearch学习记录 - 图8

ES中的核心概念

索引Index

ES数据存储的地方,可以理解成关系型数据库中数据库的概念

映射Mapping

映射优点类似于mysql中的表结构,一个mappings就是一张表,id、title‘price是字段名,内部的type表示其类别
ElasticSearch学习记录 - 图9

文档Document

ElasticSearch学习记录 - 图10
文档可以看作是一条条的数据,而文档中的数据可以生成词条,形成倒排索引

倒排索引

一个倒排索引由文档中所有不重复的词的列表构成,对于其中每个词,对应一个包含它的文档id列表

类型

一种type就像一类表。如用户表、角色表等。在ElasticSearch7.x中默认type为_doc

  • ES5.x 中一个index可以有多种type
  • ES6.x 中一个index只能有一种type
  • ES7.x 以后,主键移除type这个概念,现在操作已不再使用,莫瑞诺_doc

    操作ElasticSearch

    RESTFul风格

    GET\POST\PUT\DELETE是我们一般使用给的增删改查的方法

    往ES中添加索引

    request: PUT
    http://192.168.200.132:9200/goods_index
    response:
    {
    “acknowledged”: true,
    “shards_acknowledged”: true,
    “index”: “goods_index”
    }

    查询ES中的索引的值

    request: GET
    http://192.168.200.132:9200/goods_index
    response:
    {
    “goods_index”: {
    “aliases”: {},
    “mappings”: {},
    “settings”: {
    “index”: {
    “routing”: {
    “allocation”: {
    “include”: {
    “_tier_preference”: “data_content”
    }
    }
    },
    “number_of_shards”: “1”,
    “provided_name”: “goods_index”,
    “creation_date”: “1635681380434”,
    “number_of_replicas”: “1”,
    “uuid”: “ROuVQeF6TQOrxmObGpFXLQ”,
    “version”: {
    “created”: “7150199”
    }
    }
    }
    }
    }

    删除索引

    request:DELETE
    http://192.168.200.132:9200/goods_index
    response:
    {
    “acknowledged”: true
    }

    开启/关闭索引

    关闭索引不同于删除索引,关闭索引并不想删除索引,而是想要起到一个关闭的效果,不让别人访问,开启则是恢复
    request:POST
    http://192.168.200.132:9200/goods_index/_close
    http://192.168.200.132:9200/goods_index/_open
    response:
    {
    “acknowledged”: true,
    “shards_acknowledged”: true,
    “indices”: {
    “goods_index”: {
    “closed”: true
    }
    }
    }

{
“acknowledged”: true,
“shards_acknowledged”: true
}

一些公共指令

查询所有索引信息
http://192.168.200.132:9200/_all # 下划线开头的命令一般都是内置指令
{
“.kibana_1”: {
“aliases”: {
“.kibana”: {}
},

… 省略很多内容
}

操作映射

在ES中,支持两种数据类型
数据类型

  • 简单数据类型
  • 复杂数据类型

    简单数据类型

  • 字符串

    • text:会分词,不支持聚合
    • keyword:不会分词,将全部内容作为一个词条,支持聚合

如,华为手机
text:华为、手机
keyword:华为手机

  • 数值
    • ElasticSearch学习记录 - 图11
  • 布尔值
    • boolean
  • 二进制
    • binary
  • 范围类型
    • integer_range,float_range,long_range,double_range,date_range
  • 日期
  • 数组
    • []
  • 对象
    • {}
    • 比如
      {
      “acknowledged”: true,
      “shards_acknowledged”: true,
      “indices”: {
      “goods_index”: {
      “closed”: true
      }
      }
      }

      使用脚本来操作映射

      打开kibana面板,选择到devTool面板
      ElasticSearch学习记录 - 图12
      可以看到语法是
      方法类型 参数
      # 以下是例子以及释义,在该控制台中可以单独执行一行

GET goods_index # 查看索引为goods_index的索引信息
PUT person # 添加一个person的索引

添加映射 给person添加一个映射,下面的内容要使用json格式,且必须跟在下一行不能隔离
PUT person/_mapping
{
“properties”:{
“name”:{
“type”:”keyword”
},
“age”:{
“type”:”integer”
}
}
}

查询添加映射后结果
GET person

查询映射
GET person/_mapping
返回如下
{
“person” : {
“mappings” : {
“properties” : {
“age” : {
“type” : “integer”
},
“name” : {
“type” : “keyword”
}
}
}
}
}

创建索引并添加映射 写法和上面的先添加索引再添加映射有一些出入
PUT person
{
“properties”:{
“name”:{
“type”:”keyword”
},
“age”:{
“type”:”integer”
}
}
}
# 追加映射上的信息 添加一个address属性,并且希望其是text类型的,也就是可分词类型

PUT person/_mapping
{
“properties”:{
“address”:{
“type”:”text”
}
}
}
ElasticSearch学习记录 - 图13

使用脚本添加文档

涉及到的id,是倒排索引时要用到的id
# 添加文档,指定ID 用put或者post都可以
PUT person/_doc/1
{
“name”:”张三”,
“age”:20,
“address”:”沈阳大街”
}

添加文档,不指定ID ,ID会随机生成 注意 这里是post不是put
POST person/_doc
{
“name”:”李四”,
“age”:30,
“address”:”沈阳大街”
}

修改文档,也是使用PUT,如果ID已经存在,那么put一次就是修改该文档
PUT person/_doc/1
{
“name”:”张三2”,
“age”:20,
“address”:”沈阳大街”
}

添加文档,不指定ID
PUT person/_doc
{
“name”:”李四”,
“age”:30,
“address”:”沈阳大街”
}

ElasticSearch学习记录 - 图14

分词器

分词器(Analyzer):讲一段文本,按照一定的逻辑,分析称多个词语的一种工机具
如华为手机——》华为、手机
ES内置有许多的分词器
ElasticSearch学习记录 - 图15
# 如果不指定分词器,就使用默认的分词器
GET _analyze
{
“analyzer”: “standard”,
“text”: [“我爱北京天安门”]
}
结果如下
{
“tokens” : [
{
“token” : “我”,
“start_offset” : 0,
“end_offset” : 1,
“type” : ““,
“position” : 0
},
{
“token” : “爱”,
“start_offset” : 1,
“end_offset” : 2,
“type” : ““,
“position” : 1
},
{
“token” : “北”,
“start_offset” : 2,
“end_offset” : 3,
“type” : ““,
“position” : 2
},
{
“token” : “京”,
“start_offset” : 3,
“end_offset” : 4,
“type” : ““,
“position” : 3
},
{
“token” : “天”,
“start_offset” : 4,
“end_offset” : 5,
“type” : ““,
“position” : 4
},
{
“token” : “安”,
“start_offset” : 5,
“end_offset” : 6,
“type” : ““,
“position” : 5
},
{
“token” : “门”,
“start_offset” : 6,
“end_offset” : 7,
“type” : ““,
“position” : 6
}
]
}
# 如果不指定分词器,及使用默认的
GET _analyze
{
“analyzer”: “standard”,
“text”: [“i love you”]
}

{
“tokens” : [
{
“token” : “i”,
“start_offset” : 0,
“end_offset” : 1,
“type” : ““,
“position” : 0
},
{
“token” : “love”,
“start_offset” : 2,
“end_offset” : 6,
“type” : ““,
“position” : 1
},
{
“token” : “you”,
“start_offset” : 7,
“end_offset” : 10,
“type” : ““,
“position” : 2
}
]
}

可以看到,由于我们中文是由许多不间隔的汉字组成的,因此如果直接使用默认分词器,得到的效果是比较差的,这是看得见的,因此可以使用中文专用的分词器

中文分词器,IK分词器

  • IK分词器是一个开源的,基于JAVA开发的轻量级的中文分词工具包
  • 是一个基于Maven构建的项目
  • 具有60w字/秒的高速处理能力

IK分词器安装过程看P16。
ik分词器有两种分词模式,ik_max_word和ik_smart模式
1、ik_max_word
将文本做最细粒度的拆分,比如华为手机-》华为、手、机
# 细粒度
GET _analyze
{
“analyzer”: “ik_max_word”,
“text”: [“我爱北京”]
}
# 结果如下
{
“tokens” : [
{
“token” : “我”,
“start_offset” : 0,
“end_offset” : 1,
“type” : “CN_CHAR”,
“position” : 0
},
{
“token” : “爱”,
“start_offset” : 1,
“end_offset” : 2,
“type” : “CN_CHAR”,
“position” : 1
},
{
“token” : “北京”,
“start_offset” : 2,
“end_offset” : 4,
“type” : “CN_WORD”,
“position” : 2
}
]
}
# 粗粒度
GET _analyze
{
“analyzer”: “ik_smart”,
“text”: [“我爱北京”]
}

结果如下
{
“tokens” : [
{
“token” : “我”,
“start_offset” : 0,
“end_offset” : 1,
“type” : “CN_CHAR”,
“position” : 0
},
{
“token” : “爱”,
“start_offset” : 1,
“end_offset” : 2,
“type” : “CN_CHAR”,
“position” : 1
},
{
“token” : “北京”,
“start_offset” : 2,
“end_offset” : 4,
“type” : “CN_WORD”,
“position” : 2
}
]
}

查询文档

词条查询:trem

term 词条查询,查询的条件,字符串和词条完全匹配
GET person/_search
{
“query”:{
“term”: {
“address”: {
“value”: “沈阳”
}
}
}
}

可以看到分词如下
{
“took” : 1,
“timed_out” : false,
“_shards” : {
“total” : 1,
“successful” : 1,
“skipped” : 0,
“failed” : 0
},
“hits” : {
“total” : {
“value” : 3,
“relation” : “eq”
},
“max_score” : 0.13353139,
“hits” : [
{
“_index” : “person”,
“_type” : “_doc”,
“_id” : “hHmg2XwBGuAB1NQnp6OX”,
“_score” : 0.13353139,
“_source” : {
“name” : “李四”,
“age” : 30,
“address” : “沈阳大街”
}
},
{
“_index” : “person”,
“_type” : “_doc”,
“_id” : “1”,
“_score” : 0.13353139,
“_source” : {
“name” : “张三2”,
“age” : 20,
“address” : “沈阳大街”
}
},
{
“_index” : “person”,
“_type” : “_doc”,
“_id” : “4”,
“_score” : 0.13353139,
“_source” : {
“name” : “张三2”,
“age” : 20,
“address” : “沈阳大街”
}
}
]
}
}
全文查询:match,会将词语得到后先对分词,再查询,再求交集
GET person/_search
{
“query”: {
“match”: {
“address”: “北京东平”
}
}
}
# 会将北京东平拆分为北京、东平,然后发现有北京可以匹配
{
“took” : 15,
“timed_out” : false,
“_shards” : {
“total” : 1,
“successful” : 1,
“skipped” : 0,
“failed” : 0
},
“hits” : {
“total” : {
“value” : 1,
“relation” : “eq”
},
“max_score” : 0.9808291,
“hits” : [
{
“_index” : “person”,
“_type” : “_doc”,
“_id” : “2”,
“_score” : 0.9808291,
“_source” : {
“name” : “李四”,
“age” : 25,
“address” : “北京大街”
}
}
]
}
}

Springboot整合ES

开启一个springboot工程后添加依赖


org.elasticsearch.client
elasticsearch-rest-high-level-client
7.15.1


org.elasticsearch.client
elasticsearch-rest-client
7.15.1


org.elasticsearch
elasticsearch
7.15.1

编写配置文件application.yml 从中添加
elasticsearch:
host: 192.168.200.132
port: 9200
编写配置类
@Configuration
@ConfigurationProperties(prefix = “elasticsearch”)
public class ElasticConfig {

  1. private String host;
  2. private int port;
  3. public String getHost() {<br /> return host;<br /> }
  4. public void setHost(String host) {<br /> this.host = host;<br /> }
  5. public int getPort() {<br /> return port;<br /> }
  6. public void setPort(int port) {<br /> this.port = port;<br /> }
  7. @Bean // 到时候要用到相关api时直接注入即可<br /> RestHighLevelClient client(){<br /> RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(<br /> new HttpHost(host,port,"http")<br /> ));<br /> return client;<br /> }<br />}

之后就可以在需要用到的地方使用@Autowired注入了

添加索引

/*
添加索引
*/
@Test
public void addIndex() throws Exception{
//1、 使用client来获取操作索引的对象
IndicesClient indices = client.indices();

    //2、 具体操作,获取返回值<br />        CreateIndexRequest createIndex = new CreateIndexRequest("itcast"); // 传入一个索引名称,<br />        // 2、1 设置mapping ,上面设置的是一个index,而index上则是可以设置mapping的<br />        // 配置以下mapping

    String mapping="{\n" +<br />                "      \"properties\" : {\n" +<br />                "        \"address\" : {\n" +<br />                "          \"type\" : \"text\",\n" +<br />                "          \"analyzer\" : \"ik_max_word\"\n" +<br />                "        },\n" +<br />                "        \"age\" : {\n" +<br />                "          \"type\" : \"long\"\n" +<br />                "        },\n" +<br />                "        \"name\" : {\n" +<br />                "          \"type\" : \"keyword\"\n" +<br />                "        }\n" +<br />                "      }\n" +<br />                "    }";<br />        createIndex.mapping("_doc",mapping, XContentType.JSON); // 因为版本有点过时,因此第一个参数时type<br />        CreateIndexResponse createIndexResponse = indices.create(createIndex, RequestOptions.DEFAULT);// 通常第二个参数为DEFAULT即可

    // 根据返回值做判断<br />        System.out.println(createIndexResponse); // 有没有查到结果,都能在这里体现

}

ES高级操作

高级操作涉及到比较多的查询操作,而这些操作,是比较复杂的,也是需要花比较多时间去学习的内容

  • 批量操作
  • 导入数据
  • 各种查询
  • 索引别名和重建索引

    批量操作-脚本

    语法
    POST /_bulk
    {“action”:{“metadata”}}
    {“data”}

    # 批量操作
    # 1、删除5号记录
    # 2、增加8号记录
    # 3、更新2号记录,改名为二号
    POST _bulk
    {“delete”:{“_index”:”person”,”_id”:”5”}}
    {“create”:{“_index”:”person”,”_id”:”8”}}
    {“name”:”daoge”,”age”:”8”,”address”:”shenyang”}
    {“update”:{“_index”:”person”,”_id”:”2”}}
    {“doc”:{“name”:”二号”}}

结果如下,由于是脚本操作,一行的执行成功与否与其他行没有关系
{
“took” : 13,
“errors” : false,
“items” : [
{
“delete” : {
“_index” : “person”,
“_type” : “_doc”,
“_id” : “5”,
“_version” : 2,
“result” : “deleted”,
“_shards” : {
“total” : 2,
“successful” : 1,
“failed” : 0
},
“_seq_no” : 5,
“_primary_term” : 2,
“status” : 200
}
},
{
“create” : {
“_index” : “person”,
“_type” : “_doc”,
“_id” : “8”,
“_version” : 1,
“result” : “created”,
“_shards” : {
“total” : 2,
“successful” : 1,
“failed” : 0
},
“_seq_no” : 6,
“_primary_term” : 2,
“status” : 201
}
},
{
“update” : {
“_index” : “person”,
“_type” : “_doc”,
“_id” : “2”,
“_version” : 2,
“result” : “updated”,
“_shards” : {
“total” : 2,
“successful” : 1,
“failed” : 0
},
“_seq_no” : 7,
“_primary_term” : 2,
“status” : 200
}
}
]
}

导入数据

  • 将数据库中的GOODS表的数据导入到ES中
    实现步骤:
    1、创建goods索引
    2、查询Goods表的数据
    3、批量添加数据到ES中
    1、创建goods索引并添加相应的mapping结构
    PUT goods
    {
    “mappings”:{
    “properties”:{
    “title”:{
    “type”:”text”,
    “analyzer”:”ik_smart”
    },
    “price”:{
    “type”:”double”
    },
    “createTime”:{
    “type”:”date”
    },
    “categoryName”:{
    “type”:”keyword”
    },
    “brandName”:{
    “type”:”keyword”
    },
    “spec”:{
    “type”:”object”
    },
    “saleNum”:{
    “type”:”integer”
    },
    “stock”:{
    “type”:”integer”
    }
    }
    }
    }
    # 得到如下响应即代表成功
    {
    “acknowledged” : true,
    “shards_acknowledged” : true,
    “index” : “goods”
    }

在JavaAPI中导入数据
@Test
public void testBulkAdd() throws IOException {
List all = goodsMapper.findAll();
System.out.println(all.size());

    // 2、bulk的创建

    BulkRequest bulkRequest = new BulkRequest();<br />        // 2、1循环,创建IndexRequet添加数据<br />        for (Goods good : all) {<br />            // 2、2 设置spec规格,Map形式的数据,specStr:{}<br />            good.setSpec(JSON.parseObject(good.getSpecStr(),Map.class)); // 转换成Map格式的数据,相当于返回一个map

        IndexRequest indexRequest = new IndexRequest("goods");<br />            indexRequest.id(good.getId()+"").source(JSON.toJSONString(good),XContentType.JSON);<br />            bulkRequest.add(indexRequest);<br />        }<br />        BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);//  还是熟悉的默认请求选项,以及一个bulk请求体

}<br />在JAVA-api中做matchAll查询<br /> /**<br />     * 各种查询<br />     * 上述用到了批量导入数据到ES中,下面介绍如何进行各种查询<br />     * 1、matchAll:查询所有文档<br />     * 2、将查询结果封装为Goods对象,并装载到list中<br />     * 3、分页操作<br />     */<br />    @Test<br />    public void testMatchAll() throws Exception{<br />        // 2、构建查询请求对象, 指定查询的索引名称<br />        SearchRequest searchRequest = new SearchRequest("goods");

    // 4、创建查询条件构建起SearchBuilder<br />        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();<br />        // 3、添加条件查询构建器 SearchSourceBuilder<br />        searchRequest.source(sourceBuilder);<br />        // 8、添加分页信息<br />        sourceBuilder.from(0);<br />        sourceBuilder.size(10); // 从第0条记录开始,查10条记录

    // 6、查询条件<br />        QueryBuilder query = QueryBuilders.matchAllQuery();// 查询所有文档<br />        // 5、指定查询条件<br />        sourceBuilder.query(query);

    // 1、查询结果<br />        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

    // 7、获取命中的对象<br />        SearchHits searchResponseHits = searchResponse.getHits();<br />        long value = searchResponseHits.getTotalHits().value;<br />        System.out.println("总记录数:"+value);<br />        // 7、2 获取hits数据 数组<br />        SearchHit[] hits = searchResponseHits.getHits();<br />        List<Goods> goods = new ArrayList<>();<br />        for (SearchHit hit : hits) {

        String sourceAsString = hit.getSourceAsString();<br />            System.out.println("一条记录是: "+sourceAsString);<br />            Goods good = JSON.parseObject(sourceAsString, Goods.class);<br />            goods.add(good);<br />        }<br />        System.out.println("查询到的数量是"+goods.size());

}<br />在Java-api中做term查询<br /># 不单单查询文本,还可以根据操作查 or / and <br />GET goods/_search<br />{<br />  "query": {<br />    "match": {<br />      "title": {<br />        "query": "华为手机"<br />        , "operator": "or"<br />      }<br />    }<br />  }<br />}<br />  /**<br />     * term词条模式查询<br />     * 和上面的matchAll,其实是一样的流程,知识条件又些许不同<br />     */<br />    @Test<br />    public void testTermQuery() throws IOException{<br />        SearchRequest searchRequest = new SearchRequest("goods");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();<br />        QueryBuilder queryBuilder = QueryBuilders.termQuery("title","化为");<br />        searchSourceBuilder.query(queryBuilder);<br />        searchRequest.source(searchSourceBuilder);

    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);<br />        // 获取记录数<br />        SearchHits hits = searchResponse.getHits();<br />        long value = hits.getTotalHits().value;<br />        // 获取记录数<br />        System.out.println("总记录数:"+value);<br />        for (SearchHit hit : hits) {<br />            System.out.println(hit.getSourceAsString());<br />        }<br />    }<br />在Java-api,match模式<br />  /**<br />     * 与term相对应的,match查询<br />     * GET goods/_search<br />     * {<br />     *   "query": {<br />     *     "match": {<br />     *       "title": {<br />     *         "query": "华为手机"<br />     *         , "operator": "or"<br />     *       }<br />     *     }<br />     *   }<br />     * }<br />     */<br />    @Test<br />    public void testMatchQuery() throws Exception{<br />        SearchRequest searchMatch = new SearchRequest("goods");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();<br />        QueryBuilder query = QueryBuilders.matchQuery("title","华为手机");<br />        ((MatchQueryBuilder)query).operator(Operator.OR) ;<br />        searchMatch.source(sourceBuilder);<br />        SearchResponse search = client.search(searchMatch, RequestOptions.DEFAULT);<br />        SearchHits hits = search.getHits();<br />        long value = hits.getTotalHits().value;<br />        System.out.println("关于华为手机的搜索记录有:"+value+"条");<br />    }

模糊查询-脚本

wildcard查询:会对查询条件进行分词,还可以使用通配符,(任意单个字符)和*(0或者多个字符)
regexp查询:正则查询
prefix:前缀查询
# wildcard查询,查询条件分词,模糊匹配

GET goods/_search
{
“query”: {
“wildcard”: {
“title”: {
“value”:”手*” # 这里可以使用通配符来替代信息
}
}
}
}

@Test
public void testWildCardQuery() throws Exception{
SearchRequest searchMatch = new SearchRequest(“goods”);

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();<br />        QueryBuilder query = QueryBuilders.wildcardQuery("title","华为手机"); // 修改为wildcardQuery<br />        ((MatchQueryBuilder)query).operator(Operator.OR) ;<br />        searchMatch.source(sourceBuilder);<br />        SearchResponse search = client.search(searchMatch, RequestOptions.DEFAULT);<br />        SearchHits hits = search.getHits();<br />        long value = hits.getTotalHits().value;<br />        System.out.println("关于华为手机的搜索记录有:"+value+"条");<br />    }<br /># 正则查询,可以使用正则表达式来查询

GET goods/_search
{
“query”: {
“regexp”: {
“title”: “\w+(.)*”
}
}
}

@Test
public void testRegexQuery() throws Exception{
SearchRequest searchMatch = new SearchRequest(“goods”);

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();<br />        QueryBuilder query = QueryBuilders.regexpQuery("title","\\w+(.)*"); // 修改为正则查询,可以进行模式匹配<br />        ((MatchQueryBuilder)query).operator(Operator.OR) ;<br />        searchMatch.source(sourceBuilder);<br />        SearchResponse search = client.search(searchMatch, RequestOptions.DEFAULT);<br />        SearchHits hits = search.getHits();<br />        long value = hits.getTotalHits().value;<br />        System.out.println("关于华为手机的搜索记录有:"+value+"条");<br />    }

前缀查询,可以查询以xx为前缀的东西
GET goods/_search
{
“query”: {
“prefix”: {
“title”: {
“value”: “化”
}
}
}
}

@Test
public void testPrefixQuery() throws Exception{
SearchRequest searchMatch = new SearchRequest(“goods”);

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();<br />        QueryBuilder query = QueryBuilders.prefixQuery("title","小米"); // 修改为前缀查询,查询小米开头的<br />        ((MatchQueryBuilder)query).operator(Operator.OR) ;<br />        searchMatch.source(sourceBuilder);<br />        SearchResponse search = client.search(searchMatch, RequestOptions.DEFAULT);<br />        SearchHits hits = search.getHits();<br />        long value = hits.getTotalHits().value;<br />        System.out.println("关于华为手机的搜索记录有:"+value+"条");<br />    }

范围查询range
/
范围查询
/
/

prefix 前缀查询
@throws Exception
*/
@Test
public void testRangeQuery() throws Exception{
SearchRequest searchMatch = new SearchRequest(“goods”);

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();<br />        // 这两行指定范围查询<br />        RangeQueryBuilder query = QueryBuilders.rangeQuery("price");<br />        // 指定上下限<br />        query.gte(1000);<br />        query.lte(2000); // 查询2000-3000的数据<br />        sourceBuilder.query(query);<br />        sourceBuilder.sort("price", SortOrder.DESC); // 排序

    searchMatch.source(sourceBuilder);<br />        SearchResponse search = client.search(searchMatch, RequestOptions.DEFAULT);<br />        SearchHits hits = search.getHits();<br />        long value = hits.getTotalHits().value;<br />        System.out.println("关于华为手机的搜索记录有:"+value+"条");<br />    }<br />queryString查询-脚本<br />queryString:
  • 会对查询条件进行分词
  • 然后将分词的查询条件和词条进行等值匹配
  • 默认取并集(OR)
  • 可以指定多个查询字段

queryString
GET goods/_search
{
“query”:{
“query_string”: {
“fields”: [“title”,”brandName”,”categoryName”],
“query”: “华为 AND 手机”
}
}
}

simpleQueryString 不支持or、and这些连接符
GET goods/_search
{
“query”:{
“simple_query_string”: {
“fields”: [“title”,”brandName”,”categoryName”],
“query”: “华为 AND 手机” # 也能查,但是是分成了3个词取查 华为 AND 手机 这三个词都去查,然后取并集
}
}
}

布尔查询

布尔查询指的不是特定的查询,而是一种辅助查询,比如查询多种查询后的交集,或者说在一个查询的基础上再做查询。
boolQuery:对多个查询条件连接。连接方式:

  • must(and):条件必须成立
  • must_not(and):条件必须不成立
  • should(or):条件可以成立
  • filter:条件必须成立,性能比must高,不会计算得分

布尔查询

GET goods/_search
{
“query”: {
“bool”: {
“must”: [
{
“term”: {
“brandName”: {
“value”: “化为”
}
}

},
{

“match”: {
“title”: “手机”
}
}
]
}
}
}

boolQuery 采用filter查询和must一样的效果,但是性能更高
GET goods/_search
{
“query”: {
“bool”: {
“filter”: {
“term”: {
“title”: “化为”
}
}
}
}
}

聚合查询-脚本

  • 指标聚合(aggs):相当于Mysql的聚合函数,max,min,avg,sum等
  • 桶聚合:相当于Mysql的group by 操作。不要对text类型的数据进行分组,会失败

问一个问题,是直接给价格还是谈薪后确认价格?
# 指标聚合 聚合函数 查最贵的价格
# aggs == aggregations
GET goods/_search
{
“query”: {
“match”: {
“title”: “化为”
}
},
“aggs”: {
“max”: {
“max”: {
“field”: “price”
}
}
}
}

桶聚合 分组 goods_brands 是自己拟定的名称
GET goods/_search
{
“query”: {
“match”: {
“title”: “手机”
}
},
“aggs”: {
“goods_brands”:{
“terms”: {
“field”:”brandName”,
“size”:10
}
}
}
}

Java-api实现聚合查询

/**<br />     * 聚合查询 桶聚合,分组查询<br />     * 1、查询title包含手机的数据<br />     * 2、查询品牌列表<br />     * @throws Exception<br />     */<br />    @Test<br />    public void testAggQuery() throws Exception{<br />        SearchRequest searchMatch = new SearchRequest("goods");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();<br />        // 1、指定title查询关于手机的数据<br />        QueryBuilder query = QueryBuilders.matchQuery("title","手机");

    sourceBuilder.query(query);<br />        // 2、查询品牌列表<br />        /**<br />         * 参数:<br />         *  1、自定义名称,相当于需要分组的数据<br />         *  2、<br />         */<br />        TermsAggregationBuilder agg = AggregationBuilders.terms("brands_names").field("brandName");<br />        sourceBuilder.aggregation(agg);

    searchMatch.source(sourceBuilder);<br />        SearchResponse searchResponse = client.search(searchMatch, RequestOptions.DEFAULT);<br />        SearchHits hits = searchResponse.getHits();<br />        long value = hits.getTotalHits().value;<br />        System.out.println("关于华为手机的搜索记录有:"+value+"条");

    // 通过searchResponse 来获取agg数据<br />        Aggregations aggregations = searchResponse.getAggregations();<br />        Map<String, Aggregation> stringAggregationMap = aggregations.asMap();<br />        Set<String> keyNames = stringAggregationMap.keySet();<br />        for (String keyName : keyNames) {<br />            System.out.println(keyName);<br />        }<br />        // 该返回值类型,是有许多子类的, 需要根据返回的结果定义子类,比如这里返回的是Terms,那就用Terms进行接收

    Terms brands_names = (Terms)stringAggregationMap.get("brands_names");<br />        List<? extends Terms.Bucket> buckets = brands_names.getBuckets();<br />        // bucket内是我们象牙获取到的数据<br />        List brand = new ArrayList();<br />        for (Terms.Bucket bucket : buckets) {<br />            Object key = bucket.getKey();<br />            brand.add(key);<br />        }<br />        for (Object o : brand) {<br />            System.out.println("品牌名"+o);<br />        }

}

高亮查询

在一些网页如百度,它的高亮字体原理是在选中的字体加上标签,然后对于该标签做一个样式的选择,这样就能达到高亮的效果,我们可以定义一些高亮标签,在查询后添加上标签
ElasticSearch学习记录 - 图16
在Java-api中的查询操作
/*
高亮查询,给查询到的标签设置前后缀标签
*/
@Test
public void testHighLightQuery() throws Exception{
SearchRequest searchMatch = new SearchRequest(“goods”);

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();<br />        // 1、指定title查询关于手机的数据<br />        QueryBuilder query = QueryBuilders.matchQuery("title","手机");<br />        sourceBuilder.query(query);<br />        // 设置高亮<br />        HighlightBuilder highlightBuilder = new HighlightBuilder();<br />        // 设置三要素<br />        highlightBuilder.field("title");<br />        highlightBuilder.preTags("<font color = 'red'>");<br />        highlightBuilder.postTags("</font>");<br />        sourceBuilder.highlighter(highlightBuilder);

    // 2、查询品牌列表<br />        /**<br />         * 参数:<br />         *  1、自定义名称,相当于需要分组的数据<br />         *  2、<br />         */<br />        TermsAggregationBuilder agg = AggregationBuilders.terms("brands_names").field("brandName");<br />        sourceBuilder.aggregation(agg);

    searchMatch.source(sourceBuilder);<br />        SearchResponse searchResponse = client.search(searchMatch, RequestOptions.DEFAULT);<br />        SearchHits hits = searchResponse.getHits();<br />        long value = hits.getTotalHits().value;<br />        System.out.println("关于华为手机的搜索记录有:"+value+"条");<br />        SearchHit[] hits1 = hits.getHits();<br />        for (SearchHit hit : hits1) {<br />            // 转化为json<br />            String sourceAsString = hit.getSourceAsString();<br />            Goods goods = JSON.parseObject(sourceAsString,Goods.class);<br />            // 获取为高亮的结果<br />            Map<String, HighlightField> highlightFields = hit.getHighlightFields();<br />            HighlightField highlightField = highlightFields.get("title");<br />            Text[] fragments = highlightField.fragments(); // 0号元素就是title<br />            goods.setTitle(fragments[0].toString());<br />            System.out.println(goods.toString());<br />        }

    // 该返回值类型,是有许多子类的, 需要根据返回的结果定义子类,比如这里返回的是Terms,那就用Terms进行接收

}

重建索引

随着业务需求增加,索引的结构可能发送改变
ElasticSearch的索引一旦创建,之允许添加字段没不允许改变字段,因为改变字段需要重建倒排索引,影响内部缓存结构,性能太低。
那么此时就需要重建一个新的索引,并将原有索引的数据导入到新索引中
# ——————- 重建索引 ——————————-
# 新建student_index_v1 索引名称必须全部都小写
PUT student_index_v1
{
“mappings”: {
“properties”: {
“birthday”:{
“type”:”date”
}
}
}
}

GET student_index_v1

PUT student_index_v1/_doc/1
{
“birthday”:”1999-11-11”
}

GET student_index_v1/_search

业务变更了,需要改变birthday字段的类型为text

但是是不允许修改索引的,因此需要创建新的索引

1、创建新的索引 student_index_v2
# 2、将数据拷贝到新的索引上
PUT student_index_v2
{
“mappings”: {
“properties”: {
“birthday”:{
“type”: “text”
}
}
}
}

将student_index_v1 数据拷贝到student_index_v2
# _reindex 拷贝数据
POST _reindex
{
“source”: {
“index”: “student_index_v1”
},
“dest”:{
“index”: “student_index_v2”
}
}
PUT student_index_v2/_doc/2
{
“birthday”:”1999-11-11”
}

发现数据拷贝成功了
GET student_index_v2/_search

GET student_index_v2/_doc/2

思考:如何在代码中实现如上操作
#1、改代码(不推荐)
#2、索引别名(推荐)

步骤
# 1、给student_index_v2起一个别名,为student_index_v1
# 给v2起个别名,但是在这之前需要删除 student_index_v1 才能做别名
DELETE student_index_v1
POST student_index_v2/_alias/student_index_v1

集群

集群和分布式:

  • 集群:多个人做一样的事
  • 分布式:多个人做不一样的事【如分布式计算,去计算不同的内容,而不是和集群一样共享数据】

但是其实分布式也可以和集群一起作用,比如当一个节点不足以容纳数据时,可将剩余数据部署到另外的机器上,然后形成这样的机器集群,如下图所示
ElasticSearch学习记录 - 图17
右边红框内分布式,而三个红框组成集群
集群解决问题:

  • 让系统高可用
  • 分担请求压力

分布式解决的问题:

  • 分担存储和计算的压力,提速
  • 解耦

    ElasticSearch 集群管理

  • ES天然支持分布式

  • ES的设计隐藏了分布式的复杂性【帮我们做好了许多铺垫工作,用户只需要引入配置使用即可 】

    一些相关概念

  • 集群(cluster):一组拥有共同的cluster name的节点

  • 节点(node):组成cluster的一个ElasticSearch实例
  • 索引(index):es存储数据的地方。相当于关系数据库中的database的概念
  • 分片(shard):索引可以被拆分为不同的部位进行存储,称为分片,在集群环境下,一个索引的不同分片可以拆分到不同的节点中
  • 主分片(primary shard):相对于副本分片的定义
  • 副本分片(Replica shard):每个主分片可以有一个或者多个副本,数据和主分片一样

ElasticSearch学习记录 - 图18
ElasticSearch学习记录 - 图19
比如,0,1,2是3份数据,如果1号节点挂了,那么0号数据就会丢失了,但是如果变成图2的结构,即使1号节点挂了,也可以在其他节点中找到0 1 2三份数据。这种数据变换的操作,ES会自动帮我们实现

集群搭建流程

一般来说,集群是搭建在不同服务器上的,因为没有那么多资源,因此搭建一个为集群,在同一台虚拟机上允许多个ES实例
集群环境如下

集群名 节点名 IP地址 http端口/通信端口
itcast-es itcast1 192.168.200.132 9201/9700
itcast-es itcast2 192.168.200.132 9202/9800
itcast-es itcast3 192.168.200.132 9203/9900

拷贝程序,直接复制文件夹多3份,再去修改器内部的配置文件
节点1配置
cluster.name: itcast-es
node.name: itcast-1
node.master: true
node.data: true
node.max_local_storage_nodes: 3
network.host: 0.0.0.0
http.port: 9201
transport.tcp.port: 9700
discovery.seed_hosts: [“localhost:9700”,”localhost:9800”,”localhost:9900”]
cluster.initial_master_nodes: [“itcast-1”,”itcast-2”,”itcast-3”]
path.data: /opt/data
path.logs: /opt/logs

cluster.name: itcast-es
node.name: itcast-2
node.master: true
node.data: true
node.max_local_storage_nodes: 3
network.host: 0.0.0.0
http.port: 9202
transport.tcp.port: 9800
discovery.seed_hosts: [“localhost:9700”,”localhost:9800”,”localhost:9900”]
cluster.initial_master_nodes: [“itcast-1”,”itcast-2”,”itcast-3”]
path.data: /opt/data
path.logs: /opt/logs

cluster.name: itcast-es
node.name: itcast-3
node.master: true
node.data: true
node.max_local_storage_nodes: 3
network.host: 0.0.0.0
http.port: 9203
transport.tcp.port: 900
discovery.seed_hosts: [“localhost:9700”,”localhost:9800”,”localhost:9900”]
cluster.initial_master_nodes: [“itcast-1”,”itcast-2”,”itcast-3”]
path.data: /opt/data
path.logs: /opt/logs

搭建完后启动,访问主节点,可以查看集群状态
ElasticSearch学习记录 - 图20
http://192.168.200.132:9201/_cat/health?v
Kibana访问集群,只需要
vim kibana/config/kibana.yml
修改其中配置域名改为
elasticsearch.hosts: [“http://localhost:9201","http://localhost:9202","http://localhost:9203“]
ElasticSearch学习记录 - 图21
点进去后也能查看相关的集群状态
即可,当然这是需要映射的节点,实际中应该不会是localhost

Java访问ES集群

ElasticSearch学习记录 - 图22
首先是链接的客户端,因为集群的节点不止一个,因此传入的HttpHost对象要有多个,RestClient.builder方法支持传入HttpHost对象数组,因此需要把集群节点的url都写上。
@Bean(“clusterClient”)
RestHighLevelClient clusterClient(){
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
new HttpHost(host1,port1,”http”), new HttpHost(host2,port2,”http”), new HttpHost(host3,port3,”http”)
)); // 在该方法中具有多个HttpHost
return client;
}

在调用方法中
@Test
public void clusterClient() throws Exception{
GetRequest test_index = new GetRequest(“test_index”);
test_index.id(“1”);
GetResponse documentFields = clusterClient.get(test_index, RequestOptions.DEFAULT);
String sourceAsString = documentFields.getSourceAsString();
System.out.println(sourceAsString);
}

查看分片

在脚本中可以查看到一个索引的分片情况
GET test_index
# 结果如下
{
“test_index” : {
“aliases” : { },
“mappings” : {
“properties” : {
“name” : {
“type” : “text”
}
}
},
“settings” : {
“index” : {
“routing” : {
“allocation” : {
“include” : {
“_tier_preference” : “data_content”
}
}
},
“number_of_shards” : “1”, # 这里表示分片片号
“provided_name” : “test_index”,
“creation_date” : “1635951870643”,
“number_of_replicas” : “1”, # 分片副本数
“uuid” : “MyZfeA07Toy2wu22AREfOA”,
“version” : {
“created” : “7150199”
}
}
}
}
}


点击查看集群信息后
ElasticSearch学习记录 - 图23
点进去可以看到分片相关消息
ElasticSearch学习记录 - 图24
点击overview可以看到一些图
ElasticSearch学习记录 - 图25
从颜色可以看到1号节点是副本分片,而2号节点的是主分片

指定分片

  • 在创建索引时,如果不指定分片配置,则默认主分片为1,副分片1
  • 创建的时候指定分片配置
  • “settings”:{
    “number_of_shards”:3, # 分片数
    “number_of_replicas”:1 # 每一个分片的备份数
    }
    # setting是与mapping同级的配置 如下

PUT test_index2
{
“settings”: {
“number_of_shards”: 3,
“number_of_shards”: 1
},
“mappings”: {
“properties”: {
“name”:{
“type”: “text”
}
}
}
}

其对应分片状况如下ElasticSearch学习记录 - 图26

甚至当其中一个节点挂了,其余节点会自动调整
ElasticSearch学习记录 - 图27
这就是ES内置的分布式功能,十分强大,且不需要用户去过度干预

  • 分片与自然平衡:当节点挂点后,挂掉的节点分片会自平衡到其他节点中
  • 在ES中,每个查询在每个分片的单线程中执行,但是,可以并行处理多个分片
  • 分片数量一旦确定好,就不能修改
  • 索引分片推荐配置方案
  • 每个分片大小推荐10-30G
  • 分片推荐数量=节点数量*1-3倍

思考,有1000G的数据,应该分多少片?多少个节点
比如每个分片去20G,那么就是50个分片,分片数量/2,那么等于25,推荐25个节点是不错的选择

路由原理

  • 文档存入对应的分片,ES计算分片编号的过程,称为路由
  • ES怎么知道文档存到哪个分片中呢
  • 根据ID查询,或者词条查询,都是通过ID查询,那么ES怎么知道ID在哪个分片中呢
  • 路由算法:shard_index = hash(id) % number_of_primary_shards,找到该id的存储分片,有点hash结构内卫了

    脑裂

    有点像精神分裂的意思,就是说有多个抉择,不知道该选择哪个

  • 一个正常的集群中,只有一个主节点,主节点负责管理整个集群。如创建或者删除索引,跟踪哪些节点是集群中的一部分,并决定哪些节点分片分配给相关的节点。

  • 集群的所有节点都会选择同一个节点作为主节点
  • 脑裂问题的出现就是因为从节点在选择主节点是出现分歧导致一个集群出现多个主节点从而使集群分裂,是的集群处于异常状态【就是有的节点不认为主节点为主节点,额选择其他节点作为主节点,就造成了多个主节点】
  • 多个主节点,就会导致数据不一致。

    导致原因

    1、网络延迟
    网络的延迟可能让多个节点选择不同的节点作为主节点,如部署在外网上,延迟可能较高,
    ElasticSearch学习记录 - 图28
    如1 2 3 4 节点能够认为1号是主节点,而5 6 7 8 可能由于网络原因,连接不到1号,误以为挂了,因此5 6 7 8 统一口径认为1号是主节点,就出现了两个主节点
    2、结点负载过高
    主节点是data结点也是master结点,访问量较大时,可能导致Master结点停止响应(假死状态)
    ElasticSearch学习记录 - 图29
    3、JVM进行GC

  • 当Master设置的JVM内存较小时,引发JVM的大规模内存回收,造成ES进程停止响应