ElasticSearch详解

为什么要使用全文检索
面对复杂的搜索业务和数据量,使用传统数据库搜索就显得力不从心,一般我们都会使用全文检索技术。
常见的全文检索技术有 Lucene、solr 、elasticsearch 等。

理解索引结构
索引结构包括逻辑结构和物理结构
逻辑结构部分是一个倒排索引表:
1、将要搜索的文档内容分词,所有不重复的词组成分词列表。
2、将搜索的文档最终以Document方式存储起来。
3、每个词和docment都有关联。
如下:
ElasticSearch详解_第1张图片
Elasticsearch简介
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的
全文搜索引擎,基于RESTful web接口(可以通过postman操作索引库)。

优点:
(1)可以作为一个大型分布式集群(数百台服务器)技术,处理PB级数据,服务大公
司;也可以运行在单机上
(2)将全文检索、数据分析以及分布式技术,合并在了一起,才形成了独一无二的ES;
(3)开箱即用的,部署简单(无需安装,解压安装包后即可使用)
(4)全文检索,同义词处理,相关度排名,复杂数据分析,海量数据的近实时处理

下表是Elasticsearch与MySQL数据库逻辑结构概念的对比
Elasticsearch 关系型数据库Mysql
索引(index) 数据库(databases)
类型(type) 表(table)
文档(document) 行(row)

使用Postman操作索引库
以post方式提交 http://127.0.0.1:9200/testindex/doc
body:
{
“name”:“测试商品”,
“price”:123
}

查询某索引某类型的全部数据,以get方式请求
http://127.0.0.1:9200/testindex/doc/_search

映射与数据类型
映射(Mapping)相当于数据表的表结构。ElasticSearch中的映射(Mapping)用来定义一个文档,可以定义所包含的字段以及字段的类型
分词器及属性等等。
映射可以分为动态映射和静态映射。
动态映射 :在关系数据库中,需要事先创建数据库,然后在该数据库实例下创建数据表,然后才能在该数据表中插入数据。而ElasticSearch中不需要事先定义映射(Mapping),文档写入ElasticSearch时,会根据文档字段自动识别类型,这种机制称之为动态映射。
静态映射 :在ElasticSearch中也可以事先定义好映射,包含文档的各个字段及其类型等,这种方式称之为静态映射。

静态映射相对于动态映射来说,能对索引字段定义很多属性,比如分词的属性,而动态映射是不具备这些属性的

常用类型如下:
字符串类型
text: 设置text类型以后,字段内容会被分析,在生成倒排索引以前,字符串会被分析器分成一个一个词项。
text类型的字段不用于排序,很少用于聚合。
keyword:keyword类型的字段不会进行分词,只能通过精确值搜索到。
如果字段需要进行过滤(比如查找已发布博客中status属性为published的文章)、排序、聚合, 则需要使用keyword类型
整数类型
byte -128~127
short -32768~32767
integer
long

浮点类型
doule 64位双精度浮点类型
float 32位单精度浮点类型
half_float 16位半精度浮点类型
scaled_float 缩放类型的的浮点数 12.34会存为1234

date类型
日期类型表示格式可以是以下几种:
(1)日期格式的字符串,比如 “2018-01-13” 或 “2018-01-13 12:10:30”
(2)long类型的毫秒数( milliseconds-since-the-epoch,epoch就是指UNIX诞生的UTC时间1970年1月1日0时0分0秒)
(3)integer的秒数(seconds-since-the-epoch)

boolean类型
逻辑类型(布尔类型)可以接受true/false

binary类型
二进制字段是指用base64来表示索引中存储的二进制数据,可用来存储二进制形式
的数据,例如图像。默认情况下,该类型的字段只存储不索引。二进制类型只支持
index_name属性。

array类型
在ElasticSearch中,没有专门的数组(Array)数据类型,但是,在默认情况下,任
意一个字段都可以包含0或多个值,这意味着每个字段默认都是数组类型,只不过,
数组类型的各个元素值的数据类型必须相同。
常用的数组类型是:
(1)字符数组: [ “one”, “two” ]
(2)整数数组: productid:[ 1, 2 ]
(3)对象(文档)数组: “user”:[ { “name”: “Mary”, “age”: 12 }, { “name”: “John”, “age”:10 }],ElasticSearch内部把对象数组展开为 {“user.name”: [“Mary”, “John”], “user.age”:[12,10]}

object类型
JSON天生具有层级关系,文档会包含嵌套的对象

IK分词器
默认的中文分词是将每个字看成一个词,这显然是不符合要求的,所以我们需要安装中文分词器来解决这个问题。

IK分词器安装
(1)先将其解压,将解压后的elasticsearch文件夹重命名文件夹为ik
(2)将ik文件夹拷贝到elasticsearch/plugins 目录下。
(3)重新启动,即可加载IK分词器

IK提供了两个分词算法ik_smart 和 ik_max_word
其中 ik_smart 为最少切分,ik_max_word为最细粒度划分
{“analyzer”: “ik_smart”, “text”: “黑马程序员” } 粗粒度
{“analyzer”: “ik_max_word”, “text”: “黑马程序员” } 细粒度

Kibana简介
Kibana 是一个开源的分析和可视化平台,旨在与 Elasticsearch 合作。
如果Kibana远程连接ElasticSearch ,可以修改config\kibana.yml,默认连接的是本地的ElasticSearch,localost:9200

执行bin\kibana.bat启动kibana
打开浏览器,键入http://localhost:5601 访问Kibana(默认端口为5601)
我们这里使用Kibana进行索引操作,Kibana与Postman相比省略了服务地址,并且有语法提示,非常便捷。

索引操作
创建索引与映射字段
PUT /索引库名
{
“mappings”: {
“类型名称”:{
“properties”: {
“字段名”: {
“type”: “类型”,
“index”: true,
“store”: true,
“analyzer”: “分词器”
}
}
}
}

类型名称:就是前面将的type的概念,类似于数据库中的不同表
字段的属性:
type:类型,可以是text、long、short、date、integer、object等
index:是否索引,默认为true, 如果想要被搜索,则需要设置为true
store:是否单独存储,默认为false ,一般内容比较多的字段设置成true(比如一个文档),可提升查询性能
analyzer:分词器

index和store都有默认值,所以可以不设置, 一般情况下,如果想要分词,则设置type和analyzer, 如果不想分词, 只需要设置type属性即可

文档增加与修改
增加文档自动生成ID
语法:
POST 索引库名/类型名
{
“key”:“value”
}

通过以下命令查询sku索引的数据
GET sku/_search

新增文档指定ID
语法
PUT /索引库名/类型/id值
{

}

索引查询
基本语法
GET /索引库名/_search
{
“query”:{
“查询类型”:{
“查询条件”:“查询条件值”
}
}
}

1.查询所有数据(match_all)
GET /sku/_search
{
“query”:{
“match_all”: {}
}
}

2.匹配查询(match)
示例:查询名称包含手机的记录
GET /sku/doc/_search
{
“query”: {
“match”:{“name”:“手机”}
}
}

3.多字段查询(multi_match)
GET /sku/_search
{
“query”:{
“multi_match”: {
“query”: “小米”,
“fields”: [ “name”, “brandName”,“categoryName”]
}
}
}
在name、brandName 、categoryName字段中查询 小米 这个词(3个字段中都要包含"小米"这个词)

4.词条匹配(term) 就是精确查询
GET /sku/_search
{
“query”:{
“term”:{
“price”:1000
}
}
}

5.多词条匹配(terms)
GET /sku/_search
{
“query”:{
“terms”:{
“price”:[1000,2000,3000]
}
}
}
如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件

6.布尔组合(bool)
bool 把各种其它查询通过 must (与)、 must_not (非)、 should (或)的方式进行组合
must表示条件之间是并且的关系,should表示条件之间是或者的关系
示例:查询名称包含手机的,并且品牌为小米的。
GET /sku/_search
{
“query”:{
“bool”:{
“must”: [
{ “match”: { “name”: “手机” }} ,
{ “term”: {“brandName”: “小米”}}
]
}
}
}

示例:查询名称包含手机的,或者品牌为小米的。
GET /sku/_search
{
“query”:{
“bool”:{
“should”: [
{ “match”: { “name”: “手机” }} ,
{ “term”: {“brandName”: “小米”}}
]
}
}
}

7.过滤查询
过滤是针对搜索的结果进行过滤,过滤器主要判断的是文档是否匹配,不去计算和
判断文档的匹配度得分,所以过滤器性能比查询要高,且方便缓存,推荐尽量使用过滤
器去实现查询或者过滤器和查询共同使用。
示例:过滤品牌为小米的记录
GET /sku/_search
{
“query”:{
“bool”:{
“filter”: [
{“match”:{“brandName”: “小米”}}
]
}
}
}

8.分组查询
示例:按分组名称聚合查询,统计每个分组的数量
GET /sku/_search
{
“size” : 0,
“aggs” : {
“sku_category” : {
“terms” : {
“field” : “categoryName”
}
}
}
}
size为0 不会将数据查询出来,目的是让查询更快。
sku_category:分组的名称

查询结果:
{
“took”: 6,
“timed_out”: false,
“_shards”: {
“total”: 5,
“successful”: 5,
“skipped”: 0,
“failed”: 0
},
“hits”: {
“total”: 5,
“max_score”: 0,
“hits”: [] 因为size为0,所以hits没有数据
},
“aggregations”: {
“sku_category”: {
“doc_count_error_upper_bound”: 0,
“sum_other_doc_count”: 0,
“buckets”: [
{
“key”: “手机”,
“doc_count”: 3 分类名称为手机的一共有3条数据
},
{
“key”: “电视”,
“doc_count”: 2 分类名称为电视的一共有2条数据
}
]
}
}
}

我们也可以一次查询两种分组统计结果:
GET sku/_search
{
“size”:0,
“aggs”: {
“sku_category”: {
“terms”: {
“field”: “categoryName” 按分类名称进行分组
}
},
“sku_brand”: {
“terms”: {
“field”: “brandName” 按品牌名称进行分组
}
}
}
}

JavaRest 高级客户端入门
elasticsearch 存在三种Java客户端。

  1. Transport Client
  2. Java Low Level Rest Client(低级rest客户端)
  3. Java High Level REST Client(高级rest客户端)

elasticsearch 存在三种Java客户端。

  1. Transport Client
  2. Java Low Level Rest Client(低级rest客户端)
  3. Java High Level REST Client(高级rest客户端)
    这三者的区别是:
    TransportClient没有使用RESTful风格的接口,而是二进制的方式传输数据。
    ES官方推出了Java Low Level REST Client,它支持RESTful。但是缺点是因为TransportClient的使用者把代码迁移到Low Level REST Client的工作量比较大。
    ES官方推出Java High Level REST Client,它是基于Java Low Level REST Client的封装,并且API接收参数和返回值和TransportClient是一样的,使得代码迁移变得容易并且支持了RESTful的风格,兼容了这两种客户端的优点。强烈建议ES5及其以后的版本使用
    Java High Level REST Client。

新增和修改数据
插入单条数据:
HttpHost : url地址封装
RestClientBuilder: rest客户端构建器
RestHighLevelClient: rest高级客户端
IndexRequest: 新增或修改请求
IndexResponse:新增或修改的响应结果

//1.连接rest接口
HttpHost http=new HttpHost(“127.0.0.1”,9200,“http”); ES也是数据源,和数据库一样,想操作它,首先得先建立连接
RestClientBuilder builder= RestClient.builder(http);//rest构建器
RestHighLevelClient restHighLevelClient=newRestHighLevelClient(builder);//高级客户端对象 这两行可以合并成一行
//2.封装请求对象 如果ID不存在则新增,如果ID存在则修改。
IndexRequest indexRequest=new IndexRequest(“sku”,“doc”,“3”); 参数分别为索引库名称,索引库类型和该条数据的id
Map skuMap =new HashMap();
skuMap.put(“name”,“华为p30pro”);
skuMap.put(“brandName”,“华为”);
skuMap.put(“categoryName”,“手机”);
indexRequest.source(skuMap); 将数据封装到indexRequest中
//3.获取响应结果
IndexResponse response = restHighLevelClient.index(indexRequest,RequestOptions.DEFAULT); index相当于插入数据库的insert
int status = response.status().getStatus();
System.out.println(status);
restHighLevelClient.close();

批处理请求:(批量插入,不用频繁的连接ES,提高插入效率)
BulkRequest: 批量请求(用于增删改操作)
BulkResponse:批量请求(用于增删改操作)
//1.连接rest接口
HttpHost http=new HttpHost(“127.0.0.1”,9200,“http”);
RestClientBuilder builder= RestClient.builder(http);//rest构建器
RestHighLevelClient restHighLevelClient=newRestHighLevelClient(builder);//高级客户端对象
//2.封装请求对象
BulkRequest bulkRequest=new BulkRequest();

从数据库将需要导入ES的数据都查询出来,然后循环
IndexRequest indexRequest=new IndexRequest(“sku”,“doc”,“4”);
Map skuMap =new HashMap();
skuMap.put(“name”,“华为p30pro 火爆上市”);
skuMap.put(“brandName”,“华为”);
skuMap.put(“categoryName”,“手机”);
indexRequest.source(skuMap); 每一条数据放到一个indexRequest中
bulkRequest.add(indexRequest);//可以多次添加 再将每一个indexRequest放到bulkRequest中

//3.获取响应结果
BulkResponse response =restHighLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT);
int status = response.status().getStatus();
System.out.println(status);
String message = response.buildFailureMessage();
System.out.println(message);
restHighLevelClient.close();

匹配查询
SearchRequest: 查询请求对象
SearchResponse:查询响应对象
SearchSourceBuilder:查询源构建器
MatchQueryBuilder:匹配查询构建器

示例:查询商品名称包含手机的记录。
使用kibana操作:
GET /sku/doc/_search
{
“query”: {
“match”:{“name”:“手机”}
}
}

使用高级客户端操作:
//1.连接rest接口 相当于上面的GET请求

//2.封装查询请求
SearchRequest searchRequest=new SearchRequest(“sku”);
searchRequest.types(“doc”); //设置查询的类型
SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder(); 相当于上面的query
MatchQueryBuilder matchQueryBuilder= QueryBuilders.matchQuery(“name”,“手机”); 相当于上面的match
searchSourceBuilder.query(matchQueryBuilder); 相当于上面的_search
searchRequest.source(searchSourceBuilder); 相当于最外面的大括号
//3.获取查询结果
SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
SearchHits searchHits = searchResponse.getHits(); 相当于kibana结果里面最外面的Hits
long totalHits = searchHits.getTotalHits(); 总记录数
SearchHit[] hits = searchHits.getHits(); 得到的数据 相当于kibana里面一层的hits
for(SearchHit hit:hits){
String source = hit.getSourceAsString();
System.out.println(source);
}
restHighLevelClient.close();

布尔组合查询(这里组合的是匹配和精确查询)
示例:查询名称包含手机的,并且品牌为小米的记录
//1.连接rest接口 同上…
//2.封装查询请求
SearchRequest searchRequest=new SearchRequest(“sku”);
searchRequest.types(“doc”); //设置查询的类型
SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();//布尔查询构建器
MatchQueryBuilder matchQueryBuilder= QueryBuilders.matchQuery(“name”,“手机”);
boolQueryBuilder.must(matchQueryBuilder); //组合匹配查询
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(“brandName”,“小米”);
boolQueryBuilder.must(termQueryBuilder); //组合精确查询
searchSourceBuilder.query(boolQueryBuilder);
searchRequest.source(searchSourceBuilder);
//3.获取查询结果 同上…

过滤查询 相比匹配和精确查询,效率更高,不需要计算匹配度得分
示例:筛选品牌为小米的记录
//1.连接rest接口 同上…
//2.封装查询请求
SearchRequest searchRequest=new SearchRequest(“sku”);
searchRequest.types(“doc”); //设置查询的类型
SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();//布尔查询构建器
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(“brandName”,“小米”);
boolQueryBuilder.filter(termQueryBuilder);
searchSourceBuilder.query(boolQueryBuilder);
searchRequest.source(searchSourceBuilder);
//3.获取查询结果 同上…

分组(聚合)查询
示例:按商品分类分组查询,求出每个分类的文档数
//1.连接rest接口 同上…
//2.封装查询请求
SearchRequest searchRequest=new SearchRequest(“sku”);
searchRequest.types(“doc”); //设置查询的类型
SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder();
//第一个参数为分组名称,第二个为需要聚合的字段名称
TermsAggregationBuilder termsAggregationBuilder =AggregationBuilders.terms(“sku_category”).field(“categoryName”);
searchSourceBuilder.aggregation(termsAggregationBuilder);
searchSourceBuilder.size(0);//不需要获取查询的文档,只需要聚合后的数据
searchRequest.source(searchSourceBuilder);
//3.获取查询结果
SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
Map asMap = aggregations.getAsMap();
Terms terms = (Terms) asMap.get(“sku_category”);
List buckets = terms.getBuckets();
for(Terms.Bucket bucket:buckets){
System.out.println(bucket.getKeyAsString()+":"+ bucket.getDocCount()
);
}
restHighLevelClient.close();

搜索分页
需求:每页显示30条记录,查询第3页内容。分页语法如下:
GET sku/_search
{
“from”: 60, 代表从第几条数据开始查, 而不是代表从第几页开始查
“size”: 30
}

分页代码
Integer pageNo = Integer.parseInt(searchMap.get(“pageNo”));//页码 Map的泛型为,所以需要转为int类型
Integer pageSize = 30; //每页记录数
Integer fromIndex = (pageNo-1)*pageSize; //开始查询的索引 代表从第几条数据开始查
//from,size和query是同级的
searchSourceBuilder.from(fromIndex);//开始索引设置
searchSourceBuilder.size(pageSize);//每页记录数设置

搜索排序
需求:按价格升序排序,语法如下:
GET sku/_search
{
“sort”: [
{
“price”: {
“order”: “asc” 如果是降序,则指定order为desc
}
}
]
}

代码如下:
//排序
String sortField= searchMap.get(“sortField”);//排序字段 和前端商量好,如果升序就传ASC,如果降序就传DESC
String sortOrder = searchMap.get(“sortOrder”);//排序规则
if(!"".equals(sortField)){
//searchSourceBuilder.sort(sort,SortOrder.DESC);//这种实际中不实用,将排序规则写死了
searchSourceBuilder.sort(sortField, SortOrder.valueOf(sortOrder)); //从前台获取排序规则
}

搜索高亮
所谓高亮,就是使用特别的样式修饰某字段中包含的搜索关键字。
需求:实现搜索高亮,商品名称使用红色显示搜索关键字。
使用默认高亮:
GET /sku/doc/_search
{
“query”: {
“match”: {
“name”:“手机”
}
},
“highlight”:{
“fields”: {
“name”: {} 默认的高亮
}
},
“size”: 2
}

返回的高亮部分:
“highlight” : {
“name” : [
“Apple 苹果 iPhone XR 手机 全网通4G手机 黑色 64GB”
]
}
高亮字段name对应的值为数组是为了数据类型的兼容,因为有可能name字段对应的值本来就是一个数组

自定义高亮 执行查询:
GET /sku/doc/_search
{
“query”: {
“match”: {
“name”:“手机”
}
},
“highlight”:{
“fields”: {
“name”: {
“pre_tags”:"", 前置标签 设置高亮的颜色
“post_tags”:"
" 后置标签
}
}
},
“size”: 2
}

返回结果(高亮部分)
“highlight” : {
“name” : [
“Apple 苹果 iPhone XR 手机 全网通
4G手机 黑色 128GB”
]
}

高亮代码
//设置高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field(“name”).preTags("").postTags(""); field为高亮字段
searchSourceBuilder.highlighter(highlightBuilder);

获取高亮结果:
for(SearchHit hit:hits){
//提取高亮内容
Map highlightFields =hit.getHighlightFields();
HighlightField highlightFieldName = highlightFields.get(“name”);
Text[] fragments = highlightFieldName.fragments(); 搜素出来的高亮字段对应的是一个数组
String name = fragments[0].toString();
System.out.println(name);
}

代码实现
(1)修改SkuSearchServiceImpl的search方法,在第一个代码块中添加高亮显示处理代码
//设置高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field(“name”).preTags("").postTags("");
searchSourceBuilder.highlighter(highlightBuilder);

修改商品列表部分代码
//2.1 商品列表
List> resultList=new ArrayList>();
for(SearchHit hit:hits){ //原内容遍历
Map skuMap = hit.getSourceAsMap();
//name高亮处理
Map highlightFields =hit.getHighlightFields(); //获取高亮部分
HighlightField name = highlightFields.get(“name”); //获取高亮的字段
Text[] fragments = name.fragments(); //高亮字段对应的值是一个数组
skuMap.put(“name”,fragments[0].toString());//用高亮的内容替换原内容
resultList.add(skuMap);
}
resultMap.put(“rows”,resultList);

(3)修改模板中商品名称部分
因为返回给前端的数据中包含html代码,用text标签则会原样展示,需要使用utext标签来展示副文本

整体架构:
将不同类型的构建器放入搜索源构建器中,再将搜索源构建器放入SearchRequest中
比如:
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(“name”, searchMap.get(“keywords”));
boolQueryBuilder.must(matchQueryBuilder);
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(“categoryName”, searchMap.get(“category”));
boolQueryBuilder.filter(termQueryBuilder);
…可以组合不同类型的查询

searchSourceBuilder.query(boolQueryBuilder);
分页
searchSourceBuilder.from(fromIndex);//开始索引设置 from,size和query是同级的
searchSourceBuilder.size(pageSize);//每页记录数设置
排序
searchSourceBuilder.sort(sortField, SortOrder.valueOf(sortOrder));
高亮
searchSourceBuilder.highlighter(highlightBuilder);
聚合
searchSourceBuilder.aggregation(termsAggregationBuilder);
最后
searchRequest.source(searchSourceBuilder);

//3.获取查询结果
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits searchHits = searchResponse.getHits();
… 对结果进行处理
Aggregations aggregations = searchResponse.getAggregations();//对分组查询结果的封装
Map aggregationMap = aggregations.getAsMap();//可能有多个结果

你可能感兴趣的:(ElasticSearch详解)