Elastic Search
一、概述
es
elastic search是用于存储、搜索的项目
kibana是展示数据的项目
kibana结合es可以实时地对数据进行搜索、分析和可视化。
es是一个开源的高扩展的分布式全文搜索引擎,是整个elastic stack技术栈的核心。
它可以近乎实时地存储、检索数据。
es的全文搜索的原理是倒排索引。
Lucene是Apache软件基金会Jakarta项目组的一个子项目,提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。
但Lucene只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的服务框架搭建起来进行应用。
这里的完善的服务框架便是说的es和solr。
es是基于Java开发的。
全文搜索
Google,百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时候输入关键字,它们会将该关键字即索引匹配到的所有网页返回;还有常见的项目中应用日志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持。
一般的传统关系型数据库,都不会用来进行全文检索,一般也没有人用关系型数据库存储文本字段。进行全文检索需要扫描整个表,如果数据量大的话即使对SQL语法进行优化,也收效甚微。
使用ES或Solr这种全文检索引擎:
- 搜索的数据对象是大量的非结构化的文本数据。
- 文件记录量达到数十万或数百万甚至更多
- 对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足。
全文检索的工作原理
分词
对词进行处理,缩减为词根等等
根据TF-IDF算法对每一个词计算频率,分别是这个词在当前文档中出现的次数,以及这个词在整个数据集中在哪些文档中出现了
根据上一步计算出的两个指标,对词进行排序,这个就是构建倒排索引
TF-IDF算法是根据词在单个文档中出现的次数和在全部文档中出现的次数。
检索程序就根据事先建立的索引进行查找
二、入门
安装
windows版的es压缩包,解压即安装完成。
目录:
目录 含义 bin 可执行文件目录 config 配置目录 jdk 内置jdk lib 类库 logs 日志目录 modules 模块目录 plugins 插件目录 bin目录下的elasticsearch.bat即是windows版本的es服务启动文件。
9300端口为elasticsearch集群间组件的通信端口,9200端口为浏览器访问的http协议的restful端口。
路径是对资源的定位,方法是对资源的操作。
RESTful
ES的对数据的操作是符合RESTful风格的API
ES的请求和响应都是JSON字符串。
对RESTful的简单理解:
如果想要访问互联网上的资源,就必须向资源所在的服务器发出请求,请求体中必须包含资源的网络路径,以及对资源的操作。
REST样式的API的响应大多是JSON格式的字符串。网络中是传不了对象的,必须要进行序列化。
HTTP 1.1标准中没有规定GET方法是否可以有请求体,因此GET方法也可以带有请求体,完全符合标准。ES中就是用GET方法的请求体传递搜索条件,为了兼容性考虑,ES也接收POST方法+请求体的搜索方式。
数据格式
ElasticSearch是面向文档型数据库,一条数据在这里就是一个文档。
与MySQL对比的话,如下
index type document fields database table row column
这里type的概念被弱化,ES6.X中,一个index下已经只能包含一个Type,而不是像MySQL一样,一个数据里有多张数据表。而到ES7.X中,Type的概念已经被删除了。
全文搜索引擎,不管是ES还是solr都会进行分词;对词进行语法优化;根据TF-IDF算法,为词建立倒排索引
这里并没有表的概念。
三、基本使用
索引操作
创建索引
对比关系型数据库,创建索引就等同于创建数据库。
在postman中,向ES服务发送
PUT
请求:http://localhost:9200/shoppingPUT localhost:9200/shopping
response:
{ "acknowledged": true, "shards_acknowledged": true, "index": "shopping" }
"acknowledged": true 表示创建index成功
PUT具有幂等性,意味着只要发送同样的请求,结果一样,所以这个时候如果再发送一次请求,就会提示index已存在。
POST没有幂等性,两次操作可能结果不一样,所以如果用POST创建索引,那么可能创建两个名称一样的索引(两次创建索引都会成功),这是不允许的。
不能用POST方式来创建索引。
获取索引信息
GET localhost:9200/shopping
获取索引shopping的相关信息。
查看所有索引
GET localhost:9200/_cat/indices?v
删除索引
DELETE localhost:9200/shopping
文档操作
创建文档
添加的数据格式为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是数据的唯一性标识,类似于数据表中的主键。
创建文档--指定ID
POST/PUT localhost:9200/shopping/_doc/1001
这是一个幂等性的操作,所以用PUT请求也可以。
POST/PUT localhost:9200/shopping/_create/1002
查询文档
根据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表示查询结果,也就是数据源,这种方式类似于主键查询。
查询指定索引下的所有数据
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 } } ] } }
修改文档
根据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 }
局部修改
POST localhost:9200/shopping/_update/1001
requestBody
{ "doc": { "title": "华为手机" } }
删除文档
根据ID删除单个文档
DELETE localhost:9200/shopping/_doc/1001
查询操作
条件查询
GET localhost:9200/shopping/_search?q=category:小米
对以上的条件查询,会调整为根据请求体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"
}
}
}
多条件查询
多个条件同时成立--AND
must用数组,表示多条件的意思
localhost:9200/shopping/_search
{ "query": { "bool": { "must": [ { "match": { "category": "小米" } } ] } }, "sort": { "price": { "order": "asc" } } }
must就是and的意思,就是must后面的数组里面的条件要同时成立
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"
}
}
}
全文检索
完全匹配
{ "query": { "match_phrase": { "category": "华为" } } }
高亮
{ "query": { "match_phrase": { "category": "小米" } }, "highlight": { "fields": { "category": { } } } }
聚合操作
按分组统计
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 } ] } } }
计算某个字段的平均值
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 } } }
映射
概述
- 在MySQL中,字段类型、长度等信息都属于表的结构信息,在ES中,也有类似的概念,称之为映射mapping。
创建mapping
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信息
GET localhost:9200/user/_mapping
response
{ "user": { "mappings": { "properties": { "name": { "type": "text" }, "sex": { "type": "keyword" }, "tel": { "type": "keyword", "index": false } } } } }
四、JavaAPI
环境准备
<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>
在代码中创建ES客户端对象,连接ES服务,因为早期版本的客户端对象不再推荐使用,所以我们这里采用高级REST客户端对象。
客户端配置
创建客户端,连接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; } }
连接服务
public void testConn() { RestHighLevelClient esClient = ESClientConfig.getESClient(); System.out.println(esClient); try { esClient.close(); } catch (IOException e) { throw new RuntimeException(e); } }
索引操作
创建索引
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); } } }
查询索引
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); } }
删除索引
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); } }
文档操作
添加文档
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(); }
修改数据
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(); }
查询数据
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(); }
删除数据
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(); }
批量文档操作
批量插入
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里。
批量删除
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(); }
条件查询
查询所有数据
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(); }
指定字段查询
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(); }
分页查询
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(); }
查询结果排序
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()); sourceBuilder.sort("age", SortOrder.DESC);
返回指定字段
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()); String[] excludes = {}; String[] includes = {"name"}; sourceBuilder.fetchSource(includes, excludes);
高级查询
组合查询
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.must(QueryBuilders.matchQuery("name", "zhangsan")); boolQueryBuilder.must(QueryBuilders.matchQuery("sex", "男")); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(boolQueryBuilder); searchRequest.source(sourceBuilder);
范围查询
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age"); rangeQueryBuilder.gte(30); rangeQueryBuilder.lte(40); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(rangeQueryBuilder); searchRequest.source(sourceBuilder);
模糊查询
FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "zhangsa").fuzziness(Fuzziness.ONE); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(fuzzyQueryBuilder); searchRequest.source(sourceBuilder);
fuzziness表示偏差的距离,即差几个字符能被查出来
高亮查询
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);
聚合查询
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(); }
分组查询
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环境
概述
单机的容量是有限的,为了避免出现单点故障,企业一般都是采用集群部署的方式。
生产环境中,一般都是运行再指定服务器集群中。
单机服务器问题:
- 负载会过高
- 存储容量有限
- 无法实现高可用
- 并发处理能力有限
集群是把多个节点当成整体提供服务,分布式可以认为是思想,集群是物理层面的,是多台服务器节点组成一个集群,对外提供服务。
一个个不同微服务就部署在这多个服务器节点上;同一个微服务项目,采用分布式部署的方式,避免单点故障。
配置服务器集群时,集群中节点数量大于等于2就可以认为是集群了。
一个集群就是多个服务器节点(在kafka中这个概念是broker),共同持有整个的数据,并一起提供索引和搜索功能。
一个elasticsearch集群有一个唯一的名字标识,整个名字默认就是“elasticsearch”,一个节点只能通过指定某个集群的名字,来加入这个集群。
节点
集群中包含很多服务器,一个节点就是其中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。
一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威角色名字,这个名字会在启动的时候赋予节点。
这个名字对于管理工作来说很重要,因为在管理过程中,会去确定网络中的哪些服务器对应于ES集群中的哪些节点。
一个节点可以通过配置集群名称的方式来加入一个指定集群,默认情况下,每个节点都会被安排加入一个叫做“elasticsearch”的集群中。
多个服务器节点就形成了一个服务器集群,但是是不是一个节点就是一个服务器,一个独立的电脑呢?不是的,一台服务器上也可以启动多个节点,模拟集群,但是生产环境是不会这么做的,因为集群的目的之一就是为了避免单点故障。
WIN集群部署
修改配置
第一台
配置集群
# ---------------------------------- Cluster ----------------------------------- # # Use a descriptive name for your cluster: # cluster.name: my-application #
节点以集群名称作为标识来加入集群
配置节点
# ------------------------------------ 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等
配置网络
# ---------------------------------- 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. #
跨域配置
# ---------------------------------- Various ----------------------------------- # # Require explicit names when deleting indices: # #action.destructive_requires_name: true http.cors.enabled: true http.cors.allow-origin: "*"
第二台
除了以上四点配置
增加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服务地址!!
查询节点状态
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单点部署
修改配置
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集群部署
修改配置
第一台
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进阶
核心概念
索引(Index)
一个索引就是一个拥有几分相似特征的文档的集合,或者说数据集合,一个索引就对应于关系型数据库的一个数据库或者一个数据表,索引中的文档就是一条数据。
一个索引由一个名字来标识,必须全是小写字母,并且当我们对这个索引中的文档即数据进行CRUD时,都要用到这个名字。
能搜索的数据必须索引,这样的好处是提高查询速度。
类型(Type)
Type的概念原先可以理解为对应于关系型数据库的数据表,但是现在已经弃用这个概念了,因为索引能直接和数据关联,倒排索引也是Lucene这个全文检索引擎的特性,keyword索引能和文档关联起来,也就是数据,所以在索引和数据之间不应该再多一个概念。
文档(Document)
一个文档就是一条数据
在一个index(数据库)里能存储任意多的文档
一个文档为一条数据,也就是MySQL中的一行数据,一个表中有多行数据,而ES中,一个索引中有多条数据,所以表对应于索引。
表能和对象进行映射,在ES中,Index和对象进行映射,一个Index中的数据的字段结构都是相同的。
分片(shards)
一个索引可以存储超过单个节点硬件限制的大量数据。ES提供了将索引划分成多份的能力,每一份索引就成为分片,shard。
当你创建一个索引的时候,你可以指定你想要的分片的数量。每一个分片放在不同的服务器节点上提供服务。
至于一个分片怎样分布,它的文档怎样聚合和搜索请求,是完全由ES管理的,对用户透明,无需过分关心。
副本(Replicas)
在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点由于故障处于离线状态,这种情况下有一个故障转移机制是非常有用的。ES允许你创建分片的一份或多份拷贝,这些拷贝叫做副本。
作用:
- 提供了高可用性,副本分片从不与原主分片置于同一服务器节点。
- 提高吞吐量,因为搜索可以在所有的副本上并行运行。