65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Azure 文件、RemoteApp 和 dtSearch,从任何计算机或设备跨越 PB 级数据的各种数据类型进行安全即时搜索

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2015年10月16日

CPOL

18分钟阅读

viewsIcon

35477

downloadIcon

181

本文将 Microsoft Azure 云与 dtSearch 相结合,实现了 PB 级各种数据类型的安全托管,并结合即时 dtSearch 搜索访问,几乎可从任何计算机或设备进行访问。

Microsoft Azure 云拥有比 Amazon 和 Google 加起来还要多的数据中心,并提供 3 倍的数据复制和行业中最先进的安全实践。Azure 的 RemoteApp 功能可与几乎任何计算机或设备配合使用:OS X、iOS、Android 或 Windows。

dtSearch 使用其内置的“文档过滤器”,即时搜索 PB 级各种数据类型:“Office”文档、PDF、带有多个附件的电子邮件、Web 数据和其他数据库。dtSearch 提供超过 25 种不同的搜索选项,包括分面搜索和多种高级数据分类选项,以及显示带高亮命中词的搜索结果。

本文将 Microsoft Azure 云与 dtSearch 相结合,实现了 PB 级各种数据类型的安全托管,并结合即时 dtSearch 搜索访问,几乎可从任何计算机或设备进行访问。

图:Azure 预览门户

图:全球数据中心网络

图:为什么选择云

全球规模搜索:架构概述

我们的演示应用程序 SearchApp 的云端搜索组件将使用 Azure RemoteApp,从计算机或手持设备即时搜索云端内容。

演示应用程序的另外两个组件是 AddContentAndIndexApp,它将内容发送到 Azure 服务器供 SearchApp 搜索;以及 SocketServer,它运行在 Azure 服务器上接收和索引传入的内容。

Azure 文件存储 dtSearch 索引。Azure 文件允许您通过 SMB(服务器消息块)协议跨任意数量的应用程序共享数据。

完成的 Visual Studio 解决方案外观如下

图:解决方案中的三个核心项目
项目 函数
SearchApp 搜索 dtSearch 索引并显示结果。
AddContentAndIndexApp 上传内容以进行索引。发起索引作业。
SocketServer 执行 AddContentAndIndexApp 内容的索引。
图:项目及其功能

SearchApp

SearchApp 允许世界任何地方的任何最终用户安全地执行搜索,而无需在最终用户的计算机上安装任何实际数据或应用程序。SearchApp 驻留在云端,用户通过 RemoteApp 从几乎任何计算机或设备(iOS、OS X、Android 或 Windows)连接到它。

RemoteApp 是 Microsoft Azure 远程桌面服务的一项功能,它可以与远程计算机或设备配合使用,就像本地桌面应用程序一样。最终结果结合了 Azure 的全球可伸缩性和桌面应用程序的易用性。您可以 在此处 阅读更多相关信息。

RemoteApp 提供 Office365 集成以及许多其他超出本文范围的选项。例如,Azure 支持所谓的混合云,这意味着您可以在公共云和本地基础结构之间设置虚拟网络,支持您的业务与云之间的应用程序集成能力。

图:支持的客户端

AddContentAndIndexApp

AddContentAndIndexApp 上传数据以进行云端索引和搜索。本文演示 AddContentAndIndexApp 在本地系统上运行,并非因为它必须如此,而是因为本地系统通常连接到内容通常驻留的本地网络,然后在上传到 Azure。AddContentAndIndexApp 将内容发送到 Blob 存储,并启动与 SocketServer 的套接字连接以开始索引操作。

SocketServer

第三个应用程序 SocketServer 是一个云端应用程序,它支持套接字连接以回传给 AddContentAndIndexApp。SocketServer 侦听来自 AddContentAndIndexApp 的连接,后者作为客户端应用程序运行。连接成功后,SocketServer 将在云端启动一个索引操作。

随着此索引操作的执行,SocketServer 将状态更新发送回 AddContentAndIndexApp 客户端,从而能够跟踪索引操作的进度。SocketServer 必须是基于套接字的应用程序,因为它提供实时更新;普通的 HTTP 连接无法提供此功能。

图:高层架构

图:利用 RemoteApp

构建解决方案

您可以从 此处 下载该项目的完整源代码。该解决方案依赖于 Visual Studio 2015,您也需要它。您还需要客户端机器上的 RemoteApp。在此 下载 RemoteApp。

在准备好这些项目后,本文将引导您完成以下初始步骤

步骤 1 获取 Azure 订阅。您可以在 此处 获取免费试用。
第二步 在此 启用 Azure 文件。这是这项技术的关键组成部分,它允许您为数据和索引创建 SMB 共享,以便您可以将它们托管在云端。
步骤 3 配置存储帐户。(在此 了解更多信息。)请务必安全地将帐户名称和访问密钥存储在存储帐户外部。
步骤 4 设置一台运行 Windows 的虚拟机。(在此 了解更多信息。)
步骤 5 使用 PowerShell 创建 SMB 共享。(在此 了解更多信息。)请参阅下面的脚本。
步骤 6 编辑源代码以添加您的存储帐户信息。

遵循这些步骤后,您将能够将内容上传到 Blob 存储,并使用 dtSearch 引擎在 Azure 文件中创建索引。

然后使用 RemoteApp 进行搜索。根据需要,在 Microsoft Azure 上向上或向下扩展。

第 1 步:获取 Azure 订阅

您可以在 此处 注册免费试用。

此外,好消息是您只需为您使用的付费。Azure 的成本分为几个类别

(1)您的内容和索引的存储成本;

(2)在云中运行支持执行搜索的用户使用的 Windows 应用程序的计算机的计算成本;

(3)离开数据中心的内容的带宽成本;

(4)RemoteApp 的许可成本。

这些都是使用 Azure 的 Microsoft 费用,不涉及 dtSearch。您可以通过使用 ASP.NET 实现带有 Web 界面的 SearchApp 来而不是 RemoteApp 来消除使用 Azure 的每个客户端 RemoteApp 成本。

第 2 步:启用 Azure 文件

您可以在此 启用 Azure 文件。向下滚动到看到“Azure 文件”的地方,然后选择“试用”。启用后,您就可以从 Azure 订阅中配置此服务了。

请注意,在 Azure 文件之前创建的现有存储帐户将无法正常工作。首先,启用 Azure 文件,然后创建存储帐户。此 链接 非常清楚地定义了顺序:首先,注册 Azure 文件;然后创建新的存储帐户。

第 3 步:配置存储帐户

虽然我在之前的文章中已经向您提供了教程,但我们还是会逐步介绍一些基本步骤。有关存储帐户的概述,请参阅此 链接。此外,此处 有一个不错的快速教程,可以帮助您入门。

假设您已登录到订阅,现在可以配置存储帐户了。首先,导航到门户并单击“新建”。然后,选择“数据和存储”,接着选择“存储帐户”。

您需要提供有关存储帐户的一些详细信息,例如名称、位置和存储帐户类型。例如,您可以定义数据如何在不同数据中心之间复制。此外,您可以指定使用高级存储,它通过支持超快速 SSD 驱动器提供指数级的更好性能。

图:创建存储帐户

单击“创建”后,门户页面上会出现一个小磁贴,指示配置过程正在进行中。这应该只需要几分钟。

图:存储帐户的门户图标

存储帐户配置过程完成后,您应该会看到以下屏幕。

图:管理存储密钥

访问密钥提供了一种身份验证机制,因此只有拥有访问密钥的应用程序才能访问存储。在第 6 步中,我们将把存储帐户名称和访问密钥粘贴到我们应用程序中的三个 app.config 文件中。

第 4 步:设置虚拟机

在下一节中,您将在数据中心设置一个虚拟机来托管应用程序。要了解如何配置虚拟机,请参阅此 链接

您可以将所有内容合并到一台虚拟机中,也可以拆分 SearchApp 和 SocketServer。这取决于性能和规模。请注意,有许多类型的虚拟机可供选择。总的来说,最好从小型虚拟机开始,仅在需要时才升级到更大的虚拟机。

当然,您可以在自己的本地机器上完成所有开发工作。您也可以在 Azure 中配置一个 Windows 10 虚拟机并安装 Visual Studio,这样您就可以在云中完成所有开发工作。这就是我选择的开发方式,这样我就可以调试在数据中心中运行的应用程序。毕竟,SMB 文件共享在数据中心之外是不可用的。

第 5 步:使用 PowerShell 创建 SMB 共享

此时,您已准备好开始配置文件共享,方法是使用 Azure 文件。Azure 文件使得与 dtSearch 索引配合使用成为可能。Azure 文件使用标准的 SMB 协议公开文件共享。在 Azure 中运行的应用程序随后可以使用标准的、熟悉的(文件系统)API,如 CreateFile、ReadFile 和 WriteFile,在虚拟机之间轻松共享文件。您甚至可以将驱动器号映射到 SMB 共享,这样您的应用程序就可以像以前一样访问文件系统。顺便说一句,SMB 共享中的文件也可以通过 REST 接口访问,这为各种混合场景打开了大门。

但首先,您需要安装 PowerShell。请参阅此 链接。请注意,将 PowerShell 链接到您的订阅非常重要,这样当 PowerShell 代码运行时,它将利用您的存储帐户。您需要存储帐户名称和访问密钥才能粘贴到您的 PowerShell 脚本中。

在完成上述四项先决条件后,您就可以开始 PowerShell 脚本编写了。虽然我之前提到过,但再次提及是有意义的——在配置存储帐户之前,请务必启用 Azure 文件。如果您忘记了这个关键步骤,您的 PowerShell 脚本将神秘地失败。

现在您已经设置了文件共享,您需要返回到您的 Visual Studio 项目,并调整处理文件系统的代码,使其使用该共享,而不是本地文件系统。这里有两个重要点。首先,所有可搜索内容将存储为普通 Blob。其次,应用程序会将索引存储在我们刚刚使用 PowerShell 创建的 SMB 文件共享中。

下面的代码代表了我们需要运行的 PowerShell 脚本,以创建文件共享。

# To associate your subscription with PowerShell runtime+
# Add-AzureAccount

# import-module .\AzureStorageFile.psd1
$accesskey = "[access key from the portal]"

# create a context for account and key
$ctx=New-AzureStorageContext "dtsearchstorage" $accesskey

# create a new share
Remove-AzureStorageShare dtsearchshare2  -Context $ctx
$s = New-AzureStorageShare dtsearchshare2 -Context $ctx

# Issue this command if you would like to create the shares so that 
# the file share is still available after a reboot takes place
cmdkey /add:dtsearchstorage.file.core.windows.net /user:dtsearchstorage
     /pass:[access key from the portal]

#------------------------------------------------------
# the following commands can be ignored for the purpose of this exercise
# they are here for illustration purposes only  

# create a directory in the test share just created
# New-AzureStorageDirectory -Share $s -Path testdir
 
# upload a local file to the testdir directory just created
# Set-AzureStorageFileContent -Share $s -Source c:\temp\test.txt -Path testdir
 
# list out the files and subdirectories in a directory
# Get-AzureStorageFile -Share $s -Path testdir
 
# download files from azure storage file service
# Get-AzureStorageFileContent -Share $s -Path testdir/test.txt -Destination c:\temp
 
# remove files from azure storage file service
# Remove-AzureStorageFile -Share $s -Path testdir/test.txt  

# Drive mapping script if you wanted to use it
# net use z: /delete
# net use z: \\dtsearchstorage.file.core.windows.net\dtsearchshare2 /u:dtsearchstorage "[access key from the portal]"
图:设置共享的 PowerShell 脚本

PowerShell 脚本运行后,您将在订阅中获得以下可用共享。

\\dtsearchstorage.file.core.windows.net\dtsearchshare2

对于应用程序或用户来说,此“共享”可以被视为与您本地计算机上的普通文件系统相同。实际上,该共享是一个附加的 Blob,它被安全地、三倍地复制。这是使我们不仅能在云中存储可搜索的内容,还能存储由这些内容生成的索引的“银弹”。作为证明,请注意下面的资源管理器窗口,其中显示了 Azure 文件中的 dtSearch 索引文件。

`

第 6 步:编辑源代码以添加您的存储帐户信息

App.config 和三个 Visual Studio 项目

下表展示了我如何修改三个 app.config 文件。在这里,存储帐户名称在所有三个 app.config 文件中都是相同的。

工作 app.config
AddContentAndIndexApp
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup>
        <supportedRuntime
            version="v4.0"
            sku=".NETFramework,Version=v4.5.2"/>
    </startup>
    <appSettings>
        <add
            key="StorageAccountName"
            value="you get from the portal "/>
        <add
            key="AccessKey"
            value="you get from the portal"/>
        <add
            key="indexfolder"
            value="\\dtsearchstorage.file.core.windows.net\dtsearchshare2"/>
    </appSettings>
</configuration>
SearchApp
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup>
        <supportedRuntime
            version="v4.0"
            sku=".NETFramework,Version=v4.0"/>
    </startup>
    <appSettings>
        <add
            key="StorageAccountName"
            value="dtsearchstorage"/>
        <add
            key="AccessKey"
            value="you get from the portal"/>
        <add
            key="indexfolder"
            value="\\dtsearchstorage.file.core.windows.net\dtsearchshare2"/>
        <add
            key="StorageConnectionString"
            value="DefaultEndpointsProtocol=https;AccountName=you get from the portal;AccountKey=you get from the portal"/>
    </appSettings>
    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <dependentAssembly>
                <assemblyIdentity
                    name="Microsoft.WindowsAzure.Storage"
                    publicKeyToken="31bf3856ad364e35"
                    culture="neutral"/>
                <bindingRedirect
                    oldVersion="0.0.0.0-5.0.0.0"
                    newVersion="5.0.0.0"/>
            </dependentAssembly>
        </assemblyBinding>
        rea
    </runtime>
</configuration>
SocketServer
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <appSettings>
        <add
            key="StorageAccountName"
            value="you get from the portal"/>
        <add
            key="AccessKey"
            value="you get from the portal"/>
        <add
            key="indexfolder"
            value="\\dtsearchstorage.file.core.windows.net\dtsearchshare2"/>
        <add
            key="StorageConnectionString"
            value="DefaultEndpointsProtocol=https;AccountName=dtsearchstorage;AccountKey=you get from the portal"/>
    </appSettings>
    <startup>
        <supportedRuntime
            version="v4.0"
            sku=".NETFramework,Version=v4.5.2"/>
    </startup>
    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <dependentAssembly>
                <assemblyIdentity
                    name="Microsoft.WindowsAzure.Storage"
                    publicKeyToken="31bf3856ad364e35"
                    culture="neutral"/>
                <bindingRedirect
                    oldVersion="0.0.0.0-5.0.0.0"
                    newVersion="5.0.0.0"/>
            </dependentAssembly>
        </assemblyBinding>
    </runtime>
</configuration>
图:Azure 配置

Visual Studio 项目

下面是包含三个项目的 Visual Studio 解决方案。

图:解决方案中的三个项目

以下三个表详细展示了构成三个项目的组件。

SearchApp - 此项目允许用户搜索内容。

app.config 包括存储相关的配置信息。
BlobDataSource.cs 提供对已上传内容的访问,用于显示搜索结果。
MainForm.cs 用于搜索内容的起始窗体
ResultsForm.cs 显示搜索结果。允许用户深入查看搜索结果。
SearchForm.cs 提供用于执行搜索的用户界面。

AddContentAndIndexApp - 这允许管理员和用户上传内容以进行索引。它还通过与 SocketServer 的通信,提供了创建或更新索引的功能,以应对内容的变化。

app.config 包括存储相关的配置信息。
MainForm.cs 包含两个按钮。第一个按钮用于将内容上传到云端。第二个按钮通过连接到云端运行的索引器来索引内容。
Program.cs 通过运行 MainForm 启动应用程序。
SocketClient.cs 支持与 SocketServer 索引器的通信。根据 SocketServer 返回的信息更新用户界面。

SocketServer - 这是用于索引已上传内容的云端应用程序。

app.config 包括存储相关的配置信息。
BlobDataSource.cs 提供对已上传内容的访问,用于执行索引。
Program.cs 侦听特定端口以获取来自 AddContentAndIndexApp 的信息。

下表描述了各个应用程序的运行位置。

项目 运行位置
SearchApp 在两个地方运行。主要是在虚拟机上云端运行,但通过 RemoteApp 的神奇功能,它可以在世界任何地方的客户端计算机或设备上运行。
AddContentAndIndexApp 在这里,此应用程序运行在可搜索内容所在的位置,以方便将该内容上传到云端。
SocketServer 此应用程序在云端运行,应托管在与内容和索引相同的数据中心。毕竟,它的任务是索引基于云的内容。
图:项目运行位置

应用程序执行情况

下面的屏幕截图直观地展示了应用程序的功能。

AddContentAndIndexApp - 上传内容并执行索引。

图:AddContentAndIndexApp

SearchApp - 核心应用程序,支持内容搜索和结果显示。

图:SearchApp

图:SearchApp

图:SearchApp

SocketServer – 这个后台进程在云端运行,并接受来自 AddContentAndIndexApp 的请求。当 AddContentAndIndexApp 成功连接到 SocketServer 时,SocketServer 将开始对已上传内容进行索引操作。

图:SocketServer (索引器)

下面的屏幕显示了云端索引器正在工作,显示正在被索引的文件。AddContentAndIndexApp 也可以显示索引过程中的进度。

图:SocketServer (索引器)

支持库

这三个项目需要一些支持库。第一个是 Azure SDK,这是显而易见的。项目还需要运行最新、最新的 Azure SDK 库。RedDog Storage 支持基于我们之前设置的 SMB 文件共享挂载驱动器。它还允许我们访问索引,这是 dtSearch 提供的核心技术。

NuGet 包由 Visual Studio 安装。有关 NuGet 的帮助,请参阅此链接:http://docs.nuget.org/

当然,还需要 dtSearch。所有 3 个项目都需要 dtSearch。dll 文件可以在此处找到:C:\Program Files (x86)\dtSearch Developer\bin\dtSearchNetApi4.dll

项目 所需的 NuGet 包
AddContentAndIndexApp Azure 存储 SDK
SearchApp RedDog.Storage、Azure SDK、Azure 配置
SocketServer RedDog.Storage、Azure SDK、Azure 配置
图:所需的 NuGet 包

关键源代码元素

接下来的几个代码片段提供了一些核心操作。

如前所述,SocketServer 等待连接。连接成功后,它将开始一个索引作业。请注意,它监听端口 10100。虚拟机必须运行两个任务才能使客户端能够连接到 SocketServer:(1) 使用 Azure 门户在虚拟机上打开端口。(2) 定义允许在指定端口上连接的防火墙规则。以下是两篇来自 Microsoft 的博客文章,可以帮助您:链接链接

另请注意,下面的代码在应用程序启动时挂载 P: 驱动器。代码的其他部分使用此 P: 驱动器代码来构建索引。当 SocketServer 索引每个文件时,应用程序会将一个字符串返回给 AddContentAndIndexApp,后者可以向用户提供进度更新。发送进度报告回 AddContentAndIndexApp 客户端的代码如下所示

sw.WriteLine(data + " - " + loopcount.ToString());

public static void Main()
    {
        SetupMounts();
        listener = new TcpListener(10100);
        listener.Start();

        for (int i = 0; i < LIMIT; i++)
        {
            Thread t = new Thread(new ThreadStart(Service));
            t.Start();
        }

    }

    public static void SetupMounts()
    {

        foreach (var mappedDrive in FilesMappedDrive.GetMountedShares())
        {
            if (mappedDrive.DriveLetter == "P:")
            {
                FilesMappedDrive.Unmount("P:");
            }
        }

        // Mount a drive for a CloudFileShare.
        var share = 
             CloudStorageAccount.Parse(
              CloudConfigurationManager.GetSetting("StorageConnectionString"))
            .CreateCloudFileClient()
            .GetShareReference("dtsearchshare2");
        share.Mount("P:");
    }

    public static void Service()
    {
        while (true)
        {
            if (!fReadOnce)
            {
                fReadOnce = true;
                Console.WriteLine("Waiting for client connection");
            }
            Socket soc = listener.AcceptSocket();
            //soc.SetSocketOption(SocketOptionLevel.Socket,
            //        SocketOptionName.ReceiveTimeout,10000);
            Console.WriteLine("Connected: {0}",
                                     soc.RemoteEndPoint);
            try
            {
                Stream s = new NetworkStream(soc);
                StreamReader sr = new StreamReader(s);
                StreamWriter sw = new StreamWriter(s);
                sw.AutoFlush = true; // enable automatic flushing
                SendListIndexedFiles(sw);
                sw.WriteLine("<EOF>");
                s.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
            Console.WriteLine("Disconnected: {0}", soc.RemoteEndPoint);
            soc.Close();
        }
    }

    private static void ExecuteIndexJob(StreamWriter sw, dtSearch.Engine.IndexJob ij)
    {
        // Set the status variables
        fIndexing = true;

        // Start index job execution in a separate thread
        // In ASP.NET applications, use Execute, not ExecuteInThread
        ij.ExecuteInThread();

        Trace.TraceInformation("ExecuteIndexJob - 1");
        // Monitor the job execution thread as it progresses
        IndexProgressInfo status = new IndexProgressInfo();
        string data = null;
        int loopcount = 0;
        while (ij.IsThreadDone(50, status) == false)
        {
            loopcount += 1;
            Trace.TraceInformation("ExecuteIndexJob - 1");
            Trace.TraceInformation("ExecuteIndexJob - Ste = {0}", status.Step);
            // Set the status text based on the current indexing step
            switch (status.Step)
            {
                case IndexingStep.ixStepBegin:
                    data = "Opening Index";
                    Console.WriteLine(data);
                    sw.WriteLine(data + " - " + loopcount.ToString());
                    break;

                case IndexingStep.ixStepCheckingFiles:
                    data = "Checking Files";
                    Console.WriteLine(data);
                    sw.WriteLine(data + " - " + loopcount.ToString());
                    break;

                case IndexingStep.ixStepCompressing:
                    data = "Compressing Index";
                    Console.WriteLine(data);
                    sw.WriteLine(data + " - " + loopcount.ToString());
                    break;

                case IndexingStep.ixStepCreatingIndex:
                    data = "Creating Index";
                    Console.WriteLine(data);
                    sw.WriteLine(data + " - " + loopcount.ToString());
                    break;

                case IndexingStep.ixStepDone:
                    data = "Indexing Complete";
                    Console.WriteLine(data);
                    sw.WriteLine(data + " - " + loopcount.ToString());
                    break;
                case IndexingStep.ixStepMerging:
                    data = "Merging words into index";
                    Console.WriteLine(data);
                    sw.WriteLine(data + " - " + loopcount.ToString());
                    break;
                case IndexingStep.ixStepNone:
                    data = "Step";
                    Console.WriteLine(data);
                    sw.WriteLine(data + " - " + loopcount.ToString());
                    break;
                case IndexingStep.ixStepReadingFiles:
                    data = (status.File.Name == "" ? "Next file" : status.File.Name);
                    Console.WriteLine(data);
                    sw.WriteLine(data + " - " + loopcount.ToString());
                    break;

                case IndexingStep.ixStepStoringWords:
                    data = "(storing words)";
                    Console.WriteLine(data);
                    sw.WriteLine(data + " - " + loopcount.ToString());
                    break;

                default:
                    data = "Procesing ";
                    Console.WriteLine(data);
                    sw.WriteLine(data + " - " + loopcount.ToString());
                    break;
            }

            // Let other form events be handled while we're looping
            //Application.DoEvents();

            // Check if we need to abort (eg. stop button has been pressed)
            if (fAbortImmediately)
            {
                ij.AbortThreadImmediate();
            }
            else if (fStopPressed)
            {
                ij.AbortThread();
            }

        }

        // Update the status text based on the reason the while loop ended
        //byte[] data = null;
        if (fAbortImmediately)
        {
            data = "Indexing halted, index not updated";
            Console.WriteLine(data);
            sw.WriteLine(data);
        }
        else if (fStopPressed)
        {
            data = "Indexing halted, index partially updated";
            Console.WriteLine(data);
            sw.WriteLine(data);
        }
        else
        {
            data = "Indexing complete";
            Console.WriteLine(data);
            sw.WriteLine(data);
        }

        // Reset flags and controls
        // StopButton.Enabled = false;
        // StopImmediatelyButton.Enabled = false;
        // CloseButton.Enabled = true;
        fIndexing = false;

        Trace.TraceInformation("ExecuteIndexJob - 2");
        // If there were errors, display the errors as additions to the
        // status text
        JobErrorInfo err = ij.Errors;
        if (err.Count > 0)
        {
            string errors = null;
            for (int i = 0; i < err.Count; i++)
            {
                errors += " " + err.Message(i);
            }
            data = errors;
            sw.WriteLine(data);
            Console.WriteLine(data);
        }
    }
图:SocketServer: Program.cs

下面的代码代表 AddContentAndIndexApp 在 SocketServer 上开始索引操作。它调用 socket.main() 来执行此任务。

private void IndexButton_Click(object sender, EventArgs e)
{
    try
    {
        Socket socket = new Socket();
        socket.main(this, "Connecting and indexing");
    }
    catch (Exception ex)
    {
        throw;
    }
    finally
    {
        ShowTitle("Finished indexing..");
        int pauseTime = 2000;
        System.Threading.Thread.Sleep(pauseTime);
        ShowTitle("Search Content Uploader & Indexer");
    }
}
图:AddContentAndIndexApp:用户单击索引按钮时发生的情况

当 SocketServer 执行索引操作时,它通过发送代表正在索引的文件名的字符串来向客户端提供更新状态。下面的代码接收字符串并在窗体的标题栏上显示它们,以向用户提供有关正在发生的操作的反馈。这就是为什么使用套接字连接而不是 Web 服务调用的原因。

internal class Socket
{
    public void main(MainForm mainForm, string msg)
    {
        //ShowTitle(mainForm, msg);
        var client = new TcpClient("dtsearch-f3a0322z.cloudapp.net", 10100);
        string result = null;

        try
        {
            Stream s = client.GetStream();
            var sr = new StreamReader(s);
            var sw = new StreamWriter(s);
            sw.AutoFlush = true;
            while (true)
            {
                result = sr.ReadLine();
                if (result == null)
                    continue;
                ShowTitle(mainForm, result);
                //System.Diagnostics.Debug.WriteLine(result);
                if (result == "<EOF>") break;
            }
            s.Close();
            //ShowTitle(mainForm, "Done");
        }
        catch(Exception ex)
        {
            throw ex;
        }
        finally
        {
            client.Close();
        }
    }
    private delegate void StringParameterDelegate(MainForm mainForm, string value);

    public static void ShowTitle(MainForm mainForm, string msg)
    {
        if (mainForm.InvokeRequired)
        {
            // We're not in the UI thread, so we need to call BeginInvoke
            mainForm.BeginInvoke(new StringParameterDelegate(ShowTitle), 
                                            new object[] { mainForm, msg });
            return;
        }
        // Must be on the UI thread if we've got this far
        mainForm.Text = msg;
    }
}
图:AddContentAndIndexApp:SocketClient.cs

摘要和附加指南

还可以做更多工作来使这些应用程序更加健壮。应在整个过程中添加错误处理代码。连接字符串应加密。SocketServer 发送回客户端的数据量可以减少(即,不需要将索引的每个文件都发送回客户端)。

尽管如此,该解决方案还是为利用 Microsoft Azure 的强大功能和安全性存储数据,并结合使用 RemoteApp 使 dtSearch 搜索功能几乎可以从任何计算机或设备获得的灵活性,提供了一个很好的起点。

以下是一些可以优化性能的附加指南。

将搜索结果限制到客户端

枚举满足搜索条件文档或其他项是执行搜索时最耗时的步骤。使用 MaxFilesToRetrieve 值将搜索结果限制为最相关的项。例如,如果您执行一个搜索,并将 MaxFilesToRetrieveValue 设置为 100,并且该搜索找到 1400 个文档,则搜索结果对象将包含最佳匹配的 100 个文件。同时,搜索作业将指示该搜索实际上总共找到了 1400 个文档。

即使您减少检索的文件或其他项的数量,您仍然会获得高质量的搜索结果,因为结果仍然包含最相关的项。最终用户通常不会阅读超过前十几项搜索结果,因此提供数百个结果并不能增加价值,反而会损害性能。

dtSearch 还提供了逐页浏览结果的能力,一次只发送小批量。开发人员可以在用户界面上提供一个“显示所有文件”按钮,以防任何用户确实想一次性查看所有可能的搜索结果。

通过多线程和扩展虚拟机来扩展 dtSearch

由于 dtSearch 是无状态的,您可以通过利用并行执行查询的技术来满足任何级别的需求。这包括利用多线程技术,以及水平扩展在云中运行的虚拟机以优化性能。

.NET 优化

用 .NET 编写的应用程序有两种执行搜索的方法:ExecuteExecuteInThread。这两者之间的选择取决于调用应用程序将如何监视搜索进度。

对于 Web 服务器等的使用,最有效的方法是使用 Execute,并在搜索前设置 AutoStopLimitTimeoutSecondsMaxFilesToRetrieve 属性。

取消选项

目前,本文中的云端索引器不支持“取消索引操作”命令。解决此问题的简单方法是公开 SocketServer 上的另一个端口,以便取消操作。包含的代码库支持许多标志,可以轻松实现这一点。

一个“取消”按钮可以停止搜索并阻止检索更多搜索结果中的文件,同时显示迄今为止检索到的文件数量。实现“取消”按钮和跟踪搜索进度的两种方法是:(1) 通过 SearchJob 的 StatusHandler 属性实现的 callback 函数,或者 (2) 在单独的线程中搜索。

当应用程序使用 Execute 启动 SearchJob 时,它可以利用 StatusHandler callback 函数来监视搜索进度和搜索结果显示,并在用户按下“取消”按钮时取消搜索。ExecuteInThread 在单独的线程中启动搜索作业并立即返回,它提供了 IsThreadDoneAbortThread 方法,以便调用应用程序可以取消搜索。

在实现“取消”按钮功能的应用程序中,ExecuteInThread 比 Execute 快得多,因为它消除了对 callback 函数的需求。来自 Visual Basic 或其他 COM 基于语言的 callback 函数非常耗时,因为每个 callback 都涉及单独的 IDispatch 调用。在 .NET 中,callback 速度更快,但仍然可能耗时。因此,对于需要实现类似“取消”按钮功能的应用程序,ExecuteInThreadExecute 更适合 SearchJobs

注意:使用 ExecuteInThread 的应用程序不应连续在循环中调用 IsThreadDone() 来检查线程是否完成。相反,它应该在 IsThreadDone() 调用之间调用 Sleep(),以给搜索线程留出工作时间。

其他资源

更多关于 dtSearch
dtSearch.com
口袋里的搜索引擎 – 介绍 Android 上的 dtSearch
云端疾速源代码搜索
使用 Azure 文件、RemoteApp 和 dtSearch,从任何计算机或设备跨越 PB 级数据的各种数据类型进行安全即时搜索
使用 dtSearch 引擎进行 Windows Azure SQL 数据库开发
使用 dtSearch 进行分面搜索 - 不是普通的搜索过滤器
使用 dtSearch® ASP.NET Core WebDemo 示例应用程序极速提升您的搜索体验
在您的 Windows 10 通用 (UWP) 应用程序中嵌入搜索引擎
使用 dtSearch Engine DataSource API 索引 SharePoint 网站集
使用 dtSearch® ASP.NET Core WebDemo 示例应用程序
在 AWS 上使用 dtSearch(EC2 & EBS)
使用 dtSearch 和 AWS Aurora 进行全文搜索

© . All rights reserved.