Elasticsearch学习

Elasticsearch

一:es 与 mysql对比

mysql:擅长事务类型操作,可以确保数据的安全和一致性

Elasticsearch:擅长海量数据的搜索、分析、计算

正向索引和倒排索引

简言之,由文档查询关键字的过程,为正排索引

与正排索引相反,由关键字查询文档的过程,为倒排索引

elasticsearch 采用倒排索引:

  • 文档(document):每条数据就是一个文档

  • 词条(term):文档按照语义分成的词语

二:分词器

es 在创建倒排索引时需要对文档分词;在搜索时,需要对用户输入内容分词。但默认的分词规则对中文处理并不友好。

  • 分词器的作用

    • 创建倒排索引时对文档分词

    • 用户搜索时就,对输入的内容分词

  • ik分词器:ik_smart和ik_max_word

    • ik_smart:最少切分

    • ik_max_word:最细切分

 POST /_analyze
 {
   "analyzer": "standard / ik_smart", # 分词
   "text": "你干嘛"
 }

语法说明:

  • POST:请求方式

  • /analyzes:请求路径,这里省略了http://192.168.200.130:5601/,由kibana为我们提供

  • 请求参数,json风格:

    • analyzer:分词器类型,这里是默认的standard

    • text:要分词的类容

三:索引库操作

1. mapping属性

mapping 是对索引库中文档的约束,常见的mapping属性包括:

  • type:字段数据类型,常见的类型有:

    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)

    • 数值:long、integer、short、byte、double、float

    • 布尔:boolean

    • 日期:date

    • 对象:object

  • index:是否创建索引,默认为true

  • analyzer:使用哪种分词器,只有text使用

  • properties:该字段的子字段

  • ES 中支持两种地理坐标数据类型:

    • geo_point:由纬度 和 经度确定的一个点 "32.58859, 120.34343"

    • geo_shape:有多个geo_point 组成的复杂几何图形。例如一条直线,"LINESTRING(-77.0333 38.45454, -77.000949 38.88998)"

  • 字段拷贝可以使用 copy_to 属性将当前字段拷贝到指定字段。

 "all": {
     "type": "text",
     "analyzer": "ik_max_word"
 },
 "brand": {
     "type": "keyword",
     "copy_to": "all"
 }

2. 创建索引库

ES 中通过 Restful 请求操作索引库、文档。请求内容用DSL语句表示。创建索引库和mapping的DSL语法如下:

 PUT /索引库名称
 {
     "mappings": {
         "properties": {
             "字段名": {
                 "type": "text",
                 "analyzer": "ik_smart"
             },
             "字段名2": {
                 "type": "keyword",
                 "index": "false"
             },
             "字段名3": {
                 "properties" : {
                     "子字段": {
                         "type": "keyword"
                     }
                 }
             },
             //...略
         }
     }
 }

3. 查看、删除索引库

查看索引库语法:

 GET /索引库名
 // 查看索引库结构
 GET /索引库名/_mapping

删除索引库的语法:

 DELETE /索引库名

改索引库:

索引库 和 mapping 一旦创建无法修改,但是可以添加新的字段,如下:

 PUT /索引库名/_mapping 
 {
     "properteis": {
         "新字段名": {
             "type": "integer"
         }
     }
 }

4. 文档的增删查

  • 新增文档的 DSL 语法如下:

 POST /索引库名/_doc/文档id
 {
     "字段1": "值1",
     "字段2”: "值2",
     "字段3": {
         "子属性1": "值3",
         "子属性2": "值4"
     },
     //...
 }
  • 查看文档

     GET /索引库名/_doc/文档id
     ​
     GET /hotel/_doc/_search #查询所有数据
  • 删除文档

     DELETE /索引库名/_doc/文档id

5. 修改文档

  • 方式一:全量修改,会删除旧文档,添加新文档

     PUT /索引库名/_doc/文档id
     {
         "字段1": "值1",
         "字段2": "值2",
         //...
     }
  • 方式二:增量修改,修改指定字段值

     POST /索引库名/_update/文档id
     {
         "doc": {
             "字段名": "新的值",
         }
     }

四:RestClient 操作索引库

1. 初始化

  1. 引入es的 RestHighLevelClient 依赖:

 <dependency>
             <groupId>org.elasticsearch.client</groupId>
             <artifactId>elasticsearch-rest-high-level-client</artifactId>
         </dependency>
  1. es对应版本,与客户端版本一致

 <properties>
         <java.version>1.8</java.version>
         <elasticsearch.version>7.12.1</elasticsearch.version>
     </properties>
  1. 初始化 RestHighLevelClient:

 RestHignLevelClient client = new RestHignLevelClient(RestClient.builder(HttpHost.create("http://47.108.239.188:9200")))

2. 索引库操作的基本步骤

  • 初始化 RestHighLevelClient

  • 创建XxxIndexRequest。xxxclient.indices().exists(request, RequestOptions.DEFAULT);是CREATE、Get、Delete

  • 准备 DSL (CREATE 时需要)

  • 发送请求。调用 RestHighLevelClient.indices().xxx方法

3. 利用 javaRestClient 批量导入数据到es

  • 利用 JavaRestClient 中的 Bulk 批处理,实现批量新增文档

 // 1、批量查询数据库数据
         List<Hotel> list = hotelService.list();
         
         // 2、创建 request 对象
         BulkRequest request = new BulkRequest();
         
         // 3、转换文档格式
         for (Hotel hotel : list) {
             HotelDoc hotelDoc = new HotelDoc(hotel);
             request.add(new IndexRequest("hotel")
                     .id(hotel.getId().toString())
                     .source(JSON.toJSONString(hotelDoc), XContentType.JSON));
         }
 ​
         // 4、发送请求
         client.bulk(request, RequestOptions.DEFAULT);

五:DSL查询文档

Elasticsearch 提供了基于JSON的DSL 来定义查询。常见的查询类型包括:

  • 查询所有:查询出所欲的数据,例如:match_all

  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:

    • match_query

    • multi_match_query

  • 精确查询:根据精确词条值查找数据,一般是 查找keyword、数值、日期、boolean等类型字段。例如:

    • ids

    • range

    • term

  • 地理(geo)查询:根据经纬度查询。例如:

    • geo_distance

    • geo_bounding_box

  • 复合(compound)查询:将上述各种查询条件组合起来,合并查询条件。例如:

    • bool

    • function_score

1. DSL Query基本语法

查询的基本语法如下:

 GET /indexName/_search
 {
     "query": {
         "查询类型": {
             "查询条件": "条件值"
         }
     }
 }

  • 查询所有

 GET /indexName/_search
 {
     "query": {
         "match_all": {
         }
     }
 }

2. 全文检索查询

全文检索查询,会对用户输入内容分词,常用于搜索框搜索

2.1 math查询

全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索,语法:

 GET /indexName/_search
 {
     "query": {
         "match": {
             "FIELD": "TEXT"
         }
     }
 }

2.2 multi_match 查询

 GET /indexName/_search
 {
     "query": {
         "multi_match": {
             "query": "北京如家",
             "fields": ["brand", "name", "business"]
         }
     }
 }

match 和 multi_match 的区别:

  • match:根据一个字段查询

  • multi_match:根据多个字段查询,参与查询字段越多,查询性能越差

3. 精确查询

根据精确词条值查找数据,一般是 查找keyword、数值、日期、boolean等类型字段。

  • term:根据词条精确值查询

  • range:根据值得范围查询

3.1 term查询

GET /indexName/_search
{
	"query": {
		"term": {
			"FIELD": {
				"value": "值"
			}
		}
	}
}

3.2 range查询

GET /indexName/_search
{
	"query": {
		"range": {
			"FIELD": {
				"gte": 10, #大于等于(gt 大于)
				"lte": 20 #小于等于(lt 小于)
			}
		}
	}
}

4. 地理查询

根据经纬度查询

  • geo_bounding_box:查询geo_point 值落在某个矩形范围得所有文档

GET /indexName/_search
{
	"query": {
		"geo_bounding_box": {
			"FIELD": {
				"top_left": {
					"lat": 31.1,
					"lon": 121.5
				},
				"bottom_right": {
					"lat": 30.9, #lat 经度
					"lon": 121.7 #lon 纬度
				}
			}
		}
	}
}

  • geo_distance:查询到指定中心点小于某个距离值的所有文档

GET /indexName/_search
{
	"query": {
		"geo_distance": {
			"distance": "15km",
			"FIELD": "31.21, 121.5"
		}
	}
}

5. 复合查询

复合查询:复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑,例如:

  • function score:算分函数查询,可以控制文档相关性算分,控制文档排名

当我们利用match 查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。

es 中的相关性打分算法:

  • TF-IDF:在es5.0之前,会随着词频增加而越来越大

  • BM25:在es5.0之后,会随着词频增加而增大,但增长曲线会趋于水平

5.1 Function Score Query

使用 function score query,可以修改文档的相关性算法,根据新得到的算分排序

GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {
        "match": {
          "all": "外滩"
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "brand": "如家"
            }
          },
          "weight": 10
        }
      ],
      "boost_mode": "sum"
    } 
  }
}

5.2 function score query 定义的三要素

  • 过滤条件:哪些文档要加分

  • 算分函数:如何计算function score

  • 加权方式:function score 与 query score 如何运算

5.3 Boolean Query

布尔查询是一个或多个查询子句的组合。子查询的组合方式有:

  • must:必须匹配每个子查询,类似"与"

  • should:选择性匹配子查询,类似"或"

  • must_not:必须不匹配,不参与算分,类似"非"

  • filter:必须匹配,不参与算分

GET /hotel/_search
{
	"query": {
		"bool": {
			"must": [
				{"term": {"city": "上海"}}
			],
			"should": [
				{"term": {
					"brand": "xxx"
				}}
			]
		}
	}
}

六:搜索结果处理

1. 排序

es 支持对搜索结果排序,默认是根据相关度算法(——xcore)来排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。

GET /indexName/_search
{
	"query": {
		"match_all": {}
		"sort":[
			{
				"FIELD": "desc" //asc、desc
			}
		]
	}
}

地理排序:

GET /indexName/_search
{
	"query": {
		"match_all": {}
		"sort":[
			{
				"_geo_distance": {
					"FIELD": "经度,纬度", 
					//"FIELD": {
						"lat": 经度
						"lon": 纬度
					}
					"order": "asc",
					"unit": "km"
				}
			}
		]
	}
}

2. 分页

es 默认情况下只返回top10的数据。而如果要查询更多数据就要修改分页参数。

es 中通过修改from、size参数来控制要返回的分页结果。

GET /indexName/_search
{
	"query": {
		"match_all": {}
	},
	"from": 10, //分页开始的位置,默认为0
	"size": 10, //期望获取的文档总数
	"sort":[
		{"price": "asc"}
	]
}

  • 深度分页问题

正在上传…重新上传取消

针对深度分页,es 提供了两种解决方案:

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。

  • scroll:原理将排序数据形成快照,保存在内存。官方已经不推荐使用。

3. 分页总结

from + size:

  • 优点:支持随机翻页

  • 缺点:深度分页问题,默认查询上限(from + size)是10000

  • 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索

after search:

  • 优点:没有查询上限(单次查询的size不超过10000)

  • 缺点:只能向后逐页查询,不支持随机翻页

  • 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页

scroll:

  • 优点:没有查询上限(单次查询的size不超过10000)

  • 缺点:会有额外内存消耗,并且搜索是非实时的

  • 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 after search方案

4. 高亮

高亮:就是在搜索结果中把搜索关键字突出显示。

原理:

  • 将搜索结果中的关键字用标签标记出来

  • 在页面中给标签添加css样式

语法:

# 高亮查询,默认情况下,es搜索字段必须与高亮字段一致
GET /hotel/_search
{
	"query": {
		"match": {
			"FIELD": "TEXT"
		}
	},
	"highlight": {
		"fields": {
			"FIELD": { //指定要高亮的字段
				"pre_tags": "<em>", //用来标记高亮字段的前置标签
				"post_tags": "</em>", //用来标记高亮字段的后置标签
				"require_field_match": "false" //是否需要与搜索字段匹配
			}
		}
	}
}

七:RestClient 查询文档

1. 快速入门

// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.组织DSL参数
request.source().query(QueryBuilders.matchAllQuery());
// 3.发送请求,得到响应结果
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// ...解析响应结果
 // 4.解析结果
        SearchHits searchHits = response.getHits();
        // 4.1 查询的总条数
        long total = searchHits.getTotalHits().value;
        // 4.2 查询的结果数组
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            // 4.3 得到source
            String json = hit.getSourceAsString();
            // 4.4 打印
            System.out.println(json);
        }

2. 全文检索查询

全文检索的match 和 multi_match 查询与match_all的API基本一致。差别是查询条件,也就是query的部分。

// 单字段查询
QueryBuilders.matchQuery("all", "如家");
// 多字段查询
QueryBuilders.multiMatchQuery("如家", "name", "business");

3. 精确查询

// 词条查询
QueryBuilders.termQuery("city", "杭州");
// 范围查询
QueryBuilders.rangeQuery("price").get(100).lte(150);

4. 复合查询

4.1 boolean query

// 创建布尔查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 添加must条件
boolQuery.must(QueryBuilders.termQuery("city", "杭州"));
// 添加filter条件
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));

5. 排序和分页

搜索结果的排序和分页是与query同级的参数,对应的API如下:

// 分页
request.source().from(0).size(5);
// 价格排序
request.source().sort("price", SortOrder.ASC);
//分页排序
request.source().sort(SortBuilders
     .geoDistanceSort("location", new GeoPoint("33.21, 132.1"))
    .order(SortOrder.ASC)
    .unit(DistanceUnit.KILOMETERS)
);

6. 高亮

高亮API包括请求DSL构建和结果解析两部分。

// DSL构建
request.source().highlighter(new HighlightBuilder().field("name").requireFieldMathch(false));
// 结果解析
// 获取source
HotelDoc hotelDoc = JSON.parseObject(hit.getSourceAsString(), HotelDoc.class);
// 处理高亮
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (!CollectionUtils.isEmpty(highlightFields)) {
	 // 获取高亮字段结果
 	HighlightField highlightField = highlightFields.get("name");
    if (highlightField != null) {
       // 取出高亮结果数组中的第一个,就是名称
       String name = highlightField.getFragments()[0].string();
       hotelDoc.setName(name);
    }
}

7. 组合查询

// function score
FunctionScoreQueryBuilder functionScoreQueryBuilder = 
    QueryBuilders.functionScoreQuery(
    	QueryBuilders.matchQuery("name", "外滩"),
    	new FunctionScoreQueryBuilder.FilterFunctionBuilder[](
            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                QueryBuilders.termQuery("brand", "如家"),
                scoreFunctionBuilders.weightFactorFunction(5)
            )
        )
	);
request.source.query(functionScoreQueryBuilder);

八. 数据聚合

聚合 可以实现对文档数据的统计、分析、运算。。

参与聚合的字段类型必须是:

  • keyword

  • 数值

  • 日期

  • 布尔

1. 聚合的分类

  • 桶(Bucket)聚合:用来对文档分组

    • TermAggregation:按照文档字段值分组

    • Date Histogram:按照日期阶梯分组,例如一周为一组,一月为一组

  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值

    • Avg:求平均值

    • Max:求最大值

    • Min:求最小值

    • Stats:同时求max、min、avg、sum 等

  • 管道(Pipeline)聚合:其它聚合的结果为基础做聚合

2. 聚合

2.1 DSL 实现Bucket聚合

  • Bucket 聚合-限定聚合范围

默认情况下,Bucket聚合是对索引库的所有文档做聚合,我们可以限定要聚合的文档范围。只要添加query条件即可:

GET /hotel/_search
{
	"query": {
		"range": {
			"price": {
				"lte": 200 //只对200元以下的文档聚合
			}
		}
	}

	"size": 0, //设置size为0,结果中不包含文档,只包含聚合结果
	"aggs": { //定义聚合
		"brandAgg": { //给聚合起个名字
			"terms": {  //聚合的类型,按照品牌值聚合,所以选择term
				"field": "brand",
				"order": {
					"_count": "asc" //按照_count升序排序
				},
				"size": 20  //希望获取的聚合结果数量
			}
		}
	}
}

2.2 DSL 实现Metrics 聚合

"size": 0, 
	"aggs": {
		"brandAgg": { 
			"terms": {  
				"field": "brand",
				"size": 20 
			},
			"aggs": { // 是brands聚合的子聚合,也就是分组后对每组分别计算
				"score_stats": { // 聚合名称
					"stats": { //聚合类型,这里stats 可以计算min、max、avg等
						field": "score"  //聚合字段,这里是score
					}
				}
			}
		}
	}
}

3. RestAPI 实现聚合

request.source().size(0);
request.source().aggregation(
    AggregationBuilders
    	.terms("brand_agg")
    	.field("brand")
    	.size(20)
);
request.source().aggregation(
    AggregationBuilders
    	.terms("star_agg")
    	.field("star")
    	.size(20)
);
//解析聚合结果
Aggregations aggregations = response.getAggregations();
//根据名称获取聚合结果
Terms brandTerms = aggregation.get("brand_agg");
//获取桶
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
//遍历
for (Terms.Bucket bucket: buckets) {
    //获取key,也就是品牌信息
    String brandName = bucket.getKeyAsString();
}

九. 自动补全

1. 分词器

es 中的分词器(analyzer) 的组成包含三部分:

  • character filters:在tokenizer之前对文本进行处理。例如删除字符、替换字符

  • tokenizer:将文本按照一定的规则切割成词条(term)。例如keyword,就是不分词;还有ik_smart

  • tokenizer filter:将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等

  • 拼音分词器

    安装插件

  • 自定义分词器

    我们可以在创建索引库时,通过settings 来配置自定义的analyzer(分词器):

    PUT /test
    {
    	"settings": {
    		"analysis": {
    			"analyzer": { // 自定义分词器
    				"my_analyzer": { // 分词器名称
    					"tokenizer": "ik_max_word",
    					"filter": "pinyin"
    				}
    			}
    		}
    	}
    }

    进一步定制:

    PUT /test
    {
    	"settings": {
    		"analysis": {
    			"analyzer": { // 自定义分词器
    				"my_analyzer": { // 分词器名称
    					"tokenizer": "ik_max_word",
    					"filter": "py"
    				}
    			},
    			"filter": { //自定义tokenizer filter
    				"py": { //过滤器名称
    					"type": "pinyin", //过滤器类型,这里是pinyin
    					"keep_full_pinyin": false,
    					"keep_joined_full_pinyin": true,
    					"keep_original": true,
    					"limit_first_letter_length": 16,
    					"remove_duplicated_term": true,
    					"none_chinese_pinyin_tokenize": false
    				}
    			}
    		}
    	}
    }

字段在创建倒排索引时应该用my_analyzer 分词器;字段在搜索时应该使用ik_smart 分词器,避免搜索文字时搜出同音的词,如"狮子",搜出"狮子"和"虱子"

PUT /test
{
	"settings": {
		"analysis": {
			"analyzer": { // 自定义分词器
				"my_analyzer": { // 分词器名称
					"tokenizer": "ik_max_word",
					"filter": "py"
				}
			},
			"filter": {...}
	},
	
	"mappings": {
		"properties": {
			"name": {
				"type": "text",
				"analyzer": "my_analyzer",
				"search_analyzer": "ik_smart"
			}
		}
	}
}

2. 自动补全查询

completion suggester 查询

es 提供了 Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:

  • 参与补全查询的字段必须是 completion类型。

  • 字段的内容一般是用来补全的多个词条形成的数组。

// 创建索引库
PUT test
{
	"mappings": {
		"properties": {
			"title": {
				"type": "completion"
			}
		}
	}
}

// 示例数据
POST test/_doc
{
	"title": ["Sony", "wh-1000XM3"]
}
POST test/_doc
{
	"title": ["SK-II", "PITERA"]
}
POST test/_doc
{
	"title": ["Nintendo", "switch"]
}

查询语法如下

// 自动补全查询
GET /test/_search
{
	"suggest": {
		"title_suggest": { //自定义名字
			"text": "s", //关键字
			"completion": {
				"field": "title", // 补全查询的字段
				"skip_duplicates": true, //跳过重复的
				"size": 10
			}
		}
	}
}

3. RestAPI 实现自动补全

SearchRequest request = new SearchRequest("hotel");
request.source()
    .suggest(new SuggestBuilder().addSuggestion(
        "mySuggestion",
        SuggestBuilders
        	.completionSuggestion("suggestion")
        	.prefix("h")
        	.skipDuplicates(true)
        	.size(10)
    ));
SearchResponse response =  client.search(request, RequestOptions.DEFAULT);

//解析结果
Suggest suggest = response.getSuggest();
CompletionSuggestion suggestion = suggest.getSuggestion("mySuggestion");
for (CompletionSuggestion.Entry.Option option: suggestion.getOptions()) {
    String text = option.getText().string();
}

十. 数据同步

1. 数据同步问题分析

es 中的数据来自mysql数据库,因此mysql数据发生改变时,es也必须跟着改变,这个就是es与mysql之间的数据同步。

方案一:同步调用

方案二:异步通知

方案三:监听binlog

总结

方式一:同步调用

  • 优点:实现简单,粗暴

  • 业务耦合度高

方式二:异步通知

  • 优点:低耦合,实现难度一般

  • 缺点:依赖mq的可靠性

方式三:监听binlog

  • 优点:完全解除服务间耦合

  • 缺点:开启binlog增加数据库负担、实现复杂度高

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值