ElasticSearch的基本原理與用法


一、簡介

ElasticSearch和Solr都是基於Lucene的搜索引擎,不過ElasticSearch天生支持分布式,而Solr是4.0版本后的SolrCloud才是分布式版本,Solr的分布式支持需要ZooKeeper的支持。

這里有一個詳細的ElasticSearch和Solr的對比:http://solr-vs-elasticsearch.com/

語法參考:
Elasticsearch Java API
Elasticsearch Query DSL

ElasticSearch安裝部署:http://nero.life/2017/10/27/Elasticsearch%E7%AC%94%E8%AE%B0-%E4%B8%80-%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2/

elasticsearch Docker:http://nero.life/2017/10/27/Elasticsearch%E7%AC%94%E8%AE%B0-%E4%BA%8C-Docker/

ElasticSearch筆記:http://nero.life/2017/10/27/Elasticsearch%E7%AC%94%E8%AE%B0/

二、基本用法

集群(Cluster): ES是一個分布式的搜索引擎,一般由多台物理機組成。這些物理機,通過配置一個相同的cluster name,互相發現,把自己組織成一個集群。

節點(Node):同一個集群中的一個Elasticsearch主機。

Node類型:

1)data node: 存儲index數據。Data nodes hold data and perform data related operations such as CRUD, 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)

詳情參考:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html

主分片(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

1     public ElasticSearchService(String ipAddress, int port) {
2         client = new TransportClient()
3                 .addTransportAddress(new InetSocketTransportAddress(ipAddress,
4                         port));
5     }

這里是一個TransportClient。

ES下兩種客戶端對比:

TransportClient:輕量級的Client,使用Netty線程池,Socket連接到ES集群。本身不加入到集群,只作為請求的處理。

Node Client:客戶端節點本身也是ES節點,加入到集群,和其他ElasticSearch節點一樣。頻繁的開啟和關閉這類Node Clients會在集群中產生“噪音”。

2、創建/刪除Index和Type信息

 1     // 創建索引
 2     public void createIndex() {
 3         client.admin().indices().create(new CreateIndexRequest(IndexName))
 4                 .actionGet();
 5     }
 6 
 7     // 清除所有索引
 8     public void deleteIndex() {
 9         IndicesExistsResponse indicesExistsResponse = client.admin().indices()
10                 .exists(new IndicesExistsRequest(new String[] { IndexName }))
11                 .actionGet();
12         if (indicesExistsResponse.isExists()) {
13             client.admin().indices().delete(new DeleteIndexRequest(IndexName))
14                     .actionGet();
15         }
16     }
17 
18     // 刪除Index下的某個Type
19     public void deleteType(){
20         client.prepareDelete().setIndex(IndexName).setType(TypeName).execute().actionGet();
21     }
22 
23     // 定義索引的映射類型
24     public void defineIndexTypeMapping() {
25         try {
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             log.error(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)。

詳情參考:

https://www.elastic.co/guide/en/elasticsearch/guide/current/mapping-intro.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-store.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-all-field.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-source-field.html

3、索引數據

 1     // 批量索引數據
 2     public void indexHotSpotDataList(List<Hotspotdata> dataList) {
 3         if (dataList != null) {
 4             int size = dataList.size();
 5             if (size > 0) {
 6                 BulkRequestBuilder bulkRequest = client.prepareBulk();
 7                 for (int i = 0; i < size; ++i) {
 8                     Hotspotdata data = dataList.get(i);
 9                     String jsonSource = getIndexDataFromHotspotData(data);
10                     if (jsonSource != null) {
11                         bulkRequest.add(client
12                                 .prepareIndex(IndexName, TypeName,
13                                         data.getId().toString())
14                                 .setRefresh(true).setSource(jsonSource));
15                     }
16                 }
17 
18                 BulkResponse bulkResponse = bulkRequest.execute().actionGet();
19                 if (bulkResponse.hasFailures()) {
20                     Iterator<BulkItemResponse> iter = bulkResponse.iterator();
21                     while (iter.hasNext()) {
22                         BulkItemResponse itemResponse = iter.next();
23                         if (itemResponse.isFailed()) {
24                             log.error(itemResponse.getFailureMessage());
25                         }
26                     }
27                 }
28             }
29         }
30     }
31 
32     // 索引數據
33     public boolean indexHotspotData(Hotspotdata data) {
34         String jsonSource = getIndexDataFromHotspotData(data);
35         if (jsonSource != null) {
36             IndexRequestBuilder requestBuilder = client.prepareIndex(IndexName,
37                     TypeName).setRefresh(true);
38             requestBuilder.setSource(jsonSource)
39                     .execute().actionGet();
40             return true;
41         }
42 
43         return false;
44     }
45 
46     // 得到索引字符串
47     public String getIndexDataFromHotspotData(Hotspotdata data) {
48         String jsonString = null;
49         if (data != null) {
50             try {
51                 XContentBuilder jsonBuilder = XContentFactory.jsonBuilder();
52                 jsonBuilder.startObject().field(IDFieldName, data.getId())
53                         .field(SeqNumFieldName, data.getSeqNum())
54                         .field(IMSIFieldName, data.getImsi())
55                         .field(IMEIFieldName, data.getImei())
56                         .field(DeviceIDFieldName, data.getDeviceID())
57                         .field(OwnAreaFieldName, data.getOwnArea())
58                         .field(TeleOperFieldName, data.getTeleOper())
59                         .field(TimeFieldName, data.getCollectTime())
60                         .endObject();
61                 jsonString = jsonBuilder.string();
62             } catch (IOException e) {
63                 log.equals(e);
64             }
65         }
66 
67         return jsonString;
68     }

ES支持批量和單個數據索引。

4、查詢獲取數據

 

 1     // 獲取少量數據100個
 2     private 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 = searchResponse.getHits();
 8         for (SearchHit searchHit : searchHits) {
 9             Integer id = (Integer) searchHit.getSource().get("id");
10             ids.add(id);
11         }
12         return ids;
13     }
14 
15     // 獲取大量數據
16     private 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();
22         while (true) {
23             for (SearchHit searchHit : scrollResp.getHits().getHits()) {
24                 Integer id = (Integer) searchHit.getSource().get(IDFieldName);
25                 ids.add(id);
26             }
27             scrollResp = client.prepareSearchScroll(scrollResp.getScrollId())
28                     .setScroll(new TimeValue(600000)).execute().actionGet();
29             if (scrollResp.getHits().getHits().length == 0) {
30                 break;
31             }
32         }
33 
34         return ids;
35     }

這里的QueryBuilder是一個查詢條件,ES支持分頁查詢獲取數據,也可以一次性獲取大量數據,需要使用Scroll Search。

5、聚合(Aggregation Facet)查詢 

 1     // 得到某段時間內設備列表上每個設備的數據分布情況<設備ID,數量>
 2     public 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 = AggregationBuilders.terms("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 = searchResponse.getAggregations().get("DeviceIDAgg");
18         if (terms != null) {
19             for (Terms.Bucket entry : terms.getBuckets()) {
20                 resultsMap.put(entry.getKey(),
21                         String.valueOf(entry.getDocCount()));
22             }
23         }
24         return resultsMap;
25     }

Aggregation查詢可以查詢類似統計分析這樣的功能:如某個月的數據分布情況,某類數據的最大、最小、總和、平均值等。

詳情參考:https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-aggs.html

查詢准備

當我們獲取到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 = QueryBuilders.termQuery("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 = QueryBuilders.termsQuery("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");
2 sortBuilder.order(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);

在下面的解析結果中,會具體介紹怎么解析出這么高亮結果。

  • 7 聚合

當然,Elasticsearch也提供了強大的聚合功能:

1 // 年齡聚合計算
2 TermsAggregationBuilder ageTermsAggregationBuilder = AggregationBuilders.terms("ageAgg");
3 brandTermsAggregationBuilder.field("age");
4 brandTermsAggregationBuilder.size(1000);
5 searchRequestBuilder.addAggregation(ageTermsAggregationBuilder);
  • 8 執行查詢
1 // 開始執行查詢
2 SearchResponse searchResponse = searchRequestBuilder.execute().actionGet();
  • 9 解析結果

直接上代碼:

 1 SearchHits searchHits = searchResponse.getHits();
 2 List<Student> students = new ArrayList<>();
 3 for (SearchHit hit : searchHits.getHits()) {
 4     String json = hit.getSourceAsString();
 5     # 讀取json,轉為成對象
 6     Student student= ...
 7     // 關鍵字高亮顯示
 8     HighlightField highlightField = hit.getHighlightFields().get("name");
 9     String highName = student.getName();
10     if (highlightField != null) {
11         highName = highlightField.getFragments()[0].toString();
12     }
13     student.setHighName(highName);
14     students.add(student);
15 }

我們可以從searchResponse中,獲取到所有的命中目標列表。然后循環列表,每個命中的hit,可以直接轉化成json。
然后可以json轉化工具,映射到自己的bean上。
其中,當你設置了highlightBuilder以后,你可以在每個hit里,get到被標簽包裹着的String。
(ps: 默認是em,你也可以設置其他的標簽,以供前端渲染。)

使用QueryBuilders進行查詢

elastcisearch-query-builder接受配置文件(特定json格式)或者json格式的字符串配置,配置格式如下:

{
  "index": "user_portrait",
  "type": "docs",
  "from": "${from}",
  "size": "10",
  "include_source": ["name","age"],  //需要哪些字段
  "exclude_source": ["sex"],  //排除哪些字段
  "query_type": "terms_level_query",
  "terms_level_query": {
    "terms_level_type": "term_query",
    "term_query": {
      "value": "${value}",
      "key": "key",
      "boost": 2
    }
  },
  "aggregations": [
    {
      "aggregation_type": "terms",
      "name": "",
      "field": "field",
      "sub_aggregations": {
        "aggregation_type": "terms",
        "name": "sub",
        "field": "field",
        "size": "${size.value}",
        "sort": "asc",
        "sort_by": "_count"
      }
    }
  ],
  "highlight":{
      "fields": [
            {
              "field": "content",
              "number_of_fragment": 2,
              "no_match_size": 150
            }
       ],
      "pre_tags":["<em>"],
      "post_tags":["</em>"]
  },
  "sort": [
    "_score",
    {
      "field": "age",
      "order": "asc"
    }
  ]
}

參數說明

# index

index表示elasticSearch中的索引或者別名。

# type

type表示elasticSearch索引或者別名下的type。

# from

from表示檢索文檔時的偏移量,相當於關系型數據庫里的offset。

# include_source

include_source 搜索結果中包含某些字段,格式為json數組,"include_source": ["name","age"]

# exclude_source

exclude_source 搜索結果中排除某些字段,格式為json數組,"exclude_source":["sex"]

# query_type

query_type表示查詢類型,支持三種類型terms_level_query,text_level_query,bool_level_query,並且這三種類型
不可以一起使用。

    • terms_level_query 操作的精確字段是存儲在反轉索引中的。這些查詢通常用於結構化數據,
      如數字、日期和枚舉, 而不是全文字段,包含term_query,terms_query,range_query,exists_query 等類型。

    • text_level_query 查詢通常用於在完整文本字段 (如電子郵件正文) 上運行全文查詢。他們了解如何分析被查詢的字段,
      並在執行之前將每個字段的分析器 (或 search_analyzer) 應用到查詢字符串。

      包含 match_query,multi_match_query,query_string,simple_query_string 等類型。

    • bool_query 與其他查詢的布爾組合匹配的文檔匹配的查詢。bool 查詢映射到 Lucene BooleanQuery。它是使用一個或多個布爾子句生成的, 每個子句都有一個類型化的實例。
      布爾查詢的查詢值包括: must,filter,should,must_not. 在每個布爾查詢的查詢類型值中,可以包含terms_level_query 和 text_level_query中任意的查詢類型,如此便可以構造非常復雜的查詢情況。

QueryBuilder 是es中提供的一個查詢接口, 可以對其進行參數設置來進行查詢:

  1 package com.wenbronk.javaes;
  2 
  3 import java.net.InetSocketAddress;
  4 import java.util.ArrayList;
  5 import java.util.Iterator;
  6 import java.util.Map.Entry;
  7 
  8 import org.elasticsearch.action.ListenableActionFuture;
  9 import org.elasticsearch.action.get.GetRequestBuilder;
 10 import org.elasticsearch.action.get.GetResponse;
 11 import org.elasticsearch.action.search.SearchResponse;
 12 import org.elasticsearch.action.search.SearchType;
 13 import org.elasticsearch.client.transport.TransportClient;
 14 import org.elasticsearch.common.settings.Settings;
 15 import org.elasticsearch.common.text.Text;
 16 import org.elasticsearch.common.transport.InetSocketTransportAddress;
 17 import org.elasticsearch.common.unit.TimeValue;
 18 import org.elasticsearch.index.query.IndicesQueryBuilder;
 19 import org.elasticsearch.index.query.NestedQueryBuilder;
 20 import org.elasticsearch.index.query.QueryBuilder;
 21 import org.elasticsearch.index.query.QueryBuilders;
 22 import org.elasticsearch.index.query.QueryStringQueryBuilder;
 23 import org.elasticsearch.index.query.RangeQueryBuilder;
 24 import org.elasticsearch.index.query.SpanFirstQueryBuilder;
 25 import org.elasticsearch.index.query.WildcardQueryBuilder;
 26 import org.elasticsearch.search.SearchHit;
 27 import org.elasticsearch.search.SearchHits;
 28 import org.junit.Before;
 29 import org.junit.Test;
 30 
 31 /**
 32  * java操作查詢api
 33  * @author 231
 34  *
 35  */
 36 public class JavaESQuery {
 37     
 38     private TransportClient client;
 39     
 40     @Before
 41     public void testBefore() {
 42         Settings settings = Settings.settingsBuilder().put("cluster.name", "wenbronk_escluster").build();
 43         client = TransportClient.builder().settings(settings).build()
 44                  .addTransportAddress(new InetSocketTransportAddress(new InetSocketAddress("192.168.50.37", 9300)));
 45         System.out.println("success to connect escluster");
 46     }
 47 
 48     /**
 49      * 使用get查詢
 50      */
 51     @Test
 52     public void testGet() {
 53         GetRequestBuilder requestBuilder = client.prepareGet("twitter", "tweet", "1");
 54         GetResponse response = requestBuilder.execute().actionGet();
 55         GetResponse getResponse = requestBuilder.get();
 56         ListenableActionFuture<GetResponse> execute = requestBuilder.execute();
 57         System.out.println(response.getSourceAsString());
 58     }
 59     
 60     /**
 61      * 使用QueryBuilder
 62      * termQuery("key", obj) 完全匹配
 63      * termsQuery("key", obj1, obj2..)   一次匹配多個值
 64      * matchQuery("key", Obj) 單個匹配, field不支持通配符, 前綴具高級特性
 65      * multiMatchQuery("text", "field1", "field2"..);  匹配多個字段, field有通配符忒行
 66      * matchAllQuery();         匹配所有文件
 67      */
 68     @Test
 69     public void testQueryBuilder() {
 70 //        QueryBuilder queryBuilder = QueryBuilders.termQuery("user", "kimchy");
 71       QueryBUilder queryBuilder = QueryBuilders.termQuery("user", "kimchy", "wenbronk", "vini");
 72         QueryBuilders.termsQuery("user", new ArrayList<String>().add("kimchy"));
 73 //        QueryBuilder queryBuilder = QueryBuilders.matchQuery("user", "kimchy");
 74 //        QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery("kimchy", "user", "message", "gender");
 75         QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
 76         searchFunction(queryBuilder);
 77         
 78     }
 79     
 80     /**
 81      * 組合查詢
 82      * must(QueryBuilders) :   AND
 83      * mustNot(QueryBuilders): NOT
 84      * should:                  : OR
 85      */
 86     @Test
 87     public void testQueryBuilder2() {
 88         QueryBuilder queryBuilder = QueryBuilders.boolQuery()
 89             .must(QueryBuilders.termQuery("user", "kimchy"))
 90             .mustNot(QueryBuilders.termQuery("message", "nihao"))
 91             .should(QueryBuilders.termQuery("gender", "male"));
 92         searchFunction(queryBuilder);
 93     }
 94     
 95     /**
 96      * 只查詢一個id的
 97      * QueryBuilders.idsQuery(String...type).ids(Collection<String> ids)
 98      */
 99     @Test
100     public void testIdsQuery() {
101         QueryBuilder queryBuilder = QueryBuilders.idsQuery().ids("1");
102         searchFunction(queryBuilder);
103     }
104     
105     /**
106      * 包裹查詢, 高於設定分數, 不計算相關性
107      */
108     @Test
109     public void testConstantScoreQuery() {
110         QueryBuilder queryBuilder = QueryBuilders.constantScoreQuery(QueryBuilders.termQuery("name", "kimchy")).boost(2.0f);
111         searchFunction(queryBuilder);
112         // 過濾查詢
113 //        QueryBuilders.constantScoreQuery(FilterBuilders.termQuery("name", "kimchy")).boost(2.0f);
114         
115     }
116     
117     /**
118      * disMax查詢
119      * 對子查詢的結果做union, score沿用子查詢score的最大值, 
120      * 廣泛用於muti-field查詢
121      */
122     @Test
123     public void testDisMaxQuery() {
124         QueryBuilder queryBuilder = QueryBuilders.disMaxQuery()
125             .add(QueryBuilders.termQuery("user", "kimch"))  // 查詢條件
126             .add(QueryBuilders.termQuery("message", "hello"))
127             .boost(1.3f)
128             .tieBreaker(0.7f);
129         searchFunction(queryBuilder);
130     }
131     
132     /**
133      * 模糊查詢
134      * 不能用通配符, 不知道干啥用
135      */
136     @Test
137     public void testFuzzyQuery() {
138         QueryBuilder queryBuilder = QueryBuilders.fuzzyQuery("user", "kimch");
139         searchFunction(queryBuilder);
140     }
141     
142     /**
143      * 父或子的文檔查詢
144      */
145     @Test
146     public void testChildQuery() {
147         QueryBuilder queryBuilder = QueryBuilders.hasChildQuery("sonDoc", QueryBuilders.termQuery("name", "vini"));
148         searchFunction(queryBuilder);
149     }
150     
151     /**
152      * moreLikeThisQuery: 實現基於內容推薦, 支持實現一句話相似文章查詢
153      * {   
154         "more_like_this" : {   
155         "fields" : ["title", "content"],   // 要匹配的字段, 不填默認_all
156         "like_text" : "text like this one",   // 匹配的文本
157         }   
158     }     
159     
160     percent_terms_to_match:匹配項(term)的百分比,默認是0.3
161 
162     min_term_freq:一篇文檔中一個詞語至少出現次數,小於這個值的詞將被忽略,默認是2
163     
164     max_query_terms:一條查詢語句中允許最多查詢詞語的個數,默認是25
165     
166     stop_words:設置停止詞,匹配時會忽略停止詞
167     
168     min_doc_freq:一個詞語最少在多少篇文檔中出現,小於這個值的詞會將被忽略,默認是無限制
169     
170     max_doc_freq:一個詞語最多在多少篇文檔中出現,大於這個值的詞會將被忽略,默認是無限制
171     
172     min_word_len:最小的詞語長度,默認是0
173     
174     max_word_len:最多的詞語長度,默認無限制
175     
176     boost_terms:設置詞語權重,默認是1
177     
178     boost:設置查詢權重,默認是1
179     
180     analyzer:設置使用的分詞器,默認是使用該字段指定的分詞器
181      */
182     @Test
183     public void testMoreLikeThisQuery() {
184         QueryBuilder queryBuilder = QueryBuilders.moreLikeThisQuery("user")
185                             .like("kimchy");
186 //                            .minTermFreq(1)         //最少出現的次數
187 //                            .maxQueryTerms(12);        // 最多允許查詢的詞語
188         searchFunction(queryBuilder);
189     }
190     
191     /**
192      * 前綴查詢
193      */
194     @Test
195     public void testPrefixQuery() {
196         QueryBuilder queryBuilder = QueryBuilders.matchQuery("user", "kimchy");
197         searchFunction(queryBuilder);
198     }
199     
200     /**
201      * 查詢解析查詢字符串
202      */
203     @Test
204     public void testQueryString() {
205         QueryBuilder queryBuilder = QueryBuilders.queryStringQuery("+kimchy");
206         searchFunction(queryBuilder);
207     }
208     
209     /**
210      * 范圍內查詢
211      */
212     public void testRangeQuery() {
213         QueryBuilder queryBuilder = QueryBuilders.rangeQuery("user")
214             .from("kimchy")
215             .to("wenbronk")
216             .includeLower(true)     // 包含上界
217             .includeUpper(true);      // 包含下屆
218         searchFunction(queryBuilder);
219     }
220     
221     /**
222      * 跨度查詢
223      */
224     @Test
225     public void testSpanQueries() {
226          QueryBuilder queryBuilder1 = QueryBuilders.spanFirstQuery(QueryBuilders.spanTermQuery("name", "葫蘆580娃"), 30000);     // Max查詢范圍的結束位置  
227       
228          QueryBuilder queryBuilder2 = QueryBuilders.spanNearQuery()  
229                 .clause(QueryBuilders.spanTermQuery("name", "葫蘆580娃")) // Span Term Queries  
230                 .clause(QueryBuilders.spanTermQuery("name", "葫蘆3812娃"))  
231                 .clause(QueryBuilders.spanTermQuery("name", "葫蘆7139娃"))  
232                 .slop(30000)                                               // Slop factor  
233                 .inOrder(false)  
234                 .collectPayloads(false);  
235   
236         // Span Not
237          QueryBuilder queryBuilder3 = QueryBuilders.spanNotQuery()  
238                 .include(QueryBuilders.spanTermQuery("name", "葫蘆580娃"))  
239                 .exclude(QueryBuilders.spanTermQuery("home", "山西省太原市2552街道"));  
240   
241         // Span Or   
242          QueryBuilder queryBuilder4 = QueryBuilders.spanOrQuery()  
243                 .clause(QueryBuilders.spanTermQuery("name", "葫蘆580娃"))  
244                 .clause(QueryBuilders.spanTermQuery("name", "葫蘆3812娃"))  
245                 .clause(QueryBuilders.spanTermQuery("name", "葫蘆7139娃"));  
246   
247         // Span Term  
248          QueryBuilder queryBuilder5 = QueryBuilders.spanTermQuery("name", "葫蘆580娃");  
249     }
250     
251     /**
252      * 測試子查詢
253      */
254     @Test
255     public void testTopChildrenQuery() {
256         QueryBuilders.hasChildQuery("tweet", 
257                 QueryBuilders.termQuery("user", "kimchy"))
258             .scoreMode("max");
259     }
260     
261     /**
262      * 通配符查詢, 支持 * 
263      * 匹配任何字符序列, 包括空
264      * 避免* 開始, 會檢索大量內容造成效率緩慢;對於通配符查詢必須注意一個問題,就是參數必須小寫,即例子中“kihy”必須小寫,這是個坑
265      */
266     @Test
267     public void testWildCardQuery() {
268         QueryBuilder queryBuilder = QueryBuilders.wildcardQuery("user", "ki*hy");
269         searchFunction(queryBuilder);
270     }
271     
272     /**
273      * 嵌套查詢, 內嵌文檔查詢
274      */
275     @Test
276     public void testNestedQuery() {
277         QueryBuilder queryBuilder = QueryBuilders.nestedQuery("location", 
278                 QueryBuilders.boolQuery()
279                     .must(QueryBuilders.matchQuery("location.lat", 0.962590433140581))
280                     .must(QueryBuilders.rangeQuery("location.lon").lt(36.0000).gt(0.000)))
281         .scoreMode("total");
282         
283     }
284     
285     /**
286      * 測試索引查詢
287      */
288     @Test
289     public void testIndicesQueryBuilder () {
290         QueryBuilder queryBuilder = QueryBuilders.indicesQuery(
291                 QueryBuilders.termQuery("user", "kimchy"), "index1", "index2")
292                 .noMatchQuery(QueryBuilders.termQuery("user", "kimchy"));
293         
294     }
295     
296     
297     
298     /**
299      * 查詢遍歷抽取
300      * @param queryBuilder
301      */
302     private void searchFunction(QueryBuilder queryBuilder) {
303         SearchResponse response = client.prepareSearch("twitter")
304                 .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
305                 .setScroll(new TimeValue(60000))
306                 .setQuery(queryBuilder)
307                 .setSize(100).execute().actionGet();
308         
309         while(true) {
310             response = client.prepareSearchScroll(response.getScrollId())
311                 .setScroll(new TimeValue(60000)).execute().actionGet();
312             for (SearchHit hit : response.getHits()) {
313                 Iterator<Entry<String, Object>> iterator = hit.getSource().entrySet().iterator();
314                 while(iterator.hasNext()) {
315                     Entry<String, Object> next = iterator.next();
316                     System.out.println(next.getKey() + ": " + next.getValue());
317                     if(response.getHits().hits().length == 0) {
318                         break;
319                     }
320                 }
321             }
322             break;
323         }
324 //        testResponse(response);
325     }
326     
327     /**
328      * 對response結果的分析
329      * @param response
330      */
331     public void testResponse(SearchResponse response) {
332         // 命中的記錄數
333         long totalHits = response.getHits().totalHits();
334         
335         for (SearchHit searchHit : response.getHits()) {
336             // 打分
337             float score = searchHit.getScore();
338             // 文章id
339             int id = Integer.parseInt(searchHit.getSource().get("id").toString());
340             // title
341             String title = searchHit.getSource().get("title").toString();
342             // 內容
343             String content = searchHit.getSource().get("content").toString();
344             // 文章更新時間
345             long updatetime = Long.parseLong(searchHit.getSource().get("updatetime").toString());
346         }
347     }
348     
349     /**
350      * 對結果設置高亮顯示
351      */
352     public void testHighLighted() {
353         /*  5.0 版本后的高亮設置
354          * client.#().#().highlighter(hBuilder).execute().actionGet();
355         HighlightBuilder hBuilder = new HighlightBuilder();
356         hBuilder.preTags("<h2>");
357         hBuilder.postTags("</h2>");
358         hBuilder.field("user");        // 設置高亮顯示的字段
359         */
360         // 加入查詢中
361         SearchResponse response = client.prepareSearch("blog")
362             .setQuery(QueryBuilders.matchAllQuery())
363             .addHighlightedField("user")        // 添加高亮的字段
364             .setHighlighterPreTags("<h1>")
365             .setHighlighterPostTags("</h1>")
366             .execute().actionGet();
367         
368         // 遍歷結果, 獲取高亮片段
369         SearchHits searchHits = response.getHits();
370         for(SearchHit hit:searchHits){
371             System.out.println("String方式打印文檔搜索內容:");
372             System.out.println(hit.getSourceAsString());
373             System.out.println("Map方式打印高亮內容");
374             System.out.println(hit.getHighlightFields());
375 
376             System.out.println("遍歷高亮集合,打印高亮片段:");
377             Text[] text = hit.getHighlightFields().get("title").getFragments();
378             for (Text str : text) {
379                 System.out.println(str.string());
380             }
381         }
382     }
383 }

elasticsearch查詢api:match query:

match query

  • term query不同,match query的查詢詞是被分詞處理的(analyzed),即首先分詞,然后構造相應的查詢,所以應該確保查詢的分詞器和索引的分詞器是一致的;
  • terms query相似,提供的查詢詞之間默認是or的關系,可以通過operator屬性指定;
  • match query有兩種形式,一種是簡單形式,一種是bool形式;

match query VS match_phrase query

注意其差別:

  • match query:會對查詢語句進行分詞,分詞后查詢語句中的任何一個詞項被匹配,文檔就會被搜索到。如果想查詢匹配所有關鍵詞的文檔,可以用and操作符連接;
  • match_phrase query:滿足下面兩個條件才會被搜索到
    • (1)分詞后所有詞項都要出現在該字段中
    • (2)字段中的詞項順序要一致

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM