分布式项目的学而思7:elasticsearch(Mapping字段映射,分词,Elasticsearch-Rest-Client,商品的ES存储)

elasticsearch

Mapping字段映射

映射定义文档如何被存储和检索的
核心数据类型
(1)字符串
text ⽤于全⽂索引,搜索时会自动使用分词器进⾏分词再匹配
keyword 不分词,搜索时需要匹配完整的值

(2)数值型
整型: byte,short,integer,long
浮点型: float, half_float, scaled_float,double

(3)日期类型:date

(4)范围型
integer_range, long_range, float_range,double_range,date_range
gt是大于,lt是小于,e是equals等于。
age_limit的区间包含了此值的文档都算是匹配。

(5)布尔
boolean

(6)二进制
binary 会把值当做经过 base64 编码的字符串,默认不存储,且不可搜索

复杂数据类型
	(1)对象
		object一个对象中可以嵌套对象。
	(2)数组
			Array
嵌套类型
	nested 用于json对象数组

映射

Mapping(映射)是用来定义一个文档(document),
以及它所包含的属性(field)是如何存储和索引的。
比如:使用maping来定义:
	哪些字符串属性应该被看做全文本属性(full text fields);
	哪些属性包含数字,日期或地理位置;
	文档中的所有属性是否都嫩被索引(all 配置);
	日期的格式;
	自定义映射规则来执行动态添加属性;

查看mapping信息:GET bank/_mapping
结果:

  {
    "bank" : {
      "mappings" : {
        "properties" : {
          "account_number" : {
            "type" : "long" # long类型
          },
          "address" : {
            "type" : "text", # 文本类型,会进行全文检索,进行分词
            "fields" : {
              "keyword" : { # addrss.keyword
                "type" : "keyword",  # 该字段必须全部匹配到
                "ignore_above" : 256
              }
            }
          },
          "age" : {
            "type" : "long"
          },
          "balance" : {
            "type" : "long"
          },
          "city" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "email" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "employer" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "firstname" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "gender" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "lastname" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "state" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }
        }
      }
    }
  }

新版本改变

ElasticSearch7-去掉type概念

	关系型数据库中两个数据表示是独立的,
	即使他们里面有相同名称的列也不影响使用,
	但ES中不是这样的。elasticsearch是基于Lucene开发的搜索引擎,
	而ES中不同type下名称相同的filed最终在Lucene中的处理方式是一样的。

	两个不同type下的两个user_name,
	在ES同一个索引下其实被认为是同一个filed,
	你必须在两个不同的type中定义相同的filed映射。
	否则,不同type中的相同字段名称就会在处理中出现冲突的情况,
	导致Lucene处理效率下降。
	
去掉type就是为了提高ES处理数据的效率。
Elasticsearch 7.x URL中的type参数为可选。
比如,索引一个文档不再要求提供文档类型。

Elasticsearch 8.x 不再支持URL中的type参数。

解决:
	将索引从多类型迁移到单类型,每种类型文档一个独立索引
	将已存在的索引下的类型数据,全部迁移到指定位置即可。详见数据迁移

创建映射PUT /my_index

第一次存储数据的时候es就猜出了映射
第一次存储数据前可以指定映射
创建索引并指定映射
PUT /my_index
{
  "mappings": {
    "properties": {
      "age": {
        "type": "integer"
      },
      "email": {
        "type": "keyword" # 指定为keyword
      },
      "name": {
        "type": "text" # 全文检索。保存时候分词,检索时候进行分词匹配
      }
    }
  }
}

添加新的字段映射PUT /my_index/_mapping

PUT /my_index/_mapping
{
  "properties": {
    "employee-id": {
      "type": "keyword",
      "index": false # 字段不能被检索。检索
    }
  }
}
##这里的 “index”: false,表明新增的字段不能被检索,只是一个冗余字段。

不能更新映射

对于已经存在的字段映射,我们不能更新。
更新必须创建新的索引,进行数据迁移。

数据迁移

先创建new_twitter的正确映射。
然后使用如下方式进行数据迁移。
6.0以后写法
POST reindex
{
  "source":{
      "index":"twitter"
   },
  "dest":{
      "index":"new_twitters"
   }
}


老版本写法
POST reindex
{
  "source":{
      "index":"twitter",
      "twitter":"twitter"
   },
  "dest":{
      "index":"new_twitters"
   }
}

分词

一个tokenizer(分词器)接收一个字符流,
将之分割为独立的tokens(词元,通常是独立的单词),然后输出tokens流。

例如:whitespace tokenizer遇到空白字符时分割文本。
它会将文本"Quick brown fox!"分割为[Quick,brown,fox!]

该tokenizer(分词器)还负责记录各个terms(词条)的顺序
或position位置(用于phrase短语和word proximity词近邻查询),
以及term(词条)所代表的原始word(单词)的start(起始)
和end(结束)的character offsets(字符串偏移量)(用于高亮显示搜索的内容)。

elasticsearch提供了很多内置的分词器(标准分词器),
可以用来构建custom analyzers(自定义分词器)。
POST _analyze
{
  "analyzer": "standard",
  "text": "The 2 Brown-Foxes bone."
}

安装ik分词器

所有的语言分词,默认使用的都是“Standard Analyzer”,
但是这些分词器针对于中文的分词,并不友好。为此需要安装中文的分词器。
在前面安装的elasticsearch时,
我们已经将elasticsearch容器的“/usr/share/elasticsearch/plugins”目录,
映射到宿主机的“ /mydata/elasticsearch/plugins”目录下,
所以比较方便的做法就是下载“/elasticsearch-analysis-ik-7.4.2.zip”文件,
然后解压到该文件夹下即可。安装完毕后,需要重启elasticsearch容器。

下载地址https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
下载后解压到/mydata/elasticsearch/plugins目录下
然后chmod -R 777 elasticsearch-analysis-ik-7.4.2/修改权限,重启就可以了

测试分词器
POST _analyze
{
  "analyzer": "standard",
  "text": "我是中国人"
}

POST _analyze
{
  "analyzer": "ik_smart",
  "text": "我是中国人"
}

POST _analyze
{
  "analyzer": "ik_max_word",
  "text": "我是中国人"
}

自定义词库

安装Nginx

首先在Linux里面新建好目录
cd /mydata
mkdir nginx
随便启动一个 nginx 实例,只是为了复制出配置
	docker run -p 80:80 --name nginx -d nginx:1.10
将容器内的配置文件拷贝到当前目录(mydata):
	docker container cp nginx:/etc/nginx .
修改文件名称:mv nginx conf 把这个 conf 移动到/mydata/nginx下
		[root@jane mydata]# ls
		elasticsearch  mysql  nginx  redis
		[root@jane mydata]# mv nginx conf
		[root@jane mydata]# mkdir nginx
		[root@jane mydata]# mv conf nginx/

删掉之前的容器,创建过新的
docker run -p 80:80 --privileged=true  --name nginx \
-v /mydata/nginx/html:/usr/share/nginx/html \
-v /mydata/nginx/logs:/var/log/nginx \
-v /mydata/nginx/conf:/etc/nginx \
-d nginx:1.10

给 nginx 的 html 下面放的所有资源可
我在里面放了html/esfenci.txt,在里面写上词语
然后修改elasticsearch/plugins/ik/config/中的 IKAnalyzer.cfg.xml
cd /mydata/elasticsearch/plugins/elasticsearch-analysis-ik-7.4.2/config/
 vi 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.80.129/es/fenci.txt</entry>
        <!--用户可以在这里配置远程扩展停止词字典-->
        <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties> 
更新完成后,es 只会对新增的数据用新词分词。
历史数据是不会重新分词的。
如果想要历史数据重新分词。需要执行:
POST my_index/_update_by_query?conflicts=proceed

Elasticsearch-Rest-Client

有两种方式进行客户端连接
1)、9300:TCP
spring-data-elasticsearch:transport-api.jar;
springboot 版本不同, transport-api.jar 不同,不能适配 es 版本
7.x 已经不建议使用,8 以后就要废

2)、9200:HTTP
JestClient:非官方,更新慢
RestTemplate:模拟发 HTTP 请求,ES 很多操作需要自己封装,麻烦
HttpClient:同上
Elasticsearch-Rest-Client:官方 RestClient,封装了 ES 操作,API 层次分明,上手简单
最终选择 Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client)
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

导入依赖

<properties>
    <java.version>1.8</java.version>
    <elasticsearch.version>7.4.2</elasticsearch.version>
    <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependency>
     <groupId>org.elasticsearch.client </groupId>
     <artifactId > elasticsearch-rest-high-level-client </artifactId>
     <version>7.4.2</version>
 </dependency>
请求测试项,比如es添加了安全访问规则,
访问es需要添加一个安全头,就可以通过requestOptions设置
官方建议把requestOptions创建成单实例
package com.jane.shop.search.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author jane
 * @create 2021-05-19 16:11
 * 1.导入依赖
 * 2.编写配置,给容器注入一个RestHighLevelClient
 */
@Configuration
public class ShopElasticSearchConfig
{
    public static final RequestOptions COMMON_OPTIONS;

    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();

        COMMON_OPTIONS = builder.build();
    }

    @Bean
    public RestHighLevelClient esRestClient() {

//        RestHighLevelClient client = new RestHighLevelClient(
//                RestClient.builder(
//                        new HttpHost("localhost", 9200, "http"),
//                        new HttpHost("localhost", 9201, "http")));
        RestClientBuilder builder = null;
        // 可以指定多个es
        builder = RestClient.builder(new HttpHost("192.168.80.129", 9200, "http"));

        RestHighLevelClient client = new RestHighLevelClient(builder);
        return client;
    }
}

商品的ES

ES在内存中,所以在检索中优于mysql。ES也支持集群,数据分片存储。
需求:
	上架的商品才可以在网站展示。
	上架的商品需要可以被检索。

如何存储

1)、检索的时候输入名字,是需要按照sku的title进行全文检索的
2)、检素使用商品规格,规格是spu的公共属性,每个spu是一样的
3)、按照分类id进去的都是直接列出spu的,还可以切换。
4〕、我们如果将sku的全量信息保存到es中(包括spu属性〕就太多字段了

想法一

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

想法二

sku索引
{
    spuId:1
    skuId:11
}
attr索引
{
    skuId:11
    attr:[
        {尺寸:5},
        {CPU:高通945},
        {分辨率:全高清}
	]
}
先找到4000个符合要求的spu,再根据4000个spu查询对应的属性,
封装了4000个id,long 8B*4000=32000B=32KB
10000个人检索,就是320MB

结论:如果将规格参数单独建立索引,会出现检索时出现大量数据传输的问题,会引起网络网络

因此选用方案1,以空间换时间

nested嵌入式对象
属性是"type": “nested”,因为是内部的属性进行检索
数组类型的对象会被扁平化处理(对象的每个属性会分别存储到一起)

user.name=["aaa","bbb"]
user.addr=["ccc","ddd"]

这种存储方式,可能会发生如下错误:
错误检索到{aaa,ddd},这个组合是不存在的

数组的扁平化处理会使检索能检索到本身不存在的,
为了解决这个问题,就采用了嵌入式属性,
数组里是对象时用嵌入式属性(不是对象无需用嵌入式属性)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ReflectMirroring

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值