ElasticSearch 高階 REST 客戶端翻譯 (待續......)

J_Queue發表於2018-07-15

ElasticSearch 高階 REST 客戶端

起步

閱讀文件須知,文件基於Elasticsearch 6.x,閱讀要求,熟悉 ElasticSearch 的語法

相容性

高階客戶端要求最低的 java 版本是1.8 ,它依賴 Elasticsearch 的核心工程,客戶端的版本應該和 Elasticsearch 的版本保持一致,高階客戶端和 TransportClient【TCP 連線客戶端】 接受一樣的請求引數,並且返回一樣的響應結果,如果你想從 TransportClient 客戶端遷移到 REST 客戶端,請參考遷移手冊

高階客戶端保證能夠與執行在相同主要版本和更高版本上的Elasticsearch節點進行通訊。它不需要與通訊的Elasticsearch節點處於相同的版本,因為它是向前相容的,這意味著它支援與更高版本的Elasticsearch進行通訊,而不是與其開發的版本進行通訊。

6.0 客戶端能夠與任何6.x版本的 Elasticsearch節點通訊,而6.1客戶端肯定能夠與6.1,6.2和任何更高版本的6.x版本通訊,但在與老版的Elasticsearch節點通訊時可能存在不相容問題版本,例如6.1和6.0,6.1客戶端為一些 api 新增了新的請求體欄位支援,然而6.0節點卻不支援。

建議在將Elasticsearch叢集升級到新的主版本時升級高階客戶端,因為REST API中斷更改可能會導致意外結果,具體取決於請求所針對的節點,並且新新增的API僅支援新版本的客戶端。一旦叢集中的所有節點都升級到新的主版本,客戶端應保持同步更新。

Java api 文件

文件地址<https://artifacts.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-high-level-client/6.3.1/index.html>

maven 倉庫

高階Java REST客戶端託管在 Maven Central上。所需的最低Java版本是1.8

高階REST客戶端與Elasticsearch具有相同的釋出週期。將版本替換為所需的客戶端版本。

如果您正在尋找SNAPSHOT版本,可以通過snapshots.elastic.co/maven/獲取Elastic Maven Snapshot儲存庫。

Maven 配置

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.3.1</version>
</dependency>
複製程式碼

Gradel 配置

dependencies {
    compile 'org.elasticsearch.client:elasticsearch-rest-high-level-client:6.3.1'
}
複製程式碼

依賴

高階客戶端依賴下面的元件及其傳遞依賴性:

  • org.elasticsearch.client:elasticsearch-rest-client
  • org.elasticsearch:elasticsearch

初始化

一個RestHighLevelClient例項需要一個低階客戶端的Builder 來構建如下:

RestHighLevelClient client = new RestHighLevelClient(
        RestClient.builder(
                new HttpHost("localhost", 9200, "http"),
                new HttpHost("localhost", 9201, "http")));
複製程式碼

高階客戶端將在內部建立用於執行請求的低階客戶端,低階客戶端基於框架提供的builder,並管理其生命週期。

高階客戶端例項應該在不再需要時關閉,以便正確釋放它使用的所有資源,以及底層的http客戶端例項及其執行緒。這可以通過close 方法完成,該方法將關閉內部RestClient例項。

client.close();
複製程式碼

Document API

Java高階REST客戶端支援以下文件API:

單文件API:

  • index api - 索引API
  • get api - 獲取API
  • delete api - 刪除API
  • update api - 更新API

多文件API

  • bulk api - 批量操作 api
  • Multi-Get API - 批量獲取 api

Index API

Index 請求體

一個引索請求需要下面的引數:

IndexRequest request = new IndexRequest(
        "posts", //index 名
        "doc",  //type 名
        "1");   //文件 ID
String jsonString = "{" +
        "\"user\":\"kimchy\"," +
        "\"postDate\":\"2013-01-30\"," +
        "\"message\":\"trying out Elasticsearch\"" +
        "}";
request.source(jsonString, XContentType.JSON);//設定 string 型別的文件source
複製程式碼

構建文件 source 的方式

除了String上面顯示的示例之外,還可以以不同方式提供文件源 :

方式一:以 Map 的方式提供的文件源,Map自動轉換為JSON格式

Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("user", "kimchy");
jsonMap.put("postDate", new Date());
jsonMap.put("message", "trying out Elasticsearch");
IndexRequest indexRequest = new IndexRequest("posts", "doc", "1")
        .source(jsonMap); 
複製程式碼

方式二:以XContentBuilder物件方式提供,Elasticsearch內建了helper生成JSON內容

XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject();
{
    builder.field("user", "kimchy");
    builder.timeField("postDate", new Date());
    builder.field("message", "trying out Elasticsearch");
}
builder.endObject();
IndexRequest indexRequest = new IndexRequest("posts", "doc", "1")
        .source(builder); 
複製程式碼

方式三:以鍵值對方式提供,轉換為JSON格式

IndexRequest indexRequest = new IndexRequest("posts", "doc", "1")
        .source("user", "kimchy",
                "postDate", new Date(),
                "message", "trying out Elasticsearch");
複製程式碼

可選引數

可以選擇以下引數:

  • 設定路由
request.routing("routing");
複製程式碼
  • 設定父文件
request.parent("parent");
複製程式碼
  • 設定超時時間
request.timeout(TimeValue.timeValueSeconds(1));
request.timeout("1s");
複製程式碼
  • 設定重新整理策略
request.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL);
request.setRefreshPolicy("wait_for");
複製程式碼
  • 設定版本
request.version(2);
複製程式碼
  • 設定版本型別
request.versionType(VersionType.EXTERNAL); 
複製程式碼
  • 設定文件操作型別
request.opType(DocWriteRequest.OpType.CREATE); 
request.opType("create"); 
複製程式碼
  • 文件執行之前,設定 pipeline
request.setPipeline("pipeline"); 
複製程式碼

同步執行方式

IndexResponse indexResponse = client.index(request);
複製程式碼

非同步執行方式

索引請求的非同步執行需要將IndexRequest 例項和ActionListener例項都傳遞給非同步方法:

ActionListener<IndexResponse> listener = new ActionListener<IndexResponse>() {
    @Override
    public void onResponse(IndexResponse indexResponse) {  
    }
    @Override
    public void onFailure(Exception e) {
    }
};
IndexResponse indexResponse = client.index(request);
複製程式碼

非同步方法不會阻塞並立即返回。一旦完成,如果執行成功,則使用該方法ActionListener回撥onResponse,如果失敗則回撥onFailure方法。

引索響應結果

返回的IndexResponse包含了有關已執行操作的資訊,如下所示:

String index = indexResponse.getIndex();
String type = indexResponse.getType();
String id = indexResponse.getId();
long version = indexResponse.getVersion();
if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) {
    //建立文件操作
} else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {
    //更新文件操作
}
ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo();
if (shardInfo.getTotal() != shardInfo.getSuccessful()) {
    //處理成功分片數小於總分片數的情況
}
if (shardInfo.getFailed() > 0) {//理潛在的失敗情況
    for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) {
        String reason = failure.reason(); 
    }
}
複製程式碼

如果存在文件版本衝突,則會丟擲ElasticsearchException

IndexRequest request = new IndexRequest("posts", "doc", "1")
        .source("field", "value")
        .version(1);
try {
    IndexResponse response = client.index(request);
} catch(ElasticsearchException e) {
    if (e.status() == RestStatus.CONFLICT) {
        //引發的異常表示返回了版本衝突錯誤
    }
}
複製程式碼

如果已存在具有相同索引,型別和ID的文件,opType設定為create也會發生衝突:

IndexRequest request = new IndexRequest("posts", "doc", "1")
        .source("field", "value")
        .opType(DocWriteRequest.OpType.CREATE);
try {
    IndexResponse response = client.index(request);
} catch(ElasticsearchException e) {
    if (e.status() == RestStatus.CONFLICT) {
        
    }
}
複製程式碼

Get API

Get 請求體

構建 GetRequest 的引數如下:

GetRequest getRequest = new GetRequest("posts","doc","1"); 
複製程式碼

可選引數

  • 設定返回響應不包含任何欄位,預設情況下返回響應包含該所有欄位
request.fetchSourceContext(FetchSourceContext.DO_NOT_FETCH_SOURCE);
複製程式碼
  • 配置返回響應包含哪些欄位
String[] includes = new String[]{"message", "*Date"};
String[] excludes = Strings.EMPTY_ARRAY;
FetchSourceContext fetchSourceContext =
        new FetchSourceContext(true, includes, excludes);
request.fetchSourceContext(fetchSourceContext);
複製程式碼
  • 設定返回響應不包含哪些欄位
String[] includes = Strings.EMPTY_ARRAY;
String[] excludes = new String[]{"message"};
FetchSourceContext fetchSourceContext =
        new FetchSourceContext(true, includes, excludes);
request.fetchSourceContext(fetchSourceContext); 
複製程式碼
  • 設定檢索哪些儲存欄位
request.storedFields("message"); //為特定儲存欄位配置檢索 (要求在對映中單獨儲存欄位)
GetResponse getResponse = client.get(request);
String message = getResponse.getField("message").getValue();//獲取message儲存的值 (要求將欄位單獨儲存在對映中)
複製程式碼
  • 設定路由
request.routing("routing");
複製程式碼
  • 設定父文件
request.parent("parent");
複製程式碼
  • 設定偏好
request.preference("preference");
複製程式碼
  • 設定實時標識,預設 true
request.realtime(false);
複製程式碼
  • 設定每次獲取文件之前是否執行重新整理操作,預設 false
request.refresh(true);
複製程式碼
  • 設定版本號
request.version(2);
複製程式碼
  • 設定版本型別
request.versionType(VersionType.EXTERNAL);
複製程式碼

同步執行

GetResponse getResponse = client.get(getRequest);
複製程式碼

非同步執行

get請求的非同步執行需要將GetRequest 例項和ActionListener例項都傳遞給非同步方法

ActionListener<GetResponse> listener = new ActionListener<GetResponse>() {
    @Override
    public void onResponse(GetResponse getResponse) {
    }

    @Override
    public void onFailure(Exception e) {
    }
};
GetResponse getResponse = client.get(getRequest);
複製程式碼

非同步方法不會阻塞並立即返回。一旦完成,如果執行成功,則使用該方法ActionListener回撥onResponse,如果失敗則回撥onFailure方法。

響應結果

返回的IndexResponse包含了有關已執行操作的資訊,如下所示:

String index = getResponse.getIndex();
String type = getResponse.getType();
String id = getResponse.getId();
if (getResponse.isExists()) {
    long version = getResponse.getVersion();
    String sourceAsString = getResponse.getSourceAsString();        
    Map<String, Object> sourceAsMap = getResponse.getSourceAsMap(); 
    byte[] sourceAsBytes = getResponse.getSourceAsBytes();          
} else {
    //處理未找到文件的方案。注意,雖然返回的響應具有404狀態程式碼,但他會返回一個有效GetResponse而不是丟擲異常。此類響應不包含任何文件欄位,而且isExists方法返回false。
}
複製程式碼

當對不存在的索引(index)執行get請求時,響應會有404狀態程式碼,但是會丟擲ElasticsearchException,需要按如下方式處理:

GetRequest request = new GetRequest("does_not_exist", "doc", "1");
try {
    GetResponse getResponse = client.get(request);
} catch (ElasticsearchException e) {
    if (e.status() == RestStatus.NOT_FOUND) {
        
    }
}
複製程式碼

如果請求特定版本的文件,並且現有文件具有不同的版本號,則會引發版本衝突:

try {
    GetRequest request = new GetRequest("posts", "doc", "1").version(2);
    GetResponse getResponse = client.get(request);
} catch (ElasticsearchException exception) {
    if (exception.status() == RestStatus.CONFLICT) {
        //處理版本衝突
    }
}
複製程式碼

Exists API

如果文件存在就返回 true,否則返回 false

Exists Request

它的GetRequest就像Get API一樣。支援所有可選引數 。由於exists()只返回truefalse,所有建議關閉返回_source和任何儲存的欄位,以便請求更加輕量:

GetRequest getRequest = new GetRequest(
    "posts", 
    "doc",   
    "1");    
getRequest.fetchSourceContext(new FetchSourceContext(false)); //不返回_source
getRequest.storedFields("_none_");  //不返回儲存欄位
複製程式碼

同步執行

boolean exists = client.exists(getRequest);
複製程式碼

非同步執行

ActionListener<Boolean> listener = new ActionListener<Boolean>() {
    @Override
    public void onResponse(Boolean exists) {
    }
    @Override
    public void onFailure(Exception e) {  
    }
};
client.existsAsync(getRequest, listener); 
複製程式碼

Delete API

Delete Request

DeleteRequest 引數如下:

DeleteRequest request = new DeleteRequest("posts","doc","1");  
複製程式碼

可選引數

  • 設定路由
request.routing("routing");
複製程式碼
  • 設定父文件
request.parent("parent");
複製程式碼
  • 設定超時
request.timeout(TimeValue.timeValueMinutes(2)); 
request.timeout("2m");
複製程式碼
  • 設定重新整理策略
request.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL); 
request.setRefreshPolicy("wait_for"); 
複製程式碼
  • 設定版本號
request.version(2);
複製程式碼
  • 設定版本型別
request.versionType(VersionType.EXTERNAL);
複製程式碼

同步執行

DeleteResponse deleteResponse = client.delete(request);
複製程式碼

非同步執行

ActionListener<DeleteResponse> listener = new ActionListener<DeleteResponse>() {
    @Override
    public void onResponse(DeleteResponse deleteResponse) {
    }
    @Override
    public void onFailure(Exception e) { 
    }
};
client.deleteAsync(request, listener);
複製程式碼

Delete Response

返回的DeleteResponse包含了有關已執行操作的資訊,如下所示:

String index = deleteResponse.getIndex();
String type = deleteResponse.getType();
String id = deleteResponse.getId();
long version = deleteResponse.getVersion();
ReplicationResponse.ShardInfo shardInfo = deleteResponse.getShardInfo();
if (shardInfo.getTotal() != shardInfo.getSuccessful()) {
    
}
if (shardInfo.getFailed() > 0) {
    for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) {
        String reason = failure.reason(); 
    }
}
複製程式碼

它還可以檢查文件是否存在

DeleteRequest request = new DeleteRequest("posts", "doc", "does_not_exist");
DeleteResponse deleteResponse = client.delete(request);
if (deleteResponse.getResult() == DocWriteResponse.Result.NOT_FOUND) {
    //如果找不到要刪除的文件
}
複製程式碼

如果請求的文件版本衝突,會拋ElasticsearchException異常

try {
    DeleteRequest request = new DeleteRequest("posts", "doc", "1").version(2);
    DeleteResponse deleteResponse = client.delete(request);
} catch (ElasticsearchException exception) {
    if (exception.status() == RestStatus.CONFLICT) {
        //引發的異常表示返回了版本衝突錯誤
    }
}
複製程式碼

Update API

UpdateRequest

引數如下:

UpdateRequest request = new UpdateRequest("posts", "doc", "1");
複製程式碼

Update API允許使用指令碼或傳遞部分文件來更新現有文件。

使用指令碼更新文件

使用內聯指令碼

Map<String, Object> parameters = singletonMap("count", 4); //使用Map物件作為指令碼引數
Script inline = new Script(ScriptType.INLINE, "painless",
        "ctx._source.field += params.count", parameters);  //使用painless語言和提供的引數建立內聯指令碼
UpdateRequest request = new UpdateRequest("posts", "doc", "1");
request.script(inline); //將指令碼設定為更新請求
複製程式碼

或者使用儲存在es 中的指令碼

Script stored =new Script(ScriptType.STORED, null, "increment-field", parameters);//使用儲存在 es 中的painless指令碼,指令碼名為increment-field
request.script(stored); 
複製程式碼

傳遞部分文件作為引數來更新文件

當使用部分文件來更新現有的文件時,部分文件將與現有文件合併。

部分文件可以以不同方式提供:

UpdateRequest request = new UpdateRequest("posts", "doc", "1");
String jsonString = "{" +
        "\"updated\":\"2017-01-01\"," +
        "\"reason\":\"daily update\"" +
        "}";
request.doc(jsonString, XContentType.JSON);//用json格式的字串作為部分文件源
複製程式碼

Map 提供部分文件源,會被自動轉化成 json 格式,如下

Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("updated", new Date());
jsonMap.put("reason", "daily update");
UpdateRequest request = new UpdateRequest("posts", "doc", "1")
        .doc(jsonMap);
複製程式碼

XContentBuilder物件作為部分文件源,Elasticsearch內建的 helpers 會自動將它轉化為 json 文件

XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject();
{
    builder.timeField("updated", new Date());
    builder.field("reason", "daily update");
}
builder.endObject();
UpdateRequest request = new UpdateRequest("posts", "doc", "1")
        .doc(builder); 
複製程式碼

使用鍵值對作為部分文件源,他會被轉換成 json 文字

UpdateRequest request = new UpdateRequest("posts", "doc", "1")
        .doc("updated", new Date(),
             "reason", "daily update");
複製程式碼

Upserts

如果文件尚不存在,則可以使用以下upsert方法來將它作為新文件插入:

String jsonString = "{\"created\":\"2017-01-01\"}";
request.upsert(jsonString, XContentType.JSON);
複製程式碼

和部分文件更新一樣,upsert 方法接受StringMapXContentBuilder or 鍵值對作為入參

可選引數

  • 設定路由
request.routing("routing");
複製程式碼
  • 設定父文件
request.parent("parent");
複製程式碼
  • 設定超時時間
request.timeout(TimeValue.timeValueSeconds(1)); 
request.timeout("1s"); 
複製程式碼
  • 設定重新整理策略
request.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL); 
request.setRefreshPolicy("wait_for"); 
複製程式碼
  • 設定重試更新操作的次數

如果要更新的文件已在更新操作的get和indexing階段之間的另一個操作更改,則重試

request.retryOnConflict(3);
複製程式碼
  • 設定是否獲取新文件內容,預設 false
request.fetchSource(true);
複製程式碼
  • 指定返回哪些欄位
String[] includes = new String[]{"updated", "r*"};//正則匹配
String[] excludes = Strings.EMPTY_ARRAY;
request.fetchSource(new FetchSourceContext(true, includes, excludes));
複製程式碼
  • 指定不返回哪些欄位
String[] includes = new String[]{"updated", "r*"};
String[] excludes = Strings.EMPTY_ARRAY;
request.fetchSource(new FetchSourceContext(true, includes, excludes));
複製程式碼
  • 設定文件版本號
request.version(2);
複製程式碼
  • 設定是否啟用noop 檢測
request.detectNoop(false);
複製程式碼
  • 指示指令碼必須執行,無論文件是否存在,即如果文件尚不存在,指令碼將負責建立文件
request.scriptedUpsert(true);
複製程式碼
  • 如果文件不存在,則表明必須將部分文件用作upsert文件
request.docAsUpsert(true);
複製程式碼
  • 設定在執行更新操作之前必須處於活動狀態的分片副本數
request.waitForActiveShards(2); 
request.waitForActiveShards(ActiveShardCount.ALL);
複製程式碼

同步執行

UpdateResponse updateResponse = client.update(request);
複製程式碼

非同步執行

client.updateAsync(request, new ActionListener<UpdateResponse>() {
    @Override
    public void onResponse(UpdateResponse updateResponse) {
    }
    @Override
    public void onFailure(Exception e) {
    }
});
複製程式碼

UpdateResponse

返回的UpdateResponse包含了有關已執行操作的資訊,如下所示:

String index = updateResponse.getIndex();
String type = updateResponse.getType();
String id = updateResponse.getId();
long version = updateResponse.getVersion();
if (updateResponse.getResult() == DocWriteResponse.Result.CREATED) {
    //首次建立文件(upsert)
} else if (updateResponse.getResult() == DocWriteResponse.Result.UPDATED) {
    //文件更新
} else if (updateResponse.getResult() == DocWriteResponse.Result.DELETED) {
    //文件更新
} else if (updateResponse.getResult() == DocWriteResponse.Result.NOOP) {
    //文件未受更新影響,即未對文件執行任何操作(noop)
}
複製程式碼

UpdateRequest 通過fetchSource方法啟用獲取文件功能時,響應包含更新文件的來源:

GetResult result = updateResponse.getGetResult(); 
if (result.isExists()) {
    String sourceAsString = result.sourceAsString(); 
    Map<String, Object> sourceAsMap = result.sourceAsMap(); 
    byte[] sourceAsBytes = result.source(); 
} else {
    
}
複製程式碼

可以檢查分片失敗:

ReplicationResponse.ShardInfo shardInfo = updateResponse.getShardInfo();
if (shardInfo.getTotal() != shardInfo.getSuccessful()) {
    
}
if (shardInfo.getFailed() > 0) {
    for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) {
        String reason = failure.reason(); 
    }
}
複製程式碼

當對一個不存在的文件執行UpdateRequest時,響應具有404狀態程式碼,會丟擲ElasticsearchException,需要按如下方式處理:

UpdateRequest request = new UpdateRequest("posts", "type", "does_not_exist")
        .doc("field", "value");
try {
    UpdateResponse updateResponse = client.update(request);
} catch (ElasticsearchException e) {
    if (e.status() == RestStatus.NOT_FOUND) {
        
    }
}
複製程式碼

如果發生文件版本衝突,會丟擲異常:

UpdateRequest request = new UpdateRequest("posts", "doc", "1")
        .doc("field", "value")
        .version(1);
try {
    UpdateResponse updateResponse = client.update(request);
} catch(ElasticsearchException e) {
    if (e.status() == RestStatus.CONFLICT) {   
    }
}
複製程式碼

Bulk API

BulkRequest

BulkRequest可用於使用單個請求執行多個索引,更新和/或刪除操作

它要求至少將一個操作新增到批量請求:

BulkRequest request = new BulkRequest(); 
request.add(new IndexRequest("posts", "doc", "1")  
        .source(XContentType.JSON,"field", "foo"));
request.add(new IndexRequest("posts", "doc", "2")  
        .source(XContentType.JSON,"field", "bar"));
request.add(new IndexRequest("posts", "doc", "3")  
        .source(XContentType.JSON,"field", "baz"));
複製程式碼

並且可以新增不同的操作型別BulkRequest

BulkRequest request = new BulkRequest();
request.add(new DeleteRequest("posts", "doc", "3")); 
request.add(new UpdateRequest("posts", "doc", "2") 
        .doc(XContentType.JSON,"other", "test"));
request.add(new IndexRequest("posts", "doc", "4")  
        .source(XContentType.JSON,"field", "baz"));
複製程式碼

可選引數

  • 設定超時時間
request.timeout(TimeValue.timeValueMinutes(2)); 
request.timeout("2m"); 
複製程式碼
  • 設定重新整理策略
request.timeout(TimeValue.timeValueMinutes(2)); 
request.timeout("2m"); 
複製程式碼
  • 設定在索引/更新/刪除操作之前必須處於活動狀態的分片副本數
request.waitForActiveShards(2); 
request.waitForActiveShards(ActiveShardCount.ALL); //可選ActiveShardCount.ALL、 ActiveShardCount.ONE 、 ActiveShardCount.DEFAULT
複製程式碼

同步執行

BulkResponse bulkResponse = client.bulk(request);
複製程式碼

非同步執行

ActionListener<BulkResponse> listener = new ActionListener<BulkResponse>() {
    @Override
    public void onResponse(BulkResponse bulkResponse) {
    }
    @Override
    public void onFailure(Exception e) {
    }
};
client.bulkAsync(request, listener);
複製程式碼

BulkResponse

響應結果,允許迭代每個結果,如下所示:

for (BulkItemResponse bulkItemResponse : bulkResponse) { 
    //可以是IndexResponse、UpdateResponse、DeleteResponse,他們可以全部被視為DocWriteResponse例項
    DocWriteResponse itemResponse = bulkItemResponse.getResponse(); 

    if (bulkItemResponse.getOpType() == DocWriteRequest.OpType.INDEX
            || bulkItemResponse.getOpType() == DocWriteRequest.OpType.CREATE) { 
        IndexResponse indexResponse = (IndexResponse) itemResponse;

    } else if (bulkItemResponse.getOpType() == DocWriteRequest.OpType.UPDATE) { 
        UpdateResponse updateResponse = (UpdateResponse) itemResponse;

    } else if (bulkItemResponse.getOpType() == DocWriteRequest.OpType.DELETE) { 
        DeleteResponse deleteResponse = (DeleteResponse) itemResponse;
    }
}
複製程式碼

批量響應提供了一種快速檢查一個或多個操作是否失敗的方法:

if(bulkResponse.hasFailures()){ 
//如果至少有一個操作失敗,則返回true
}
複製程式碼

在這種情況下,有必要迭代所有操作結果,以檢查操作是否失敗,如果是,則獲取相應的失敗資訊:

for(BulkItemResponse bulkItemResponse:bulkResponse){
    if(bulkItemResponse.isFailed()){ 
        BulkItemResponse.Failure failure = bulkItemResponse.getFailure(); 

    }
}
複製程式碼

批量處理器

BulkProcessor提供了一個工具類簡化操作,它可以透明地執行新增到 processor 中的 index/update/delete操作。

為了執行請求,BulkProcessor需要以下元件:

  • RestHighLevelClient

    此客戶端用於執行BulkRequest 和獲取BulkResponse

  • BulkProcessor.Listener

    在每次BulkRequest執行之前、之後或BulkRequest失敗時呼叫器監聽

然後該BulkProcessor.builder方法可用於構建新的BulkProcessor

BulkProcessor.Listener listener = new BulkProcessor.Listener() { 
    @Override
    public void beforeBulk(long executionId, BulkRequest request) {
        
    }

    @Override
    public void afterBulk(long executionId, BulkRequest request,
            BulkResponse response) {
        
    }

    @Override
    public void afterBulk(long executionId, BulkRequest request, Throwable failure) {
        
    }
};

BulkProcessor bulkProcessor =
        BulkProcessor.builder(client::bulkAsync, listener).build(); 
複製程式碼

BulkProcessor.Builder提供了方法來配置BulkProcessor處理請求的行為:

BulkProcessor.Builder builder = BulkProcessor.builder(client::bulkAsync, listener);
builder.setBulkActions(500);//根據當前新增的運算元設定何時重新整理新的批量請求(預設為1000,使用-1禁用它) 
builder.setBulkSize(new ByteSizeValue(1L, ByteSizeUnit.MB)); //根據當前新增的操作內容大小設定何時重新整理新的批量請求(預設為5Mb,使用-1禁用它)
builder.setConcurrentRequests(0); //設定允許執行的併發請求數(預設為1,使用0只允許執行單個請求)
builder.setFlushInterval(TimeValue.timeValueSeconds(10L)); //BulkRequest如果間隔超過,則 設定重新整理間隔重新整理任何掛起(預設為未設定)
builder.setBackoffPolicy(BackoffPolicy
        .constantBackoff(TimeValue.timeValueSeconds(1L), 3));//設定一個最初等待1秒的常量重試策略,最多重試3次。見BackoffPolicy.noBackoff()、BackoffPolicy.constantBackoff()、BackoffPolicy.exponentialBackoff() 提供更多的選擇
複製程式碼

一旦BulkProcessor被建立,請求可以被新增到processor

IndexRequest one = new IndexRequest("posts", "doc", "1").
        source(XContentType.JSON, "title",
                "In which order are my Elasticsearch queries executed?");
IndexRequest two = new IndexRequest("posts", "doc", "2")
        .source(XContentType.JSON, "title",
                "Current status and upcoming changes in Elasticsearch");
IndexRequest three = new IndexRequest("posts", "doc", "3")
        .source(XContentType.JSON, "title",
                "The Future of Federated Search in Elasticsearch");

bulkProcessor.add(one);
bulkProcessor.add(two);
bulkProcessor.add(three);
複製程式碼

BulkProcessor 執行所有的請求,並且為每次的 BulkRequest 回撥BulkProcessor.Listener,監聽器提供了訪問 BulkRequestBulkResponse 的方法

BulkProcessor.Listener listener = new BulkProcessor.Listener() {
    @Override
    public void beforeBulk(long executionId, BulkRequest request) {
        int numberOfActions = request.numberOfActions(); 
        logger.debug("Executing bulk [{}] with {} requests",
                executionId, numberOfActions);
    }

    @Override
    public void afterBulk(long executionId, BulkRequest request,
            BulkResponse response) {
        if (response.hasFailures()) { 
            logger.warn("Bulk [{}] executed with failures", executionId);
        } else {
            logger.debug("Bulk [{}] completed in {} milliseconds",
                    executionId, response.getTook().getMillis());
        }
    }

    @Override
    public void afterBulk(long executionId, BulkRequest request, Throwable failure) {
        //執行失敗後呼叫
        logger.error("Failed to execute bulk", failure); 
    }
};
複製程式碼

將所有請求新增到BulkProcessor後,需要關閉其例項,有兩種關閉方式。

awaitClose()方法可用於等待所有請求都已處理或指定的等待時間:

boolean terminated = bulkProcessor.awaitClose(30L,TimeUnit.SECONDS);//true:如果所有批量請求都已完成,false:在所有批量請求完成之前等待時間已過
複製程式碼

close()方法可用於立即關閉BulkProcessor

bulkProcessor.close();
複製程式碼

兩種方法在關閉處理器之前重新整理已經新增到處理器的請求,並且禁止新增新請求

Multi-Get API

multiGet API 可以在單個請求中執行多個 get 請求

Multi-Get Request

獲取一個 MultiGetRequest例項,然後新增多個 MultiGetRequest.Item:

MultiGetRequest request = new MultiGetRequest();
request.add(new MultiGetRequest.Item(
    "index",         
    "type",          
    "example_id"));  
request.add(new MultiGetRequest.Item("index", "type", "another_id"));
複製程式碼

可選引數

multiGetget Api支援相同的可選引數. 你可以在 Item上設定可選引數:

  • 設定不返回任何文件,預設返回文件
request.add(new MultiGetRequest.Item("index", "type", "example_id")
            .fetchSourceContext(FetchSourceContext.DO_NOT_FETCH_SOURCE)
    );  
複製程式碼
  • 設定返回文件的哪些欄位
String[] includes = new String[] {"foo", "*r"};
String[] excludes = Strings.EMPTY_ARRAY;
FetchSourceContext fetchSourceContext =
        new FetchSourceContext(true, includes, excludes);
request.add(new MultiGetRequest.Item("index", "type", "example_id")
    .fetchSourceContext(fetchSourceContext));  
複製程式碼
  • 設定不返回文件的哪些欄位
String[] includes = Strings.EMPTY_ARRAY;
String[] excludes = new String[] {"foo", "*r"};
FetchSourceContext fetchSourceContext =
        new FetchSourceContext(true, includes, excludes);
request.add(new MultiGetRequest.Item("index", "type", "example_id")
    .fetchSourceContext(fetchSourceContext));
複製程式碼
  • 配置返回指定的儲存欄位
request.add(new MultiGetRequest.Item("index", "type", "example_id")
    .storedFields("foo"));  //設定返回儲存欄位 foo
MultiGetResponse response = client.multiGet(request);
MultiGetItemResponse item = response.getResponses()[0];
String value = item.getResponse().getField("foo").getValue(); //獲取儲存欄位foo的值
複製程式碼
  • 其他可選引數
request.add(new MultiGetRequest.Item("index", "type", "with_routing")
    .routing("some_routing"));//設定路由       
request.add(new MultiGetRequest.Item("index", "type", "with_parent")
    .parent("some_parent"));//設定父文件       
request.add(new MultiGetRequest.Item("index", "type", "with_version")
    .versionType(VersionType.EXTERNAL)//設定文件版本型別
    .version(10123L)); //設定版本號
request.preference("some_preference");  //設定偏好
request.realtime(false); //設定實時標識,預設為 true       
request.refresh(true); //獲取文件之前執行重新整理操作,預設 false
複製程式碼

同步執行

MultiGetResponse response = client.multiGet(request);
複製程式碼

非同步執行

ActionListener<MultiGetResponse> listener = new ActionListener<MultiGetResponse>() {
    @Override
    public void onResponse(MultiGetResponse response) {
        
    }

    @Override
    public void onFailure(Exception e) {
        
    }
};
MultiGetResponse response = client.multiGet(request);
複製程式碼

MultiGetResponse

返回的MultiGetResponse通過getResponses方法可以獲取一個 MultiGetItemResponse 列表,列表中的響應與請求的順序相同,如果 get 成功 MultiGetItemResponse包含一個 GetResponse,如果它失敗了會包含一個MultiGetResponse.Failure

MultiGetItemResponse firstItem = response.getResponses()[0];
assertNull(firstItem.getFailure());//如果成功,返回 null       
GetResponse firstGet = firstItem.getResponse(); //獲取 GetResponse
String index = firstItem.getIndex();
String type = firstItem.getType();
String id = firstItem.getId();
if(firstGet.isExists())//判斷文件是否存在
    long version = firstGet.getVersion();
    String sourceAsString = firstGet.getSourceAsString();        
    Map <String,Object> sourceAsMap = firstGet.getSourceAsMap(); 
    byte [] sourceAsBytes = firstGet.getSourceAsBytes();          
} else {
    
}
複製程式碼

如果請求的 index 不存在,則返回響應會包含一個異常資訊

assertNull(missingIndexItem.getResponse());                
Exception e = missingIndexItem.getFailure().getFailure();  
ElasticsearchException ee = (ElasticsearchException) e;    
// TODO status is broken! fix in a followup
// assertEquals(RestStatus.NOT_FOUND, ee.status());        
assertThat(e.getMessage(),
    containsString("reason=no such index"));
複製程式碼

請求文件版本衝突,則返回響應會包含一個異常資訊

MultiGetRequest request = new MultiGetRequest();
request.add(new MultiGetRequest.Item("index", "type", "example_id")
    .version(1000L));
MultiGetResponse response = client.multiGet(request);
MultiGetItemResponse item = response.getResponses()[0];
assertNull(item.getResponse());                          
Exception e = item.getFailure().getFailure();            
ElasticsearchException ee = (ElasticsearchException) e;  
// TODO status is broken! fix in a followup
// assertEquals(RestStatus.CONFLICT, ee.status());          
assertThat(e.getMessage(),
    containsString("version conflict, current version [1] is "
        + "different than the one provided [1000]")); 
複製程式碼

Search API

高階客戶端支援下面的 Search API:

  • Search API
  • Search Scroll API
  • Clear Scroll API
  • Multi-Search API
  • Ranking Evaluation API

Search API

SearchRequest

SearchRequest searchRequest = new SearchRequest(); 
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); 
searchSourceBuilder.query(QueryBuilders.matchAllQuery()); 
searchRequest.source(searchSourceBuilder); 
複製程式碼

可選引數

SearchRequest searchRequest = new SearchRequest("posts"); 
searchRequest.types("doc"); 
searchRequest.routing("routing"); 
searchRequest.indicesOptions(IndicesOptions.lenientExpandOpen());
searchRequest.preference("_local"); 
複製程式碼

使用 SearchBuilder

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 
sourceBuilder.query(QueryBuilders.termQuery("user", "kimchy")); 
sourceBuilder.from(0); 
sourceBuilder.size(5); 
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS)); 
複製程式碼

構建查詢語句

MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("user", "kimchy");
matchQueryBuilder.fuzziness(Fuzziness.AUTO); 
matchQueryBuilder.prefixLength(3); 
matchQueryBuilder.maxExpansions(10); 
matchQueryBuilder.fuzziness(Fuzziness.AUTO); 
matchQueryBuilder.prefixLength(3); 
matchQueryBuilder.maxExpansions(10); 
searchSourceBuilder.query(matchQueryBuilder);
複製程式碼

設定排序

sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC)); 
sourceBuilder.sort(new FieldSortBuilder("_uid").order(SortOrder.ASC));
複製程式碼

文件過濾

sourceBuilder.fetchSource(false);
String[] includeFields = new String[] {"title", "user", "innerObject.*"};
String[] excludeFields = new String[] {"_type"};
sourceBuilder.fetchSource(includeFields, excludeFields);
複製程式碼

欄位高亮

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
HighlightBuilder highlightBuilder = new HighlightBuilder(); 
HighlightBuilder.Field highlightTitle =
        new HighlightBuilder.Field("title"); 
highlightTitle.highlighterType("unified");  
highlightBuilder.field(highlightTitle);  
HighlightBuilder.Field highlightUser = new HighlightBuilder.Field("user");
highlightBuilder.field(highlightUser);
searchSourceBuilder.highlighter(highlightBuilder);
複製程式碼

新增聚合查詢

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
TermsAggregationBuilder aggregation = AggregationBuilders.terms("by_company")
        .field("company.keyword");
aggregation.subAggregation(AggregationBuilders.avg("average_age")
        .field("age"));
searchSourceBuilder.aggregation(aggregation);
複製程式碼

請求建議詞

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
SuggestionBuilder termSuggestionBuilder =
    SuggestBuilders.termSuggestion("user").text("kmichy"); 
SuggestBuilder suggestBuilder = new SuggestBuilder();
suggestBuilder.addSuggestion("suggest_user", termSuggestionBuilder); 
searchSourceBuilder.suggest(suggestBuilder);
複製程式碼

分析查詢和聚合

profile API 可用於為特定搜尋分析查詢和聚合的執行情況。為了使用它, 必須在 SearchSourceBuilder 上設定profile標誌為 true:

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.profile(true);
複製程式碼

執行 SearchRequest 後, 相應的 SearchResponse 將包含分析結果。

同步執行

SearchResponse searchResponse = client.search(searchRequest);
複製程式碼

非同步執行

執行 SearchRequest 也可以以非同步方式進行, 以便客戶端可以直接返回。使用者需要通過將請求和監聽器傳遞給非同步搜尋方法來指定如何處理響應或潛在故障:

ActionListener<SearchResponse> listener = new ActionListener<SearchResponse>() {
    @Override
    public void onResponse(SearchResponse searchResponse) {
        
    }

    @Override
    public void onFailure(Exception e) {
        
    }
};
client.searchAsync(searchRequest, listener); 
複製程式碼

非同步方法不會阻止執行緒,也不會立即返回。完成該操作後, 如果執行成功則回撥ActionListeneronResponse 方法,失敗則回撥 onFailure` 方法

SearchResponse

執行搜尋返回的 SearchResponse 提供了有關搜尋執行本身以及對返回訪問的文件的詳細資訊。看下有關於請求執行操作的資訊, 如 HTTP 狀態程式碼、執行時間或請求是否提前終止或超時:

RestStatus status = searchResponse.status();
TimeValue took = searchResponse.getTook();
Boolean terminatedEarly = searchResponse.isTerminatedEarly();
boolean timedOut = searchResponse.isTimedOut();
複製程式碼

其次, 響應還提供有關在分片級別上執行的資訊, 提供有關受影響搜尋的分片總數以及成功與失敗的分片的統計資料。潛在的故障也可以通過迭代 ShardSearchFailure 陣列來處理, 如下面的示例所示:

int totalShards = searchResponse.getTotalShards();
int successfulShards = searchResponse.getSuccessfulShards();
int failedShards = searchResponse.getFailedShards();
for (ShardSearchFailure failure : searchResponse.getShardFailures()) {
    // failures should be handled here
}
複製程式碼

獲取搜尋命中的文件

要獲得對返回的文件的訪問許可權, 我們首先需要得到響應中包含的 SearchHits:

SearchHits hits = searchResponse.getHits();
long totalHits = hits.getTotalHits();
float maxScore = hits.getMaxScore();
複製程式碼

SearchHits 提供有關所有命中的全域性資訊, 如命中總數或最大得分:

long totalHits = hits.getTotalHits();
float maxScore = hits.getMaxScore();
複製程式碼

巢狀在 SearchHits 中的是可以迭代的單個搜尋結果:

SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
    // do something with the SearchHit
}
複製程式碼

SearchHit 提供對基本資訊的訪問, 如索引、型別、docId 和每個搜尋命中的分數:

String index = hit.getIndex();
String type = hit.getType();
String id = hit.getId();
float score = hit.getScore();
複製程式碼

此外, 它還允許您返回文件源, 既可以是簡單的 JSON 字串, 也可以是鍵/值對的對映。在此對映中,通常鍵值對的鍵為欄位名, 值為欄位值。多值欄位作為物件的列表返回, 巢狀物件作為另一個鍵/值對映。這些案件需要相應地強制執行:

String sourceAsString = hit.getSourceAsString();
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String documentTitle = (String) sourceAsMap.get("title");
List<Object> users = (List<Object>) sourceAsMap.get("user");
Map<String, Object> innerObject =
        (Map<String, Object>) sourceAsMap.get("innerObject");
複製程式碼

獲取高亮結果

可以從結果中獲取每個 SearchHit 中高亮顯示的文字片段。SearchHit提供對 HighlightField 例項的訪問, 其中每一個都包含一個或多個突出顯示的文字片段:

SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits.getHits()) {
    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
    HighlightField highlight = highlightFields.get("title"); 
    Text[] fragments = highlight.fragments();  
    String fragmentString = fragments[0].string();
}
複製程式碼

獲取聚合結果

可以從 SearchResponse 獲取聚合結果, 首先獲取聚合樹的根、聚合物件, 然後按名稱獲取聚合

Aggregations aggregations = searchResponse.getAggregations();
Terms byCompanyAggregation = aggregations.get("by_company"); 
Bucket elasticBucket = byCompanyAggregation.getBucketByKey("Elastic"); 
Avg averageAge = elasticBucket.getAggregations().get("average_age"); 
double avg = averageAge.getValue();
複製程式碼

請注意, 如果按名稱訪問聚合, 則需要根據所請求的聚合型別指定聚合介面, 否則將引發丟擲:

Range range = aggregations.get("by_company"); //這將引發異常, 因為 "by_company" 是一個term聚合, 但這裡嘗試將它一範圍聚合取出
複製程式碼

還可以將所有的聚合 轉化成 map ,以聚合名作為 key 值。在這種情況下,需要顯示強制轉換到正確的型別:

Map<String, Aggregation> aggregationMap = aggregations.getAsMap();
Terms companyAggregation = (Terms) aggregationMap.get("by_company");
複製程式碼

還有一些 getter 將所有頂層聚合作為列表返回:

List<Aggregation> aggregationList = aggregations.asList();
複製程式碼

最後, 可以遍歷所有聚合, 然後根據它們的型別決定如何進一步處理它們:

for (Aggregation agg : aggregations) {
    String type = agg.getType();
    if (type.equals(TermsAggregationBuilder.NAME)) {
        Bucket elasticBucket = ((Terms) agg).getBucketByKey("Elastic");
        long numberOfDocs = elasticBucket.getDocCount();
    }
}
複製程式碼

獲取建議

要從 SearchResponse 中返回suggestions, 請使用suggestion 物件作為入口點, 然後檢索巢狀的建議物件:

Suggest suggest = searchResponse.getSuggest(); 
TermSuggestion termSuggestion = suggest.getSuggestion("suggest_user"); 
for (TermSuggestion.Entry entry : termSuggestion.getEntries()) { 
    for (TermSuggestion.Entry.Option option : entry) { 
        String suggestText = option.getText().string();
    }
}
複製程式碼

獲取效能分析結果

使用 getProfileResults () 方法從 SearchResponse 檢索效能分析結果。此方法返回一個Map,包含 SearchRequest 執行中所涉及的每個分片的 ProfileShardResult 物件。ProfileShardResult 儲存在對映中, 使用唯一標識配置檔案結果對應的碎片的鍵.

下面是一個示例程式碼, 它演示如何迴圈訪問每個分片的所有效能分析結果:

Map<String, ProfileShardResult> profilingResults =
        searchResponse.getProfileResults(); 
for (Map.Entry<String, ProfileShardResult> profilingResult : profilingResults.entrySet()) { 
    String key = profilingResult.getKey(); 
    ProfileShardResult profileShardResult = profilingResult.getValue(); 
}
複製程式碼

ProfileShardResult 物件本身包含一個或多個QueryProfileShardResult:

List<QueryProfileShardResult> queryProfileShardResults =
        profileShardResult.getQueryProfileResults(); 
for (QueryProfileShardResult queryProfileResult : queryProfileShardResults) { 

}
複製程式碼
for (ProfileResult profileResult : queryProfileResult.getQueryResults()) { 
    String queryName = profileResult.getQueryName(); 
    long queryTimeInMillis = profileResult.getTime(); 
    List<ProfileResult> profiledChildren = profileResult.getProfiledChildren(); 
}
複製程式碼

相關文章