最近幾年出現的雲計算爲組織和用戶帶來了福音。組織對客戶的瞭解達到史無前例的透徹,並可以採用個性化通訊鎖定客戶。用戶幾乎能夠隨時隨地獲取其數據,使其更加易於訪問和使用。爲了存儲全部這些數據,大型數據中心遍及全世界。可是,大數據一樣也意味着大挑戰。node
John Naisbitt 在其所著書籍《大趨勢:改變咱們生活的十個新方向》(華納書局,1982 年)中的著名引述:「咱們淹沒在數據中卻信息匱乏」形象地描述了大數據市場的現狀。公司可以存儲千兆字節的數據,但要弄明白這些數據並使其可搜索卻很難,尤爲是由於大多數數據倉庫在特定大數據存儲內跨多個集合以非結構化方式 (NoSQL) 存儲數據或着甚至在不一樣倉庫之間以分佈的形式存儲數據。此外,數據格式也是各類各樣,如 JSON 文檔、Microsoft Office 文件等。搜索單個非結構化集合一般不是問題,可是若要查找用戶徹底不知道其可能所在位置的特小結果子集,在多個集合之間搜索全部非結構化數據會很是困難。這時,企業級搜索即可發揮做用了。web
下面是企業級搜索面臨的基本挑戰:擁有不少數據源的大型組織如何可以經過一個界面向內部和外部用戶提供搜索全部公共公司數據源的功能?這個單一界面多是一個 API、公司網站或甚至是一個在後臺實現了自動完成功能的簡單文本框。不管公司選擇哪一種界面,它必須可以讓用戶搜索其整個數據領域,這可能包括結構化和非結構化數據庫、不一樣格式的 Intranet 文檔、其餘 API 和其餘類型的數據源。sql
因爲搜索多個數據集至關複雜,所以公認的企業級搜索解決方案只有幾個 — 且標準過高。企業級搜索解決方案必須包括如下功能:數據庫
內容感知:知道特定類型的數據可能位於的位置。apache
實時索引:保持全部數據都有索引。設計模式
內容處理:使不一樣的數據源均可以訪問。api
最受歡迎的企業級搜索解決方案之一就是開源 Elasticsearch (elasticsearch.org)。這個基於 Java 的服務器構建在 Apache Lucene (lucene.apache.org) 之上,其經過 JSON 支持和 REST Web 接口提供對多個數據源的可伸縮全文搜索,並具備高可用性、衝突管理和實時分析。請訪問 bit.ly/1vzoUrR 查看它的完整功能集。瀏覽器
從較高層面來講,Elasticsearch 存儲數據的方式很是簡單。服務器內結構的最頂層元素稱爲索引,多個索引能夠位於同一數據存儲中。索引自己只是文檔(一個或多個)容器,每一個文檔是一個或多個字段的集合(沒有定義的結構)。每一個索引均可以包含按稱爲類型的單位聚合的數據,用於表示特定索引內數據的邏輯組。服務器
在將 Elasticsearch 視爲相似來自關係數據庫領域的表時,這可能頗有用。表的行和列與索引的文檔和字段之間存在着相同的關聯,其中文檔對應行,字段對應列。可是,經過 Elasticsearch,沒有固定的數據結構或數據庫架構。網絡
如前所述,開發人員能夠經過 REST Web 接口與 Elasticsearch 服務器通訊。這意味着,他們只需經過從瀏覽器或任何其餘類型的 Web 客戶端發送 REST Web 請求便可查詢索引、類型、數據或其餘系統信息。如下是一些 GET 請求示例:
查詢全部索引:
http://localhost:9200/_cat/indices/?v
查詢索引元數據:
http://localhost:9200/clients/_stats
查詢全部索引數據:
http://localhost:9200/clients/_search?q=*:*
搜索索引內的特定字段值:
http://localhost:9200/clients/_search?q=field:value
獲取索引映射類型內的全部數據:
http://localhost:9200/clients/orders/_search?q=*:*
爲了演示如何建立一個簡單的多源解決方案,我將結合使用 Elasticsearch 1.3.4 與 JSON 文檔、PDF 文檔和 SQL Server 數據庫。開始以前,我將簡要介紹 Elasticsearch 安裝,而後演示如何插入每一個數據源以使數據可搜索。爲簡便起見,我將演示一個貼近現實的示例,其中使用的數據源來自知名的 Contoso 公司。
我將使用一個 SQL Server 2014 數據庫與其中的多個表,儘管我只會使用一個 dbo.Orders。正如表名所示,其將存儲有關公司客戶訂單的記錄 — 大量的記錄,然而易於管理:
CREATE TABLE [dbo].[Orders] ( [Id] [int] IDENTITY(1,1) NOT NULL primary key, [Date] [datetime] NOT NULL, [ProductName] [nvarchar](100) NOT NULL, [Amount] [int] NOT NULL, [UnitPrice] [money] NOT NULL );
我還在網絡共享中存放着多個以文件夾層次結構組織的公司文檔。這些文檔涉及公司在過去組織的不一樣產品營銷活動而且存儲格式多種多樣,其中包括 PDF 和 Microsoft Office Word。文檔平均大小約爲 1 MB。
最後,我有一個用於以 JSON 格式公開公司客戶端信息的內部 API;由於我知曉響應的結構,因此我可以輕鬆地將其反序列化到客戶端類型的對象。個人目標是使全部數據源可以使用 Elasticsearch 引擎進行搜索。此外,我想在底層建立一個基於 Web API 2 的 Web 服務,以經過對 Elasticsearch 服務器進行一次調用就可在全部索引之間執行企業級查詢。(若要詳細瞭解 Web API 2,請訪問 bit.ly/1ae6uya。)該 Web 服務將向最終用戶返回包含潛在提示的建議列表;此列表以後可供嵌入在 ASP.NET MVC 應用程序中的自動完成控件使用,或供任何其餘類型的 Web 站點使用。
我須要作的第一件事就是安裝 Elasticsearch 服務器。對於 Windows,您能夠經過自動方式或手動方式進行安裝,結果同樣 — 託管 Elasticsearch 服務器的正在運行的 Windows 服務。自動安裝的過程很是快速而簡單,您須要作的就是下載並運行 Elasticsearch MSI 安裝程序 (bit.ly/12RkHDz)。遺憾的是,沒有選擇 Java 版本或者更爲重要的 Elasticsearch 版本的方法。相反,手動安裝過程稍微有點費勁,但您對組件會有更多的控制權,所以這種方法更適合目前的狀況。
將 Elasticsearch 手動設置爲 Windows 服務須要執行如下步驟:
下載並安裝最新的 Java SE 運行時環境 (bit.ly/1m1oKlp)。
添加名爲 JAVA_HOME 的環境變量。它的值將是您在其中安裝 Java 的文件夾路徑(例如,C:\Program Files\Java\jre7),如圖 1 中所示。
下載 Elasticsearch 文件 (bit.ly/1upadla) 並將其解壓縮。
將解壓縮的源移動到 Program Files| Elasticsearch(可選)。
以管理員身份運行命令提示符並使用 install 參數執行 service.bat:
C:\Program Files\Elasticsearch\elasticsearch-1.3.4\bin>service.bat install
圖 1 設置 Java_Home 環境變量
如此,Windows 服務啓動並運行,Elasticsearch 服務器可經過端口 9200 在本地主機上進行訪問。如今我能夠經過任何 Web 瀏覽器向 URL http://localhost:9200/ 發出 Web 請求並將得到以下所示的響應:
{ "status" : 200, "name" : "Washout", "version" : { "number" : "1.3.4", "build_hash" : "a70f3ccb52200f8f2c87e9c370c6597448eb3e45", "build_timestamp" : "2014-09-30T09:07:17Z", "build_snapshot" : false, "lucene_version" : "4.9" }, "tagline" : "You Know, for Search" }
如今,個人本地 Elasticsearch 實例已準備就緒。可是,原始版本不容許我鏈接到 SQL Server 或經過數據文件運行全文搜索。若要實現上述功能,我還必須安裝多個插件。
正如我以前所述,Elasticsearch 的原始版本不容許索引外部數據源,如 SQL Server、Office 甚至 PDF。若要使全部這些數據源可搜索,我須要安裝幾個插件,這很是簡單。
個人第一個目標支持對附件進行全文搜索。這裏的附件,指的是做爲 JSON 文檔已上載至 Elasticsearch 數據存儲的源文件的 base64 編碼表示。(請參閱 bit.ly/12RGmvg 瞭解有關附件類型的信息。)我須要用於實現此目的的插件是適用於 Elasticsearch 版本 2.3.2 的 Mapper Attachments 類型(可在 bit.ly/1Alj8sy 得到)。這是一個 Elasticsearch 擴展,支持對文檔進行全文搜索,其基於 Apache Tika 項目 (tika.apache.org),該項目從各類類型的文檔檢測並提取元數據和文本內容,併爲 bit.ly/1qEyVmr 列出的文件格式提供支持。
與大多數適用於 Elasticsearch 的插件同樣,此安裝很是簡單,我須要作的就是以管理員身份運行命令提示符並執行如下命令:
bin>plugin --install elasticsearch/elasticsearch-mapper-attachments/2.3.2
在下載並提取了該插件以後,我須要重啓 Elasticsearch Windows 服務。
完成後,我須要配置 SQL Server 支持。固然,這裏也有一個插件可用於執行此操做。它的名稱爲 JDBC River (bit.ly/12CK8Zu),容許從 JDBC 源(如 SQL Server)提取數據以在 Elasticsearch 中創建索引。儘管安裝過程分三個階段完成,但該插件的安裝和配置並不難。
首先,安裝 Microsoft JDBC Driver 4.0,這是一個適用於 SQL Server 的基於 Java 的數據提供程序(可從 bit.ly/1maiM2j 進行下載)。須要記住的重要一點是,我須要將下載的文件的內容提取到名爲 Microsoft JDBC Driver 4.0 for SQL Server 的文件夾中(若不存在,則需建立),該文件夾直接位於 Program Files 文件夾下,所以生成的路徑以下所示:C:\Program Files\Microsoft JDBC Driver 4.0 for SQL Server。
接下來,使用如下命令安裝該插件:
bin> plugin --install
jdbc --url "http://xbib.org/repository/org/xbib/elasticsearch/plugin/elasticsearch-river-jdbc/1.3.4.4/elasticsearch-river-jdbc-1.3.4.4-plugin.zip"
最後,將第一步中提取的 SQLJDBC4.jar 文件 (C:\Program Files\Microsoft JDBC DRIVER 4.0 for SQL Server\sqljdbc_4.0\enu\SQLJDBC4.jar) 複製到 Elasticsearch 目錄中的 lib 文件夾 (C:\Program Files\Elasticsearch\lib)。當這些完成後,我須要記得從新啓動該 Windows 服務。
如今,全部必需插件均已安裝。然而,若要驗證安裝是否正確完成,可將下命令做爲 HTTP GET 請求發出:
http://localhost:9200/_nodes/_all/plugins
在響應中,我但願看到兩個已安裝插件都有列出,如圖 2 中所示。
圖 2 已安裝的插件
若要結合使用 JDBC River 與 SQL Server,SQL Server 實例須要可經過 TCP/IP 進行訪問,默認狀況下,此功能禁用。可是,啓用也很簡單,須要作的就是打開 SQL Server 配置管理器,並在 SQL Server 網絡配置下,對於要鏈接到的 SQL Server 實例,將「狀態」值更改成「針對 TCP/IP 協議啓用」,如圖 3 中所示。執行完此操做後,我應該可以憑藉在「服務器名稱」中使用本地主機 1433(端口 1433 是經過 TCP/IP 訪問 SQL Server 的默認端口)經過 Management Studio 登陸個人 SQL Server 實例。
圖 3 爲 SQL Server 實例啓用 TCP/IP
全部必需插件均已安裝,如今開始加載數據。正如以前所述,我有三個不一樣的數據源(JSON 文檔、文件和來自 SQL Server 數據庫的訂單表),並但願在 Elasticsearch 上進行索引。我能夠經過許多不一樣的方法索引這些數據源,但我想展現如何使用我已實現的基於 .NET 的應用程序輕鬆實現此目的。所以,做爲索引的前提條件,我須要爲個人項目安裝一個名爲 NEST 的外部庫 (bit.ly/1vZjtCf),這只是一個封裝 Elasticsearch Web 接口的託管包裝。由於此庫經過 NuGet 提供,因此將其做爲個人項目的一部分就像在程序包管理器控制檯中執行一個命令那樣簡單:
PM> Install-Package NEST
如今,個人解決方案中有了 NEST 庫,我能夠建立一個名爲 ElasticSearchRepository 的新類庫項目。之因此採用此名稱,是由於我決定未來自 ElasticClient 類(這是 NEST 庫的一部分)的全部函數調用與該解決方案的其他部分分開。如此,項目會變得相似於基於實體框架的應用程序中普遍應用的存儲庫設計模式,所以其應易於理解。此外,在此項目中,只有三個類:BaseRepository 類,該類將初始化並公開繼承類和 ElasticClient 的實例;其餘兩個存儲庫類是:
IndexRepository — 用於操做索引、設置映射並上載文檔的讀/寫類。
DiscoveryRepository — 將在基於 API 的搜索操做期間使用的只讀類。
圖 4 顯示了 BaseRepository 類的結構,其中包含了 ElasticClient 類型的受保護屬性。此類型(做爲 NEST 庫的一部分提供)會集中 Elasticsearch 服務器和客戶端應用程序之間的通訊。若要爲其建立實例,能夠將 URL 傳遞到 Elasticsearch 服務器,做爲可選類構造函數參數進行傳遞。若是該參數爲 null,則將使用默認值 http://localhost:9200。
圖 4 BaseRepository 類
namespace ElasticSearchRepository { using System; using Nest; public class BaseRepository { protected ElasticClient client; public BaseRepository(Uri elastiSearchServerUrl = null) { this.client = elastiSearchServerUrl != null ? new ElasticClient(new ConnectionSettings(elastiSearchServerUrl)) : : new ElasticClient(); } } }
客戶端準備就緒後,首先我將對客戶端數據進行索引;這是最簡單的情形,由於無需其餘插件:
public class Client { public int Id { get; set; } public string Name { get; set; } public string Surname { get; set; } public string Email { get; set; } }
若要索引此類型的數據,我會調用 Elasticsearch 客戶端的實例,以及索引<T> 函數,其中 T 是個人客戶端類的類型,表示從 API 返回的序列化數據。此泛型函數採用三個參數:T 對象類的實例、目標索引名以及索引內的映射名:
public bool IndexData<T>(T data, string indexName = null, string mappingType = null) where T : class, new() { if (client == null) { throw new ArgumentNullException("data"); } var result = this.client.Index<T>(data, c => c.Index(indexName).Type(mappingType)); return result.IsValid; }
最後兩個參數可選,由於 NEST 將應用其默認邏輯,基於泛型類型建立目標索引名。
如今,我想要索引與公司產品相關的營銷文檔。由於這些文件存儲在網絡共享中,所以我能夠將有關每一個特定文檔的信息封裝到一個簡單的 MarketingDocument 類中。值得注意的是,若是我要在 Elasticsearch 中索引某個文檔,則須要將其做爲 Base64 編碼的字符串進行上載:
public class MarketingDocument { public int Id { get; set; } public string Title { get; set; } public string ProductName { get; set; } // Base64-encoded file content. public string Document { get; set; } }
該類準備就緒,所以我可使用 ElasticClient 將 MarketingDocument 類中的特定字段標記爲附件。爲了實現此目的,我能夠建立一個名爲「products」的新索引並向其中添加新的營銷映射(爲簡單起見,類名稱將是映射名稱):
private void CreateMarketingIndex() { client.CreateIndex("products", c => c.AddMapping<Marketing> (m => m.Properties(ps =>ps.Attachment(a => a.Name(o =>o.Document).TitleField(t => t.Name(x => x.Name) TermVector(TermVectorOption.WithPositionsOffsets) ))))); }
如今,.NET 類型、營銷數據的映射以及附件的定義所有具有了,我能夠採用與索引客戶端數據相同的方法開始索引文件了:
var documents = GetMarketingDocumentsMock(); documents.ForEach((document) => { indexRepository.IndexData<MarketingDocument>(document, "marketing"); });
最後一步是在 Elasticsearch 上設置 JDBC River。遺憾的是,NEST 尚不支持 JDBC River。從理論上講,我能夠經過使用 Raw 函數經過 JSON 發送原始請求以建立 JDBC River 映射,但我不想事情過度複雜化。所以,若要完成映射建立過程,我要指定如下參數:
到 SQL Server 數據庫的鏈接字符串
用於查詢數據的 SQL 查詢
更新計劃
目標索引名和類型(可選)
(您可在 bit.ly/12CK8Zu 查找可配置參數的完整列表。)
若要建立新的 JDBC River 映射,我須要將其中指定了請求主體的 PUT 請求發送到如下 URL:
http://localhost:9200/_river/{river_name}/_meta
在圖 5 的示例中,我放置了一個請求正文以建立新的 JDBC River 映射,這將鏈接到在本地 SQL Server 實例(可經過 TCP/IP 在端口 1433 上訪問)上託管的 Contoso 數據庫。
圖 5 HTTP PUT 請求以建立新的 JDBC River 映射
PUT http://localhost:9200/_river/orders_river/_meta { "type":"jdbc", "jdbc": { "driver": "com.microsoft.sqlserver.jdbc.SQLServerDriver", "url":"jdbc:sqlserver://127.0.0.1:1433;databaseName=Contoso", "user":"elastic", "password":"asd", "sql":"SELECT * FROM dbo.Orders", "index" : "clients", "type" : "orders", "schedule": "0/30 0-59 0-23 ? * *" } }
其使用登陸名「elastic」和密碼「asd」對用戶進行身份驗證並執行如下 SQL 命令:
SELECT * FROM dbo.Orders
此 SQL 查詢返回數據的每一行都將在訂單映射類型中的客戶端索引下進行索引,該索引將每隔 30 秒進行一次(以 Cron 表示法表示,請參閱 bit.ly/1hCcmnN 瞭解詳細信息)。
此過程完成後,您應該在 Elasticsearch 日誌文件 (/logs/ elasticsearch.log) 中看到相似以下所示的信息:
[2014-10-2418:39:52,190][INFO][river.jdbc.RiverMetrics] pipeline org.xbib.elasticsearch.plugin.jdbc.RiverPipeline@70f0a80d complete: river jdbc/orders_river metrics: 34553 rows, 6.229481683638776 mean, (0.0 0.0 0.0), ingest metrics: elapsed 2 seconds, 364432.0 bytes bytes, 1438.0 bytes avg, 0.1 MB/s
若是因 River 配置而致使某些事情不對勁,此日誌中也會列出錯誤消息。
一旦全部數據都在 Elasticsearch 引擎中進行了索引,我就能夠開始查詢了。固然,我能夠向 Elasticsearch 服務器發送簡單請求以同時查詢一個或多個索引和類型映射,但我想要構建更貼近現實情形的比較有用的方案。所以,我要將個人項目拆分爲三個不一樣的組件。第一個組件,我已經介紹了,是 Elasticsearch,可經過 http://localhost:9200/ 獲取。第二個組件是我要使用 Web API 2 技術構建的 API。最後一個組件是一個控制檯應用程序,將用於在 Elasticsearch 上設置個人索引以及向其中填充數據。
若要建立新的 Web API 2 項目,首先須要建立一個空的 ASP.NET Web 應用程序項目,而後從程序包管理器控制檯中,運行如下安裝命令:
Install-Package Microsoft.AspNet.WebApi
建立項目後,接下來是添加新的控制器,以用於處理來自客戶端的查詢請求並將其傳遞給 Elasticsearch。添加名爲 DiscoveryController 的新控制器只會涉及到新項 Web API ApiController 類 (v2.1) 的添加。而且,我須要實現一個搜索函數,其將經過如下 URL 公開:http://website/api/discovery/search?searchTerm=user_input:
[RoutePrefix("api/discovery")] public class DiscoveryController : ApiController { [HttpGet] [ActionName("search")] public IHttpActionResult Search(string searchTerm) { var discoveryRepository = new DiscoveryRepository(); var result = discoveryRepository.Search(searchTerm); return this.Ok(result); } }
若是 Web API 2 引擎因爲自引用循環而沒法序列化響應,您必須將如下內容添加到 WebApiConfig.cs 文件中(位於 AppStart 文件夾中):
GlobalConfiguration.Configuration .Formatters .JsonFormatter .SerializerSettings .ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
如圖 6 中所示,在我建立的控制器的主體中,我實例化了 DiscoveryRepository 類型的類,這只是一個來自 NEST 庫的封裝 ElasticClient 類型的包裝。在此非泛型的只讀儲庫內,我實現了兩種類型的搜索函數而且兩者都會返回動態類型。這部分很重要,由於經過在這兩個函數體中執行此操做,個人查詢不會被限制爲一個索引;而是能夠同時查詢全部索引和全部類型。這意味着個人結果將具備不一樣的結構(不一樣的類型)。這兩個函數的惟一區別是查詢方法。在第一個函數中,我只使用 QueryString 方法 (bit.ly/1mQEEg7),這是一個精確匹配搜索,而在第二個函數中,我使用的是 Fuzzy 方法 (bit.ly/1uCk7Ba),其將在索引之間執行模糊搜索。
圖 6 兩種搜索類型的實現
namespace ElasticSearchRepository { using System; using System.Collections.Generic; using System.Linq; public class DiscoveryRepository : BaseRepository { public DiscoveryRepository(Uri elastiSearchServerUrl = null) : base(elastiSearchServerUrl) { } ///<summary> public List<Tuple<string, string>> SearchAll(string queryTerm) { var queryResult=this.client.Search<dynamic>(d => d.AllIndices() .AllTypes() .QueryString(queryTerm)); return queryResult .Hits .Select(c => new Tuple<string, string>( c.Indexc.Source.Name.Value)) .Distinct() .ToList(); } ///<summary> public dynamic FuzzySearch(string queryTerm) { return this.client.Search<dynamic>(d => d.AllIndices() .AllTypes() .Query(q => q.Fuzzy(f => f.Value(queryTerm)))); } } }
如今,個人 API 準備就緒,我能夠運行並開始測試了,只需將 GET 請求發送到 http://website:port/api/discovery/search?searchTerm=user_input,並將用戶輸入做爲 searchTerm 查詢參數的值進行傳遞便可。所以,圖 7 顯示了個人 API 針對搜索詞「scrum」生成的結果。如屏幕快照中的突出顯示所示,搜索函數對數據存儲中的全部索引執行了查詢,並同時從多個索引返回符合條件的結果。
圖 7 搜索詞「scrum」的 API 搜索結果
經過實現該 API 層,我創造了實現多個客戶端(如網站或移動應用)的可能性,這些客戶端將可以使用該層。這使得最終用戶可以使用企業級搜索功能。您能夠在個人博客上找到針對基於 ASP.NET MVC 4 的 Web 客戶端實現自動完成控件示例 (bit.ly/1yThHiZ)。
大數據爲技術市場帶來許多機遇和挑戰。挑戰之一(這可能也是一個可貴的機遇)是在數千兆字節的數據中實現快速搜索,而無需知曉所需數據在數據領域中的準確位置。在本文中,我介紹瞭如何實現企業級搜索,並演示瞭如何結合 Elasticsearch 與 NEST 庫在 .NET Framework 中實現此目標。