【java學習】Elasticsearch基本操作

與傳統數據庫的對比

結構

Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields

sql查詢

es也可以使用sql進行查詢,將es結構對應關係數據庫進行sql的查詢即可如下:

http://127.0.0.1:9200/_sql?sql=select count(*) from abc_index*

僅用於驗證es查詢結果,因爲sql的執行在內存中進行,非常耗時。

JAVA API

Elasticsearch的交互,可以使用JAVA API,也可以直接使用es提供的REST API 。

REST API

功能

  • 檢查集羣、節點和索引的健康信息、狀態以及各種統計信息
  • 管理集羣、節點、索引數據以及元數據
  • 對索引進行 CRUD(創建、讀取、更新和刪除)和搜索操作
  • 執行高級的搜索操作, 例如分頁、排序、過濾、腳本編寫(scripting)、聚合(aggregations)以及其它操作

格式

curl -X <HTTP Verb> /<Index>/<Type>/<ID>

Elasticsearch原理

配置

es的安裝和可視化

運行elasticsearch-5.1.1、kibana-5.1.1。

java yml配置

首先添加依賴;
yml文件的配置如下:

#es的默認名稱,如果安裝es時沒有做特殊的操作名字都是此名稱
spring.data.elasticsearch.cluster-name=elasticsearch
# Elasticsearch 集羣節點服務地址,用逗號分隔,如果沒有指定其他就啓動一個客戶端節點,默認java訪問端口9300
spring.data.elasticsearch.cluster-nodes=localhost:9300
# 設置連接超時時間
spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s

查看集羣健康信息

REST API

    curl -X GET "localhost:9200/_cat/health?v"

get爲訪問方式,localhost:9200/_cat/health?v爲訪問連接。
在這裏插入圖片描述
從這個響應中,我們可以看到集羣的名稱,狀態,節點數,分片數等等,其中:

  1. status 狀態有green、yellow和red三種:
  • green:集羣運行正常;
  • yellow:集羣所有數據都是可用的,集羣功能也齊全,但存在某些複製沒有被分配;
  • red:集羣的部分數據不可用,集羣的功能也是不全的,但是集羣還是可以運行的,它可以繼續處理搜索請求,不過開發者要儘快修復它。一般這種情況下es已經基本掛掉了。
  1. 當前集羣:一共有3個節點,5724個分片等信息。
    如果已經安裝了Kibana,也可以通過Kibana查看這些信息。

JAVA API

ClusterHealthResponse response = client.admin().cluster()
.prepareHealth("library")
.execute().actionGet();

查看節點的詳細信息

    curl -X GET "localhost:9200/_cat/nodes?v"

TransportClient

import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
 
 
Settings settings = Settings.builder()
                .put("cluster.name", "elasticsearch").build();
TransportClient client= =  new PreBuiltTransportClient(settings). addTransportAddress(new TransportAddress(InetAddress.getByName("XXX.XXX.XX.XX"), 9300));

索引操作

創建index

curl -X PUT "localhost:9200/customer?pretty"

通過一個PUT請求,添加了一個名爲customer的索引;
pretty參數:表示請求響應的JSON格式化之後打印出來,方便開發者閱讀。

刪除index

curl -X DELETE http://192.168.168.101:9200/index02

判斷index是否存在

在es查詢之前,判斷index是否存在,以免拋出異常。

	private Client client;
	private void init() throws Exception{
		Settings settings = Settings.settingsBuilder().put("cluster.name", "log-test")
				.build();
		client = TransportClient.builder().settings(settings)
				.addPlugin(DeleteByQueryPlugin.class)
				.build()
				.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300))
				.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
	}
	
public boolean indexExists(String index){
		IndicesExistsRequest request = new IndicesExistsRequest(index);
		IndicesExistsResponse response = client.admin().indices().exists(request).actionGet();
		if (response.isExists()) {
			return true;
		}
		return false;
	}

查詢所有index

http://127.0.0.1:9200/_cat/indices?v

查詢具體索引內容

查詢bank,bank2:

curl -X GET "localhost:9200/bank,bank2/_search?q=*&sort=account_number:asc&pretty&ignore_unavailable=true"

多索引查詢:

  • 支持使用簡單表示法,如test1,test2,test3表示法
  • 使用_all表示所有索引
  • 使用通配符,如 test*或 *test或 te*t或 *test*
  • 也支持排除能力,例如:test*,-test3
    常用其它參數:
參數 說明 默認值 舉例
ignore_unavailable 當指定多個索引時,如果有索引不可用(不存在或者已經關閉)那麼是否忽略該索引 false
allow_no_indices 允許通配符匹配索引 true curl -X GET "localhost:9200/bank3*/_search?q=*&pretty&allow_no_indices=false",若bank3不存在,則報錯。
expand_wildcards 查詢索引的範圍 open表示查詢所有匹配並open的索引,closed則表示查詢所有匹配的索引
pretty 響應的JSON將被格式化 true
format=yaml 請求以更可讀的yaml格式響應
human=true 以人類可讀的格式來返回數據 true則返回{"exists_time":"1h"},否則,返回:{"exists_time_in_millis":3600000}

打開/關閉index

curl -XPOST http://192.168.168.101:9200/index01/_close
curl -XPOST http://192.168.168.101:9200/index01/_open

文檔操作

創建/全量更新Document

不存在則創建,存在則覆蓋。

    curl -X PUT "localhost:9200/customer/_doc/1?pretty" -H 'Content-Type: application/json' -d'
    {
      "name": "John Doe"
    }'
  1. document是不可變的,如果要修改document的內容,可以通過全量替換,直接對document重新建立索引,替換裏面所有的內容。
  2. es會將老的document標記爲deleted(邏輯刪除),然後新增我們給定的一個document,當我們創建越來越多的document的時候,es會在適當的時機在後臺自動刪除(物理刪除)標記爲deleted的document。
  3. 替換必須帶上所有的field,否則其他數據會丟失。
  4. Elasticsearch中,並不強制要求顯式的創建索引,即前面案例中,如果開發者在添加文檔之前,還沒有創建customer索引,那麼該文檔一樣也會創建成功的(此時索引會被自動創建)。

未指定id時,系統會自動創建id。

POST /_bulk
{ "index": { "_index": "ecommerce", "_type":"product"}}
{ "name": "test yagao", "desc": "youxiao fangzhu"}

強制創建文檔create

POST /_bulk
{ "create": { "_index": "ecommerce", "_type": "product", "_id": "4" }}
{ "test_field":    "test12" }

查詢Document

Rest api:curl -X GET "localhost:9200/customer/_doc/1?pretty"
java api:queryBuilder = QueryBuilders.matchAllQuery().boost(11f).normsField("title");

boost參數被用來增加一個子句的相對權重(當boost大於1時),或者減小相對權重(當boost介於0到1時),但是增加或者減小不是線性的。換言之,boost設爲2並不會讓最終的_score加倍。
相反,新的_score會在適用了boost後被歸一化(Normalized)。每種查詢都有自己的歸一化算法(Normalization Algorithm)。但是能夠說一個高的boost值會產生一個高的_score。
如果你在實現你自己的不基於TF/IDF的相關度分值模型並且你需要對提升過程擁有更多的控制,你可以使用function_score查詢,它不通過歸一化步驟對文檔的boost進行操作。

更新Document

    POST /ecommerce/product/1/_update
    {
      "doc": {
        "name": "jiaqiangban gaolujie yagao"
      }
    }
POST /_bulk
{ "update": { "_index": "ecommerce", "_type": "product", "_id": "4","retry_on_conflict" : 3 }}
{ "doc" : {"test_field" : "test update"} }

刪除Document

curl -X DELETE "localhost:9200/customer?pretty"

在刪除一個document之後,我們可以從側面證明,它不是立即物理刪除的,因爲它的一些版本號等信息還是保留的。

POST /_bulk
{ "delete": { "_index": "ecommerce", "_type": "product", "_id": "4"}} 

由於刪除只需要被刪除文檔的ID,所以並沒有對應的源文檔。
bulk API按順序執行這些操作。如果其中一個操作因爲某些原因失敗了,它將會繼續處理後面的操作。當bulk API返回時,它將提供每個操作的狀態(按照同樣的順序),所以開發者能夠看到每個操作成功與否。

查詢操作

term query

場景:用於精確查詢一個字段。

curl -XGET http://192.168.168.101:9200/index01/_search -d {'query':{'term':{'title':'你好'}}}

term查找時直接對關鍵詞進行查找。

java api:

SearchResponse response = client.prepareSearch("index1", "index2")
        .setTypes("type1", "type2")
        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
        .setQuery(QueryBuilders.termQuery("multi", "test"))                 //term Query
        .setPostFilter(QueryBuilders.rangeQuery("age").from(12).to(18))     // range query、Filter query
        .setFrom(0).setSize(60).setExplain(true)
        .get();

terms query

場景:用於精確查詢多個字段。

{
    'query':{
        'terms':{
            'tag':["search",'nosql','hello']   //json中必須包含數組。
        }
    }
}

i> Preparing a query 準備查詢請求

SearchResponse response = client.prepareSearch("library")
.addFields("title", "_source")
.execute().actionGet();
for(SearchHit hit: response.getHits().getHits()) {
     System.out.println(hit.getId());
     if (hit.getFields().containsKey("title")) {
            System.out.println("field.title: "+ hit.getFields().get("title").getValue());
     }
     System.out.println("source.title: " + hit.getSource().get("title"));
}

ii> Building queries 構造查詢

import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termQuery("sid_s", text));
Search search = new Search.Builder(searchSourceBuilder.toString()).addIndex("webpage").build();
SearchResult result = client.execute(search);

match query

場景:用於當前文檔的全文模糊查找。
match在匹配時會對所查找的關鍵詞進行分詞,然後按分詞匹配查找。

GET /ecommerce/product/_search
{'query':{'match':{'title':'你好'}}}

{
   "query": {
     "match": {
       "__type": "info"
     }
   },
   "sort": [
     {
       "campaign_end_time": {
         "order": "desc"
       }
     }
   ]
}

匹配查詢:

queryBuilder = QueryBuilders
.matchQuery("message", "a quick brown fox")
.operator(Operator.AND)
.zeroTermsQuery(ZeroTermsQuery.ALL);

match_all

場景:查詢指定索引下的所有文檔。

{'query':{'match_all':{'title':'標題一樣'}}}

multi match

場景:多值匹配查詢。

{
  "query": {
    "multi_match": {
      "query": "運動 上衣",
      "fields": [
        "brandName^100",
        "brandName.brandName_pinyin^100",
        "brandName.brandName_keyword^100",
        "sortName^80",
        "sortName.sortName_pinyin^80",
        "productName^60",
        "productKeyword^20"
      ],
      "type": <multi-match-type>,
      "operator": "AND"
    }
  }
}

match_phrase(短語查詢)

場景:精確查找所有字段。
match(全文檢索)會將輸入的搜索串拆解開來,去倒排索引裏面去一一匹配,只要能匹配上任意一個拆解後的單詞,就可以作爲結果返回。
match_phrase要求輸入的搜索串,必須在指定的字段文本中,完全包含一模一樣的,纔可以算匹配,才能作爲結果返回。

GET /ecommerce/product/_search
{
    "query" : {
        "match_phrase" : {
            "producer" : "yagao producer"
        }
    }
}

Bool query

場景:多條件查詢。
bool查詢包含四個子句,must,filter,should,must_not。

  • must:表示一定要滿足,相當於and;
  • should:表示可以滿足也可以不滿足,相當於or;
  • must_not:表示不能滿足該條件,相當於not。
  • filter: 過濾查詢。Elasticsearch在2.x版本將filter query去掉,併入bool query。
{
    'query':{
        'bool':{
            'must':[{
                'term':{
                    '_type':{
                        'value':'age'
                    }
                }
             },{
                 'term':{
                     'account_grade':{
                         'value':'23'
                     }
                 }
             }
           ]
        }
    }  
}

{
	"bool":{
            "must":{
                "term":{"user":"lucy"}
            },
            "filter":{
                "term":{"tag":"teach"}	
            },
            "should":[
              	{"term":{"tag":"wow"}},
                {"term":{"tag":"elasticsearch"}}
            ],
           	"mininum_should_match":1,
           	"boost":1.0  		            
        }
}

“minimum_should_match”: 1,表示最小匹配度,可以設置爲百分百,詳情看源文檔Elasticsearch Reference [6.4] » Query DSL » Minimum Should Match,設置了這個值的時候就必須滿足should裏面的設置了,另外注意這邊should裏面同一字段設置的多個值(意思是當這個值等於X或者等於Y的時候都成立,務必注意格式)。

match來指定查詢條件;
filter執行速度高於查詢,原因如下:

  • 過濾器不會計算相關度的得分(即結果中的_score字段,如果不關注這個字段,使用filter更好)
  • 過濾器可以被緩存到內存中,在重複搜索時,速度會比較快。
    過濾查詢java api:
QueryBuilder filterBuilder = QueryBuilders  
         .filteredQuery(  
              QueryBuilders.existsQuery("title").queryName("exist"),  
              QueryBuilders.termQuery("title", "elastic")  
          );  
  
SearchResponse response = client.prepareSearch("library")  
          .setPostFilter(filterBuilder)  
          .execute().actionGet();  

range query

{
	'query':{
    	'range':{
            'age':{
                'gte':'30',
                'lte':'20'
            }
    	}
	}
}

java api 見 term query例子。

java api-精確匹配:termQuery,相當於=
java api-範圍匹配:rangeQuery,相當於SQL between and

分頁查詢

    GET /ecommerce/product/_search
    {
      "query": { "match_all": {} },
      "_source": ["name", "price"]
	  "size": 1,
  	  "from": 10
    }

size表示返回的文檔個數爲1,默認爲10。
from表示從第10個開始查詢,主要用於分頁查詢。
_source表示自定義返回字段。默認會返回文檔的所有字段。

分頁查詢:

SearchResponse response = client.prepareSearch("library")
.setQuery(QueryBuilders.matchAllQuery())
.setFrom(10)   //跳過前10個文檔
.setSize(20)   //獲取20個文檔
.execute().actionGet();
response.getHits().totalHits()//可以統計當前匹配到的結果數

批量查詢

優點:能夠大大減少網絡的請求次數,縮減網絡開銷。
自定義設置index、type以及document id:(id爲1的沒有查到(found爲false))

GET /_mget
{
   "docs" : [
      {
         "_index" : "ecommerce",
         "_type" :  "product",
         "_id" :    1
      },
      {
         "_index" : "ecommerce",
         "_type" :  "product",
         "_id" :    2
      }
   ]
}

在對應的index、type下進行批量查詢:(注意:在ElasticSearch6.0以後一個index下只能有一個type,否則會報錯)

GET /ecommerce/product/_mget
{
    "ids": [2, 3]
}
或者:
GET /ecommerce/product/_mget
{
   "docs" : [
      {
         "_id" :    2
      },
      {
         "_id" :    3
      }
   ]
}

通配符查詢

{
    'query':{
        'wildcard':{
            'title':'cr?me'
        }
    }   
}

正則表達式查詢

{
    'query':{
        'regex':{
            'title':{
                'value':'cr.m[ae]',
                'boost':10.0
            }
        }
    }
}

match_phrase_prefix query

場景:前綴查詢。

{
    'query':{
        'match_phrase_prefix':{
            'title':{
                'query':'crime punish',
                'slop':1
            }
        }
    }
}

query_string

{
    'query':{
        'query_string':{
            'query':'title:crime^10 +title:punishment -otitle:cat +author:(+Fyodor +dostoevsky)'
        }
    }
}

sort 排序

降序排序desc,升序asc。

GET /ecommerce/product/_search
{
    "query" : {
        "match" : {
            "name" : "yagao"
        }
    },
    "sort": [
        { "price": "desc" }
    ]
}

java api:

searchSourceBuilder.fetchSource(null, "content").sort("_score");
		searchSourceBuilder.sort("date", SortOrder.DESC);
SortBuilders.scriptSort(script, type) //使用腳本來實現排序
 
SortBuilders.geoDistanceSort(fieldName) //根據空間距離來進行排序
searchRequestBuilder.addSort("publish_time", SortOrder.DESC);

按照某個字段排序的話,hit.getScore()將會失效。

聚合查詢

bucket和metric:

  • bucket(桶):group by 分組之後,相同的數據放進一個bucket。
  • metric(度量/指標):對一個數據分組執行的統計。如:avg\max\min

group by缺點:

  1. 涉及group by的查詢會降低查詢速率
  2. group by之後無法拿到其它信息(通過後文講解的tophits可以拿到)
  3. group by之後無法排序

terms聚合

terms根據字段值項分組聚合。field按什麼字段分組,size指定返回多少個分組,shard_size指定每個分片上返回多少個分組,order排序方式。可以指定include和exclude正則篩選表達式的值,指定missing設置缺省值。

【terms】 java api見【max/min/avg/sum/stats】中的例子。

計算每個tag下的商品數量:

GET /ecommerce/product/_search
{
  "size": 0,   //size=0,表示只獲取聚合結果,而不要執行聚合的原始數據。
  "aggs": {  //aggs:固定語法,要對一份數據執行分組聚合操作
    "all_tags": {   //all_tags:自定義對每個aggs取名。
      "terms": { "field": "tags" }   //terms根據字段的值進行分組;field:根據指定的字段的值進行分組將文本
    }
  }
}

返回結果:

{
  "took": 53,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "all_tags": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "fangzhu",
          "doc_count": 2
        },
        {
          "key": "meibai",
          "doc_count": 2
        }
      ]
    }
  }
}

hits.hits:我們指定了size是0,所以hits.hits就是空的,否則會把執行聚合的那些原始數據給你返回回來
aggregations:聚合結果
all_tags:我們指定的某個聚合的名稱
buckets:根據我們指定的field劃分出的buckets
key:每個bucket對應的分組字段的值
doc_count:這個bucket分組內,有多少個數據
默認的排序規則:按照doc_count降序排序

max/min/avg/sum/stats

stats:bucket,terms,自動就會有一個doc_count,就相當於是數量。
avg:avg aggs,求平均值
max:求一個bucket內,指定field值最大的那個數據
min:求一個bucket內,指定field值最小的那個數據
sum:求一個bucket內,指定field值的總和先分組,再算每組的平均值

GET /ecommerce/product/_search
{
    "size": 0,
    "aggs" : {
        "group_by_tags" : {
            "terms" : { "field" : "tags" },
            "aggs" : {
                "avg_price": { "avg": { "field": "price" } },
                "min_price" : { "min": { "field": "price"} }, 
                "max_price" : { "max": { "field": "price"} },
                "sum_price" : { "sum": { "field": "price" } } 
            }
        }
    }
{
   "aggs":{
      "avg_fees":{
      		"avg":{
      			"field":"fees"
      		}
      	}
   }
}

聚合操作主要是調用了SearchRequestBuilder的addAggregation方法,通常是傳入一個TermsBuilder。
多字段上的聚合操作需要用到子聚合(subAggregation),子聚合調用TermsBuilder的subAggregation方法,可以添加的子聚合有TermsBuilder、SumBuilder、AvgBuilder、MaxBuilder、MinBuilder等常見的聚合操作。

從實現上來講,SearchRequestBuilder在內部保持了一個私有的 SearchSourceBuilder實例, SearchSourceBuilder內部包含一個List,每次調用addAggregation時會調用 SearchSourceBuilder實例,添加一個AggregationBuilder。
同樣的,TermsBuilder也在內部保持了一個List,調用addAggregation方法(來自父類addAggregation)時會添加一個AggregationBuilder。

聚合操作

例如要計算每個球隊年齡最大/最小/總/平均的球員年齡,如果使用SQL語句,應表達如下:

select team, max(age) as max_age from player group by team;

ES的java api:

TermsBuilder teamAgg= AggregationBuilders.terms("player_count ").field("team");
MaxBuilder ageAgg= AggregationBuilders.max("max_age").field("age");
sbuilder.addAggregation(teamAgg.subAggregation(ageAgg));
SearchResponse response = sbuilder.execute().actionGet();

子聚合

例如要計算每個球隊球員的平均年齡,同時又要計算總年薪,如果使用SQL語句,應表達如下:

select team, avg(age)as avg_age, sum(salary) as total_salary from player group by team;

ES的java api:

TermsBuilder teamAgg= AggregationBuilders.terms("team");
AvgBuilder ageAgg= AggregationBuilders.avg("avg_age").field("age");
SumBuilder salaryAgg= AggregationBuilders.avg("total_salary ").field("salary");
sbuilder.addAggregation(teamAgg.subAggregation(ageAgg).subAggregation(salaryAgg));
SearchResponse response = sbuilder.execute().actionGet();

一次計算出count max min avg sum

    public void stats(){
        SearchResponse response = client.prepareSearch(indexName).setTypes(typeName)
                .addAggregation(AggregationBuilders.stats("ageAgg").field("age"))
                .get();
        Stats ageAgg = response.getAggregations().get("ageAgg");
        System.out.println("總數:"+ageAgg.getCount());
        System.out.println("最小值:"+ageAgg.getMin());
        System.out.println("最大值:"+ageAgg.getMax());
        System.out.println("平均值:"+ageAgg.getAvg());
        System.out.println("和:"+ageAgg.getSum());
    }

group by多個field

例如要計算每個球隊每個位置的球員數,如果使用SQL語句,應表達如下:

select team, position, count(*) as pos_count from player group by team, position;

ES的java api:

TermsBuilder teamAgg= AggregationBuilders.terms("player_count ").field("team");
TermsBuilder posAgg= AggregationBuilders.terms("pos_count").field("position");
sbuilder.addAggregation(teamAgg.subAggregation(posAgg));
SearchResponse response = sbuilder.execute().actionGet();

group by/count

例如要計算每個球隊的球員數,如果使用SQL語句,應表達如下:

select team, count(*) as player_count from player group by team;
TermsBuilder teamAgg= AggregationBuilders.terms("player_count ").field("team");
sbuilder.addAggregation(teamAgg);
SearchResponse response = sbuilder.execute().actionGet();

CountResponse response = client.prepareCount("library")
.setQuery(QueryBuilders.termQuery("title", "elastic"))
.execute().actionGet();

聚合後對Aggregation結果排序

例如要計算每個球隊總年薪,並按照總年薪倒序排列,如果使用SQL語句,應表達如下:

select team, sum(salary) as total_salary from player group by team order by total_salary desc;

ES的java api:

TermsBuilder teamAgg= AggregationBuilders.terms("team").order(Order.aggregation("total_salary ", false);
SumBuilder salaryAgg= AggregationBuilders.avg("total_salary ").field("salary");
sbuilder.addAggregation(teamAgg.subAggregation(salaryAgg));
SearchResponse response = sbuilder.execute().actionGet();

需要特別注意的是,排序是在TermAggregation處執行的,Order.aggregation函數的第一個參數是aggregation的名字,第二個參數是boolean型,true表示正序,false表示倒序。

Aggregation結果條數的問題

默認情況下,search執行後,僅返回10條聚合結果,如果想反悔更多的結果,需要在構建TermsBuilder 時指定size:

TermsBuilder teamAgg= AggregationBuilders.terms("team").size(15);

Aggregation結果的解析/輸出

得到response後:

Map<String, Aggregation> aggMap = response.getAggregations().asMap();
StringTerms teamAgg= (StringTerms) aggMap.get("keywordAgg");
Iterator<Bucket> teamBucketIt = teamAgg.getBuckets().iterator();
while (teamBucketIt .hasNext()) {
Bucket buck = teamBucketIt .next(); //分桶
//球隊名
String team = buck.getKey();
//記錄數
long count = buck.getDocCount();
//得到所有子聚合
Map subaggmap = buck.getAggregations().asMap();
//avg值獲取方法
double avg_age= ((InternalAvg) subaggmap.get("avg_age")).getValue();
//sum值獲取方法
double total_salary = ((InternalSum) subaggmap.get("total_salary")).getValue();
//...
//max/min以此類推
}

Top Hits Aggregation

i> 作用
Top Hits聚合主要用於桶聚合後查詢分組後的其它數據。
比如對於下表,通過max(time)group by ip進行分組後,我們還想知道每一組數據hostname等其它字段內容,則需要使用Top Hits,再每個bucket中查詢對應的數據,具體代碼如下:

score ip hostname time
		 	TermsAggregationBuilder depIpGroup = AggregationBuilders.terms("group_by_ip").field("dip").size(10000);//一次最多拿到10000條數據,要拿到更多的數據參考後文scroll的相關講解
            TopHitsAggregationBuilder detail = AggregationBuilders.topHits("detail").size(1);//用於拿到分組以外的其它詳情數據。size來確定數量,默認返回3條數據。sort用於組內排序。
            MaxAggregationBuilder maxTime = AggregationBuilders.max("max_time").field("time");

            SearchRequestBuilder searchRequestBuilder = client
                    .prepareSearch(indexExistsList.toArray(new String[indexNameList.size()]))//通過變長數組查詢多個index
                    .setTypes(indexType).addAggregation(depIpGroup.subAggregation(maxTime).subAggregation(detail));
            SearchResponse searchResponse = searchRequestBuilder
                    .execute()
                    .actionGet();
            Terms ipTerms = searchResponse.getAggregations().get("group_by_ip");
            for (Terms.Bucket bucket : ipTerms.getBuckets()) {//分桶
                ListEntity listEntity = new ListEntity();
                listEntity.setIpAddress(bucket.getKey().toString());

                TopHits topHits = bucket.getAggregations().get("detail");
                SearchHit hit = topHits.getHits().getHits()[0];//????沒看懂原理,先這樣用吧。返回的是一個id不同、其它數據相同的數組,由size決定長度。
                listEntity.setHostname(hit.getSource().get("hostname").toString());
                listEntity.setScore(Integer.parseInt(hit.getSource().get("score").toString()));
                dataList.add(listEntity);
            }

cardinality去重

{
    "size": 0, 
    "aggs": {
      "count_type": {
        "cardinality": {
          "field": "__type"
        }
      }
    }
}
cardinality

percentiles百分比

percentiles對指定字段(腳本)的值按從小到大累計每個值對應的文檔數的佔比(佔所有命中文檔數的百分比),返回指定佔比比例對應的值。默認返回[ 1, 5, 25, 50, 75, 95, 99 ]分位上的值。

{
    "size": 0, 
    "aggs": {
      "age_percents":{
        "percentiles": {
          "field": "age",
          "percents": [
            1,
            5,
            25,
            50,
            75,
            95,
            99
          ]
        }
      }
       
    }
}


{
  "size": 0,
  "aggs": {
    "states": {
      "terms": {
        "field": "gender"
      },
      "aggs": {
        "banlances": {
          "percentile_ranks": {
            "field": "balance",
            "values": [
              20000,
              40000
            ]
          }
        }
      }
    }
  }

percentiles rank

統計小於等於指定值的文檔比。

{
    "size": 0, 
    "aggs": {
      "tests": {
        "percentile_ranks": {
          "field": "age",
          "values": [
            10,
            15
          ]
        }
      }
    }
}

filter聚合

場景:對不同的bucket下的aggs,進行filter。
filter對滿足過濾查詢的文檔進行聚合計算,在查詢命中的文檔中選取過濾條件的文檔進行聚合,先過濾在聚合。

如果放query裏面的filter,是全局的,會對所有的數據都有影響。
但是,如果,比如說,你要統計,長虹電視,最近1個月的平均值; 最近3個月的平均值; 最近6個月的平均值,用bucket filter。

{
    "size": 0, 
    "aggs": {
      "agg_filter":{
        "filter": {
          "match":{"gender":"F"}
        },
        "aggs": {
          "avgs": {
            "avg": {
              "field": "age"
            }
          }
        }
      }
    }
}

filtters聚合

多個過濾組聚合計算。

{
    "size": 0, 
    "aggs": {
      "message": {
        "filters": {
          
          "filters": {
            "errors": {
              "exists": {
                "field": "__type"
              }
            },
            "warring":{
              "term": {
                "__type": "info"
              }
            }
          }
        }
      }
    }
}

range聚合

{
    "aggs": {
      "agg_range": {
        "range": {
          "field": "cost",
          "ranges": [
            {
              "from": 50,
              "to": 70
            },
            {
              "from": 100
            }
          ]
        },
        "aggs": {
          "bmax": {
            "max": {
              "field": "cost"
            }
          }
        }
      }
    }
}

date_range聚合

{
     "aggs": {
       "date_aggrs": {
         "date_range": {
           "field": "accepted_time",
           "format": "MM-yyy", 
           "ranges": [
             {
               "from": "now-10d/d",
               "to": "now"
             }
           ]
         }
       }
     }
}

date_histogram聚合(時間直方圖聚合)

按天、月、年等進行聚合統計。可按 year (1y), quarter (1q), month (1M), week (1w), day (1d), hour (1h), minute (1m), second (1s) 間隔聚合或指定的時間間隔聚合。

{ 
  "aggs": {
    "sales_over_time": {
      "date_histogram": {
        "field": "accepted_time",
        "interval": "quarter",
        "min_doc_count" : 0, //可以返回沒有數據的月份
        "extended_bounds" : { //強制返回數據的範圍
           "min" : "2014-01-01",
           "max" : "2014-12-31"
        }
      }
    }
  }
}

missing聚合

{ 
  
  "aggs": {
    "account_missing": {
      "missing": {
        "field": "__type"
      }
    }
  }
}

global bucket

將所有數據納入聚合的scope,而不管之前的query。
aggregation,scope,一個聚合操作,必須在query的搜索結果範圍內執行
出來兩個結果,一個結果,是基於query搜索結果來聚合的; 一個結果,是對所有數據執行聚合的。

GET /tvs/sales/_search 
{
  "size": 0, 
  "query": {
    "term": {
      "brand": {
        "value": "長虹"
      }
    }
  },
  "aggs": {
    "single_brand_avg_price": {
      "avg": {
        "field": "price"
      }
    },
    "all": {
      "global": {},
      "aggs": {
        "all_brand_avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

返回結果:

    {
      "took": 4,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
      },
      "hits": {
        "total": 3,
        "max_score": 0,
        "hits": []
      },
      "aggregations": {
        "all": {
          "doc_count": 8,
          "all_brand_avg_price": {
            "value": 2650
          }
        },
        "single_brand_avg_price": {
          "value": 1666.6666666666667
        }
      }
    }

single_brand_avg_price:就是針對query搜索結果,執行的,拿到的,就是長虹品牌的平均價格
all.all_brand_avg_price:拿到所有品牌的平均價格

top_hits 按搜索結果聚合

top_hits 獲取前幾個doc_(即分組內前幾個doc_,由size指定,默認爲3個)
source 返回指定field(主要用於group by之後不能查看其它字段詳情)。

    GET /ecommerce/product/_search
    {
        "size": 0,
        "aggs" : {
            "group_by_tags" : {
                "terms" : { "field" : "tags" },
                "aggs" : {
                    "top_tags": {
                      "top_hits": { 
                        "_source": {
                          "include": "name"
                        }, 
                        "size": 1
                      }
                    } 
                }
            }
        }
    }

collect_mode 子聚合計算

depth_first

直接進行子聚合的計算
計算每個tag下的商品的平均價格,並且按照平均價格降序排序:

"order": { "avg_price": "desc" }
GET /ecommerce/product/_search
{
    "size": 0,
    "aggs" : {
        "all_tags" : {
            "terms" : { "field" : "tags", "collect_mode" : "breadth_first", "order": { "avg_price": "desc" } },
            "aggs" : {
                "avg_price" : {
                    "avg" : { "field" : "price" }
                }
            }
        }
    }
}

vii> breadth_first
先計算出當前聚合的結果,針對這個結果在對子聚合進行計算。

"ranges": [{},{}]

按照指定的價格範圍區間進行分組,然後在每組內再按照tag進行分組,最後再計算每組的平均價格:

GET /ecommerce/product/_search
{
  "size": 0,
  "aggs": {
    "group_by_price": {
      "range": {
        "field": "price",
        "ranges": [
          {
            "from": 0,
            "to": 20
          },
          {
            "from": 20,
            "to": 40
          },
          {
            "from": 40,
            "to": 50
          }
        ]
      },
      "aggs": {
        "group_by_tags": {
          "terms": {
            "field": "tags"
          },
          "aggs": {
            "average_price": {
              "avg": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}

histogram

類似於terms,也是進行bucket分組操作,接收一個field,按照這個field的值的各個範圍區間,進行bucket分組操作

interval:10,劃分範圍,010,1020,20~30

GET /ecommerce/product/_search
{
   "size" : 0,
   "aggs":{
      "price":{
         "histogram":{ 
            "field": "price",
            "interval": 10
         },
         "aggs":{
            "revenue": {
               "sum": { 
                 "field" : "price"
               }
             }
         }
      }
   }
}

date histogram

按照我們指定的某個date類型的日期field,以及日期interval,按照一定的日期間隔,去劃分bucket
date interval = 1m,
2017-01-01~2017-01-31,就是一個bucket
2017-02-01~2017-02-28,就是一個bucket
然後會去掃描每個數據的date field,判斷date落在哪個bucket中,就將其放入那個bucket

min_doc_count:即使某個日期interval,2017-01-01~2017-01-31中,一條數據都沒有,那麼這個區間也是要返回的,不然默認是會過濾掉這個區間的
extended_bounds,min,max:劃分bucket的時候,會限定在這個起始日期,和截止日期內

GET /tvs/sales/_search
{
   "size" : 0,
   "aggs": {
      "sales": {
         "date_histogram": {
            "field": "sold_date",
            "interval": "month", 
            "format": "yyyy-MM-dd",
            "min_doc_count" : 0, 
            "extended_bounds" : { 
                "min" : "2016-01-01",
                "max" : "2017-12-31"
            }
         }
      }
   }
}

滾動(翻頁)查詢

java api

滾動搜索(Scroll API)

String scrollId = "";
            QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(queryBuilder).size(10000);
            SearchRequest request = Requests.searchRequest(indexName);
            request.scroll("1s");
            request.source(sourceBuilder);
            SearchResponse response = client.search(request).actionGet();
            severityCount = deelHits(severityCount, response.getHits());
            scrollId = response.getScrollId();
            while (true) {
                SearchScrollRequestBuilder searchScrollRequestBuilder = client.prepareSearchScroll(scrollId);
                searchScrollRequestBuilder.setScroll("1s");
                // 請求
                SearchResponse response1 = searchScrollRequestBuilder.get();
                SearchHits hits = response1.getHits();
                if (hits.getHits().length == 0) {
                    break;
                }else {
                    severityCount = deelHits(severityCount, hits); //hit.getSource().get("detail").toString()讀取數據
                    //下一批處理
                    scrollId = response1.getScrollId();
                }
            }
public class Scroll {

    public static void main(String[] args) {

        try{
            long startTime = System.currentTimeMillis();
            /*創建客戶端*/
            //client startup
            //設置集羣名稱
            Settings settings = Settings.builder()
                    .put("cluster.name", "elsearch")
                    .put("client.transport.sniff", true)
                    .build();
            //創建client
            TransportClient client = new PreBuiltTransportClient(settings)
                    .addTransportAddress(new InetSocketTransportAddress(
                    InetAddress.getByName("54.223.232.95"),9300));

            List<String> result = new ArrayList<>();

            String scrollId = "";

            //第一次請求
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();


            //TODO: 設置查詢條件
            RangeQueryBuilder rangequerybuilder = QueryBuilders
                .rangeQuery("inputtime")
                .from("2016-12-14 02:00:00").to("2016-12-14 07:59:59");
            sourceBuilder.query(QueryBuilders.boolQuery()
                .must(QueryBuilders
                        .matchPhraseQuery("pointid","W3.UNIT1.10HFC01CT013"))
                    .must(rangequerybuilder))
                    .size(100)//如果開啓遊標,則滾動獲取
                    .sort("inputtime", SortOrder.ASC);
            //查詢
            SearchRequest request = Requests.searchRequest("pointdata");
                request.scroll("2m");
                request.source(sourceBuilder);
            SearchResponse response = client.search(request).actionGet();
            //TODO:處理數據
            SearchHits hits = response.getHits();
            for(int i = 0; i < hits.getHits().length; i++) {
                //System.out.println(hits.getHits()[i].getSourceAsString());
                result.add(hits.getHits()[i].getSourceAsString());
            }
            //記錄滾動ID
            scrollId = response.getScrollId();


            while(true){
                //後續的請求
                //scrollId = query.getScollId();
                SearchScrollRequestBuilder searchScrollRequestBuilder = client
                    .prepareSearchScroll(scrollId);            
                // 重新設定滾動時間            
                //TimeValue timeValue = new TimeValue(30000);
                searchScrollRequestBuilder.setScroll("2m");
                // 請求            
                SearchResponse response1 = searchScrollRequestBuilder.get();

                //TODO:處理數據
                SearchHits hits2 = response1.getHits();
                if(hits2.getHits().length == 0){
                    break;
                }
                for(int i = 0; i < hits2.getHits().length; i++) {
                    result.add(hits2.getHits()[i].getSourceAsString());
                }
                //下一批處理
                scrollId = response1.getScrollId();
            }

            System.out.println(result.size());
            long endTime = System.currentTimeMillis();
            System.out.println("Java程序運行時間:" + (endTime - startTime) + "ms");
        }catch(Exception e){
            e.printStackTrace();
        }

    }

REST API

scroll原理

scroll搜索會在第一次搜索的時候,保存一個當時的視圖快照,之後只會基於該舊的視圖快照提供數據搜索,如果這個期間數據變更,是不會讓用戶看到的;
採用基於_doc(不使用_score)進行排序的方式,性能較高
每次發送scroll請求,我們還需要指定一個scroll參數,指定一個時間窗口,每次搜索請求只要在這個事件窗口內能完成就可以了

# sort默認是相關度排序("sort":[{"FIELD":{"order":"desc"}}]),不按_score排序,按_doc排序
# size設置的是這批查三條
# 第一次查詢會生成快照
GET /lib3/user/_search?scroll=1m #這一批查詢在一分鐘內完成
{
	"query":{
		"match":{}
	},
	"sort":[  
		"_doc"
	],
	"size":3 
}

# 第二次查詢通過第一次的快照ID來查詢,後面以此類推
GET /_search/scroll
{
	"scroll":"1m",
	"scroll_id":"DnF1ZXJ5VGhIbkXIdGNoAwAAAAAAAAAdFkEwRENOVTdnUUJPWVZUd1p2WE5hV2cAAAAAAAAAHhZBMERDTIU3Z1FCT1|WVHdadIhOYVdnAAAAAAAAAB8WQTBEQ05VN2dRQk9ZVIR3WnZYTmFXZw=="
}

基於 scroll 解決深度分頁問題

原理上是對某次查詢生成一個遊標 scroll_id , 後續的查詢只需要根據這個遊標去取數據,直到結果集中返回的 hits 字段爲空,就表示遍歷結束。
注意:scroll_id 的生成可以理解爲建立了一個臨時的歷史快照,在此之後的增刪改查等操作不會影響到這個快照的結果。

使用 curl 進行分頁讀取過程如下:

  1. 先獲取第一個 scroll_id,url 參數包括 /index/_type/ 和 scroll,scroll 字段指定了scroll_id 的有效生存期,以分鐘爲單位,過期之後會被es 自動清理。如果文檔不需要特定排序,可以指定按照文檔創建的時間返回會使迭代更高效。
GET /product/info/_search?scroll=2m
{
	"query":{
		"match_all":{
		}
	},
	"sort":["_doc"]
}

# 返回結果
{
  "_scroll_id": "DnF1ZXJ5VGhIbkXIdGNoAwAAAAAAAAAdFkEwRENOVTdnUUJPWVZUd1p2WE5hV2cAAAAAAAAAHhZBMERDTIU3Z1FCT1|WVHdadIhOYVdnAAAAAAAAAB8WQTBEQ05VN2dRQk9ZVIR3WnZYTmFXZw==",
  "took": 1,
  "timed_out": false,
  "_shards": {
  		"total": 1,
  		"successful": 1,
  		"failed": 0
  },
  "hits":{...}
}
  1. 後續的文檔讀取上一次查詢返回的scroll_id 來不斷的取下一頁,如果srcoll_id 的生存期很長,那麼每次返回的 scroll_id 都是一樣的,直到該 scroll_id 過期,纔會返回一個新的 scroll_id。請求指定的 scroll_id 時就不需要 /index/_type 等信息了。每讀取一頁都會重新設置 scroll_id 的生存時間,所以這個時間只需要滿足讀取當前頁就可以,不需要滿足讀取所有的數據的時間,1 分鐘足以。
GET /product/info/_search?scroll=DnF1ZXJ5VGhIbkXIdGNoAwAAAAAAAAAdFkEwRENOVTdnUUJPWVZUd1p2WE5hV2cAAAAAAAAAHhZBMERDTIU3Z1FCT1|WVHdadIhOYVdnAAAAAAAAAB8WQTBEQ05VN2dRQk9ZVIR3WnZYTmFXZw==
{
	"query":{
		"match_all":{
		}
	},
	"sort":["_doc"]
}

# 返回結果
{
    "_scroll_id": "DnF1ZXJ5VGhIbkXIdGNoAwAAAAAAAAAdFkEwRENOVTdnUUJPWVZUd1p2WE5hV2cAAAAAAAAAHhZBMERDTIU3Z1FCT1|WVHdadIhOYVdnAAAAAAAAAB8WQTBEQ05VN2dRQk9ZVIR3WnZYTmFXZw==",
    "took": 106,
    "_shards": {
        "total": 1,
        "successful": 1,
        "failed": 0
    },
    "hits": {
        "total": 22424,
        "max_score": 1.0,
        "hits": [{
                "_index": "product",
                "_type": "info",
                "_id": "did-519392_pdid-2010",
                "_score": 1.0,
                "_routing": "519392",
                "_source": {
                    ....
                }
            }
        ]
    }
}
  1. 所有文檔獲取完畢之後,需要手動清理掉 scroll_id 。雖然es 會有自動清理機制,但是 srcoll_id 的存在會耗費大量的資源來保存一份當前查詢結果集映像,並且會佔用文件描述符。所以用完之後要及時清理。使用 es 提供的 CLEAR_API 來刪除指定的 scroll_id。
# 刪掉指定的多個 srcoll_id 
DELETE /_search/scroll -d 
{
	"scroll_id":[
		"cXVlcnlBbmRGZXRjaDsxOzg3OTA4NDpTQzRmWWkwQ1Q1bUlwMjc0WmdIX2ZnOzA7"
	]
}

# 刪除掉所有索引上的 scroll_id 
DELETE /_search/scroll/_all

# 查詢當前所有的scroll 狀態
GET /_nodes/stats/indices/_search?pretty

# 返回結果
{
  "cluster_name" : "200.200.107.232",
  "nodes" : {
    "SC4fYi0CT5mIp274ZgH_fg" : {
      "timestamp" : 1514346295736,
      "name" : "200.200.107.232",
      "transport_address" : "200.200.107.232:9300",
      "host" : "200.200.107.232",
      "ip" : [ "200.200.107.232:9300", "NONE" ],
      "indices" : {
        "search" : {
          "open_contexts" : 0,
          "query_total" : 975758,
          "query_time_in_millis" : 329850,
          "query_current" : 0,
          "fetch_total" : 217069,
          "fetch_time_in_millis" : 84699,
          "fetch_current" : 0,
          "scroll_total" : 5348,
          "scroll_time_in_millis" : 92712468,
          "scroll_current" : 0
        }
      }
    }
  }
}

基於 search_after 實現深度分頁

search_after 是 ES5.0 及之後版本提供的新特性,search_after 有點類似 scroll,但是和 scroll 又不一樣,它提供一個活動的遊標,通過上一次查詢最後一條數據來進行下一次查詢。
search_after 分頁的方式和 scroll 有一些顯著的區別,首先它是根據上一頁的最後一條數據來確定下一頁的位置,同時在分頁請求的過程中,如果有索引數據的增刪改查,這些變更也會實時的反映到遊標上。

  1. 第一頁的請求和正常的請求一樣。
GET /order/info/_search
{
    "size": 10,
    "query": {
        "match_all" : {
        }
    },
    "sort": [
        {"date": "asc"}
    ]
}

# 返回結果
{
    "_index": "zmrecall",
    "_type": "recall",
    "_id": "60310505115909",
    "_score": null,
    "_source": {
      ...
      "date": 1545037514
    },
    "sort": [
    	1545037514
    ]
  }
  1. 第二頁的請求,使用第一頁返回結果的最後一個數據的值,加上 search_after 字段來取下一頁。注意:使用 search_after 的時候要將 from 置爲 0 或 -1。
curl -XGET 127.0.0.1:9200/order/info/_search
{
    "size": 10,
    "query": {
        "match_all" : {
        }
    },
    "search_after": [1463538857], # 這個值與上次查詢最後一條數據的sort值一致,支持多個
    "sort": [
        {"date": "asc"}
    ]
}
注意:
如果 search_after 中的關鍵字爲654,那麼654323的文檔也會被搜索到,所以在選擇 search_after 的排序字段時需要謹慎,可以使用比如文檔的id或者時間戳等。
search_after 適用於深度分頁+ 排序,因爲每一頁的數據依賴於上一頁最後一條數據,所以無法跳頁請求。
返回的始終是最新的數據,在分頁過程中數據的位置可能會有變更。這種分頁方式更加符合 moa 的業務場景。

es返回結果

獲取所有數據:

GET /_search

接口訪問鏈接:127.0.0.1:9200/_search
返回數據含義:

返回參數 說明 備註
found 表示查詢的數據是否存在
took 耗費時間(毫秒)。
timed_out 是否超時 默認無timeout
_source 表示查詢到的數據
_shards shards fail的條件(primary和replica全部掛掉),不影響其他shard 默認情況下來說,一個搜索請求,會打到一個index的所有primary shard上去,當然了,每個primary shard都可能會有一個或多個replic shard,所以請求也可以到primary shard的其中一個replica shard上去
_shards.total 表示應執行索引操作的分片(主分片和副本分片)的數量
_shards.successful 表示索引操作成功的分片數
_shards.failed 返回一個數組,這個數組是在副本分片上索引操作失敗的情況下相關錯誤的數組 如果沒有失敗的分片,failed將會爲0。
hits.total 本次搜索返回了幾條結果
hits.max_score score的含義,就是document對於一個search的相關度的匹配分數,越相關,就越匹配,分數也高
hits.hits 包含了匹配搜索的document的詳細數據,默認查詢前10條數據,按_score降序排序 在java api中,可以通過client.setSize設置返回數量。
hits.hits._index 索引名,對應sql的庫
hits.hits._type 類型,對應sql的表
hits.hits._id 搜索的id
hits.hits._score 描述搜索結果的匹配度,得分越高,文檔匹配度越高,得分越低,文檔的匹配度越低。
hits.hits._source 搜索到的具體數據
hits.hits._source.fields 搜索到的具體字段 在java api中通過hit.getSource().get(“field_name”)獲取

可以通過設置timeout這個值,來定時返回已經搜索到的數據。timeout機制,指定每個shard,就只能在timeout時間範圍內,將搜索到的部分數據(也可能是搜索到的全部數據),直接返回給client,而不是等到所有數據全部搜索出來後再返回。可以通過如下方式進行設置:

    timeout=10ms,timeout=1s,timeout=1m
    GET /_search?timeout=10m

基於bulk的增刪改

Elasticsearch也提供了相關操作的批處理功能,這些批處理功能通過使用_bulk API實現。通過批處理可以非常高效的完成多個文檔的操作,同時可以減少不必要的網絡請求。
bulk語法:

  • delete:刪除一個文檔,只要1個json串就可以了
  • create:PUT /index/type/id/_create,強制創建
  • index:普通的put操作,可以是創建文檔,也可以是全量替換文檔
  • update:執行的partial update操作

注意點:

  • bulk api對json的語法有嚴格的要求,除了delete外,每一個操作都要兩個json串,且每個json串內不能換行,非同一個json串必須換行,否則會報錯;
  • bulk操作中,任意一個操作失敗,是不會影響其他的操作的,但是在返回結果裏,會告訴你異常日誌;

bulk api奇特的json格式
目前處理流程:

  • 直接按照換行符切割json,不用將其轉換爲json對象,不會出現內存中的相同數據的拷貝;
  • 對每兩個一組的json,讀取meta,進行document路由;
  • 直接將對應的json發送到node上去;

換成良好json格式的處理流程:

  1. 將json數組解析爲JSONArray對象,這個時候,整個數據,就會在內存中出現一份一模一樣的拷貝,一份數據是json文本,一份數據是JSONArray對象;
  2. 解析json數組裏的每個json,對每個請求中的document進行路由;
  3. 爲路由到同一個shard上的多個請求,創建一個請求數組;
  4. 將這個請求數組序列化;
  5. 將序列化後的請求數組發送到對應的節點上去;

數據錄入

可以將json數據(可以在http://www.json-generator.com/網站上自動生成)放到當前用戶目錄下,然後執行如下命令,將數據導入到Elasticsearch中,如下:

    curl -H "Content-Type: application/json" -XPOST "localhost:9200/bank/_doc/_bulk?pretty&refresh" --data-binary "@accounts.json"

參考文獻

ElasticSearch教程——彙總篇
elasticsearch文檔Delete By Query API(二)
Elasticsearch-基礎介紹及索引原理分析
ElasticSearch教程——Java常用操作
ElasticSearch AggregationBuilders java api常用聚合查詢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章