• 高并发高可用之ElasticSearch


    ES里面的数据怎么保持与mysql实时同步?
    都存内存 数据不会越来越多吗?有过期时间吗?

    ES对比MySql数据库

    ES的数据存储在磁盘中,数据操作在内存中。

    • 索引:数据库
    • 类型:数据表
    • 文档:表里的数据
    • 属性:表列名

    在这里插入图片描述
    注意:ElasticSearch6.0之后移除了类型的概念。7.x使用类型会警告,8.x将彻底废除。

    Docker下安装ES和kibana

    安装ES

    # 将docker里的目录挂载到linux的/mydata目录中
    # 修改/mydata就可以改掉docker里的
    mkdir -p /mydata/elasticsearch/config
    mkdir -p /mydata/elasticsearch/data
    
    # es可以被远程任何机器访问
    echo "http.host: 0.0.0.0" >/mydata/elasticsearch/config/elasticsearch.yml
    
    # 递归更改文件访问权限,es需要访问
    chmod -R 777 /mydata/elasticsearch/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    docker pull elasticsearch:7.4.2
    docker pull kibana:7.4.2
    版本要统一
    
    • 1
    • 2
    • 3
    # 9200是用户交互端口 9300是集群心跳端口
    # -e指定是单阶段运行
    # -e ES_JAVA_OPTS="-Xms64m -Xmx512m"指定初始占用内存大小和最大占用大小
    # 反斜杠表示换行
    docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
    -e "discovery.type=single-node" \
    -e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
    -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
    -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
    -v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
    -d elasticsearch:7.4.2
    
    
    # 设置开机启动elasticsearch
    docker update elasticsearch --restart=always
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    查看日志命令:docker logs elasticsearch
    查看docker镜像ID命令:docker ps -a
    运行docker镜像:docker start 镜像ID
    访问:
    在这里插入图片描述

    安装kibana

    # 指定了ES交互端口9200和IP地址
    docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.239.134:9200 -p 5601:5601 -d kibana:7.4.2
    
    
    # 设置开机启动kibana
    docker update kibana  --restart=always
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    kibana访问地址:http://192.168.239.134:5601/

    增删改查操作

    (1)GET /_cat/nodes:查看所有节点
    (2)GET /_cat/health:查看es健康状况
    (3)GET /_cat/master:查看主节点
    (4)GET /_cat/indices:查看所有索引 ,等价于mysql数据库的show databases;

    新增/更新
    PUT/POST /索引名/类型名/ID

    http://192.168.56.10:9200/索引名/类型名/ID
    请求参数Json{
     "name":"John Doe"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    支持put和post,post不写ID可以自动生产。对一个ID多次操作都会变为update操作。

    查询
    GET /索引名/类型名/ID

    更新

    POST /索引名/类型名/ID/_update
    {
        "doc":{
            "name":"111"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    加_update参数就要加doc。
    POST时带_update会对比元数据,如果一样就不进行任何操作。

    删除
    删除文档数据
    DELETE /索引名/类型名/ID
    删除索引
    DELETE /索引名

    注:elasticsearch并没有提供删除类型的操作,只提供了删除索引和文档的操作。

    批量执行
    在指定索引和类型下批量执行
    POST /索引名/类型名/_bulk
    在整个ES中批量执行
    POST /_bulk

    高级检索Query DSL

    1. query/match匹配查询
      如果是非字符串,会进行精确匹配。如果是字符串,会进行全文检索

      GET bank/_search
      {
        "query": {
          "match": {
            "account_number": "20"
          }
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    2. query/match_phrase 【不拆分匹配】
      将需要匹配的值当成一整个单词(不分词)进行检索。
      – match_phrase:不拆分字符串进行检索,包含就匹配成功。
      – 字段.keyword:必须全匹配上才检索成功。

      GET bank/_search
      {
        "query": {
          "match_phrase": {
            "address": "990 Mill"
          }
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      GET bank/_search
      {
        "query": {
          "match": {
            "address.keyword": "990 Mill"  # 字段后面加上 .keyword
          }
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    3. query/multi_math 【多字段匹配】

      GET bank/_search
      {
        "query": {
          "multi_match": {  # 前面的match仅指定了一个字段。
            "query": "mill",
            "fields": [ # state和address有mill子串  不要求都有
              "state",
              "address"
            ]
          }
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    4. query/bool/must 【复合查询】
      – must:必须达到must所列举的所有条件
      – must_not:必须不匹配must_not所列举的所有条件。
      – should:应该满足should所列举的条件。满足条件最好,不满足也可以,满足得分更高

      GET bank/_search
      {
        "query": {
          "bool": {
            "must": [
              {
                "match": {
                  "gender": "M"
                }
              },
              {
                "match": {
                  "address": "mill"
                }
              }
            ],
            "must_not": [
              {
                "match": {
                  "age": "18"
                }
              }
            ],
            "should": [
              {
                "match": {
                  "lastname": "Wallace"
                }
              }
            ]
          }
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
    5. query/filter 【结果过滤】
      must 贡献得分
      should 贡献得分
      must_not 不贡献得分
      filter 不贡献得分

      GET bank/_search
      {
        "query": {
          "bool": {
            "must": [
              { "match": {"address": "mill" } }
            ],
            "filter": {  # query.bool.filter
              "range": {
                "balance": {  # 哪个字段
                  "gte": "10000",
                  "lte": "20000"
                }
              }
            }
          }
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
    6. query/term
      和match一样。匹配某个属性的值。
      – 全文检索字段用match,
      – 其他非text文本字段匹配用term。

    7. aggs 【聚合】
      复杂子聚合例子:查出所有年龄分布,并且这些年龄段中M的平均薪资和F的平均薪资以及这个年龄段的总体平均薪资

      GET bank/_search
      {
        "query": {
          "match_all": {}
        },
        "aggs": {
          "ageAgg": {
            "terms": {  #  看age分布
              "field": "age",
              "size": 100
            },
            "aggs": { # 子聚合
              "genderAgg": {
                "terms": { # 看gender分布
                  "field": "gender.keyword" # 注意这里,文本字段应该用.keyword
                },
                "aggs": { # 子聚合
                  "balanceAvg": {
                    "avg": { # 男性的平均
                      "field": "balance"
                    }
                  }
                }
              },
              "ageBalanceAvg": {
                "avg": { #age分布的平均(男女)
                  "field": "balance"
                }
              }
            }
          }
        },
        "size": 0
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34

    更多Aggregations聚合函数请参考官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.5/search-aggregations.html

    映射

    存入数据后ES会把字段自动映射一个数据类型。如果自动映射的数据类型不正确还可以手动指定映射。
    创建索引并指定映射

    PUT /my_index
    {
      "mappings": {
        "properties": {
          "age": {
            "type": "integer"
          },
          "email": {
            "type": "keyword" # 指定为keyword
          },
          "name": {
            "type": "text" # 全文检索。保存时候分词,检索时候进行分词匹配
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    查看映射:GET /my_index

    有映射的情况下添加新的字段并指定映射

    PUT /my_index/_mapping
    {
      "properties": {
        "employee-id": {
          "type": "keyword",
          "index": false # 字段不能被检索。检索
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    更新映射
    由于改变映射会影响到该字段下的数据,故想要更新映射只支持把数据迁移到新的映射规则下。
    数据迁移:

    POST _reindex
    {
      "source": {
        "index": "bank",		#数据源索引
        "type": "account" 		#6.0后没有类型可以不写该行
      },
      "dest": {
        "index": "newbank"		#要迁移到的新索引
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    安装中文IK分词器

    下载并解压elasticsearch-analysis-ik-7.4.2到安装ES时挂载的插件外部目录/mydata/elasticsearch/plugins
    配置ik插件目录访问权限并重启ES容器
    注意:IK版本必须和ES版本一致

    使用
    支持两种分词模式:ik_smart , ik_max_word

    GET _analyze
    {
       "analyzer": "ik_smart", 
       "text":"我是中国人"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    扩展IK分词器有两种方式

    1. 编写一个项目让IK访问。
    2. 词条配置到一个nginx让IK访问
      – 在nginx的html目录下创建es目录并创建fenci.txt文件,在fenci.txt中写入自定义的词语,每行一条。
      – 修改/plugins/ik/config中的IKAnalyzer.cfg.xml文件:
      – 配置远程扩展字典访问地址
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
      <properties>
      	<comment>IK Analyzer 扩展配置</comment>
      	<!--用户可以在这里配置自己的扩展字典 -->
      	<entry key="ext_dict"></entry>
      	 <!--用户可以在这里配置自己的扩展停止词字典-->
      	<entry key="ext_stopwords"></entry>
      	<!--用户可以在这里配置远程扩展字典 -->
      	<entry key="remote_ext_dict">http://192.168.56.10/es/fenci.txt</entry> 
      	<!--用户可以在这里配置远程扩展停止词字典-->
      	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
      </properties>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

    参考:https://github.com/medcl/elasticsearch-analysis-ik

    SpringBoot整合ES

    推荐使用Elasticsearch-Rest-Client:官方RestClient,封装了ES操作,API层次分明,上手简单。

    1. 创建一个es-search微服务,可以勾选spring web组件,依赖common模块,配置注册中心,配置中心等配置

    2. 引入maven依赖,依赖版本要和ES版本保持一致

      <dependency>
          <groupId>org.elasticsearch.client</groupId>
          <artifactId>elasticsearch-rest-high-level-client</artifactId>
          <version>7.4.2</version>
      </dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5

      由于当前spring-boot版本默认依赖管理的ES版本是6.8.5,故要改为手动管理ES版本

      <properties>
          <java.version>1.8</java.version>
          <elasticsearch.version>7.4.2</elasticsearch.version>
      </properties>
      
      • 1
      • 2
      • 3
      • 4
    3. 编写ES配置类

      @Configuration
      public class ESConfig {
          //对所有请求进行配置项
          public static final RequestOptions COMMON_OPTIONS;
      
          static {
              RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
              COMMON_OPTIONS = builder.build();
          }
      
          @Bean
          public RestHighLevelClient esRestClient() {
              // 这里可以一次性指定多个es
              RestClientBuilder builder = RestClient.builder(new HttpHost("192.168.239.134", 9200, "http"));
              RestHighLevelClient client = new RestHighLevelClient(builder);
              return client;
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    4. 使用,参考官方文档 https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-getting-started-initialization.html

      package com.example.essearch;
      
      import com.alibaba.fastjson.JSON;
      import com.example.essearch.config.ESConfig;
      import org.elasticsearch.action.index.IndexRequest;
      import org.elasticsearch.action.index.IndexResponse;
      import org.elasticsearch.action.search.SearchRequest;
      import org.elasticsearch.action.search.SearchResponse;
      import org.elasticsearch.client.RestHighLevelClient;
      import org.elasticsearch.common.xcontent.XContentType;
      import org.elasticsearch.index.query.QueryBuilders;
      import org.elasticsearch.search.SearchHit;
      import org.elasticsearch.search.SearchHits;
      import org.elasticsearch.search.aggregations.AggregationBuilders;
      import org.elasticsearch.search.aggregations.Aggregations;
      import org.elasticsearch.search.aggregations.bucket.terms.Terms;
      import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
      import org.elasticsearch.search.aggregations.metrics.Avg;
      import org.elasticsearch.search.aggregations.metrics.AvgAggregationBuilder;
      import org.elasticsearch.search.builder.SearchSourceBuilder;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      
      import java.io.IOException;
      
      @SpringBootTest
      class EsSearchApplicationTests {
      
          @Autowired
          private RestHighLevelClient client;
          
          /**
           * 创建/更新索引
           * @throws IOException
           */
          @Test
          public void indexData() throws IOException {
              User user = new User();
              user.setUserName("张三");
              user.setAge(20);
              user.setGender("男");
              String jsonString = JSON.toJSONString(user);
      
              // 设置索引,索引名为users
              IndexRequest indexRequest = new IndexRequest ("users");
              indexRequest.id("1");
              //设置要保存的内容,指定数据和类型
              indexRequest.source(jsonString, XContentType.JSON);
      
              //执行创建索引和保存数据
              IndexResponse index = client.index(indexRequest, ESConfig.COMMON_OPTIONS);
      
              System.out.println(index);
          }
      
      
          /**
           * 高级检索与聚合分析
           * @throws IOException
           */
          @Test
          public void searchData() throws IOException {
              SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
              // 构造检索条件
              //sourceBuilder.query();
              //sourceBuilder.from();
              //sourceBuilder.size();
              //sourceBuilder.aggregation();
              sourceBuilder.query(QueryBuilders.matchQuery("address","mill"));
      
              // 聚合
              //AggregationBuilders工具类构建AggregationBuilder
              // 构建第一个聚合条件:按照年龄的值分布
              TermsAggregationBuilder agg1 = AggregationBuilders.terms("agg1").field("age").size(10);// 设置聚合名称为agg1
              sourceBuilder.aggregation(agg1);
              // 构建第二个聚合条件:平均薪资
              AvgAggregationBuilder agg2 = AggregationBuilders.avg("agg2").field("balance");// 设置聚合名称为agg2
              sourceBuilder.aggregation(agg2);
      
              System.out.println("检索条件"+sourceBuilder.toString());
      
              // 1 创建检索请求
              SearchRequest searchRequest = new SearchRequest();
              searchRequest.indices("bank");  //设置请求索引为bank
              searchRequest.source(sourceBuilder);
      
              // 2 执行检索
              SearchResponse response = client.search(searchRequest, ESConfig.COMMON_OPTIONS);
              // 3 分析响应结果
              System.out.println(response.toString());
      
      
              // 3.1 获取java bean
              SearchHits hits = response.getHits();
              SearchHit[] hitsList = hits.getHits();
              for (SearchHit hit : hitsList) {
                  hit.getId();
                  hit.getIndex();
                  String sourceAsString = hit.getSourceAsString();
                  Account account = JSON.parseObject(sourceAsString, Account.class);
                  System.out.println(account);
      
              }
      
              // 3.2 获取检索到的聚合分析信息
              Aggregations aggregations = response.getAggregations();
              Terms agg1Terms = aggregations.get("agg1");
              for (Terms.Bucket bucket : agg1Terms.getBuckets()) {
                  String keyAsString = bucket.getKeyAsString();
                  System.out.println("年龄:"+keyAsString+"=====>"+bucket.getDocCount());
              }
      
              Avg agg2Avg = aggregations.get("agg2");
              System.out.println("平均薪资:"+agg2Avg.getValue());
          }
      
      
      
      
      
      
          class User{
              private String userName;
              private Integer age;
              private String gender;
      
              public String getUserName() {
                  return userName;
              }
      
              public void setUserName(String userName) {
                  this.userName = userName;
              }
      
              public Integer getAge() {
                  return age;
              }
      
              public void setAge(Integer age) {
                  this.age = age;
              }
      
              public String getGender() {
                  return gender;
              }
      
              public void setGender(String gender) {
                  this.gender = gender;
              }
          }
      
      
          static class Account
          {
              private int account_number;
      
              private int balance;
      
              private String firstname;
      
              private String lastname;
      
              private int age;
      
              private String gender;
      
              private String address;
      
              private String employer;
      
              private String email;
      
              private String city;
      
              private String state;
      
              public void setAccount_number(int account_number){
                  this.account_number = account_number;
              }
              public int getAccount_number(){
                  return this.account_number;
              }
              public void setBalance(int balance){
                  this.balance = balance;
              }
              public int getBalance(){
                  return this.balance;
              }
              public void setFirstname(String firstname){
                  this.firstname = firstname;
              }
              public String getFirstname(){
                  return this.firstname;
              }
              public void setLastname(String lastname){
                  this.lastname = lastname;
              }
              public String getLastname(){
                  return this.lastname;
              }
              public void setAge(int age){
                  this.age = age;
              }
              public int getAge(){
                  return this.age;
              }
              public void setGender(String gender){
                  this.gender = gender;
              }
              public String getGender(){
                  return this.gender;
              }
              public void setAddress(String address){
                  this.address = address;
              }
              public String getAddress(){
                  return this.address;
              }
              public void setEmployer(String employer){
                  this.employer = employer;
              }
              public String getEmployer(){
                  return this.employer;
              }
              public void setEmail(String email){
                  this.email = email;
              }
              public String getEmail(){
                  return this.email;
              }
              public void setCity(String city){
                  this.city = city;
              }
              public String getCity(){
                  return this.city;
              }
              public void setState(String state){
                  this.state = state;
              }
              public String getState(){
                  return this.state;
              }
          }
      
      
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
      • 109
      • 110
      • 111
      • 112
      • 113
      • 114
      • 115
      • 116
      • 117
      • 118
      • 119
      • 120
      • 121
      • 122
      • 123
      • 124
      • 125
      • 126
      • 127
      • 128
      • 129
      • 130
      • 131
      • 132
      • 133
      • 134
      • 135
      • 136
      • 137
      • 138
      • 139
      • 140
      • 141
      • 142
      • 143
      • 144
      • 145
      • 146
      • 147
      • 148
      • 149
      • 150
      • 151
      • 152
      • 153
      • 154
      • 155
      • 156
      • 157
      • 158
      • 159
      • 160
      • 161
      • 162
      • 163
      • 164
      • 165
      • 166
      • 167
      • 168
      • 169
      • 170
      • 171
      • 172
      • 173
      • 174
      • 175
      • 176
      • 177
      • 178
      • 179
      • 180
      • 181
      • 182
      • 183
      • 184
      • 185
      • 186
      • 187
      • 188
      • 189
      • 190
      • 191
      • 192
      • 193
      • 194
      • 195
      • 196
      • 197
      • 198
      • 199
      • 200
      • 201
      • 202
      • 203
      • 204
      • 205
      • 206
      • 207
      • 208
      • 209
      • 210
      • 211
      • 212
      • 213
      • 214
      • 215
      • 216
      • 217
      • 218
      • 219
      • 220
      • 221
      • 222
      • 223
      • 224
      • 225
      • 226
      • 227
      • 228
      • 229
      • 230
      • 231
      • 232
      • 233
      • 234
      • 235
      • 236
      • 237
      • 238
      • 239
      • 240
      • 241
      • 242
      • 243
      • 244
      • 245
      • 246
      • 247
      • 248

    实战应用

    ES数据模型结构的设计
    空间和时间不可兼得两种只能选其一。
    方案1:

    {
        skuId:1
        spuId:11
        skyTitile:华为xx
        price:999
        saleCount:99
        attr:[
            {尺寸:5},
            {CPU:高通945},
            {分辨率:全高清}
    	]
    缺点:如果每个sku都存储规格参数(如尺寸),会有冗余存储,因为每个spu对应的sku的规格参数都一样
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    方案2:

    sku索引
    {
        spuId:1
        skuId:11
    }
    attr索引
    {
        skuId:11
        attr:[
            {尺寸:5},
            {CPU:高通945},
            {分辨率:全高清}
    	]
    }
    先找到4000个符合要求的spu,再根据4000个spu查询对应的属性,封装了4000个id,
    每次传输大小:如id为long类型,8B*4000=32000B=32KB
    1K个人检索,就是32MB,高并发下会造成严重阻塞。
    
    结论:如果将规格参数单独建立索引,会出现检索时出现大量数据传输的问题,会引起网络网络
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    创建索引并设置映射

    PUT product
    {
        "mappings":{
            "properties": {
                "skuId":{ "type": "long" },
                "spuId":{ "type": "keyword" },  # 不可分词
                "skuTitle": {
                    "type": "text",
                    "analyzer": "ik_smart"  # 中文分词器
                },
                "skuPrice": { "type": "keyword" },  
                "skuImg"  : { 
                	"type": "keyword" ,
                	"index": false,  # 降低占用空间,不可被检索,不生成索引,只用做页面展示
                    "doc_values": false # 降低占用空间,不可被聚合,默认为true
                }, 
                "saleCount":{ "type":"long" },
                "hasStock": { "type": "boolean" },
                "hotScore": { "type": "long"  },
                "brandId":  { "type": "long" },
                "catalogId": { "type": "long"  },
                "brandName": { "type": "keyword" }, 
                "brandImg":{
                    "type": "keyword",
                    "index": false,  
                    "doc_values": false 
                },
                "catalogName": {"type": "keyword" }, 
                "attrs": {
                    "type": "nested",	# 重要!!!表示嵌入式,防止被ES自动扁平化处理
                    "properties": {
                        "attrId": {"type": "long"  },
                        "attrName": {
                            "type": "keyword",
                            "index": false,
                            "doc_values": false
                        },
                        "attrValue": {"type": "keyword" }
                    }
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    创建ES数据模型实体类

    @Data
    public class SkuEsModel { 
        private Long skuId;
        private Long spuId;
        private String skuTitle;
        private BigDecimal skuPrice;
        private String skuImg;
        private Long saleCount;
        private Boolean hasStock;
        private Long hotScore;
        private Long brandId;
        private Long catalogId;
        private String brandName;
        private String brandImg;
        private String catalogName;
        private List<Attr> attrs;
    
        @Data
        public static class Attr{
            private Long attrId;
            private String attrName;
            private String attrValue;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    封装数据到ES数据模型实体类并存入ES
    商品上架的同时进行封装商品数据并远程调用ES微服务保存到ES中
    (封装代码略)

    编写ES微服务保存数据的Controller层

    /*** 上架商品*/
    @PostMapping("/product") // ElasticSaveController
    public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels){
    
        boolean status;
        try {
            status = productSaveService.productStatusUp(skuEsModels);
        } catch (IOException e) {
            log.error("ElasticSaveController商品上架错误: {}", e);
            return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnum.PRODUCT_UP_EXCEPTION.getMsg());
        }
        if(!status){
            return R.ok();
        }
        return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnum.PRODUCT_UP_EXCEPTION.getMsg());
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    编写ES微服务保存数据的Service层

    public class ProductSaveServiceImpl implements ProductSaveService {
    
    	@Resource
    	private RestHighLevelClient client;
    
    	/**
    	 * 将数据保存到ES
    	 * 用bulk代替index,进行批量保存
    	 * BulkRequest bulkRequest, RequestOptions options
    	 */
    	@Override // ProductSaveServiceImpl
    	public boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
    		// 1.给ES建立一个索引 product
    		BulkRequest bulkRequest = new BulkRequest();
    		// 2.构造保存请求
    		for (SkuEsModel esModel : skuEsModels) {
    			// 设置es索引
    			IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
    			// 设置索引id
    			indexRequest.id(esModel.getSkuId().toString());
    			// json格式
    			String jsonString = JSON.toJSONString(esModel);
    			indexRequest.source(jsonString, XContentType.JSON);
    			// 添加到文档
    			bulkRequest.add(indexRequest);
    		}
    		// bulk批量保存
    		BulkResponse bulk = client.bulk(bulkRequest, GuliESConfig.COMMON_OPTIONS);
    		// TODO 是否拥有错误
    		boolean hasFailures = bulk.hasFailures();
    		if(hasFailures){
    			List<String> collect = Arrays.stream(bulk.getItems()).map(item -> item.getId()).collect(Collectors.toList());
    			log.error("商品上架错误:{}",collect);
    		}
    		return hasFailures;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    检索查询参数模型分析
    可能用到的参数:
    全文检索:skuTitle->keyword
    排序:saleCount(销量)、hotScore(热度分)、skuPrice(价格)
    过滤:hasStock、skuPrice区间、brandId、catalog3Id、attrs(规格属性)
    聚合:attrs

    /**
    封装页面所有可能传递过来的关键字
     * catalog3Id=225&keyword=华为&sort=saleCount_asc&hasStock=0/1&brandId=25&brandId=30
     */
    @Data
    public class SearchParam {
    
        // 页面传递过来的全文匹配关键字
        private String keyword;
    
        /** 三级分类id*/
        private Long catalog3Id;
        //排序条件:sort=price/salecount/hotscore_desc/asc
        private String sort;
        // 仅显示有货
        private Integer hasStock;
    
        /*** 价格区间 */
        private String skuPrice;
    
        /*** 品牌id 可以多选 */
        private List<Long> brandId;
    
        /*** 按照属性进行筛选 */
        private List<String> attrs;
    
        /*** 页码*/
        private Integer pageNum = 1;
    
        /*** 原生所有查询属性*/
        private String _queryString;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    检索返回结果模型分析

    /**
     * <p>Title: SearchResponse</p>
     * Description:包含页面需要的所有信息
     */
    @Data
    public class SearchResult {
    
        /** * 查询到的所有商品信息(即前面的ES数据模型实体类)*/
        private List<SkuEsModel> products;
    
        /*** 当前页码*/
        private Integer pageNum;
        /** 总记录数*/
        private Long total;
        /** * 总页码*/
        private Integer totalPages;
    
        /** 当前查询到的结果, 所有涉及到的品牌*/
        private List<BrandVo> brands;
        /*** 当前查询到的结果, 所有涉及到的分类*/
        private List<CatalogVo> catalogs;
    	/** * 当前查询的结果 所有涉及到所有属性*/
        private List<AttrVo> attrs;
    
    	/** 导航页   页码遍历结果集(分页)  */
    	private List<Integer> pageNavs;
    //	================以上是返回给页面的所有信息================
    
        /** 导航数据*/
        private List<NavVo> navs = new ArrayList<>();
    
        /** 便于判断当前id是否被使用*/
        private List<Long> attrIds = new ArrayList<>();
    
        @Data
        public static class NavVo {
            private String name;
            private String navValue;
            private String link;
        }
    
        @Data
        public static class BrandVo {
    
            private Long brandId;
            private String brandName;
            private String brandImg;
        }
    
        @Data
        public static class CatalogVo {
            private Long catalogId;
            private String catalogName;
        }
    
        @Data
        public static class AttrVo {
    
            private Long attrId;
            private String attrName;
            private List<String> attrValue;
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    写出DSL检索语句,(如果是嵌入式的映射属性字段,检索查询,聚合,分析等都应该用相应的嵌入式语法nested)

    GET gulimall_product/_search
    {
      "query": {
        "bool": {
          "must": [ {"match": {  "skuTitle": "华为" }} ], # 检索出华为
          "filter": [ # 过滤
            { "term": { "catalogId": "225" } },
            { "terms": {"brandId": [ "2"] } }, 
            { "term": { "hasStock": "false"} },
            {
              "range": {
                "skuPrice": { # 价格1K~7K
                  "gte": 1000,
                  "lte": 7000
                }
              }
            },
            {
              "nested": {
                "path": "attrs", # 聚合名字
                "query": {
                  "bool": {
                    "must": [
                      {
                        "term": { "attrs.attrId": { "value": "6"} }
                      }
                    ]
                  }
                }
              }
            }
          ]
        }
      },
      "sort": [ {"skuPrice": {"order": "desc" } } ],
      "from": 0,
      "size": 5,
      "highlight": {  
        "fields": {"skuTitle": {}}, # 高亮的字段
        "pre_tags": "<b style='color:red'>",  # 前缀
        "post_tags": "</b>"
      },
      "aggs": { # 查完后聚合
        "brandAgg": {
          "terms": {
            "field": "brandId",
            "size": 10
          },
          "aggs": { # 子聚合
            "brandNameAgg": {  # 每个商品id的品牌
              "terms": {
                "field": "brandName",
                "size": 10
              }
            },
          
            "brandImgAgg": {
              "terms": {
                "field": "brandImg",
                "size": 10
              }
            }
            
          }
        },
        "catalogAgg":{
          "terms": {
            "field": "catalogId",
            "size": 10
          },
          "aggs": {
            "catalogNameAgg": {
              "terms": {
                "field": "catalogName",
                "size": 10
              }
            }
          }
        },
        "attrs":{
          "nested": {"path": "attrs" },
          "aggs": {
            "attrIdAgg": {
              "terms": {
                "field": "attrs.attrId",
                "size": 10
              },
              "aggs": {
                "attrNameAgg": {
                  "terms": {
                    "field": "attrs.attrName",
                    "size": 10
                  }
                }
              }
            }
          }
        }
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    检索查询代码实现
    controller

    @GetMapping(value = {"/search.html","/"})
    public String getSearchPage(SearchParam searchParam, // 检索参数,
                                Model model, HttpServletRequest request) {
        searchParam.set_queryString(request.getQueryString());//_queryString是个字段
        SearchResult result=searchService.getSearchResult(searchParam);
        model.addAttribute("result", result);
        return "search";
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    service

    @Slf4j
    @Service
    public class ProductSearchServiceImpl {
    
        @Resource
        private RestHighLevelClient restHighLevelClient;
    
        /**
         *  根据请求参数检索ES数据,并将检索结果封装为系统返回响应实体类
         * @param searchParam
         * @return
         */
        public SearchResult getSearchResult(SearchParam searchParam) {//根据带来的请求内容封装
            SearchResult searchResult= null;
            // 通过请求参数构建es查询请求
            SearchRequest request = bulidSearchRequest(searchParam);
            try {
                SearchResponse searchResponse = restHighLevelClient.search(request,
                        ESConfig.COMMON_OPTIONS);
                // 将es响应数据封装成结果
                searchResult = bulidSearchResult(searchParam,searchResponse);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return searchResult;
        }
    
    
        /**
         * 通过请求参数构建ES查询请求
         * @param searchParam
         * @return
         */
        private SearchRequest bulidSearchRequest(SearchParam searchParam) {
            // 用于构建DSL语句
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            //1. 构建bool query
            BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
            //1.1 bool must
            if (!StringUtils.isEmpty(searchParam.getKeyword())) {
                boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", searchParam.getKeyword()));
            }
    
            //使用不参与评分的filter,性能效率更高
            //1.2 bool filter
            //1.2.1 catalog
            if (searchParam.getCatalog3Id()!=null){
                boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", searchParam.getCatalog3Id()));
            }
            //1.2.2 brand
            if (searchParam.getBrandId()!=null&&searchParam.getBrandId().size()>0) {
                //值有多个为List时termsQuery
                boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
            }
            //1.2.3 hasStock
            if (searchParam.getHasStock() != null) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
            }
            //1.2.4 priceRange
            //解析自定义的区间参数格式,这里为0_6000,_6000,6000_分别表示大于0小于6000,小于6000,大于6000
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
            if (!StringUtils.isEmpty(searchParam.getSkuPrice())) {
                String[] prices = searchParam.getSkuPrice().split("_");
                if (prices.length == 1) {
                    if (searchParam.getSkuPrice().startsWith("_")) {
                        rangeQueryBuilder.lte(Integer.parseInt(prices[0]));
                    }else {
                        rangeQueryBuilder.gte(Integer.parseInt(prices[0]));
                    }
                } else if (prices.length == 2) {
                    //_6000会截取成["","6000"]
                    if (!prices[0].isEmpty()) {
                        rangeQueryBuilder.gte(Integer.parseInt(prices[0]));
                    }
                    rangeQueryBuilder.lte(Integer.parseInt(prices[1]));
                }
                boolQueryBuilder.filter(rangeQueryBuilder);
            }
            //1.2.5 attrs-nested  嵌入式属性使用嵌入式语法
            //attrs=1_5寸:8寸&2_16G:8G
            List<String> attrs = searchParam.getAttrs();
            BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
            if (attrs!=null&&attrs.size() > 0) {
                attrs.forEach(attr->{
                    String[] attrSplit = attr.split("_");
                    queryBuilder.must(QueryBuilders.termQuery("attrs.attrId", attrSplit[0]));
                    String[] attrValues = attrSplit[1].split(":");
                    queryBuilder.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
                });
            }
            NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs", queryBuilder, ScoreMode.None);
            boolQueryBuilder.filter(nestedQueryBuilder);
            //1.X bool query构建完成
            searchSourceBuilder.query(boolQueryBuilder);
    
    
    
    
            //2. sort  eg:sort=saleCount_desc/asc
            if (!StringUtils.isEmpty(searchParam.getSort())) {
                String[] sortSplit = searchParam.getSort().split("_");
                searchSourceBuilder.sort(sortSplit[0], sortSplit[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC);
            }
    
            //3. 分页 // 是检测结果分页
            searchSourceBuilder.from((searchParam.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE);
            searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
    
            //4. 高亮highlight
            if (!StringUtils.isEmpty(searchParam.getKeyword())) {
                HighlightBuilder highlightBuilder = new HighlightBuilder();
                highlightBuilder.field("skuTitle");
                highlightBuilder.preTags("<b style='color:red'>");
                highlightBuilder.postTags("</b>");
                searchSourceBuilder.highlighter(highlightBuilder);
            }
    
    
    
    
            //5. 聚合
            //5.1 按照brand聚合
            TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brandAgg").field("brandId");
            TermsAggregationBuilder brandNameAgg = AggregationBuilders.terms("brandNameAgg").field("brandName");
            TermsAggregationBuilder brandImgAgg = AggregationBuilders.terms("brandImgAgg").field("brandImg");
            //通过子聚合的方式就可以获取brand的中文名和图片了!!!
            brandAgg.subAggregation(brandNameAgg);
            brandAgg.subAggregation(brandImgAgg);
            searchSourceBuilder.aggregation(brandAgg);
    
            //5.2 按照catalog聚合
            TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalogAgg").field("catalogId");
            // 子聚合
            TermsAggregationBuilder catalogNameAgg = AggregationBuilders.terms("catalogNameAgg").field("catalogName");
            catalogAgg.subAggregation(catalogNameAgg);
            searchSourceBuilder.aggregation(catalogAgg);
    
            //5.3 按照attrs聚合  嵌入式属性使用嵌入式聚合语法
            NestedAggregationBuilder nestedAggregationBuilder = new NestedAggregationBuilder("attrs", "attrs");
            //按照attrId聚合     //按照attrId聚合之后再按照attrName和attrValue聚合
            TermsAggregationBuilder attrIdAgg    = AggregationBuilders.terms("attrIdAgg"   ).field("attrs.attrId");
            TermsAggregationBuilder attrNameAgg  = AggregationBuilders.terms("attrNameAgg" ).field("attrs.attrName");
            TermsAggregationBuilder attrValueAgg = AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue");
            attrIdAgg.subAggregation(attrNameAgg);
            attrIdAgg.subAggregation(attrValueAgg);
    
            nestedAggregationBuilder.subAggregation(attrIdAgg);
            searchSourceBuilder.aggregation(nestedAggregationBuilder);
    
            log.debug("构建的DSL语句 {}",searchSourceBuilder.toString());
    
            SearchRequest request = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, searchSourceBuilder);
            return request;
        }
    
    
        /**
         * 将ES响应数据封装成结果
         * @param searchParam
         * @param searchResponse
         * @return
         */
        private SearchResult bulidSearchResult(SearchParam searchParam, SearchResponse searchResponse) {
            SearchResult result = new SearchResult();
    
            SearchHits hits = searchResponse.getHits();
            //1. 封装查询到的商品信息
            if (hits.getHits()!=null&&hits.getHits().length>0){
                List<SkuEsModel> skuEsModels = new ArrayList<>();
                for (SearchHit hit : hits) {
                    String sourceAsString = hit.getSourceAsString();
                    SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
                    //设置高亮属性
                    if (!StringUtils.isEmpty(searchParam.getKeyword())) {
                        HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                        String highLight = skuTitle.getFragments()[0].string();
                        skuEsModel.setSkuTitle(highLight);
                    }
                    skuEsModels.add(skuEsModel);
                }
                result.setProducts(skuEsModels);
            }
    
            //2. 封装分页信息
            //2.1 当前页码
            result.setPageNum(searchParam.getPageNum());
            //2.2 总记录数
            long total = hits.getTotalHits().value;
            result.setTotal(total);
            //2.3 总页码
            Integer totalPages = (int)total % EsConstant.PRODUCT_PAGESIZE == 0 ?
                    (int)total / EsConstant.PRODUCT_PAGESIZE : (int)total / EsConstant.PRODUCT_PAGESIZE + 1;
            result.setTotalPages(totalPages);
            List<Integer> pageNavs = new ArrayList<>();
            for (int i = 1; i <= totalPages; i++) {
                pageNavs.add(i);
            }
            result.setPageNavs(pageNavs);
    
            //3. 查询结果涉及到的品牌
            List<SearchResult.BrandVo> brandVos = new ArrayList<>();
            Aggregations aggregations = searchResponse.getAggregations();
            //ParsedLongTerms用于接收terms聚合的结果,并且可以把key转化为Long类型的数据
            ParsedLongTerms brandAgg = aggregations.get("brandAgg");
            for (Terms.Bucket bucket : brandAgg.getBuckets()) {
                //3.1 得到品牌id
                Long brandId = bucket.getKeyAsNumber().longValue();
    
                //获取子聚合拿到brand中文名和图片
                Aggregations subBrandAggs = bucket.getAggregations();
                //3.2 得到品牌图片
                ParsedStringTerms brandImgAgg=subBrandAggs.get("brandImgAgg");
                String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
                //3.3 得到品牌名字
                Terms brandNameAgg=subBrandAggs.get("brandNameAgg");
                String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
                SearchResult.BrandVo brandVo = new SearchResult.BrandVo(brandId, brandName, brandImg);
                brandVos.add(brandVo);
            }
            result.setBrands(brandVos);
    
            //4. 查询涉及到的所有分类
            List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
            ParsedLongTerms catalogAgg = aggregations.get("catalogAgg");
            for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
                //4.1 获取分类id
                Long catalogId = bucket.getKeyAsNumber().longValue();
                Aggregations subcatalogAggs = bucket.getAggregations();
                //4.2 获取分类名
                ParsedStringTerms catalogNameAgg=subcatalogAggs.get("catalogNameAgg");
                String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
                SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo(catalogId, catalogName);
                catalogVos.add(catalogVo);
            }
            result.setCatalogs(catalogVos);
    
            //5 查询涉及到的所有属性
            List<SearchResult.AttrVo> attrVos = new ArrayList<>();
            //ParsedNested用于接收内置嵌入式属性的聚合
            ParsedNested parsedNested=aggregations.get("attrs");
            ParsedLongTerms attrIdAgg=parsedNested.getAggregations().get("attrIdAgg");
            for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
                //5.1 查询属性id
                Long attrId = bucket.getKeyAsNumber().longValue();
    
                //获取子聚合
                Aggregations subAttrAgg = bucket.getAggregations();
                //5.2 查询属性名
                ParsedStringTerms attrNameAgg=subAttrAgg.get("attrNameAgg");
                String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
                //5.3 查询属性值
                ParsedStringTerms attrValueAgg = subAttrAgg.get("attrValueAgg");
                List<String> attrValues = new ArrayList<>();
                for (Terms.Bucket attrValueAggBucket : attrValueAgg.getBuckets()) {
                    String attrValue = attrValueAggBucket.getKeyAsString();
                    attrValues.add(attrValue);
                    List<SearchResult.NavVo> navVos = new ArrayList<>();
                }
                SearchResult.AttrVo attrVo = new SearchResult.AttrVo(attrId, attrName, attrValues);
                attrVos.add(attrVo);
            }
            result.setAttrs(attrVos);
    
    
    
            
            return result;
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272

    ES集群

    ELasticsearch的集群是由多个节点组成的,通过cluster.name设置集群名称,并且用于区分其它的集群,每个节点通过node.name指定节点的名称。

    ES集群中的节点类型:
    1、主节点
    主节点负责创建索引、删除索引、分配分片、追踪集群中的节点状态等工作。ElasticSearch中的主节点的工作量相对较轻,用户的请求可以发往集群中任何一个节点,由该节点负责分发和返回结果,而不需要经过主节点转发。而主节点是由候选主节点通过ZenDiscovery机制选举出来的,所以要想成为主节点,首先要先成为候选主节点。

    2、候选主节点
    在ElasticSearch集群初始化或者主节点宕机的情况下,由候选主节点中选举其中一个作为主节点。指定候选主节点的配置为:node.master:true。

    3、数据节点
    数据节点负责数据的存储和相关具体操作,比如CRUD、搜索、聚合。所以,数据节点对机器配置要求比较高,首先需要有足够的磁盘空间来存储数据,其次数据操作对系统CPU、Memory和IO的性能消耗都很大。通常随着集群的扩大,需要增加更多的数据节点来提高可用性。指定数据节点的配置:node.data:true。
    ElasticSearch是允许一个节点既做候选主节点也做数据节点的,但是数据节点的负载较重,所以需要考虑将二者分离开,设置专用的候选主节点和数据节点,避免因数据节点负责重导致主节点不响应。

    4、客户端节点
    客户端节点就是既不做候选主节点也不做数据节点的节点,只负责请求的分发、汇总等等,但是这样的工作,其实任何一个节点都可以完成,因为在ElasticSearch中一个集群内的节点都可以执行任何请求,其会负责将请求转发给对应的节点进行处理。所以单独增加这样的节点更多是为了负载均衡。指定该节点的配置为:
    node.master:false
    node.data:false

    分片
    为了将数据添加到Elasticsearch,我们需要索引(index)——一个存储关联数据的地方。实际上,索引只是一个用来指向一个或多个分片(shards)的“逻辑命名空间(logical namespace)”.
    在这里插入图片描述

    集群新增节点

    • 向集群增加一个节点前后,索引发生了些什么。在左端,索引的主分片全部分配到节点 Node1,而副本分片没有地方分配。在这种状态下,集群是黄色的。

    • 一旦第二个节点加入,尚未分配的副本分片就会分配到新的节点 Node2,这使得集群变为了绿色的状态。

    • 当另一个节点加入的时候,Elasticsearch 会自动地尝试将分片在所有节点上进行均匀分配。
      在这里插入图片描述

    集群参考:http://dljz.nicethemes.cn/news/show-107233.html
    集群参考:https://blog.csdn.net/qq_40977118/article/details/123301013

  • 相关阅读:
    微信“刷掌支付”上线!出门带手就可以了~
    正负折线图凸显数据趋势变化
    09 | Harbor中的镜像清除
    GB28181状态信息报送解读及Android端国标设备接入技术实现
    SpringBoot的error用全局异常去处理
    聊聊redis分布式锁的8大坑
    学习笔记 --- RabbitMQ
    解决 urllib2 中 CookiesMiddleware 的 cookie 问题
    如何将一个PPT生成一个二维码?扫码就能查看文件内容
    【深度学习实验】卷积神经网络(三):自定义二维卷积层:步长、填充、输入输出通道
  • 原文地址:https://blog.csdn.net/m0_48268301/article/details/125232549