ElasticSearch的基本原理与⽤法
⼀、简介
ElasticSearch和Solr都是基于Lucene的搜索引擎,不过ElasticSearch天⽣⽀持分布式,⽽Solr是4.0版本后的SolrCloud才是分布式版
本,Solr的分布式⽀持需要ZooKeeper的⽀持。
语法参考:
⼆、基本⽤法
集(Cluster): ES是⼀个分布式的搜索引擎,⼀般由多台物理机组成。这些物理机,通过配置⼀个相同的cluster name,互相发现,把⾃⼰组织成⼀个集。
节点(Node):同⼀个集中的⼀个Elasticsearch主机。
Node类型:
1)data node: 存储index数据。Data nodes hold data and perform data related operations such as CRU
D, search, and aggregations.
2)client node: 不存储index,处理转发客户端请求到Data Node。
3)master node: 不存储index,集管理,如管理路由信息(routing infomation),判断node是否available,当有node出现或消失时重定位分⽚(shards),当有node failure时协调恢复。(所有的master node会选举出⼀个master leader node)
主分⽚(Primary shard):索引(下⽂介绍)的⼀个物理⼦集。同⼀个索引在物理上可以切多个分⽚,分布到不同的节点上。分⽚的实现是Lucene 中的索引。
注意:ES中⼀个索引的分⽚个数是建⽴索引时就要指定的,建⽴后不可再改变。所以开始建⼀个索引时,就要预计数据规模,将分⽚的个数分配在⼀个合理的范围。
副本分⽚(Replica shard):每个主分⽚可以有⼀个或者多个副本,个数是⽤户⾃⼰配置的。ES会尽量将同⼀索引的不同分⽚分布到不同的节点上,提⾼容错性。对⼀个索引,只要不是所有shards所在的机器都挂了,就还能⽤。
索引(Index):逻辑概念,⼀个可检索的⽂档对象的集合。类似与DB中的database概念。同⼀个集中可建⽴多个索引。⽐如,⽣产环境常见的⼀种⽅法,对每个⽉产⽣的数据建索引,以保证单个索引的量
级可控。
类型(Type):索引的下⼀级概念,⼤概相当于数据库中的table。同⼀个索引⾥可以包含多个 Type。
⽂档(Document):即搜索引擎中的⽂档概念,也是ES中⼀个可以被检索的基本单位,相当于数据库中的row,⼀条记录。
字段(Field):相当于数据库中的column。ES中,每个⽂档,其实是以json形式存储的。⽽⼀个⽂档可以被视为多个字段的集合。⽐如⼀篇⽂章,可能包括了主题、摘要、正⽂、作者、时间等信息,每个信息都是⼀个字段,最后被整合成⼀个json串,落地到磁盘。
映射(Mapping):相当于数据库中的schema,⽤来约束字段的类型,不过 Elasticsearch 的 mapping 可以不显⽰地指定、⾃动根据⽂档数据创建。
Database(数据库)Index(索引)
Table(表)Type(类型)
Row(⾏)Document(⽂档)
Column(列)Field(字段)
Schema(⽅案)Mapping(映射)
Index(索引)Everthing Indexed by default(所有字段都被索引)
SQL(结构化查询语⾔)Query DSL(查询专⽤语⾔)
Elasticsearch集可以包含多个索引(indices),每⼀个索引可以包含多个类型(types),每⼀个类型包含多个⽂档(documents),然后每个⽂档包含多个字段(Fields),这种⾯向⽂档型的储存,也算是NoSQL的⼀种吧。
ES⽐传统关系型数据库,对⼀些概念上的理解:
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices  -> Types  -> Documents -> Fields
从创建⼀个Client到添加、删除、查询等基本⽤法:
1、创建Client
1public ElasticSearchService(String ipAddress, int port) {
2        client = new TransportClient()
3                .addTransportAddress(new InetSocketTransportAddress(ipAddress,
4                        port));
define的基本用法
5    }
这⾥是⼀个TransportClient。
ES下两种客户端对⽐:
TransportClient:轻量级的Client,使⽤Netty线程池,Socket连接到ES集。本⾝不加⼊到集,只作为请求的处理。
Node Client:客户端节点本⾝也是ES节点,加⼊到集,和其他ElasticSearch节点⼀样。频繁的开启和关闭这类Node Clients会在集中产⽣“噪⾳”。
2、创建/删除Index和Type信息
1// 创建索引
2public void createIndex() {
3        client.admin().indices().create(new CreateIndexRequest(IndexName))
4                .actionGet();
5    }
6
7// 清除所有索引
8public void deleteIndex() {
9        IndicesExistsResponse indicesExistsResponse = client.admin().indices()
10                .exists(new IndicesExistsRequest(new String[] { IndexName }))
11                .actionGet();
12if (indicesExistsResponse.isExists()) {
13            client.admin().indices().delete(new DeleteIndexRequest(IndexName))
14                    .actionGet();
15        }
16    }
17
18// 删除Index下的某个Type
19public void deleteType(){
20        client.prepareDelete().setIndex(IndexName).setType(TypeName).execute().actionGet();
21    }
22
23// 定义索引的映射类型
24public void defineIndexTypeMapping() {
25try {
26            XContentBuilder mapBuilder = XContentFactory.jsonBuilder();
27            mapBuilder.startObject()
28                    .startObject(TypeName)
29                    .startObject("_all").field("enabled", false).endObject()
30                    .startObject("properties")
31                    .startObject(IDFieldName).field("type", "long").endObject()
32                    .startObject(SeqNumFieldName).field("type", "long").endObject()
33                    .startObject(IMSIFieldName).field("type", "string").field("index", "not_analyzed").endObject()
34                    .startObject(IMEIFieldName).field("type", "string").field("index", "not_analyzed").endObject()
35                    .startObject(DeviceIDFieldName).field("type", "string").field("index", "not_analyzed").endObject()
36                    .startObject(OwnAreaFieldName).field("type", "string").field("index", "not_analyzed").endObject()
37                    .startObject(TeleOperFieldName).field("type", "string").field("index", "not_analyzed").endObject()
38                    .startObject(TimeFieldName).field("type", "date").field("store", "yes").endObject()
39                    .endObject()
40                    .endObject()
41                    .endObject();
42
43            PutMappingRequest putMappingRequest = Requests
44                    .putMappingRequest(IndexName).type(TypeName)
45                    .source(mapBuilder);
46            client.admin().indices().putMapping(putMappingRequest).actionGet();
47        } catch (IOException e) {
48            (e.toString());
49        }
50    }
这⾥⾃定义了某个Type的索引映射(Mapping):
1)默认ES会⾃动处理数据类型的映射:针对整型映射为long,浮点数为double,字符串映射为string,时间为date,true或false为boolean。
2)字段的默认配置是indexed,但不是stored的,也就是 field("index", "yes").field("store", "no")。
3)这⾥Disable了“_all”字段,_all字段会把所有的字段⽤空格连接,然后⽤“analyzed”的⽅式index这个字段,这个字段可以被search,但是不能被retrieve。
4)针对string,ES默认会做“analyzed”处理,即先做分词、去掉stop words等处理再index。如果你需要把⼀个字符串做为整体被索引到,需要把这个字段这样设置:field("index", "not_analyzed")。
5)默认_source字段是enabled,_source字段存储了原始Json字符串(original JSON document body that was passed at index time)。详情参考:
3、索引数据
1// 批量索引数据
2public void indexHotSpotDataList(List<Hotspotdata> dataList) {
3if (dataList != null) {
4int size = dataList.size();
5if (size > 0) {
6                BulkRequestBuilder bulkRequest = client.prepareBulk();
7for (int i = 0; i < size; ++i) {
8                    Hotspotdata data = (i);
9                    String jsonSource = getIndexDataFromHotspotData(data);
10if (jsonSource != null) {
11                        bulkRequest.add(client
12                                .prepareIndex(IndexName, TypeName,
13                                        Id().toString())
14                                .setRefresh(true).setSource(jsonSource));
15                    }
16                }
17
18                BulkResponse bulkResponse = ute().actionGet(); 19if (bulkResponse.hasFailures()) {
20                    Iterator<BulkItemResponse> iter = bulkResponse.iterator();
21while (iter.hasNext()) {
22                        BulkItemResponse itemResponse = ();
23if (itemResponse.isFailed()) {
24                            (FailureMessage());
25                        }
26                    }
27                }
28            }
29        }
30    }
31
32// 索引数据
33public boolean indexHotspotData(Hotspotdata data) {
34        String jsonSource = getIndexDataFromHotspotData(data);
35if (jsonSource != null) {
36            IndexRequestBuilder requestBuilder = client.prepareIndex(IndexName,
37                    TypeName).setRefresh(true);
38            requestBuilder.setSource(jsonSource)
39                    .execute().actionGet();
40return true;
41        }
42
43return false;
44    }
45
46// 得到索引字符串
47public String getIndexDataFromHotspotData(Hotspotdata data) {
48        String jsonString = null;
49if (data != null) {
50try {
51                XContentBuilder jsonBuilder = XContentFactory.jsonBuilder();
52                jsonBuilder.startObject().field(IDFieldName, Id())
53                        .field(SeqNumFieldName, SeqNum())
54                        .field(IMSIFieldName, Imsi())
55                        .field(IMEIFieldName, Imei())
56                        .field(DeviceIDFieldName, DeviceID())
57                        .field(OwnAreaFieldName, OwnArea())
58                        .field(TeleOperFieldName, TeleOper())
59                        .field(TimeFieldName, CollectTime())
60                        .endObject();
61                jsonString = jsonBuilder.string();
62            } catch (IOException e) {
63                log.equals(e);
64            }
65        }
66
67return jsonString;
68    }
ES⽀持批量和单个数据索引。
4、查询获取数据
1// 获取少量数据100个
2private List<Integer> getSearchData(QueryBuilder queryBuilder) {
3        List<Integer> ids = new ArrayList<>();
4        SearchResponse searchResponse = client.prepareSearch(IndexName)
5                .setTypes(TypeName).setQuery(queryBuilder).setSize(100)
6                .execute().actionGet();
7        SearchHits searchHits = Hits();
8for (SearchHit searchHit : searchHits) {
9            Integer id = (Integer) Source().get("id");
10            ids.add(id);
11        }
12return ids;
13    }
14
15// 获取⼤量数据
16private List<Integer> getSearchDataByScrolls(QueryBuilder queryBuilder) {
17        List<Integer> ids = new ArrayList<>();
18// ⼀次获取100000数据
19        SearchResponse scrollResp = client.prepareSearch(IndexName)
20                .setSearchType(SearchType.SCAN).setScroll(new TimeValue(60000))
21                .setQuery(queryBuilder).setSize(100000).execute().actionGet();
22while (true) {
23for (SearchHit searchHit : Hits().getHits()) {
24                Integer id = (Integer) Source().get(IDFieldName);
25                ids.add(id);
26            }
27            scrollResp = client.ScrollId())
28                    .setScroll(new TimeValue(600000)).execute().actionGet();
29if (Hits().getHits().length == 0) {
30break;
31            }
32        }
33
34return ids;
35    }
这⾥的QueryBuilder是⼀个查询条件,ES⽀持分页查询获取数据,也可以⼀次性获取⼤量数据,需要使⽤Scroll Search。
5、聚合(Aggregation Facet)查询
1// 得到某段时间内设备列表上每个设备的数据分布情况<;设备ID,数量>
2public Map<String, String> getDeviceDistributedInfo(String startTime,
3                                                        String endTime, List<String> deviceList) {
4
5        Map<String, String> resultsMap = new HashMap<>();
6
7        QueryBuilder deviceQueryBuilder = getDeviceQueryBuilder(deviceList);
8        QueryBuilder rangeBuilder = getDateRangeQueryBuilder(startTime, endTime);
9        QueryBuilder queryBuilder = QueryBuilders.boolQuery()
10                .must(deviceQueryBuilder).must(rangeBuilder);
11
12        TermsBuilder termsBuilder = s("DeviceIDAgg").size(Integer.MAX_VALUE)
13                .field(DeviceIDFieldName);
14        SearchResponse searchResponse = client.prepareSearch(IndexName)
15                .setQuery(queryBuilder).addAggregation(termsBuilder)
16                .execute().actionGet();
17        Terms terms = Aggregations().get("DeviceIDAgg");
18if (terms != null) {
19for (Terms.Bucket entry : Buckets()) {
20                resultsMap.Key(),
21                        String.DocCount()));
22            }
23        }
24return resultsMap;
25    }
Aggregation查询可以查询类似统计分析这样的功能:如某个⽉的数据分布情况,某类数据的最⼤、最⼩、总和、平均值等。
查询准备
当我们获取到connection以后,接下来就可以开始做查询的准备。
1 ⾸先我们可以通过client来获取到⼀个SearchRequestBuilder的实例,这个是我们查询的主⼲。
然后你需要给SearchRequestBuilder指定查询⽬标, Index以及Type(映射到数据库就是数据库名称以及表名)
2 指定分页信息,setFrom以及setSize。
PS: 千万不要指望能⼀次性的查询到所有的数据,尤其是document特别多的时候。Elasticsearch最多只会给你返回1000条数据,⼀次性返回过量的数据是灾难性,这点⽆论是Elasticsearch亦或是Database都适⽤。
1 SearchRequestBuilder searchRequestBuilder = client.prepareSearch(ES_ITEM_INDEX).setTypes(ES_ITEM_TYPE)
2                .setFrom((pageNum - 1) * pageSize)
3                .setSize(pageSize);
3 接下来,你可能需要指定返回的字段,可以⽤如下的代码设置:
1 String[] includes = {"id", "name"};
2 searchRequestBuilder.setFetchSource(includes, null);
4 构建查询器
我们以⾃⼰熟悉的SQL来做实例讲解,如下sql:
select * from student where id = '123' and age > 12 and name like '⼩明' and hid in (...)
我们看SQL后⾯where条件。包含了’等于,⼤于,模糊查询,in查询’。
⾸先我们需要⼀个能包含复杂查询条件的BoolQueryBuilder,你可以将⼀个个⼩查询条件设置进去,最后将它设给searchRequestBuilder。
1 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
id = ‘123’:
1 QueryBuilder idQueryBuilder = Query("id", 123);
2 boolQueryBuilder.must(idQueryBuilder);
age > 12:
1 RangeQueryBuilder ageQueryBuilder = QueryBuilders.rangeQuery("age").gt(12);
2 boolQueryBuilder.must(ageQueryBuilder);
name like ‘⼩明’:
1 MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name", '⼩明');
2 matchQueryBuilder.operator(Operator.AND);
3 boolQueryBuilder.must(matchQueryBuilder);
这个就是我们⽐较关注的关键字查询了。因为我的Elasticsearch中字段name,已经配置了ik_smart分词器。
所以此处会将我的条件”⼩明”进⾏ik_smart的分词查询。⽽我设置的Operator.AND属性,指的是必须要我传
⼊的查询条件分词后字段全部匹配,才会返回结果。默认是Operator.OR,也就是其中有⼀个匹配,结果就返回。
hid in (…):
1 QueryBuilder queryBuilder = sQuery("hid", hidList);
2 boolQueryBuilder.must(queryBuilder);
PS: 上述的SQL条件中,皆是以AND关键字进⾏拼接。如果是其他的,⽐如OR,⽐如Not in呢?请看下⾯:
and – must
or – should
not in – must not
好,最后我们将queryBuilder设置进searchRequestBuilder中。
1// 设置查询条件
2 searchRequestBuilder.setQuery(boolQueryBuilder);
5 排序
我们同样需要⼀个排序构造器:SortBuilder。
咱先根据age字段,做降序。如下代码:
1 SortBuilder sortBuilder = SortBuilders.fieldSort("age");
der(SortOrder.fromString("DESC"));
如果想要做⼀些较为复杂的排序,⽐如多个字段相加:
1 String scriptStr = "doc['clickCount'].value + doc['soldNum'].value";
2 sortBuilder = SortBuilders.scriptSort(new Script(scriptStr), ScriptSortBuilder.ScriptSortType.NUMBER);
1// 设置排序规则
2 searchRequestBuilder.addSort(sortBuilder);
6 ⾼亮显⽰
在具体业务查询中,其实我们通常需要⽤到如该图的效果。
将查询命中的关键,进⾏标记颜⾊样式。
Elasticsearch同样提供了功能的实现,我们只需要设置被查询的字段即可。如下代码:
1 HighlightBuilder highlightBuilder = new HighlightBuilder();
2 highlightBuilder.field("name");
3 searchRequestBuilder.highlighter(highlightBuilder);
在下⾯的解析结果中,会具体介绍怎么解析出这么⾼亮结果。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。