最近几年出现的云计算为组织和用户带来了福音。组织对客户的了解达到史无前例的透彻,并可以采用个性化通讯锁定客户。用户几乎能够随时随地获取其数据,使其更加易于访问和使用。为了存储全部这些数据,大型数据中心遍及全世界。可是,大数据一样也意味着大挑战。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 中实现此目标。