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小时内删除。
发表评论