搜索可靠词典

Search through Reliable Dictionary

我需要通过系统中的所有用户创建 Web API 搜索功能。 客户端(使用 phone)使用端点向我发送请求:

HTTP 1.1 GET http://sf.cluster:80/
Path /search/users?q=Aa&take=10

其中 q 是用户在搜索字段中输入的字符串。 take - phone 想要显示多少条目。

我从 Azure 存储 Table 中上传了 89000 个项目到我的可靠词典中。它有结构:

IReliableDictionary<Guid, string>

我的搜索方法如下:

    public async Task<IEnumerable<UserInfo>> Search(string q, int take)
    {
        var usersDictionary = await GetUsersDictionary();

        IEnumerable<UserInfo> results;
        using (var tx = StateManager.CreateTransaction())
        {
            var searchResults = (from r in (await usersDictionary.CreateEnumerableAsync(tx)).ToEnumerable()
                          where r.Value.StartsWith(q, StringComparison.InvariantCultureIgnoreCase)
                          select new UserInfo()
                          {
                              Id = r.Key,
                              Name = r.Value
                          }).Take(take);

            results = new List<UserInfo>(searchResults);

            await tx.CommitAsync();
        }

        return results;
    }

问题: 它在 phone 上运行良好,我得到了我所期望的。但是当我开始用一堆请求推送我的端点时(使用 Soap UI 工具同时大约 ~60 个线程),超时开始从 1 秒增加到 35 秒!看来我在某处犯了错误或选择了错误的搜索实现方式。

有人实现过这样的功能吗?有人可以帮助正确的搜索方法吗?

UPD: 实现了无状态服务,我用名字存储 List<string> 并做同样的事情(搜索列表)。结果:150-300 毫秒。看起来我应该将 List 存储在一个状态中(在有状态服务中)并根据请求获取它..

我不确定您的 ToEnumerable 方法的实现是什么,但我看到的大多数都是相当懒惰的实现,只是采用异步可枚举并将其复制到列表中。现在,使用包含 890,000 个元素的可靠字典,这是非常低效的。此外,事务就像一个互斥锁,因此当您复制这个巨大的列表时,您正在锁定底层集合。我建议检查 this library 中的 AsyncEnumerable linq 实现,因为它实现了一种将 linq 与服务结构 AsyncEnumerable 一起使用的有效方法。使用它,您的搜索将如下所示:

    using (var tx = StateManager.CreateTransaction())
    {
        var enumerable = await usersDictionary.CreateEnumerableAsync(tx);
        results = await enumerable.Where(kvp=>kvp.Value.StartsWith(q, StringComparison.InvariantCultureIgnoreCase))
            .Select(kvp=> new UserInfo()
                  {
                      Id = r.Key,
                      Name = r.Value
                  })
            .Take(take)
            .ToListAsync(tx);
    }

另外,作为旁注,由于您没有以任何方式修改基础集合,因此您不需要提交事务。提交事务只是一种告诉状态管理器您已修改状态并且您已完成更改的方式,然后它会将更改后的值传播到辅助节点。如果这是一个读取密集的状态,您甚至可以在辅助节点上调用此方法,但请注意,写入可能尚未传播。

ReliableDictinonary returns IAsyncEnumerable 因为 ReliableDictionary 分页了一些值。这意味着可能需要磁盘 IO 来读取某些值。 IAsyncEnumerable 允许我们阻塞尽可能少的线程。

如果担心读取延迟,您可以使用通知来构建完全内存中的二级索引。您还可以按值对二级索引进行排序,以提高前缀匹配搜索的效率。以下是相关文档:https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-notifications

对 pdylanross 的回答的小修正:CreateEnumerableAsync 使用不锁定集合的 mvcc 模型提供快照隔离。因此,其他事务可以在快照读取事务运行时继续执行读写操作。有关隔离级别的更多信息:https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-reliable-collections

希望这对您有所帮助,