如何在 ASP.NET Core 中集成 ElasticSearch
本文來自:https://www.blexin.com/en-US/Article/Blog/How-to-integrate-ElasticSearch-in-ASPNET-Core-70
我敢打賭,您肯定會被要求向Web應用程序中添加高級搜索功能,而且通常是全文的類似Google的搜索。
在技術電子商務的開發過程中,我們被要求允許用戶對產品進行高級研究,以便他們可以高效,完全地找到所需的內容。
我們基于對象的所有字段上給定字符串的搜索嘗試了自定義搜索的實現。為了優化時間,我們嘗試在服務和數據庫級別之間添加一個緩存層,以避免對數據庫造成過多壓力,但是我們對結果不滿意。然后,我們在市場上搜索了可以滿足我們需求的第三方產品,經過深入分析,我們選擇采用ElasticSearch:一個基于REST協議的,可管理研究和分析的分布式,易于適應的搜索引擎同樣,也方便了數據的外推和轉換。
具體來說,我們正在談論基于Apache Lucene的開源全文搜索引擎,該引擎可用于管理文檔的索引和研究。讓我們嘗試了解基本概念。
ElasticSearch將數據存儲在一個或多個索引中。ES的索引與SQL DB的索引非常相似,因為我們使用它來存儲和讀取文檔。
文檔是ElasticSearch世界的主要實體。它由一組具有名稱和一個或多個值的字段組成。每個文檔可能具有一組字段,并且沒有給出任何架構或定義的結構。這只是一個JSON對象。
所有文檔在存儲之前都經過分析。這種分析過程(稱為映射)是通過過濾數據內容(例如,刪除HTML標簽)并將其標記化來執行的,以便將文檔拆分為標記。
ElasticSearch中的每個文檔都有一個類型。這樣就可以將各種文檔類型存儲在同一索引上,并為幾種類型獲取幾種映射。
ElasticSearch服務器的單個實例稱為Node。在很多情況下,單個節點就足夠了,但是有時您需要管理故障,或者您有太多數據無法使用單個節點進行管理。在這種情況下,您可以使用多節點集群,這是一組協同工作的節點來管理比單個實例無法處理的更大的負載。您可以配置群集,以便即使某些節點不可用,也可以保證搜索和管理功能。
為了使群集正常運行,ElasticSearch將數據分布在Apache Lucene的多個物理索引上。這些索引稱為“ 碎片”,而擴展過程稱為“ 碎片”。ElasticSearch自動管理分片,因此最終用戶似乎只是一個大索引。
副本是分片的副本,可用于以原始分片的相同模式進行查詢。
副本可減輕無法處理所有請求的單個節點上的負載,并提供更高的數據安全性,因為如果您丟失了原始分片中的數據,則可以在副本上對其進行恢復。
ElasticSearch收集了大量有關集群狀態,索引設置的信息,并將它們存儲到網關中。
從結構上講,ElasticSearch基于一些簡單的關鍵概念:
- 默認設置和值使得默認配置足以立即使用ElasticSearch。
- 它以分布式方式工作。節點自動成為集群的一部分,并且在設置過程中,節點嘗試加入集群。
- 沒有SPOF的P2P體系結構(單點故障)。節點自動連接到群集的其他計算機以更改數據和相互監視;
- 只需在集群中添加新節點,就可以輕松地進行擴展,無論是在數據量上還是在容量上。
- 在組織索引中的數據方面沒有任何限制。允許用戶修改數據模型而不會對搜索產生任何影響;
- NRT(近實時)搜索和版本控制。由于其分布式特性,無法避免延遲和位于不同節點上的數據之間的差異。因此,它提供了版本控制機制。
當ElasticSearch節點啟動時,它使用多播(或單播,如果已配置)來查找同一集群中的其他節點并連接到它們。
在群集中,選擇一個節點作為主節點。該節點負責管理集群狀態和將分片分配給節點的過程。主節點讀取集群狀態,并在需要時啟動恢復模式,該模式允許知道哪些分片可用,并指定其中一個作為主分片。這樣,即使群集沒有可用的全部資源,它也似乎可以正常工作。然后,主節點查找重復的分片,并將其作為副本處理。
在標準運行期間,主節點檢查所有可用節點是否正常工作。如果其中之一在配置的時間范圍內不可用,則將該節點視為已損壞,并運行容錯過程。容錯的主要活動是平衡已損壞節點的群集和碎片,并分配一個負責這些碎片的新節點。然后,對于每個主分片丟失,將定義一個在可用副本之間選擇的新主分片。
如前所述,ElasticSearch提供了一些API REST,可供每個能夠發送HTTP請求和接收HTTP響應的系統使用(大多數開發框架的所有瀏覽器和庫)。
ElasticSearch請求由一些包含的已定義URL發送。最終是JSON主體。響應也是JSON文檔。
ElasticSearch提供了四種索引數據的方式。
1.索引API:它允許將文檔發送到已定義的索引;
2.批量API:它允許通過HTTP協議發送多個文檔;
3.UDP批量API:它允許通過任何協議發送多個文檔(更快但更不可靠);
4.插件:在節點上執行,它們從外部系統獲取數據。
重要的是要記住,索引只是在主分片上而不是在其副本上,因此,如果將索引請求發送到不包含主分片或可能包含其副本的節點,則該請求將轉發到主分片。
使用Query API執行搜索。使用查詢DSL(基于JSON的語言來構建復雜的查詢),可以:
- 使用各種類型的查詢,包括簡單查詢,短語,范圍,布爾值,空間查詢和其他查詢;
- 通過組合簡單查詢來構建復雜查詢;
- 通過排除不符合選定條件的文檔而不影響其分數來過濾文檔;
- 查找與其他文件相似的文件;
- 查找給定短語的建議或更正;
- 查找與給定文檔匹配的查詢。
搜索不是一個單階段的簡單過程,但是,通常可以將其分為兩個階段:scatter(分散),在其中查詢索引的所有相關分片;gather(收集),在其中收集,處理和排序所有寶貴的結果。
弄臟你的手!
ES提供了云和本地兩種使用方式。如果要在Windows計算機上安裝它,則需要具有Java虛擬機的更新版本(https://www.elastic.co/support/matrix#matrix_jvm),然后可以從ElasticSearch下載中下載一個zip文件。頁面(https://www.elastic.co/downloads/elasticsearch)并將其提取到磁盤上的文件夾中,例如C:\ Elasticsearch。
要執行它,您可以運行C:\ Elasticsearch \ bin \ elasticsearch.bat。
如果要將ElasticSearch用作服務,以便可以使用Windows工具啟動或停止它,則需要在文件C:\ Elasticsearch \ config \ jvm.options中添加一行。
對于32位系統,您必須鍵入-Xss320k*,對于64位系統-Xss1m。*
更改此設置后,您必須打開命令提示符或Powershell并執行
C:\ Elasticsearch \ bin \ elasticsearch-service.bat
。可用的命令包括 install, remove, start, stop 和manager.。
要創建服務,我們必須輸入:
C:\ Elasticsearch \ bin \ elasticsearch-service.bat install
要管理服務,我們鍵入:
C:\ Elasticsearch \ bin \ elasticsearch-service.bat manager
器,該管理器打開Elastic Service Manager,這是一個GUI,可通過該GUI進行有關服務的自定義設置并管理其狀態。
默認cluster.name和node.name是elasticsearch分別和你的主機名。如果您打算繼續使用該群集或添加更多節點,則最好通過在elasticsearch.yml
文件中對其進行修改來將這些默認值更改為唯一名稱。
我們可以通過瀏覽http:// localhost:9200 /來驗證ElasicSearch的正確執行。如果一切正常,我們將得到以下結果:
為了實現基于.NET Core的解決方案,我們使用了NEST軟件包,可以通過以下命令安裝該軟件包:
- dotnet add package NEST
NEST允許我們在索引和搜索文檔以及節點和分片的管理中本地使用所有ElasticSearch功能。為了管理NEST插件,我們創建了ElasticsearchExtensions類:
- public static class ElasticsearchExtensions
- {
- public static void AddElasticsearch(this IServiceCollection services, IConfiguration configuration)
- {
- var url = configuration["elasticsearch:url"];
- var defaultIndex = configuration["elasticsearch:index"];
- var settings = new ConnectionSettings(new Uri(url))
- .DefaultIndex(defaultIndex);
- AddDefaultMappings(settings);
- var client = new ElasticClient(settings);
- services.AddSingleton(client);
- CreateIndex(client, defaultIndex);
- }
- private static void AddDefaultMappings(ConnectionSettings settings)
- {
- settings
- DefaultMappingFor<Product>(m => m
- .Ignore(p => p.Price)
- .Ignore(p => p.Quantity)
- .Ignore(p => p.Rating)
- );
- }
- private static void CreateIndex(IElasticClient client, string indexName)
- {
- var createIndexResponse = client.Indices.Create(indexName,
- index => index.Map<Product>(x => x.AutoMap())
- );
- }
- }
在其中我們找到對象的配置和映射,在本例中為Product類。在此類中,我們決定忽略在索引階段存儲價格,數量和評級。通過以下指令在Startup.cs中調用此類:
- public void ConfigureServices(IServiceCollection services)
- {
- // ...
- services.AddElasticsearch(Configuration);
- }
這使我們能夠在啟動時加載的所有設置,在修改它們elasticsearch的appsettings.json文件,在其中我們插入如下一行:
- "elasticsearch": {
- "index": "products",
- "url": "http://localhost:9200/"
- }
索引表示選擇用來存儲文檔的默認索引,而url是我們的ElasticSearch實例的地址。我們的產品對象定義如下:
- public class Product
- {
- public int Id { get; set; }
- public string Ean { get; set; }
- public string Name { get; set; }
- public string Description { get; set; }
- public string Brand { get; set; }
- public string Category { get; set; }
- public string Price { get; set; }
- public int Quantity { get; set; }
- public float Rating { get; set; }
- public DateTime ReleaseDate { get; set; }
- }
如前所述,可以分別或在列表中為產品建立索引。在我們的產品服務中,我們實現了兩種方式:
- public async Task SaveSingleAsync(Product product)
- {
- if (_cache.Any(p => p.Id == product.Id))
- {
- await _elasticClient.UpdateAsync<Product>(product, u => u.Doc(product));
- }
- else
- {
- _cache.Add(product);
- await _elasticClient.IndexDocumentAsync(product);
- }
- }
- public async Task SaveManyAsync(Product[] products)
- {
- _cache.AddRange(products);
- var result = await _elasticClient.IndexManyAsync(products);
- if (result.Errors)
- {
- // the response can be inspected for errors
- foreach (var itemWithError in result.ItemsWithErrors)
- {
- _logger.LogError("Failed to index document {0}: {1}",
- itemWithError.Id, itemWithError.Error);
- }
- }
- }
- public async Task SaveBulkAsync(Product[] products)
- {
- _cache.AddRange(products);
- var result = await _elasticClient.BulkAsync(b => b.Index("products").IndexMany(products));
- if (result.Errors)
- {
- // the response can be inspected for errors
- foreach (var itemWithError in result.ItemsWithErrors)
- {
- _logger.LogError("Failed to index document {0}: {1}",
- itemWithError.Id, itemWithError.Error);
- }
- }
- }
在這里我們使用_cache數組來進一步緩存產品列表。對于多模式,我們也實現了批量版本,這使我們能夠在更短的時間內索引大量文檔,并且我們已經處理了日志插入中的任何錯誤。
請注意,SaveSingleAsync方法通過檢查緩存數組來管理文檔的插入和修改。
對于文檔刪除,我們實現了DeleteAsync方法:
- public async Task DeleteAsync(Product product)
- {
- await _elasticClient.DeleteAsync<Product>
- (product);
- if (_cache.Contains(product))
- {
- _cache.Remove(product);
- }
- }
GetSearchUrl方法允許我們獲取用于管理頁面調度的URL。出于開發目的,我們實現了ReIndex方法,該方法允許我們刪除索引上的所有文檔,然后一次又一次地導入它們。這對于導入現有和未加載文檔的列表很有用。
- //Only for development purpose
- [HttpGet("/search/reindex")]
- public async Task<IActionResult>ReIndex()
- {
- await _elasticClient.DeleteByQueryAsync<Product>(q => q.MatchAll());
- var allProducts = (await _productService.GetProducts(int.MaxValue)).ToArray();
- foreach (var product in allProducts)
- {
- await _elasticClient.IndexDocumentAsync(product);
- }
- return Ok($"{allProducts.Length} product(s) reindexed");
- }
出于示例目的,我們創建了一個界面,該界面允許我們通過Bogus插件添加N個動態生成的產品,并管理產品的CRUD。運行項目后,我們將看到以下屏幕:
例如,如果我們嘗試將10種產品添加到索引中,在文本框中輸入10,然后單擊“ 導入文檔”按鈕,則可以使用搜索框查看結果,也可以直接從瀏覽器中瀏覽到http頁面:// localhost:9200 / products / _search,我們將在其中得到這樣的結果:
本文中使用的代碼可在此處獲得[2]。
References
[1] 查看原文: https://www.blexin.com/en-US/Article/Blog/How-to-integrate-ElasticSearch-in-ASPNET-Core-70
[2] 在此處獲得: https://github.com/enricobencivenga/ElasticSearch