天天看點

分布式項目的學而思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

結論:如果将規格參數單獨建立索引,會出現檢索時出現大量資料傳輸的問題,會引起網絡網絡      
nested嵌入式對象
屬性是"type": “nested”,因為是内部的屬性進行檢索
數組類型的對象會被扁平化處理(對象的每個屬性會分别存儲到一起)

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

這種存儲方式,可能會發生如下錯誤:
錯誤檢索到{aaa,ddd},這個組合是不存在的

數組的扁平化處理會使檢索能檢索到本身不存在的,
為了解決這個問題,就采用了嵌入式屬性,
數組裡是對象時用嵌入式屬性(不是對象無需用嵌入式屬性)      

繼續閱讀