ES 学习笔记分享

85 阅读9分钟

ES

1.基本概念

1.1 Index(索引)

动词,相当于MySQL的insert

名词,相当于MySQL中的Database

1.2 Type(类型)


在Index(索引中),可以定义一个或者多个类型。

类似于MySQL中的table。每一种类型的数据放在一起。

1.3 Document(文档)

保存在某个索引下,某种类型的有一个数据。文档是JSON形式的,Document就像是MySQL中的某个Table里面的内容。

1.4 倒排索引

2. Docker安装

2.1 下载镜像文件

docker pull elasticsearch:7.4.2
docker pull kibana:7.4.2 

2.2创建实例

2.2.1 安装ElasticSearch

mkdir -p /data/elasticsearch/config
mkdir -p /data/elasticsearch/data
​
echo "http.host: 0.0.0.0">>/data/elasticsearch/config/elasticsearch.yml
​
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e  "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx128m" \
-v /data/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /data/elasticsearch/data:/usr/share/elasticsearch/data \
-v  /data/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2 
​
chmod -R 777 /data/elasticsearch/

9200是发送http请求es的端口,9300是es在分布式集群状态下节点之间的通信端口,\是换行,

-e指定参数,single-node指定es单节点运行,ES_JAVA_OPTS指定es初始内存和最大内存,如果不指定会占用全部内存导致整个虚拟机卡死

-v挂载

-d,后台启动es用es7.4.2镜像

chmod -R 777 /data/elasticsearch/ 是给文件下所有用户的读写权限

看见这个信息说明安装成功。

{
  "name" : "a89ff87659af",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "AC6ze4rTRFWouH11lZUtbg",
  "version" : {
    "number" : "7.4.2",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "2f90bbf7b93631e52bafb59b3b049cb44ec25e96",
    "build_date" : "2019-10-28T20:40:44.881551Z",
    "build_snapshot" : false,
    "lucene_version" : "8.2.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

2.2.1 安装kibana

# kibana指定了了ES交互端口9200  # 5600位kibana主页端口
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://124.220.26.66:9200/ -p 5601:5601 -d kibana:7.4.2
​
​
# 设置开机启动kibana
docker update kibana  --restart=always
​

3.初步索引

3.1 _cat

GET /_cat/nodes 查看所有节点

GET /_cat/health 查看es健康状况

GET /_cat/master 查看主节点

GET /_cat/indices 查看所有索引

3.2 索引一个文档(保存)

保存一个数据,保存在哪个索引的哪个类型下,指定用哪一个唯一标识。

PUT /customer/external/1 首次是新增,多次调用带id是更新(PUT请求必须带id)

POST /customer/external 是一个新增操作,多次调用带id是新增,不带id是更新

3.3 查询文档

GET /customer/external/1

{
    "_index": "customer", //在哪个索引
    "_type": "external", //在哪个类型
    "_id": "1", //记录id
    "_version": 2 //版本号
    "_seq_no": 1, //并发控制字段,每次更新就会+1,用来做乐观锁
    "_primary_term": 1,//同上,主分片重新分配,如重启,就会变化
    "found": true,
    "_source": {  //真正的内容
        "name": "张三"
    }
}

保存更新(乐观锁形式 CAS)

PUT /customer/external/1?if_seq_no=7&if_primary_term=1

3.4 更新文档

POST /customer/external/1/_update 会对比原来数据,与原来一样就什么都不做,version版本号和seq_no都不变

{
    "doc":{
        "name":"张三"
    }
}

POST /customer/external/1 会不断更新,不检查版本号

{
    "name":"张三"
}

3.5 删除文档&索引

DELETE /customer/external/1 删除文档

DELETE /customer 删除索引

3.6 bulk批量API

POST /customer/external/_bulk

{"index":{"_id":1}}
{"name:":"张三1"}
{"index":{"_id":2}}
{"name:":"张三2"}

4.进阶搜索

4.1 Search API

ES支持两种基本方式检索:

  • 一种是通过REST request URI 发送搜索参数(uri+检索参数)
  • 另一种是通过使用REST request body 来发送它们(uri+请求体)
GET bank/_search?q=*&sort=account_number:asc

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "account_number": {
        "order": "desc"
      }
    }
  ]
}

4.2 Query DSL

ElasticSearch提供了一个可以执行的JSON风格的DSL(domain-specific language 领域特定语言),这个被称为Query DSL,该查询语言非常全面,并且刚开始的时候感觉有点复杂,真正学好它的方法就是从一些基础案例开始的。

完整的语法结构:

{
   QUERY_NAME:{
      ARGUMENT:VALUE,
      ARGUMENT:VALUE,...
   }
}

如果是针对某个字段,那么它的结构为

{
    QUERY_NAME:{
        FIELD_NAME:{
            ARGUMENT:VALUE,
            ARGUMENT:VALUE,...
        }
    }
}

4.2.1 match

上面我们用到来的match_all是匹配所有的数据,而我们现在要讲的match是条件匹配

如果对应的字段是基本类型(非字符串类型),则是精确匹配。

GET bank/_search
{
   "query":{
       "match":{
          "account_number":20
      }
   }
}

match返回的是 account_number:20的记录

如果对应的字段是字符串类型,则是全文检索

GET bank/_search
{
   "query":{
       "match":{
          "address":"mill"
      }
   }
}

match返回的就是address中包含mill字符串的记录

4.2.2 match_phrase

将需要匹配的值当成一个整体单词(不分词)进行检索,短语匹配

GET bank/_search
{
   "query":{
       "match_phrase":{
          "address":"mill road"
      }
   }
}

查询出address中包含 mill road的所有记录,并给出相关性得分

4.2.3 multi_match 多字段匹配

GET bank/_search
{
   "query":{
       "multi_match":{
          "query":"mill road",
          "fields":["address","state"]
      }
   }
}

查询出state或者address中包含 mill road的记录

4.2.4 复合查询

布尔查询又叫组合查询,bool用来实现复合查询,

bool把各种其它查询通过 must(与)、must_not(非)、should(或)的方式进行组合

复合语句可以合并任何其他查询语句,包括复合语句也可以合并,了解这一点很重要,这意味着,复合语句之间可以相互嵌套,可以表达非常复杂的逻辑。

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "age": "40" } }
      ],
      "must_not": [
        { "match": { "state": "ID" } }
      ]
    }
  }
}

4.2.5 filter 结果过滤

并不是所有的查询都需要产生分数,特别是那些仅用于"filtering"的文档,为了不计算分数,ElasticSearch会自动检查场景并且优化查询的执行。

GET /bank/_search
{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}

4.2.6 term

和match一样,匹配某个属性的值,全文检索字段用match,其他非text字段匹配用term

GET bank/_search
{
   "query":{
       "term":{
          "account_number":20
      }
   }
}

4.3 Mapping

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

  • 哪些字符串属性应该被看做全文本属性(full text fields)。
  • 哪些属性包含数字,日期或者地理位置。
  • 文档中的所有属性是否都能被索引(_all 配置)。
  • 日期的格式。
  • 自定义映射规则来执行动态添加属性。

使用动态映射和显式映射来定义数据。每种方法根据您在数据之旅中所处的阶段提供不同的优势。例如,显式映射不想使用默认值的字段,或者获得对创建哪些字段的更大控制。然后您可以允许Elasticsearch 动态添加其他字段。

Es7 及以上移除了 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 参数。 解决:

1)、将索引从多类型迁移到单类型,每种类型文档一个独立索引

2)、将已存在的索引下的类型数据,全部迁移到指定位置即可。详见数据迁移

4.3.1 创建映射

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

4.3.2 添加映射

PUT /my-index/_mapping
{ "properties": 
    { "employee-id":
         { "type": "keyword", "index": false
        }
     }
}

4.3.3 更新映射

对于已经存在的映射字段,我们不能更新。更新必须创建新的索引进行数据迁移。(因为已经建立好了索引字段)

4.3.4 数据迁移

POST _reindex
{
  "source": {
    "index": "bank",
    "type": "account"
  },
  "dest": {
    "index": "newbank"
  }
}

4.4 分词器

分词器是es中的一个组件,通俗意义上理解,就是将一段文本按照一定的逻辑,分析成多个词语,同时对这些词语进行常规化的一种工具;ES会将text格式的字段按照分词器进行分词,并编排成倒排索引,正是因为如此,es的查询才如此之快。

ES的中文分词器需要下载插件安装使用的,将下载的插件解压到ik目录下,安装好后记得重启es容器。

下载地址:

github.com/medcl/elast…

4.5自定义分词器

通过nginx反向代理一个远程词库,来实现自定义分词。

docker run -p 80:80 --name nginx \
-v /data/nginx/html:/usr/share/nginx/html \
-v /data/nginx/logs:/var/logs/nginx \
-v /data/nginx/conf:/etc/nginx \
-d nginx:1.10

5 Elasticsearch-RestClient

1.9300:TCP

  • springboot版本不同,transport-api.jar不同,不能适配es版本
  • 7.x已经不建议使用,8以后就要废弃

2.9200:HTTP

  • JestClient:非官方,更新慢
  • RestTemplate:模拟HTTP请求,ES很多操作需要自己封装,麻烦
  • HttpClient: 同上
  • Elasticsearch-Rest-Client:官方RestClient,封装了ES操作,API层次分明,上手简单。

5.1 导入依赖

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.4.2</version>
</dependency>

5.2 编写配置

/**
 * 1.导入依赖
 * 2.编写配置,给容器注入一个RestHighLevelClient
 * 3.参照API
 */
@Configuration
public class ElasticsearchConfig {

    public static final RequestOptions 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();
    }

    
    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(new HttpHost("hostname", port, "http")));
        return client;
    }

}

5.3 代码测试

package com.atjm.gulimall.search;

import com.alibaba.fastjson.JSONObject;
import com.atjm.gulimall.search.config.ElasticsearchConfig;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.Avg;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.validation.constraints.NotBlank;
import java.io.IOException;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallSearchApplicationTests {

	@Autowired
	private RestHighLevelClient restHighLevelClient;

	/**
	 * 测试存储数据到es
	 */
	@Test
	public void indexData() throws IOException {
		IndexRequest indexRequest = new IndexRequest("users");
		//indexRequest.id("1").source("username","zhangsan","age",18);
		User user = new User("张三","男",18);
		//要保存的内容
		indexRequest.source(JSONObject.toJSONString(user), XContentType.JSON);
		//执行操作
		IndexResponse index = restHighLevelClient.index(indexRequest, ElasticsearchConfig.COMMON_OPTIONS);
		//提取响应数据
		System.out.println(index);
	}

	/**
	 * 测试查询es
	 * @throws IOException
	 */
	@Test
	public void searchData() throws IOException {
		//1.创建检索请求
		SearchRequest searchRequest = new SearchRequest();
		//指定索引
		searchRequest.indices("bank");
		//指定DSL、检索条件
		SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
		//1.1 构造检索条件
//		searchSourceBuilder.query();
//		searchSourceBuilder.from();
//		searchSourceBuilder.size();
//		searchSourceBuilder.aggregation();
		searchSourceBuilder.query(QueryBuilders.matchQuery("address","mill"));
		//1.2 按照年龄的值分布聚合
		searchSourceBuilder.aggregation(AggregationBuilders.terms("ageAgg").field("age").size(10));
		//1.3 计算平均薪资
		searchSourceBuilder.aggregation(AggregationBuilders.avg("balanceAvg").field("balance"));

		System.err.println(searchSourceBuilder);
		searchRequest.source(searchSourceBuilder);

		//2.执行检索
		SearchResponse searchResponse = restHighLevelClient.search(searchRequest, ElasticsearchConfig.COMMON_OPTIONS);

		//3.分析结果
		System.err.println(searchResponse);
		//3.1 获取所有查到的数据
		SearchHit[] hits = searchResponse.getHits().getHits();
		/**
		 * "_index" : "users",
		 *         "_type" : "_doc",
		 *         "_id" : "fmhpvIYBD8pZWX53Nj0P",
		 *         "_score" : 1.0,
		 *         "_source" : {}
		 */
		for (SearchHit hit : hits) {
			Map<String, Object> sourceAsMap = hit.getSourceAsMap();
			String sourceAsString = hit.getSourceAsString();
		}
		//3.2获取检索到的分析信息
		Aggregations aggregations = searchResponse.getAggregations();
//		List<Aggregation> aggregations1 = aggregations.asList();
//		for (Aggregation aggregation : aggregations1) {
//			String name = aggregation.getName();
//			String type = aggregation.getType();
//			Map<String, Object> metaData = aggregation.getMetaData();
//		}
		Terms aggAgg = aggregations.get("ageAgg");
		for (Terms.Bucket bucket : aggAgg.getBuckets()) {
			String keyAsString = bucket.getKeyAsString();
			System.out.println("年龄:"+keyAsString);
		}
		Avg balanceAvg = aggregations.get("balanceAvg");
		System.out.println("平均薪资:"+balanceAvg);

	}

	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	class User{
		private String username;
		private String gender;
		private Integer age;
	}

}