Elasticsearch入门

安装

elasticsearch 与可视化工具 kibana 版本必须对应,配置好Xms和Xmx,大小要合适,elasticsearch 是将会将数据放置在内存中,所以一般都是集群搭建

基本命令

  • GET /_cat/nodes:查看所有节点 _
  • _GET /_cat/health:查看 es 健康状况
  • GET /_cat/master:查看主节点 _
  • _GET /_cat/indices:查看所有索引

基本概念

索引 Index:相当于数据库中的 database

类型 Type:在 Index(索引)中,可以定义一个或多个类型。 类似于数据库中的 Table。每一种类型的数据放在一起,6.0版本就已经抛弃了该概念,8.0计划删除

文档 Document:保存在某个索引(Index)下,某种类型(Type)的一个数据(Document),文档是 JSON 格式的,Document 就像是 MySQL 中的某个 Table 里面的内容。注意:高版本已删除Type

倒排索引: 使用一种称为 倒排索引 的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。

假设有文档:①我喜欢JAVA编程语言 ②我爱JAVA ③我爱JAVA编程

文档评率文档1文档2文档3倒排列表
词汇ID 1(我)3(1,1,<1>),(2,1,<1>),(3,1,<1>)
词汇ID 2(爱)2(2,1,<2>),(3,1,<2>)
词汇ID 3(喜欢)1(1,1,<2>)
词汇ID 4(JAVA)3(1,1,<3>),(2,1,<3>),(3,1,<3>)
词汇ID 5(编程)2(1,1,<4>),(3,1,<4>)
词汇ID 6(语言)1(1,1,<5>)

根据文档列表,可以得到每个词汇出现的频率,词汇位置、命中规则多少给出评分、倒排列表等。

倒排列表:(DocId,TF,POS)

  • DocId:单词出现的文档id
  • TF:单词在某个文档中出现的次数
  • POS:单词在文档中出现的位置

比如编程在 1,3 号文档中出现过,出现评率都是 1,出现位置都是 4

增删改命令

基本命令见官方API https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html

删除文档或索引

DELETE customer/external/1
DELETE customer

elasticsearch并没有提供删除类型的操作,只提供了删除索引和文档的操作。

bulk 批量 API

POST customer/_bulk 
{"index":{"_id":"1"}} {"name": "John Doe" }  ## 两组为一条记录,前是操作类型
{"index":{"_id":"2"}} {"name": "Jane Doe" }  ## 后面是 requestBody

语法格式
{action:{metadata}}\n
{request body  }\n
{action:{metadata}}\n
{request body  }\n

如果一个单个的动作因任何原因而失败,它将继续处理它后面剩余的动作。当 bulk API 返回时,它将提供每个动作的状态(与发送的顺序相同),所以您可以检查是否一个指定的动作是不是失败了

基本查询

{ 
	"_index": "customer", //在哪个索引 
 	"_type": "external", //在哪个类型 
 	"_id": "1", //记录 id 
 	"_version": 2, //版本号 
	"_seq_no": 1, //并发控制字段,每次更新就会+1,用来做乐观锁 
  	"_primary_term": 1,//同上,主分片重新分配,如重启,就会变化
  	"found": true, 
  	"_source": { //真正的内容
  		 "name": "John Doe" 
	} 
}
######################进阶查询######################
# 默认查10条,按account_number升序排列
GET bank/_search?q=*&sort=account_number:asc

## 查20条,按balance升序排列,相等按account_number降序
GET bank/_search
{
  "query": {
    "match_all": {}
  }
  , "from": 0
  , "size": 20
  , "sort": [
    {
      "balance": {
        "order": "asc"
      }
    },{
      "account_number": {
        "order": "desc"
      }
    }
  ]
}


## 按account_number降序,只查询balance,age
GET bank/_search
{
  "query": {
    "match_all": {}
  }
  , "from": 0
  , "size": 20
  , "sort": [
    {
      "account_number": {
        "order": "desc"
      }
    }
  ]
  , "_source": ["age","balance"]
}


## match 返回 account_number=20GET bank/_search 
{
  "query": { 
    "match": { 
      "account_number": "20" 
      
    } 
  }
}

## 字符串全文检查,包含单词mill的
GET bank/_search
{
  "query": {
    "match": {
      "address": "mill"
    }
  }
}

## 分词 address 中包含 mill 或者 road 或者 mill road 的所有记录,并给出相关性得分
GET bank/_search
{
  "query": {
    "match": {
      "address": "mill road"
    }
  }
}

## 不分词 address 中包含 mill road 的所有记录,并给出相关性得分
GET bank/_search
{
  "query": {
    "match_phrase": {
      "address": "mill road"
    }
  }
}

## 多字段匹配 state 或者 address 包含 mill
GET bank/_search
{
  "query": {
    "multi_match": {
      "query": "mill",
      "fields": ["state","address"]
    }
  }
}

######################复合查询######################
##bool 用来做复合查询: 复合语句可以合并 任何 其它查询语句,包括复合语句,了解这一点是很重要的。这就意味 着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑##


## must:必须达到 must 列举的所有条件
## address 包含mill, gender包含M
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"address": "mill"}},
        {"match": {"gender": "M"}}
      ]
    }
  }
}


## should:应该达到 should 列举的条件,达到增加评分
## 并不会改变查询结果,如果 query 中只有 should 且只有
## 一种匹配规则,那么should会被作为默认条件影响结果
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {
          "address": "mill"
        }},
        {"match_phrase": {
          "gender": "M"
        }}
      ],
      "should": [
        {"match": {
          "address": "lane"
        }}
      ]
    }
  }
}



## must_not  必须不是指定的情况
## address 包含 mill,并且 gender 是 M,如果 address
## 里面有 lane 最好不过,但是email必须不包含 baluba.com
## 如果用match.keyword=mill和match_phrase=mill
## match_phrase 短语匹配能找到,包含就行
## match.keyword=mill 必须值等于这个
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {
          "address": "mill"
        }},
        {"match_phrase": {
          "gender": "M"
        }}
      ],
      "should": [
        {"match": {
          "address": "lane"
        }}
      ],
      "must_not": [
        {"match": {
          "email": "baluba.com"
        }}
      ]
    }
  }
}

######################结果过滤######################
## 并不是所有的查询都需要产生分数,特别是那些仅用
## “filtering”(过滤)的文档。为了不计算分数
## Elasticsearch 会自动检查场景并且优化查询的执行
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {
          "address": "mill"
        }}
      ],
      "filter": {"range": {
        "balance": {
          "gte": 10000,
          "lte": 20000
        }
      }}
    }
  }
}
######################term######################
## term =》和match一样,匹配某个属性的值
## 全文检索字段用 match,其他非 text 字段匹配用 term
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {"term": {
          "age": {
            "value": "28"
          }
        }},
        {
          "match": {
            "address": "990 Mill Road"
          }
        }
      ]
    }
  }
}

###############aggregations(执行聚合################
## 聚合提供了从数据中分组和提取数据的能力
## 最简单的聚合方法大致等于SQL GROUP BYSQL 聚合函数
## 在 Elasticsearch,您有执行搜索返回hits(命中结果)
## 并且同时返 回聚合结果
## 把一个响应中的所有 hits(命中结果)分隔开的能力
## 这是非常强大的,您可以执行查询和多个聚合
## 并且在一次使用中得到各自的(任何一个的)返回结果
## 使用一次简洁和简化的 API 来避免网络往返


## aggs 执行聚合,语法如下
## "aggs":{
##    "aggs_name": 这次聚合的名字,方便展示在结果集中
##     {
##        "agg_type聚合的类型(avg,term,terms)":{}
##     }
## }

## 搜索address中包含mill的所有人的年龄分布及平均年龄
## 但不显示这些人的详情 size 0
GET bank/_search
{
  "query": {
    "match": {
      "address": "mill"
    }
  },
  "aggs": {
    "group_by_age": {
      "terms": {
        "field": "age",
        "size": 10
      }
    },
    "avg_age":{
      "avg": {
        "field": "age"
      }
    }
  },
  "size": 0
}


## 按照年龄聚合,并且请求这些年龄段的这些人的平均薪资
## 然后排序
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "age_group_by": {
      "terms": {
        "field": "age",
        "size": 10000
      },
      "aggs": {
        "balance_avg":{
          "avg": {
            "field": "balance"
          }
        },
        "sss":{
          "bucket_sort": {
            "sort": [{
              "balance_avg": {
                "order": "desc"
              }
            }]
          }
        }
      }
    }
  }
  , "size": 0
}

## 查出所有年龄分布,并且这些年龄段中 M 的平均薪资和
## F 的平均薪资以及这个年龄 段的总体平均薪资

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "age_group_by": {
      "terms": {
        "field": "age",
        "size": 1000
      },
      "aggs": {
        "gender_FM": {
          "terms": {
            "field": "gender.keyword",
            "size": 1000
          },
          "aggs": {
            "balance_avg": {
              "avg": {
                "field": "balance"
              }
            }
          }
        },
        "balance_zt_avg_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  },
  "size": 0
}

ElasticSearch 7.0 开始移除 type。两个不同 type 下的两个 user_name,在 ES 同一个索引下其实被认为是同一个 filed,

必须在两个不同的 type 中定义相同的 filed 映射。否则,不同 type 中的相同字段名称就会在处理中出现冲突的情况,导致 Lucene 处理效率下降。因此为了增加效率,减少出错移除了 type。

ES集群 ->索引->类型->文档 ==》ES集群 ->索引->文档

字段类型

在这里插入图片描述

Mapping 映射

Mapping 是用来定义一个文档(document),以及它所包含的属性(field)是如何存储和索引的

##############Mapping########
## 字段类型 可以直接指定字段类型
## 不知道有默认的 数字long ,字符串text
GET bank/_mapping

PUT /my_index
{
  "mappings": {
    "properties": {
      "age":{"type": "integer"},
      "name":{"type": "text"},
      "email":{"type": "keyword"}
    }
  }
}

##############Mapping########
## 字段类型 可以直接指定字段类型
## 不知道有默认的 数字long ,字符串text
GET bank/_mapping


## 添加映射
PUT /my_index
{
  "mappings": {
    "properties": {
      "age":{"type": "integer"},
      "name":{"type": "text"},
      "email":{"type": "keyword"}
    }
  }
}

## 添加映射
## 添加 employee-id
## index:false 表示不需要被索引(不能检索)
## 默认所有字段都是被索引的
PUT /my_index/_mapping
{
  "properties": {
    "employee-id": {
      "type": "keyword",
      "index": false
    }
  }
}

## 修改映射 (ES不能修改映射)
## 对于已经存在的映射字段,不能更新
## 更新必须创建新的索引进行数据迁移
##############数据迁移##############
GET /bank/_mapping
## ①添加新index并设置映射
PUT /newbank
{
  "mappings": {
    "properties": {
      "account_number": {
        "type": "long"
      },
      "address": {
        "type": "text"
      },
      "age": {
        "type": "integer"
      },
      "balance": {
        "type": "long"
      },
      "city": {
        "type": "text"
      },
      "email": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "employer": {
        "type": "text"
      },
      "firstname": {
        "type": "text"
      },
      "gender": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "lastname": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "state": {
        "type": "keyword"
      }
    }
  }
}
GET /newbank/_mapping
## ②迁移数据
GET /bank/_search
## 发现bak有类型“hits-> "_type"”
## 如果没有去除那一行
POST _reindex
{
  "source": {
    "index": "bank",
    "type": "account"
  },
  "dest": {
    "index": "newbank"
  }
}

GET /newbank/_search

分词

一个 tokenizer(分词器)接收一个字符流,将之分割为独立的 tokens(词元,通常是独立的单词),然后输出 tokens 流。例如whitespace tokenizer 遇到空白字符时分割文本。它会将文本 “Quick brown fox!” 分割为 [Quick, brown, fox!]。该 tokenizer(分词器)还负责记录各个 term(词条)的顺序或 position 位置(用于 phrase 短语和 word proximity 词近邻查询),以及 term(词条)所代表的原始 word(单词)的 start (起始)和 end(结束)的 character offsets(字符偏移量)(用于高亮显示搜索的内容)。Elasticsearch 提供了很多内置的分词器,可以用来构建 custom analyzers(自定义分词器)。但是不好用,对中文不够友好。一般安装自定义的"ik"分词器

安装 ik 分词器

ik分词器地址 对应 es 版本安装。如果是在docker容器中,进入 es 容器内部 plugins 目录 docker exec -it 容器 id /bin/bash(如果做了映射就不需要这一步,直接进入对应映射好的文件夹)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nanqM54m-1640423960075)(C:\Users\zhe\AppData\Roaming\Typora\typora-user-images\image-20211213232535461.png)]在这里插入图片描述
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
在这里插入图片描述
在这里插入图片描述进入容器内部 bin目录查看是否安装成功在这里插入图片描述在这里插入图片描述

ik分词器的使用

安装成功后再Kibana上测试默认的和ik的两种分词器

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


### ik_smart 分词器
## 我是 中国人
POST _analyze
{
  "analyzer": "ik_smart",
  "text": "我是中国人"
}

### ik_max_word 分词器
## 我 是 中国人 中国 国人
POST _analyze
{
  "analyzer": "ik_max_word",
  "text": "我是中国人"
}

ik分词器自定义词库

修改/elasticsearch/plugins/ik/config/IKAnalyzer.cfg.xml文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AkoQX14a-1640423960091)(C:\Users\zhe\AppData\Roaming\Typora\typora-user-images\image-20211214094242187.png)]

#随便启动一个nginx实例,没有会自动创建
docker run -p 80:80 --name nginx -d nginx:1.10
# 将配置文件拷贝到当前目录
docker container cp nginx:/etc/nginx .
#把这个 conf 移动到/mydata/nginx下
mv nginx conf 
# 停止刚刚启动的容器并删除
docker stop nginx
docker rm nginx
# 创建新容器
docker run -p 80:80 --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
# 设置开机自启动
docker update nginx --restart=always

nginx 的 html 下面放的所有资源可以直接访问,然后我们自定义词库放到这里,配置文件那就可以直接写
在这里插入图片描述

<entry key = "remote_ext_dict">http://192.168.56.10/es/fenci.txt</entry>

配置好后,我们的 ElasticSearch 就能识别词库中的自定义词语了

配置完成重启后 ElasticSearch 只会对新增的数据用新词分词。历史数据是不会重新分词的。如果想要历 史数据重新分词。需要执行以下指令

POST my_index/_update_by_query?conflicts=proceed

JAVA整合 ElasticSearch - Elasticsearch-Rest-Client

官方文档:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.4/index.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lssdT4gD-1640423960094)(C:/Users/zhe/Desktop/note/images/ElasticSearch/image-20211214212812344.png)]

点击 “+” 可以查看详细操作哦,然后我们就可以按照文档一步步操作,里面都非常之详细

第一步:导入依赖
<!-- 导入ES高阶API -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.4.2</version>
</dependency>

<!-- 自带版本ES也要设置成7.4.2 -->
<properties>
    <java.version>1.8</java.version>
    <elasticsearch.version>7.4.2</elasticsearch.version>
</properties>
第二步:编写配置类
public class ElasticSearchConfig {
    public static final RequestOptions COMMON_OPTIONS;
    
    // 请求是要携带类似于密钥的 COMMON_OPTIONS
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//        builder.addHeader("Authorization", "Bearer " + TOKEN);
//        builder.setHttpAsyncResponseConsumerFactory(
//                new HttpAsyncResponseConsumerFactory
//                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
        COMMON_OPTIONS = builder.build();
    }

    /**
     * 以后要操作ElasticSearch都是有该类
     */
    @Bean
    public RestHighLevelClient esRestClient() {
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("192.168.56.10", 9200, "http"),
                        // new HttpHost("localhost", 9201, "http"))); // 集群多个时可以添加多个
        return client;
    }

}
第三步:参照官方文档操作(官方文档

举几个例子,可以从中发现,使用简单,和官方的 DSL 语言一样,都提供了一个 Builder 去操作,然后参照写好的 DSL 语句无限套娃即可 ,需要注意的是,对于数组类型必须定义为要加上type:nested ,不然会被扁平化处理。实际上我们是没有的也被检索出来
在这里插入图片描述

@Autowired
private RestHighLevelClient client;

// 测试存储数据(保存/更新)
@Test
void indexData() throws IOException {
    // 创建index请求
    IndexRequest indexRequest = new IndexRequest("users");
    // 数据id
    indexRequest.id("1");
    // 1.map key-value
    //        indexRequest.source("userName","zhangsan","age",18,"gender","M");
    // 常用方式
    User user = new User();
    user.setGender("男");
    user.setUserName("张三");
    user.setAge(18);
    String jsonString = JSON.toJSONString(user);
    // 要保存的内容
    indexRequest.source(jsonString, XContentType.JSON);
    // 执行语句
    IndexResponse index = client.index(indexRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
    // 提取有用响应数据
    System.out.println(index);
}

@Test
void searchData() throws IOException {
    // 1.创建检索请求
    SearchRequest searchRequest = new SearchRequest("users");
    // 在哪个index下检索
    searchRequest.indices("bank");
    // 2.DSL,指定检索条件 SearchSourceBuilder 封装的条件
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 2.1构造检索条件
    sourceBuilder.query(QueryBuilders.matchQuery("address","mill"));
    System.out.println(sourceBuilder);

    searchRequest.source(sourceBuilder);

    // 3.执行检索
    SearchResponse response = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

    // 4.分析结果
    System.out.println(response);
}

/**
     * 复杂检索:在bank中搜索address中包含mill的所有人的年龄分布以及平均年龄,平均薪资
     *
     * @throws IOException
     */
@Test
public void searchDataComplex() throws IOException {
    //1. 创建检索请求
    SearchRequest searchRequest = new SearchRequest();

    //1.1)指定索引
    searchRequest.indices("bank");
    //1.2)构造检索条件
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.matchQuery("address", "Mill"));

    //1.2.1)按照年龄分布进行聚合
    TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
    sourceBuilder.aggregation(ageAgg);

    //1.2.2)计算平均年龄
    AvgAggregationBuilder ageAvg = AggregationBuilders.avg("ageAvg").field("age");
    sourceBuilder.aggregation(ageAvg);
    //1.2.3)计算平均薪资
    AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
    sourceBuilder.aggregation(balanceAvg);

    System.out.println("检索条件:" + sourceBuilder);
    searchRequest.source(sourceBuilder);
    //2. 执行检索
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    System.out.println("检索结果:" + searchResponse);

    //3. 将检索结果封装为Bean
    SearchHits hits = searchResponse.getHits();
    SearchHit[] searchHits = hits.getHits();
    for (SearchHit searchHit : searchHits) {
        String sourceAsString = searchHit.getSourceAsString();
        Account account = JSON.parseObject(sourceAsString, Account.class);
        System.out.println(account);

    }

    //4. 获取聚合信息
    Aggregations aggregations = searchResponse.getAggregations();

    Terms ageAgg1 = aggregations.get("ageAgg");

    for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
        String keyAsString = bucket.getKeyAsString();
        System.out.println("年龄:" + keyAsString + " ==> " + bucket.getDocCount());
    }
    Avg ageAvg1 = aggregations.get("ageAvg");
    System.out.println("平均年龄:" + ageAvg1.getValue());

    Avg balanceAvg1 = aggregations.get("balanceAvg");
    System.out.println("平均薪资:" + balanceAvg1.getValue());
}


@Data
class User {
    private String userName;
    private int age;
    private String gender;
}

@Data
static class Account {
    private int account_number;
    private int balance;
    private String firstname;
    private String lastname;
    private int age;
    private String gender;
    private String address;
    private String employer;
    private String email;
    private String city;
    private String state;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值