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中查询到了文章来源:https://www.toymoban.com/news/detail-762724.html
复杂检索功能(使用ES提供的API)
一个带有聚合的复杂检索示例文章来源地址https://www.toymoban.com/news/detail-762724.html
/**
* 创建检索请求
*/
@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());
}
到了这里,关于【中间件】ElasticSearch:ES的基本概念与基本使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!