1. 程式人生 > >elasticsearch-java api之過濾

elasticsearch-java api之過濾

一、過濾相關點:

1、查詢與過濾:

Elasticsearch 使用的查詢語言(DSL) 擁有一套查詢元件,這些元件可以以無限組合的方式進行搭配。這套元件可以在以下兩種情況下使用:過濾情況(filtering context)和查詢情況(query context)。

當使用於 過濾情況 時,查詢被設定成一個“不評分”或者“過濾”查詢。即,這個查詢只是簡單的問一個問題:“這篇文件是否匹配?”。回答也是非常的簡單,yes 或者 no ,二者必居其一。

  • created 時間是否在 2013 與 2014 這個區間?
  • status 欄位是否包含 published 這個單詞?
  • lat_lon 欄位表示的位置是否在指定點的 10km 範圍內?
當使用於 查詢情況 時,查詢就變成了一個“評分”的查詢。和不評分的查詢類似,也要去判斷這個文件是否匹配,同時它還需要判斷這個文件匹配的有 _多好_(匹配程度如何)。 此查詢的典型用法是用於查詢以下文件:
  • 查詢與 full text search 這個詞語最佳匹配的文件
  • 包含 run 這個詞,也能匹配 runs 、 running 、 jog 或者 sprint
  • 包含 quick 、 brown 和 fox 這幾個詞 — 詞之間離的越近,文件相關性越高
  • 標有 lucene 、 search 或者 java 標籤 — 標籤越多,相關性越高
一個評分查詢計算每一個文件與此查詢的 _相關程度_,同時將這個相關程度分配給表示相關性的欄位 `_score`,並且按照相關性對匹配到的文件進行排序。這種相關性的概念是非常適合全文搜尋的情況,因為全文搜尋幾乎沒有完全 “正確” 的答案。

說明:

自 Elasticsearch 問世以來,查詢與過濾(queries and filters)就獨自成為 Elasticsearch 的元件。但從 Elasticsearch 2.0 開始,過濾(filters)已經從技術上被排除了,同時所有的查詢(queries)擁有變成不評分查詢的能力

然而,為了明確和簡單,我們用 "filter" 這個詞表示不評分、只過濾情況下的查詢。你可以把 "filter" 、 "filtering query" 和 "non-scoring query" 這幾個詞視為相同的。相似的,如果單獨地不加任何修飾詞地使用 "query" 這個詞,我們指的是 "scoring query" 。

2、效能差異:

過濾查詢(Filtering queries)只是簡單的檢查包含或者排除,這就使得計算起來非常快。考慮到至少有一個過濾查詢(filtering query)的結果是 “稀少的”(很少匹配的文件),並且經常使用不評分查詢(non-scoring queries),結果會被快取到記憶體中以便快速讀取,所以有各種各樣的手段來優化查詢結果。相反,評分查詢(scoring queries)不僅僅要找出 匹配的文件,還要計算每個匹配文件的相關性,計算相關性使得它們比不評分查詢費力的多。同時,查詢結果並不快取。

多虧倒排索引(inverted index),一個簡單的評分查詢在匹配少量文件時可能與一個涵蓋百萬文件的filter表現的一樣好,甚至會更好。但是在一般情況下,一個filter 會比一個評分的query效能更優異,並且每次都表現的很穩定。過濾(filtering)的目標是減少那些需要通過評分查詢(scoring queries)進行檢查的文件。

參考:https://www.elastic.co/guide/cn/elasticsearch/guide/current/_queries_and_filters.html

二、過濾例項:

在之前es版本(2.0之前)的官網中,介紹了兩種過濾:一個是Filtered Query,一個是Post Filter。前者是先使用filter,過濾後的結果再query;後者是先query,query的結果再過濾。而在es中,filter的操作使用了cache,速度比query快。

在最新2.0版本的官網上中,提到了:

現在是時候忘掉你所知道的關於查詢和過濾器的一切了:Elasticsearch 2.0將自己做出更好的決定,而不是依靠使用者來制定一個優化的查詢。這一變化在API層面幾乎是看不見的,但是讓我們來深入探討使之成為可能的內部變化。本篇文章中提到的大部分更改都是在Lucene 5.0,5.1和5.2中完成的,並將整合到Elasticsearch 2.0中。

在Elasticsearch 2.0中,查詢和過濾器是相同的內部物件,可以配置這些物件來評分文件或跳過評分。然後,查詢DSL確保正確傳播資訊:例如,如果bool查詢需要產生分數,則bool查詢的must子句將需要產生分數,而must_not子句永遠不需要產生分數,因為它可能只是用於過濾檔案。為了使查詢的DSL更加符合這種變化,我們已經廢棄了過濾的查詢,贊成在bool查詢中使用一個新的過濾器子句:過濾器子句就像must子句,除了它們沒有貢獻分數。意味著:

{
  “filtered” : {
    “query”: { query definition },
    “filter”: { filter definition }
  }
}
應該被替代成:
{
  “bool” : {
    “must”: { query definition },
    “filter”: { filter definition }
  }
}
注:查詢DSL仍然是向後相容的,儘管有這種改變:如果你嘗試執行一個過濾的查詢,它將在內部解析為一個bool查詢。但是,我們鼓勵您遷移到新的語法,因為在未來的發行版中,已過濾的查詢將被刪除。

例項1:postfilter

public static void filterQuery1(String indexName,String indexType) {
		QueryBuilder qb = QueryBuilders.boolQuery()
				.must(QueryBuilders.constantScoreQuery(QueryBuilders.matchQuery("title", "java")).boost(1f))
				.should(QueryBuilders.constantScoreQuery(QueryBuilders.termQuery("title", "elasticsearch")).boost(5f))
				.should(QueryBuilders.termQuery("title", "hadoop"));
		
		QueryBuilder fb = QueryBuilders.rangeQuery("age").from(10).to(20);
		SearchResponse searchResponse = transportClient.prepareSearch(indexName).setTypes(indexType)
				.setQuery(qb)
				.setPostFilter(fb)
				.get();
		SearchHits hits = searchResponse.getHits();
		//long totalHits = hits.getTotalHits();
		SearchHit[] hits2 = hits.getHits();
		for (SearchHit sh:hits2) {
			Map<String, Object> source = sh.getSource();
			String id = sh.getId();
			float score = sh.getScore();
			System.out.println("source:"+source+",id:"+id+",score:"+score);
		}
	}
注:使用QueryBuilder建立過濾(代替了之前版本的FilterBuilder)

例項2:filter

public static void filterQuery2(String indexName,String indexType) {
		QueryBuilder qb = QueryBuilders.boolQuery()
				.must(QueryBuilders.constantScoreQuery(QueryBuilders.matchQuery("title", "java")).boost(1f))
				.should(QueryBuilders.constantScoreQuery(QueryBuilders.termQuery("title", "elasticsearch")).boost(5f))
				.should(QueryBuilders.termQuery("title", "hadoop"))
				.filter(QueryBuilders.rangeQuery("age").from(10).to(20));
		
		System.out.println(qb.toString());
		SearchResponse searchResponse = transportClient.prepareSearch(indexName).setTypes(indexType)
				.setQuery(qb)
				.get();
		SearchHits hits = searchResponse.getHits();
		//long totalHits = hits.getTotalHits();
		SearchHit[] hits2 = hits.getHits();
		for (SearchHit sh:hits2) {
			Map<String, Object> source = sh.getSource();
			String id = sh.getId();
			float score = sh.getScore();
			System.out.println("source:"+source+",id:"+id+",score:"+score);
		}
	}
A、之前這種寫法已經被廢棄:QueryBuilders.filteredQuery(qb, qb);
B、對於Filtered Query已經被bool查詢+filter替代,我們可以手工在bool查詢中指定filter,同時es內部將bool查詢也會自動優化成filter。例如:
QueryBuilder qb = QueryBuilders.boolQuery()
				.must(QueryBuilders.constantScoreQuery(QueryBuilders.matchQuery("title", "java")).boost(1f))
				.should(QueryBuilders.constantScoreQuery(QueryBuilders.termQuery("title", "elasticsearch")).boost(5f))
				.should(QueryBuilders.termQuery("title", "hadoop"));
		System.out.println(qb.toString());
這樣的boolean查詢,會被優化成如下的dsl:
{
  "bool" : {
    "must" : {
      "constant_score" : {
        "filter" : {
          "match" : {
            "title" : {
              "query" : "java",
              "type" : "boolean"
            }
          }
        },
        "boost" : 1.0
      }
    },
    "should" : [ {
      "constant_score" : {
        "filter" : {
          "term" : {
            "title" : "elasticsearch"
          }
        },
        "boost" : 5.0
      }
    }, {
      "term" : {
        "title" : "hadoop"
      }
    } ]
  }
}
參考:

https://www.elastic.co/blog/better-query-execution-coming-elasticsearch-2-0

https://www.elastic.co/guide/en/elasticsearch/reference/2.0/breaking_20_java_api_changes.html

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_filter_bucket.html