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},这个组合是不存在的
数组的扁平化处理会使检索能检索到本身不存在的,
为了解决这个问题,就采用了嵌入式属性,
数组里是对象时用嵌入式属性(不是对象无需用嵌入式属性)