Elastic Search

一、概述

es

  1. elastic search是用于存储、搜索的项目

    kibana是展示数据的项目

    kibana结合es可以实时地对数据进行搜索、分析和可视化。

  2. es是一个开源的高扩展的分布式全文搜索引擎,是整个elastic stack技术栈的核心。

    它可以近乎实时地存储、检索数据。

  3. es的全文搜索的原理是倒排索引。

  4. Lucene是Apache软件基金会Jakarta项目组的一个子项目,提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。

    但Lucene只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的服务框架搭建起来进行应用。

    这里的完善的服务框架便是说的es和solr。

  5. es是基于Java开发的。

全文搜索

  1. Google,百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时候输入关键字,它们会将该关键字即索引匹配到的所有网页返回;还有常见的项目中应用日志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持。

  2. 一般的传统关系型数据库,都不会用来进行全文检索,一般也没有人用关系型数据库存储文本字段。进行全文检索需要扫描整个表,如果数据量大的话即使对SQL语法进行优化,也收效甚微。

  3. 使用ES或Solr这种全文检索引擎:

    • 搜索的数据对象是大量的非结构化的文本数据。
    • 文件记录量达到数十万或数百万甚至更多
    • 对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足。
  4. 全文检索的工作原理

    • 分词

    • 对词进行处理,缩减为词根等等

    • 根据TF-IDF算法对每一个词计算频率,分别是这个词在当前文档中出现的次数,以及这个词在整个数据集中在哪些文档中出现了

    • 根据上一步计算出的两个指标,对词进行排序,这个就是构建倒排索引

      TF-IDF算法是根据词在单个文档中出现的次数和在全部文档中出现的次数。

    • 检索程序就根据事先建立的索引进行查找

二、入门

安装

  1. windows版的es压缩包,解压即安装完成。

  2. 目录:

    目录含义
    bin可执行文件目录
    config配置目录
    jdk内置jdk
    lib类库
    logs日志目录
    modules模块目录
    plugins插件目录
  3. bin目录下的elasticsearch.bat即是windows版本的es服务启动文件。

  4. 9300端口为elasticsearch集群间组件的通信端口,9200端口为浏览器访问的http协议的restful端口。

  5. 路径是对资源的定位,方法是对资源的操作。

RESTful

  1. ES的对数据的操作是符合RESTful风格的API

    ES的请求和响应都是JSON字符串。

  2. 对RESTful的简单理解:

    如果想要访问互联网上的资源,就必须向资源所在的服务器发出请求,请求体中必须包含资源的网络路径,以及对资源的操作。

    REST样式的API的响应大多是JSON格式的字符串。网络中是传不了对象的,必须要进行序列化。

  3. HTTP 1.1标准中没有规定GET方法是否可以有请求体,因此GET方法也可以带有请求体,完全符合标准。ES中就是用GET方法的请求体传递搜索条件,为了兼容性考虑,ES也接收POST方法+请求体的搜索方式。

数据格式

  1. ElasticSearch是面向文档型数据库,一条数据在这里就是一个文档。

  2. 与MySQL对比的话,如下

    index    type  document fields
    database table row      column
    

    这里type的概念被弱化,ES6.X中,一个index下已经只能包含一个Type,而不是像MySQL一样,一个数据里有多张数据表。而到ES7.X中,Type的概念已经被删除了。

  3. 全文搜索引擎,不管是ES还是solr都会进行分词;对词进行语法优化;根据TF-IDF算法,为词建立倒排索引

    这里并没有表的概念。

三、基本使用

索引操作

创建索引

  1. 对比关系型数据库,创建索引就等同于创建数据库。

    在postman中,向ES服务发送PUT请求:http://localhost:9200/shopping

  2. PUT localhost:9200/shopping

    response:

    {
        "acknowledged": true,
        "shards_acknowledged": true,
        "index": "shopping"
    }
    

    "acknowledged": true 表示创建index成功

    PUT具有幂等性,意味着只要发送同样的请求,结果一样,所以这个时候如果再发送一次请求,就会提示index已存在。

    POST没有幂等性,两次操作可能结果不一样,所以如果用POST创建索引,那么可能创建两个名称一样的索引(两次创建索引都会成功),这是不允许的。

    不能用POST方式来创建索引。

获取索引信息

  1. GET localhost:9200/shopping

    获取索引shopping的相关信息。

  2. 查看所有索引

    GET localhost:9200/_cat/indices?v

删除索引

  1. DELETE localhost:9200/shopping

文档操作

创建文档

  1. 添加的数据格式为JSON格式。

    指定索引,向ES服务器发送POST请求。这里不能使用PUT请求。

    创建

    http://localhost:9200/shopping/_doc

    requestBody:

    {
    	"title": "小米手机",
        "category": "小米",
        "images": "http://www.gulixueyuan.com/xm.jpg",
        "price": 3999.00
    }
    

    response

    {
        "_index": "shopping",
        "_type": "_doc",
        "_id": "Xs9ZEoMBp69JQLv9Eto8",
        "_version": 1,
        "result": "created",
        "_shards": {
            "total": 2,
            "successful": 1,
            "failed": 0
        },
        "_seq_no": 1,
        "_primary_term": 1
    }
    

    可以看到response中有一个字段是_id,这是由es随机生成的,也就意味着,每次发送请求,返回的结果都不一样,而PUT请求是幂等性的,多次发出请求,返回的结果一样,所以这里不能使用具有幂等性的PUT请求。

    这个_id是数据的唯一性标识,类似于数据表中的主键。

  2. 创建文档--指定ID

    POST/PUT localhost:9200/shopping/_doc/1001

    这是一个幂等性的操作,所以用PUT请求也可以。

    POST/PUT localhost:9200/shopping/_create/1002

查询文档

  1. 根据ID进行单条数据查询

    GET localhost:9200/shopping/_doc/1002

    response

    {
        "_index": "shopping",
        "_type": "_doc",
        "_id": "1002",
        "_version": 1,
        "_seq_no": 3,
        "_primary_term": 1,
        "found": true,
        "_source": {
            "title": "小米手机",
            "category": "小米",
            "images": "http://www.gulixueyuan.com/xm.jpg",
            "price": 3999.00
        }
    }
    

    _source表示查询结果,也就是数据源,这种方式类似于主键查询。

  2. 查询指定索引下的所有数据

    GET localhost:9200/shopping/_search

    response

    {
        "took": 8,
        "timed_out": false,
        "_shards": {
            "total": 1,
            "successful": 1,
            "skipped": 0,
            "failed": 0
        },
        "hits": {
            "total": {
                "value": 4,
                "relation": "eq"
            },
            "max_score": 1.0,
            "hits": [
                {
                    "_index": "shopping",
                    "_type": "_doc",
                    "_id": "Xc9XEoMBp69JQLv9LNpW",
                    "_score": 1.0,
                    "_source": {
                        "title": "小米手机",
                        "category": "小米",
                        "images": "http://www.gulixueyuan.com/xm.jpg",
                        "price": 3999.00
                    }
                },
                {
                    "_index": "shopping",
                    "_type": "_doc",
                    "_id": "Xs9ZEoMBp69JQLv9Eto8",
                    "_score": 1.0,
                    "_source": {
                        "title": "小米手机",
                        "category": "小米",
                        "images": "http://www.gulixueyuan.com/xm.jpg",
                        "price": 3999.00
                    }
                },
                {
                    "_index": "shopping",
                    "_type": "_doc",
                    "_id": "1001",
                    "_score": 1.0,
                    "_source": {
                        "title": "小米手机",
                        "category": "小米",
                        "images": "http://www.gulixueyuan.com/xm.jpg",
                        "price": 3999.00
                    }
                },
                {
                    "_index": "shopping",
                    "_type": "_doc",
                    "_id": "1002",
                    "_score": 1.0,
                    "_source": {
                        "title": "小米手机",
                        "category": "小米",
                        "images": "http://www.gulixueyuan.com/xm.jpg",
                        "price": 3999.00
                    }
                }
            ]
        }
    }
    

修改文档

  1. 根据id修改文档

    PUT/POST localhost:9200/shopping/_doc/1001

    requestBody

    {
    	"title": "小米手机",
        "category": "小米",
        "images": "http://www.gulixueyuan.com/xm.jpg",
        "price": 4000.00
    }
    

    response

    {
        "_index": "shopping",
        "_type": "_doc",
        "_id": "1001",
        "_version": 2,
        "result": "updated",
        "_shards": {
            "total": 2,
            "successful": 1,
            "failed": 0
        },
        "_seq_no": 5,
        "_primary_term": 1
    }
    
  2. 局部修改

    POST localhost:9200/shopping/_update/1001

    requestBody

    {
    	"doc": {
            "title": "华为手机"
        }
    }
    

删除文档

  1. 根据ID删除单个文档

    DELETE localhost:9200/shopping/_doc/1001

查询操作

条件查询

  1. GET localhost:9200/shopping/_search?q=category:小米

  2. 对以上的条件查询,会调整为根据请求体requestBody来传递参数

    GET localhost:9200/shopping/_search

    {
    	"query": {
    		"match": {
    			"category": "小米"
    		}
    	}
    }
    

    没有在地址栏上写查询的参数

全查询

GET localhost:9200/shopping/_search

{
	"query": {
		"match_all": {
			
		}
	}
}

等价于GET localhost:9200/shopping/_search

可以不带请求体!HTTP 1.1标准中没有规定GET方法是否可以有请求体,因此GET方法也可以带请求体,完全符合标准。elasticsearch中就是用GET方法的请求体传递搜索条件。为了兼容性考虑,elasticsearch也接受POST方法+请求体的搜索方式。

不带请求体只适用于全查询,因为不需要给查询条件。

分页查询

GET localhost:9200/shopping/_search

{
	"query": {
		"match_all": {
			
		}
	},
    "from": 0,
    "size": 2
}

from表示当前页数据的起始位置

size表示当前页有多少条数据

返回指定字段

GET localhost:9200/shopping/_search

{
	"query": {
		"match_all": {
			
		}
	},
    "from": 2,
    "size": 2,
    "_source": ["title"]
}

指定字段排序

GET localhost:9200/shopping/_search

{
	"query": {
		"match_all": {
			
		}
	},
    "sort": {
        "price": {
            "order": "asc"
        }
    }
}

多条件查询

  1. 多个条件同时成立--AND

    must用数组,表示多条件的意思

    localhost:9200/shopping/_search

    {
    	"query": {
    		"bool": {
    			"must": [
                    {
                        "match": {
                        "category": "小米"
                        }
                    }
                ]
    		}
    	},
        "sort": {
            "price": {
                "order": "asc"
            }
        }
    }
    

    must就是and的意思,就是must后面的数组里面的条件要同时成立

  2. OR

    must改为了should

        {
            "query": {
                "bool": {
                    "should": [
                        {
                            "match": {
                            "category": "小米"
                            }
                        },
                         {
                            "match": {
                            "category": "华为"
                            }
                        }
                    ]
                }
            },
            "sort": {
                "price": {
                    "order": "asc"
                }
            }
        }
    

范围查询

{
	"query": {
		"bool": {
			"should": [{
					"match": {
						"category": "小米"
					}
				},
				{
					"match": {
						"category": "华为"
					}
				}
			],
			"filter": {
				"range": {
					"price": {
						"gt": 3000
					}
				}
			}
		}
	},

	"sort": {
		"price": {
			"order": "asc"
		}
	}
}

全文检索

  1. 完全匹配

        {
            "query": {
                "match_phrase": {
                    "category": "华为"
                }
                    
            }
        }
    

高亮

  1.     {
            "query": {
                "match_phrase": {
                    "category": "小米"
                }
            },
            "highlight": {
                "fields": {
                    "category": {
    
                    }
                }
            }
        }
    

聚合操作

  1. 按分组统计

    request

        {
            "aggs": {
                "price_group": {
                    "terms": { //分组
                        "field": "price"
                    }
                }
            }
        }
    

    response

    {
        "took": 25,
        "timed_out": false,
        "_shards": {
            "total": 1,
            "successful": 1,
            "skipped": 0,
            "failed": 0
        },
        "hits": {
            "total": {
                "value": 4,
                "relation": "eq"
            },
            "max_score": 1.0,
            "hits": [
                {
                    "_index": "shopping",
                    "_type": "_doc",
                    "_id": "Xc9XEoMBp69JQLv9LNpW",
                    "_score": 1.0,
                    "_source": {
                        "title": "小米手机",
                        "category": "小米",
                        "images": "http://www.gulixueyuan.com/xm.jpg",
                        "price": 3999.00
                    }
                },
                {
                    "_index": "shopping",
                    "_type": "_doc",
                    "_id": "Xs9ZEoMBp69JQLv9Eto8",
                    "_score": 1.0,
                    "_source": {
                        "title": "小米手机",
                        "category": "小米",
                        "images": "http://www.gulixueyuan.com/xm.jpg",
                        "price": 3999.00
                    }
                },
                {
                    "_index": "shopping",
                    "_type": "_doc",
                    "_id": "1002",
                    "_score": 1.0,
                    "_source": {
                        "title": "小米手机",
                        "category": "小米",
                        "images": "http://www.gulixueyuan.com/xm.jpg",
                        "price": 3999.00
                    }
                },
                {
                    "_index": "shopping",
                    "_type": "_doc",
                    "_id": "1004",
                    "_score": 1.0,
                    "_source": {
                        "title": "小米手机",
                        "category": "小米",
                        "images": "http://www.gulixueyuan.com/xm.jpg",
                        "price": 3999.00
                    }
                }
            ]
        },
        "aggregations": {
            "price_group": {
                "doc_count_error_upper_bound": 0,
                "sum_other_doc_count": 0,
                "buckets": [
                    {
                        "key": 3999.0,
                        "doc_count": 4
                    }
                ]
            }
        }
    }
    
  2. 计算某个字段的平均值

    request

    size 为0表示不要原始数据。

        {
            "aggs": {
                "price_avg": {
                    "avg": {
                        "field": "price"
                    }
                }
            },
            "size":0
        }
    

    response

    {
        "took": 14,
        "timed_out": false,
        "_shards": {
            "total": 1,
            "successful": 1,
            "skipped": 0,
            "failed": 0
        },
        "hits": {
            "total": {
                "value": 4,
                "relation": "eq"
            },
            "max_score": null,
            "hits": []
        },
        "aggregations": {
            "price_avg": {
                "value": 3999.0
            }
        }
    }
    

映射

概述

  1. 在MySQL中,字段类型、长度等信息都属于表的结构信息,在ES中,也有类似的概念,称之为映射mapping。

创建mapping

  1. PUT localhost:9200/user/_mapping

    {
    	"properties": {
    		"name": {
    			"type": "text", //text意思是name的类型是文本,意味着可以分词
    			"index": true //表示这个字段可以索引查询
    		},
    		"sex": {
    			"type": "keyword", //keyword表示不能分词,必须完整匹配
    			"index": true //表示这个字段可以索引查询
    		},
    		"tel": {
    			"type": "keyword", //keyword表示不能分词,必须完整匹配
    			"index": false //表示这个字段不可以索引查询
    		}
    	}
    }
    

查询mapping信息

  1. GET localhost:9200/user/_mapping

    response

    {
        "user": {
            "mappings": {
                "properties": {
                    "name": {
                        "type": "text"
                    },
                    "sex": {
                        "type": "keyword"
                    },
                    "tel": {
                        "type": "keyword",
                        "index": false
                    }
                }
            }
        }
    }
    

四、JavaAPI

环境准备

  1.     <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.11</version>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.elasticsearch</groupId>
                <artifactId>elasticsearch</artifactId>
                <version>7.8.0</version>
            </dependency>
    
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-high-level-client</artifactId>
                <version>7.8.0</version>
            </dependency>
    
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-api</artifactId>
                <version>2.8.2</version>
            </dependency>
        </dependencies>
    
  2. 在代码中创建ES客户端对象,连接ES服务,因为早期版本的客户端对象不再推荐使用,所以我们这里采用高级REST客户端对象。

客户端配置

  1. 创建客户端,连接ES服务

    public class ESClientConfig {
        public static RestHighLevelClient getESClient() {
            //创建ES客户端
            RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost("localhost", 9200, "http"));
            RestHighLevelClient esClient = new RestHighLevelClient(restClientBuilder);
            return esClient;
        }
    }
    
  2. 连接服务

        public void testConn() {
            RestHighLevelClient esClient = ESClientConfig.getESClient();
            System.out.println(esClient);
            try {
                esClient.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    

索引操作

创建索引

  1.     public void createIndex() {
            RestHighLevelClient esClient = ESClientConfig.getESClient();
            CreateIndexRequest createIndexRequest = new CreateIndexRequest("user");
    
            try {
                CreateIndexResponse createIndexResponse = esClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
                //打印响应状态
                System.out.println(createIndexResponse.isAcknowledged());
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                try {
                    esClient.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    

查询索引

  1.     public void getIndex() {
            GetIndexRequest getIndexRequest = new GetIndexRequest("user");
            RestHighLevelClient esClient = ESClientConfig.getESClient();
            try {
                GetIndexResponse getIndexResponse = esClient.indices().get(getIndexRequest, RequestOptions.DEFAULT);
                System.out.println(getIndexResponse.getAliases());
                System.out.println(getIndexResponse.getMappings());
                System.out.println(getIndexResponse.getSettings());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                esClient.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    

删除索引

  1.     public void deleteIndex() {
            DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("user");
            RestHighLevelClient esClient = ESClientConfig.getESClient();
            try {
                AcknowledgedResponse delete = esClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
                System.out.println(delete);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                esClient.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    

文档操作

添加文档

  1.     public void createDoc() throws IOException {
            IndexRequest userIndexRequest = new IndexRequest();
            userIndexRequest.index("user").id("1001");
            User user = new User();
            user.setName("zhangsan");
            user.setAge(30);
            user.setSex("男");
            // ES提供RESTful API进行数据的操作,Java客户端同样需要提供json格式的requestBody
            ObjectMapper mapper = new ObjectMapper();
            String userJson = mapper.writeValueAsString(user);
            userIndexRequest.source(userJson, XContentType.JSON);
            IndexResponse response = esClient.index(userIndexRequest, RequestOptions.DEFAULT);
            System.out.println(response.getResult());
            esClient.close();
        }
    

修改数据

  1.     public void updateDoc() throws IOException {
            UpdateRequest userUpdateRequest = new UpdateRequest();
            userUpdateRequest.index("user").id("1001");
            userUpdateRequest.doc(XContentType.JSON, "sex", "女");
            UpdateResponse updateResponse = esClient.update(userUpdateRequest, RequestOptions.DEFAULT);
            System.out.println(updateResponse.getResult());
            esClient.close();
        }
    

查询数据

  1.     public void getDoc() throws IOException  {
            GetRequest getRequest = new GetRequest();
            getRequest.index("user").id("1001");
            GetResponse response = esClient.get(getRequest, RequestOptions.DEFAULT);
            String res = response.getSourceAsString();
            ObjectMapper mapper = new ObjectMapper();
            User user = mapper.readValue(res, User.class);
            System.out.println(user);
            esClient.close();
        }
    

删除数据

  1.     public void deleteDoc() throws IOException {
            DeleteRequest deleteRequest = new DeleteRequest();
            deleteRequest.index("user").id("1001");
            DeleteResponse response = esClient.delete(deleteRequest, RequestOptions.DEFAULT);
            System.out.println(response.toString());
            esClient.close();
        }
    

批量文档操作

批量插入

  1.     public void bulkInsert() throws IOException {
            BulkRequest bulkRequest = new BulkRequest();
            ObjectMapper mapper = new ObjectMapper();
            IndexRequest indexRequest = new IndexRequest().index("user").id("1001");
            User user = new User();
            user.setName("zhangsan");
            user.setAge(30);
            user.setSex("男");
            // ES提供RESTful API进行数据的操作,Java客户端同样需要提供json格式的requestBody
            indexRequest.source(mapper.writeValueAsString(user), XContentType.JSON);
            bulkRequest.add(indexRequest);
    
            IndexRequest indexRequest2 = new IndexRequest().index("user").id("1002");
            user.setName("lisi");
            user.setAge(30);
            user.setSex("女");
            indexRequest2.source(mapper.writeValueAsString(user), XContentType.JSON);
            bulkRequest.add(indexRequest2);
            BulkResponse response = esClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            System.out.println(response.getTook());
            System.out.println(response.getItems());
            esClient.close();
        }
    

    把之前的indexRequest放进BulkRequest里。

批量删除

  1.     public void bulkDelete() throws IOException {
            BulkRequest bulkRequest = new BulkRequest();
            bulkRequest.add(new DeleteRequest().index("user").id("1001"));
            bulkRequest.add(new DeleteRequest().index("user").id("1002"));
            BulkResponse response = esClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            System.out.println(response.getTook());
            System.out.println(response.getItems());
            esClient.close();
        }
    

条件查询

查询所有数据

  1.     public void docQuery() throws IOException {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("user");
            //构造查询条件
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
            searchRequest.source(sourceBuilder);
            SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(response.getHits().getTotalHits());
            for (SearchHit hit : response.getHits()) {
                System.out.println(hit.getSourceAsString());
            }
            esClient.close();
        }
    

指定字段查询

  1.     public void docQuery1() throws IOException {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("user");
            //构造查询条件
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(QueryBuilders.termQuery("name", "lisi"));
            searchRequest.source(sourceBuilder);
            SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(response.getHits().getTotalHits());
            for (SearchHit hit : response.getHits()) {
                System.out.println(hit.getSourceAsString());
            }
            esClient.close();
        }
    

分页查询

  1.     public void docQuery2() throws IOException {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("user");
            //构造查询条件
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
            sourceBuilder.from(0);
            sourceBuilder.size(1);
            searchRequest.source(sourceBuilder);
            SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(response.getHits().getTotalHits());
            for (SearchHit hit : response.getHits()) {
                System.out.println(hit.getSourceAsString());
            }
            esClient.close();
        }
    

查询结果排序

  1. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
    sourceBuilder.sort("age", SortOrder.DESC);
    

返回指定字段

  1. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
    String[] excludes = {};
    String[] includes = {"name"};
    sourceBuilder.fetchSource(includes, excludes);
    

高级查询

组合查询

  1.         BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            boolQueryBuilder.must(QueryBuilders.matchQuery("name", "zhangsan"));
            boolQueryBuilder.must(QueryBuilders.matchQuery("sex", "男"));
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(boolQueryBuilder);
            searchRequest.source(sourceBuilder);
    

范围查询

  1.         RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age");
            rangeQueryBuilder.gte(30);
            rangeQueryBuilder.lte(40);
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(rangeQueryBuilder);
            searchRequest.source(sourceBuilder);
    

模糊查询

  1.         FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "zhangsa").fuzziness(Fuzziness.ONE);
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(fuzzyQueryBuilder);
            searchRequest.source(sourceBuilder);
    

    fuzziness表示偏差的距离,即差几个字符能被查出来

高亮查询

  1.         SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "zhangsan");
            sourceBuilder.query(termQueryBuilder);
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("name");
            sourceBuilder.highlighter(highlightBuilder);
            searchRequest.source(sourceBuilder);
    

聚合查询

  1.     public void aggrQuery() throws IOException {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("user");
            //构造查询条件
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            //由于是聚合,这里使用的是AggregationBuilder。maxAge,
            // age,也就是要在该字段中聚合出最大值
            AggregationBuilder aggregationBuilder = AggregationBuilders.max("maxAge").field("age");
            sourceBuilder.aggregation(aggregationBuilder);
            searchRequest.source(sourceBuilder);
            SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(response.getHits().getTotalHits());
            for (SearchHit hit : response.getHits()) {
                System.out.println(hit.getSourceAsString());
            }
    
            Max maxAge = response.getAggregations().get("maxAge");
            System.out.println(maxAge.getType() + "--" + maxAge.getName() + ":" + maxAge.getValue());
            esClient.close();
        }
    

分组查询

  1.     public void groupQuery() throws IOException {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("user");
            //构造查询条件
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            // 分组
            AggregationBuilder aggregationBuilder = AggregationBuilders.terms("ageGroup").field("age");
            sourceBuilder.aggregation(aggregationBuilder);
            searchRequest.source(sourceBuilder);
            SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(response.getHits().getTotalHits());
            for (SearchHit hit : response.getHits()) {
                System.out.println(hit.getSourceAsString());
            }
            Terms terms = response.getAggregations().get("ageGroup");
            for (Terms.Bucket b: terms.getBuckets()) {
                System.out.println(b.getKey() + ":" + b.getDocCount());
            }
            esClient.close();
        }
    

五、ES环境

概述

  1. 单机的容量是有限的,为了避免出现单点故障,企业一般都是采用集群部署的方式。

    生产环境中,一般都是运行再指定服务器集群中。

  2. 单机服务器问题:

    • 负载会过高
    • 存储容量有限
    • 无法实现高可用
    • 并发处理能力有限
  3. 集群是把多个节点当成整体提供服务,分布式可以认为是思想,集群是物理层面的,是多台服务器节点组成一个集群,对外提供服务。

    一个个不同微服务就部署在这多个服务器节点上;同一个微服务项目,采用分布式部署的方式,避免单点故障。

  4. 配置服务器集群时,集群中节点数量大于等于2就可以认为是集群了。

    一个集群就是多个服务器节点(在kafka中这个概念是broker),共同持有整个的数据,并一起提供索引和搜索功能。

    一个elasticsearch集群有一个唯一的名字标识,整个名字默认就是“elasticsearch”,一个节点只能通过指定某个集群的名字,来加入这个集群。

节点

  1. 集群中包含很多服务器,一个节点就是其中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。

  2. 一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威角色名字,这个名字会在启动的时候赋予节点。

    这个名字对于管理工作来说很重要,因为在管理过程中,会去确定网络中的哪些服务器对应于ES集群中的哪些节点。

    一个节点可以通过配置集群名称的方式来加入一个指定集群,默认情况下,每个节点都会被安排加入一个叫做“elasticsearch”的集群中。

  3. 多个服务器节点就形成了一个服务器集群,但是是不是一个节点就是一个服务器,一个独立的电脑呢?不是的,一台服务器上也可以启动多个节点,模拟集群,但是生产环境是不会这么做的,因为集群的目的之一就是为了避免单点故障。

WIN集群部署

修改配置

第一台

  1. 配置集群

    # ---------------------------------- Cluster -----------------------------------
    #
    # Use a descriptive name for your cluster:
    #
    cluster.name: my-application
    #
    

    节点以集群名称作为标识来加入集群

  2. 配置节点

    # ------------------------------------ Node ------------------------------------
    #
    # Use a descriptive name for the node:
    #
    node.name: node-1001
    node.master: true
    node.data: true
    #
    # Add custom attributes to the node:
    #
    #node.attr.rack: r1
    #
    

    配置节点名称,节点是否是master等

  3. 配置网络

    # ---------------------------------- Network -----------------------------------
    #
    # Set the bind address to a specific IP (IPv4 or IPv6):
    #
    network.host: localhost
    #
    # Set a custom port for HTTP:
    #
    http.port: 1001
    transport.tcp.port: 9301
    #
    # For more information, consult the network module documentation.
    #
    
  4. 跨域配置

    # ---------------------------------- Various -----------------------------------
    #
    # Require explicit names when deleting indices:
    #
    #action.destructive_requires_name: true
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    

第二台

  1. 除了以上四点配置

    增加discovery配置,要去发现集群的其他节点,建立连接!

    # --------------------------------- Discovery ----------------------------------
    #
    # Pass an initial list of hosts to perform discovery when this node is started:
    # The default list of hosts is ["127.0.0.1", "[::1]"]
    #
    discovery.seed_hosts: ["localhost:9301"]
    discovery.zen.fd.ping_timeout: 1m
    discovery.zen.fd.ping_retries: 5
    

    localhost:9301是内部通信端口,前面配置的http.port: 1001是对外的http服务地址!!

查询节点状态

  1. GET http://localhost:1001/_cluster/health

    响应:

    {
        "cluster_name": "my-application",
        "status": "green",
        "timed_out": false,
        "number_of_nodes": 3,
        "number_of_data_nodes": 3,
        "active_primary_shards": 0,
        "active_shards": 0,
        "relocating_shards": 0,
        "initializing_shards": 0,
        "unassigned_shards": 0,
        "delayed_unassigned_shards": 0,
        "number_of_pending_tasks": 2,
        "number_of_in_flight_fetch": 0,
        "task_max_waiting_in_queue_millis": 45286,
        "active_shards_percent_as_number": 100.0
    }
    

Linux单点部署

修改配置

  1. config目录下的elasticsearch.yml文件

    #集群名称、节点名称、对外地址和port
    cluster.name: elasticsearch
    node.name: node-1
    network.host: 0.0.0.0
    http.port: 9200
    cluster.initial_master_nodes: ["node-1"] #把当前机器当作主节点。
    

Linux集群部署

修改配置

第一台

  1. config目录下的elasticsearch.yml文件

    # 集群名称
    cluster.name: cluster-es
    # 节点名称,每个节点的名称不能重复
    node.name: node-1
    # ip地址,每个节点的ip地址不能重复
    network.host: 192.168.15.214
    # 该节点是不是有资格主节点
    node.master: true
    node.data: true
    http.port: 9201
    http.cors.allow-origin: "*"
    http.cors.enabled: true
    http.max_content_length: 200mb
    # es7.x之后新增的配置,初始化一个新的集群时需要此配置来选举master
    cluster.initial_master_nodes: ["node-1"]
    # 节点发现,内部通信端口
    discovery.seed_hosts: ["192.168.15.214:9301", "192.168.15.214:9302", "192.168.15.214:9303"]
    gateway.recover_after_nodes: 2
    network.tcp.keep_alive: true
    network.tcp.no_delay: true
    transport.tcp.compress: true
    # 集群内同时启动的数据任务个数,默认是2个
    cluster.routing.allocation.cluster_concurrent_rebalance: 16
    # 添加或删除节点及负载均衡时并发恢复的线程个数,默认4个
    cluster.routing.allocation.node_concurrent_recoveries: 16
    # 初始化数据恢复时,并发恢复线程的个数,默认4个
    cluster.routing.allocation.node_initial_primaries_recoveries: 16
    

    其余两个服务器节点也按照此配置,要修改端口,节点名称等

六、ES进阶

核心概念

  1. 索引(Index)

    一个索引就是一个拥有几分相似特征的文档的集合,或者说数据集合,一个索引就对应于关系型数据库的一个数据库或者一个数据表,索引中的文档就是一条数据。

    一个索引由一个名字来标识,必须全是小写字母,并且当我们对这个索引中的文档即数据进行CRUD时,都要用到这个名字。

    能搜索的数据必须索引,这样的好处是提高查询速度。

  2. 类型(Type)

    Type的概念原先可以理解为对应于关系型数据库的数据表,但是现在已经弃用这个概念了,因为索引能直接和数据关联,倒排索引也是Lucene这个全文检索引擎的特性,keyword索引能和文档关联起来,也就是数据,所以在索引和数据之间不应该再多一个概念。

  3. 文档(Document)

    一个文档就是一条数据

    在一个index(数据库)里能存储任意多的文档

    一个文档为一条数据,也就是MySQL中的一行数据,一个表中有多行数据,而ES中,一个索引中有多条数据,所以表对应于索引。

    表能和对象进行映射,在ES中,Index和对象进行映射,一个Index中的数据的字段结构都是相同的。

  4. 分片(shards)

    一个索引可以存储超过单个节点硬件限制的大量数据。ES提供了将索引划分成多份的能力,每一份索引就成为分片,shard。

    当你创建一个索引的时候,你可以指定你想要的分片的数量。每一个分片放在不同的服务器节点上提供服务。

    至于一个分片怎样分布,它的文档怎样聚合和搜索请求,是完全由ES管理的,对用户透明,无需过分关心。

  5. 副本(Replicas)

    在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点由于故障处于离线状态,这种情况下有一个故障转移机制是非常有用的。ES允许你创建分片的一份或多份拷贝,这些拷贝叫做副本。

    作用:

    • 提供了高可用性,副本分片从不与原主分片置于同一服务器节点。
    • 提高吞吐量,因为搜索可以在所有的副本上并行运行。
Last Updated:
Contributors: 陈杨