【中间件】ElasticSearch:ES的基本概念与基本使用

ElasticSearch

ElasticSearch基本概念

Index索引、Type类型,类似于数据库中的数据库和表,我们说,ES的数据存储在某个索引的某个类型中(某个数据库的某个表中),Document文档(JSON格式),相当于是数据库中内容的存储方式

MySQL:数据库、表、数据

ElasticSearch:索引、类型、文档

概念:倒排索引

ElasticSearch的检索功能基于其倒排索引机制,该机制允许对检索的关键词进行拆分并判断其相关性得分,根据相关性得分再取得检索的结果排序,根据该排序返回具体的结果

ElasticSearch的安装

Docker安装ElasticSearch以及其可视化界面Kibana

拉取ES和Kibana,注意一定要注意ES的版本问题,不能直接拉取latest版本

进行ES的进一步安装,包括挂载等操作:

创建两个文件夹:mydata/elasticsearch/config 和 mydata/elasticsearch/data 用来挂载ES的配置和数据

进行一个写入操作:

echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml

创建并挂载ElasticSearch:

docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xms128m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:latest
  • run --name 是为容器起名并使其暴露两个端口,端口1是发送请求的端口,端口2是集群的通信端口

  • -e “discovery.type=single-node” \ 是令其以单节点方式启动

  • -e ES_JAVA_OPTS=“-Xms64m -Xms128m” \ 设置允许其占用的内存大小,64-128M,在实际上线中,其大概分配32G以上

  • 后面的就是挂载和指定镜像

Docker安装ES的一些细节

注意在Docker安装ES时,可能会出现权限不足导致ES已启动就立刻自动关闭的情况,这时就需要我们修改挂载在外部系统的文件夹的权限:

执行:

chmod -R 777 /mydata/elasticsearch

之后在浏览器中发送:网址:9200就可以判断ES是否安装成功

安装ES的可视化界面KIBANA

PULL拉取kibana之后执行下面操作:

 docker run --name kibana -e ELASTICSEARCH_HOSTS=http://8.141.85.98:9200 -p 5601:5601 \
 -d kibana:7.4.2
  • -e 指定的是对应的ES的地址,
  • -p 是指kibana暴露出的端口号
  • -d 指的是使用哪个镜像进行容器的创建

注意就算这里没有指定,我们也可以在kibana的配置文件中进行指定

ElasticSearch基本使用

ES的所有使用方式都以RestFul API的方式被封装为请求了,我们可以通过浏览器发送请求的方式来实现ES的使用

一些小功能

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

尝试:新增、修改数据

在customer索引下的external类型中保存1号数据的内容为:

{
    "name":"Jhon Doe"
}

注意一定要是PUT操作:

http://8.141.85.98:9200/customer/external/1

在发送的JSON中写入内容,注意新增和更新都是PUT操作,第一次是新增,后续再操作就是更新

但注意,如果我们在发送请求时不指定id,ES会为我们自动生成一个随机id,而我们的操作每次都是新增操作

注意PUT和POST有相同的效果,但PUT要求必须指定ID,POST在不指定ID的情况下会随机生成

查询数据

查询数据要发送get请求,举例查询:

返回的内容:

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 1,
    "_seq_no": 0,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "name": "Jhon Doe"
    }
}

此处要注意的是"_seq_no"字段,该字段是并发控制字段,使用乐观锁的方式进行并发控制:

我们可以在发送请求时携带并发判断字段:若版本未被其他业务修改,才进行修改,若已被其他业务修改,则自己不做任何操作:

POST:

http://8.141.85.98:9200/customer/external/1?if_seq_no=0&if_primary_term=1

若两个if后携带的字段都匹配的话,才会进行修改操作,这样规避了并发操作场景下结果的不可预测性

更新的几种操作

  • POST带_update:

    http://8.141.85.98:9200/customer/external/1/_update
    

    这种操作会对比原先的数据,若一致则什么也不做(版本号,并发控制字段都不改变),若不一致才进行修改,但注意我们带_update的修改要在修改的内容前加doc

    {
    	"doc":{
    		"name":"Jhon Doe"
    	}
    }
    

    对于其他的更新操作都不会进行数据对比,直接进行更新覆盖,并修改版本号和并发编程字段

删除

以DELETE的方式发送和查询一样的请求就可以做到删除了

ES复杂查询

Bulk批量操作API

使用对应的操作进行批量的增删改操作:

发送POST请求:

http://192.168.0.1:9200/customer/external/_bulk

索引、类型、_bulk进行批量操作,这些批量操作的方式是:

每一次修改都是两个JSON数据,第一条是元数据,用来指定操作的具体数据以及操作的方式

POST /customer/external/_bulk
{"index":{"_id":1}}
{"name":"Jhon Doe"}
{"index":{"_id":2}}
{"name":"Tom Jackson"}

在Kibana中进行测试:

{
  "took" : 10,					// 花费的时间:10ms
  "errors" : false,				// 未发生错误
  "items" : [
    {
      "index" : {
        "_index" : "customer",			// 操作的索引
        "_type" : "external",			// 操作的类型
        "_id" : "1",					// 操作的数据项id
        "_version" : 3,					// 版本
        "result" : "updated",			// 操作属性
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 2,					// 并发控制版本号
        "_primary_term" : 1,
        "status" : 200					// 200代表修改、201代表新建
      }
    },
    {
      "index" : {
        "_index" : "customer",
        "_type" : "external",
        "_id" : "2",
        "_version" : 1,
        "result" : "created",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 3,
        "_primary_term" : 1,
        "status" : 201
      }
    }
  ]
}

复杂的批量操作

不在发送请求时指定索引和类型,在文档信息中进行具体的指定

POST _bulk
{"delete":{"_index":"website", "_type":"blog", "_id":"123"}}
{"create":{"_index":"website", "_type":"blog", "_id":"123"}}
{"title": "My first blog"}
{"index":{"_index":"website", "_type":"blog"}}
{"title": "My second blog"}
{"update":{"_index":"website", "_type":"blog", "_id":"123"}}
{"doc": {"title": "My updated Blog"}}

导入测试数据进行测试:(省略)

发送如下请求查看ES中数据的整体信息:

http://8.141.85.98:9200/_cat/indices

复杂查询

查询:查询所有数据的所有信息,并按照账户id排序:

GET bank/_search?q=*&sort=account_number:asc

查询出的结果:

{
  "took" : 30,					
  "timed_out" : false,
  "_shards" : {						// shards中存放的是分布式存储过程中的各个结点的操作信息
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1000,
      "relation" : "eq"
    },
    "max_score" : null,				// 查询操作匹配的最大得分
    "hits":[]						// 具体内容
  }
}

QueryDSL(ES专属查询语言)

基本操作

ES所提供的查询操作:请求发送之后,紧接着发送一个JSON格式的查询语言,用来规定查询条件:

GET bank/_search
{
    "query":{
        "match_all":{}				// 不设置匹配条件,查询所有信息
    },
    "sort": [
        {
            "balance": {
                "order": "desc"			// 以账户余额降序排序
            }
        }
    ],
    "from": 5,				// 从第五条数据开始
    "size": 6,				// 展示6条数据
    "_source": ["balance", "firstname"]
}

设置查询条件的情况:

若我们的查询条件不为字符串,则代表我们所做的是精确查询,若为字符串,则是模糊查询:

精确查询
GET bank/_search
{
  "query": {
    "match":{
      "balance": 16418
    } 
  }
}

模糊查询
GET bank/_search
{
  "query": {
    "match":{
      "address": "Kings"
    } 
  }
}

ES全文检索会对分词进行匹配,对每一个分词都在全文中进行检索,并以得分从高到低进行数据的返回

使用match_phrase进行不分词的整体匹配,这样就会和MySql中的模糊查询一样进行整体匹配了:

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

多字段查询

使用multi_match进行多字段的查询:

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

对于每个字段,都进行查询,并且会进行分词查询

多字段拼接查询条件

使用bool进行查询条件的拼接:

GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {
          "gender": "M"
        }},
        {"match": {
          "address": "mill"
        }}
      ],
      "must_not": [
        {"match": {
          "age": 18
        }}
      ],
      "should": [
        {
          "match": {
            "lastname": "Wallace"
          }
        }  
      ]
    }
  }
}
  • must:必须匹配
  • must_not:必须不匹配
  • should:最好满足(应该)若满足会增加权重

区间:

"filter": {
    "range": {
        "age": {
            "gte": 18, 
            "lte": 30
        }
    }
}

注意filter会只进行过滤,不会增加得分,而must、must_not、should都会增加相关性得分

  • term:term只适用于精确查询,尤其适合对于非文本类型的检索,注意对于文档类型的查询,一定不要使用term

  • match_phrase:模糊查询

  • "match": {
    	"address.keyword": "xxxxxx"
    }
    

    这个是精确查询

总结:对于非文本字段,都以term进行查询,对于文本字段,都以match进行查询

ES的数据分析功能(聚合)

示例

## 搜索address中包含mill的所有人的年龄分布(取出每个年龄的人有几个(term)并显示10条记录)以及平均年龄
GET bank/_search
{
  "query":{
    "match": {
      "address": "mill"
    }
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age", 
        "size": 10
      }
    },
    "avgAgg": {
      "avg": {
        "field": "age"
      }
    }
  }
}

注意:在以文本字段为组进行分组时,我们必须在字段后添加.keyword

## 按照年龄聚合,并求这些年龄段人的平均薪资,并按照年龄分布对同一性别的人进行聚合,查他们的平均薪资
GET bank/_search
{
  "query": {
    "match_all": {}
  }, 
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age", 
        "size": 100
      },
      "aggs": {
        "genderAgg": {
          "terms": {
            "field": "gender.keyword"
          },
          "aggs": {
            "balanceAgg": {
              "avg": {
                "field": "balance"
              }
            }
          }
        }
      }
    }
  }
}

映射

我们在向ES中存储数据时,ES会自动猜测我们的数据类型,如果是纯数字会被映射为Long类型,其他会被映射为text类型,而这种映射有时候不是我们想要的,我们在创建的时候就可以提前指定映射类型。

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

映射类型不为text的都不会被分词器进行检索,keyword只会被精确检索

添加一个属性只能新增:

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

通过下面请求来查看映射信息:

GET my_index/_mapping

映射的修改

映射是不可以进行直接修改的,我们必须进行数据迁移才能完成对ES中映射关系的修改:

新增一个对应的索引

PUT newbank
{
  "mappings": {
    "properties": {
        "account_number" : {
          "type" : "long"
        },
        "address" : {
          "type" : "text"
        },
        "age" : {
          "type" : "integer"
        },
        "balance" : {
          "type" : "long"
        },
        "city" : {
          "type" : "keyword"
        },
        "email" : {
          "type" : "keyword"
        },
        "employer" : {
          "type" : "keyword"
        },
        "firstname" : {
          "type" : "text"
        },
        "gender" : {
          "type" : "keyword"
        },
        "lastname" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "state" : {
          "type" : "keyword"
        }
    }
  }
}

将老索引的配置迁移给新索引:

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

分词

ES将一句话分成若干个词,并按照分成的词进行全文检索,返回一个相关性排序的结果。

分词器

标准分词器尝试(中文):

POST _analyze
{
  "analyzer": "standard", 
  "text": "分词器测试"
}

分词结果为:‘分’、‘词’、‘器’、‘测’、‘试’

其实很不符合中文语境

ik分词器

ik分词器是一个基于中文语境的ES分词器,我们在github上找到他并对应ES版本进行安装(7.4.2)

使用如下命令安装ik分词器对应的7.4.2版本

wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip

这样很简便,但非常慢,我们也可以直接下载下来再上传完成这个操作

之后解压就算安装好了ik分词器,我们进入容器的bin目录执行:elasticsearch-list命令来查看分词器是否安装完成

之后我们就可以使用,ik分词器最常用的两种分词方式:

## 智能分词方式,
POST _analyze
{
  "analyzer": "ik_smart", 
  "text": "我是中国人"
}
我、是、中国人

## 最大分词方式
POST _analyze
{
  "analyzer": "ik_max_word", 
  "text": "我是中国人"
}
我、是、中国人、中国、国人

但是,ik分词器虽然能够对中文分词有较为合理的分词基础,但对于一些比较具有时效性的分词,其还是不能进行很智能的分词,故我们还要进行词库拓展:

删除原先的ES容器,并重新安装,给它赋予更大的内存,但注意,ES不会丢失数据,因为数据已经进行过挂载(这里其实不用):

docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2

在Linux的mydata文件夹下创建一个文件夹:nginx用来存放nginx的相关配置信息:

直接创建一个nginx容器用来复制器配置信息(不需要拉取镜像,如果没有镜像会自动拉取):

docker run -p 80:80 --name nginx -d nginx:1.10

复制这个nginx的配置文件到我们新建的nginx文件夹中:

docker container cp nginx:/etc/nginx .

移除刚刚创建的nginx容器,将刚刚的文件夹重命名为conf,再将这个文件夹移动到新建一个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

这样nginx就创建好了,我们可以在html文件中创建一个index.html来显示首页

我们在html文件夹中创建一个es文件夹,用来存放定制化分词信息

创建一个fenci.txt的文件,存放以下内容(分词内容):

清河
南开
中移

在es的挂载的plugins文件夹中的conf文件夹中的IKAnalyzer.cfg.xml文件夹中进行修改:

更改远程那部分为自己的远程部分:http://xxxxxxxxxxxxxx/es/fenci.txt就可以了

SpringBoot整合ES

  • JestClient: 非官方、更新慢
  • RestTemplate、HttpClient等模拟HTTP请求发送的组件:模拟HTTP请求的发送,很多ES操作需要自己封装,操作较为繁琐
  • 使用elasticsearch-rest-high-level-client依赖,这个依赖提供了便捷丰富的ES功能,这里也使用这种方式进行整合

在Java项目中建立一个新的包用来管理ES搜索服务

注意:只添加Web依赖,我选用SpringBoot 2.7.15版本

导入ElasticSearch依赖:

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

同时注意:SpringBoot也集成了ES版本,这个版本很可能与我们自己选用的ES版本冲突,故我们必须对SpringBoot自动集成的ES版本进行修改:

	<properties>
		<java.version>1.8</java.version>
		<elasticsearch.version>7.4.2</elasticsearch.version>
	</properties>

在application.properties中配置注册中心:

spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

spring.application.name=gulimail-search

创建配置类:config/GulimailElasticSearchConfig

@Configuration
public class GulimailElasticSearchConfig {
}

在启动类中添加启动注解:

@EnableDiscoveryClient

在配置类中添加Bean

    @Bean
    public RestHighLevelClient esRestClient() {
        RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("8.141.85.94", 9200, "http")));
        return client;
    }

之后测试一下:看看能不能注入

	@Autowired
	private RestHighLevelClient client;

导入ES配置项信息

想配置类中导入ES的配置项信息,搜索ElasticSearch High Level 找 RequestOptions

这些配置项可以要求ES在被访问时必须携带某些信息才可以进行访问

    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();
    }

简单测试

	@Test
	public void indexData() throws IOException {
		IndexRequest indexRequest = new IndexRequest("users");		// 调用构造器时指定索引
		indexRequest.id("1");										// 指定所添加字段的id
		User user = new User();
		user.setId(8L);
		user.setName("虎开发");
		user.setAge(20);
		String userString = JSON.toJSONString(user);
		indexRequest.source(userString, XContentType.JSON);					// 将要保存的内容存在对应的IndexRequest对象中
		IndexResponse index = client.index(indexRequest, GulimailElasticSearchConfig.COMMON_OPTIONS);
	}

之后就可以在ES中查询到了

复杂检索功能(使用ES提供的API)

一个带有聚合的复杂检索示例

/**
	 * 创建检索请求
	 */
	@Test
	public void searchData() throws IOException {
		// 1. 创建检索请求
		SearchRequest searchRequest = new SearchRequest();
		searchRequest.indices("bank");			// 指定要搜索的索引
		// 指定DSL检索条件
		SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
		/**
		 * sourceBuilder.query
		 * sourceBuilder.from
		 * sourceBuilder.size
		 * sourceBuilder.aggregation
		 *
		 * sourceBuilder.query()里面也可以拼matchAll方法用来查全部
		 */
		sourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));		// 匹配address字段中mill的值
		searchRequest.source(sourceBuilder);			// 将查询条件传给searchRequest

		// 聚合(构建聚合条件)
		TermsAggregationBuilder terms = AggregationBuilders.terms("aggAgg").field("age").size(10);
		sourceBuilder.aggregation(terms);		// 将聚合拼接到查询中

		AvgAggregationBuilder balanceAgg = AggregationBuilders.avg("balanceAgg").field("balance");
		sourceBuilder.aggregation(balanceAgg);


		SearchResponse searchResponse = client.search(searchRequest, GulimailElasticSearchConfig.COMMON_OPTIONS);

		SearchHits hits = searchResponse.getHits();// 获取大hits
		SearchHit[] hitsObjects = hits.getHits();// 获取具体的数据
		// 之后遍历就行,这里不遍历了

		Aggregations aggregations = searchResponse.getAggregations();
		Terms term = aggregations.get("aggAgg");		// 以Term方式的聚合可以直接转换为Term类型
		for (Terms.Bucket bucket : term.getBuckets()) {
			String keyAsString = bucket.getKeyAsString();
			System.out.println("年龄:" + keyAsString + "==>" + bucket.getDocCount());
		}

		Avg balanceAgg1 = aggregations.get("balanceAgg");
		System.out.println("平均薪资:" + balanceAgg1.getValue());

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值