作者:來自 Elastic?Jeffrey Rengifo
回顧生產環境中的最佳實踐,并講解如何在無服務器環境中運行 Elasticsearch Node.js 客戶端。
想獲得 Elastic 認證?查看下一期 Elasticsearch Engineer 培訓的時間!
Elasticsearch 擁有大量新功能,能幫助你為你的使用場景構建最佳搜索解決方案。深入查看我們的示例筆記本,了解更多信息,開始免費云試用,或立即在本地機器上試用 Elastic。
這是我們 Elasticsearch in JavaScript 系列的第二部分。在第一部分中,我們學習了如何正確設置環境、配置 Node.js 客戶端、索引數據以及進行搜索。在第二部分中,我們將學習如何實現生產環境中的最佳實踐,并在無服務器環境中運行 Elasticsearch Node.js 客戶端。
我們將回顧:
- 生產環境最佳實踐
- 錯誤處理
- 測試
- 無服務器環境
- 在 Elastic Serverless 上運行客戶端
- 在函數即服務環境中運行客戶端
你可以在這里查看包含示例的源代碼。
生產環境最佳實踐
錯誤處理
Elasticsearch 的 Node.js 客戶端的一個有用功能是,它會暴露出可能出現的 Elasticsearch 錯誤對象,這樣你就可以用不同的方式進行驗證和處理。
要查看所有錯誤對象,運行以下命令:
const { errors } = require('@elastic/elasticsearch')
console.log(errors)
讓我們回到搜索示例,處理一些可能出現的錯誤:
app.get("/search/lexic", async (req, res) => {....} catch (error) {if (error instanceof errors.ResponseError) {let errorMessage ="Response error!, query malformed or server down, contact the administrator!";if (error.body.error.type === "parsing_exception") {errorMessage = "Query malformed, make sure mappings are set correctly";}res.status(error.meta.statusCode).json({erroStatus: error.meta.statusCode,success: false,results: null,error: errorMessage,});}res.status(500).json({success: false,results: null,error: error.message,});}
});
特別是 ResponseError,會在響應為 4xx 或 5xx 時出現,意味著請求不正確或服務器不可用。
我們可以通過生成錯誤的查詢來測試這種類型的錯誤,比如嘗試在 text 類型字段上執行 term 查詢:
默認錯誤:
{"success": false,"results": null,"error": "parsing_exception\n\tRoot causes:\n\t\tparsing_exception: [terms] query does not support [visit_details]"
}
自定義錯誤:
{"erroStatus": 400,"success": false,"results": null,"error": "Response error!, query malformed or server down; contact the administrator!"
}
我們也可以以特定方式捕捉和處理每種類型的錯誤。例如,我們可以在出現 TimeoutError 時添加重試邏輯。
app.get("/search/semantic", async (req, res) => {try {...} catch (error) {if (error instanceof errors.TimeoutError) {// Retry logic...res.status(error.meta.statusCode).json({erroStatus: error.meta.statusCode,success: false,results: null,error:"The request took more than 10s after 3 retries. Try again later.",});}}
});
測試
測試是保障應用穩定性的關鍵。為了在與 Elasticsearch 隔離的情況下測試代碼,我們可以在創建集群時使用庫 elasticsearch-js-mock。
這個庫允許我們實例化一個與真實客戶端非常相似的客戶端,但它會根據我們的配置進行響應,只替換客戶端的 HTTP 層為模擬層,其他部分保持與原始客戶端一致。
我們將安裝 mocks 庫和用于自動化測試的 AVA。
npm install @elastic/elasticsearch-mocknpm install --save-dev ava
我們將配置 package.json 文件來運行測試。確保它如下所示:
"type": "module","scripts": {"test": "ava"},"devDependencies": {"ava": "^5.0.0"}
現在讓我們創建一個 test.js 文件并安裝我們的模擬客戶端:
const { Client } = require('@elastic/elasticsearch')
const Mock = require('@elastic/elasticsearch-mock')const mock = new Mock()
const client = new Client({node: 'http://localhost:9200',Connection: mock.getConnection()
})
現在,添加一個語義搜索的模擬:
function createSemanticSearchMock(query, indexName) {mock.add({method: "POST",path: `/${indexName}/_search`,body: {query: {semantic: {field: "semantic_field",query: query,},},},},() => {return {hits: {total: { value: 2, relation: "eq" },hits: [{_id: "1",_score: 0.9,_source: {owner_name: "Alice Johnson",pet_name: "Buddy",species: "Dog",breed: "Golden Retriever",vaccination_history: ["Rabies", "Parvovirus", "Distemper"],visit_details:"Annual check-up and nail trimming. Healthy and active.",},},{_id: "2",_score: 0.7,_source: {owner_name: "Daniel Kim",pet_name: "Mochi",species: "Rabbit",breed: "Mixed",vaccination_history: [],visit_details:"Nail trimming and general health check. No issues.",},},],},};});
}
現在,我們可以為代碼創建一個測試,確保 Elasticsearch 部分始終返回相同的結果:
import test from 'ava';test("performSemanticSearch must return formatted results correctly", async (t) => {const indexName = "vet-visits";const query = "Which pets had nail trimming?";createSemanticSearchMock(query, indexName);async function performSemanticSearch(esClient, q, indexName = "vet-visits") {try {const result = await esClient.search({index: indexName,body: {query: {semantic: {field: "semantic_field",query: q,},},},});return {success: true,results: result.hits.hits,};} catch (error) {if (error instanceof errors.TimeoutError) {return {success: false,results: null,error: error.body.error.reason,};}return {success: false,results: null,error: error.message,};}}const result = await performSemanticSearch(esClient, query, indexName);t.true(result.success, "The search must be successful");t.true(Array.isArray(result.results), "The results must be an array");if (result.results.length > 0) {t.true("_source" in result.results[0],"Each result must have a _source property");t.true("pet_name" in result.results[0]._source,"Results must include the pet_name field");t.true("visit_details" in result.results[0]._source,"Results must include the visit_details field");}
});
讓我們運行測試。
npm run test
完成!從現在起,我們可以 100% 專注于代碼本身進行測試,而不受外部因素影響。
無服務器環境
在 Elastic Serverless 上運行客戶端
我們之前講過在 Cloud 或本地運行 Elasticsearch;不過,Node.js 客戶端也支持連接到 Elastic Cloud Serverless。
Elastic Cloud Serverless 允許你創建項目,無需擔心基礎設施,因為 Elastic 會內部處理,你只需關注要索引的數據及其保留時長。
從使用角度看,Serverless 將計算和存儲解耦,為搜索和索引提供自動擴展功能,這樣你只需擴展實際需要的資源。
客戶端對連接 Serverless 做了以下適配:
- 關閉嗅探(sniffing)功能,忽略所有與嗅探相關的選項
- 忽略配置中除第一個節點外的所有節點,忽略任何節點過濾和選擇選項
- 啟用壓縮和
TLSv1_2_method
(與配置 Elastic Cloud 時相同) - 為所有請求添加
elastic-api-version
HTTP 頭 - 默認使用
CloudConnectionPool
,而非WeightedConnectionPool
- 關閉內置的
content-type
和accept
頭,使用標準 MIME 類型
連接無服務器項目時,需要使用參數 serverMode: serverless
。
const { Client } = require('@elastic/elasticsearch')
const client = new Client({node: 'ELASTICSEARCH_ENDPOINT',auth: { apiKey: 'ELASTICSEARCH_API_KEY' },serverMode: "serverless",
});
在函數即服務(function-as-a-service)環境中運行客戶端
在示例中,我們使用了 Node.js 服務器,但你也可以使用函數即服務環境連接,比如 AWS Lambda、GCP Run 等函數。
'use strict'const { Client } = require('@elastic/elasticsearch')const client = new Client({// client initialisation
})exports.handler = async function (event, context) {// use the client
}
另一個例子是連接到像 Vercel 這樣的無服務器服務。你可以查看這個完整示例,了解如何操作,但搜索端點中最相關的部分如下:
const response = await client.search({index: INDEX,// You could directly send from the browser// the Elasticsearch's query DSL, but it will// expose you to the risk that a malicious user// could overload your cluster by crafting// expensive queries.query: {match: { field: req.body.text },},},{headers: {Authorization: `ApiKey ${token}`,},}
);
該端點位于 /api 文件夾中,從服務器端運行,這樣客戶端只控制對應搜索詞的 “text” 參數。
使用函數即服務的意義在于,與 24/7 運行的服務器不同,函數只在運行時啟動機器,完成后機器進入休眠狀態,減少資源消耗。
如果應用請求不多,這種配置很方便;否則成本可能較高。你還需考慮函數的生命周期和運行時間(有時僅幾秒)。
總結
本文中,我們學習了如何處理錯誤,這在生產環境中至關重要。還介紹了如何在模擬 Elasticsearch 服務的情況下測試應用,這樣測試更可靠,不受集群狀態影響,能專注于代碼。
最后,我們演示了如何通過配置 Elastic Cloud Serverless 和 Vercel 應用,搭建完全無服務器的架構。
原文:Elasticsearch in JavaScript the proper way, part II - Elasticsearch Labs