自动完成服务器端实现

Posted

技术标签:

【中文标题】自动完成服务器端实现【英文标题】:Autocomplete server-side implementation 【发布时间】:2010-11-01 13:06:13 【问题描述】:

html 输入框中实现自动完成功能的服务器端组件的快速有效方法是什么?

我正在编写一个服务来在我们的 Web 界面的主搜索框中自动完成用户查询,并且完成显示在一个 ajax 驱动的下拉列表中。我们正在运行查询的数据只是我们系统知道的一个大型概念表,它与***页面标题的集合大致匹配。对于这项服务,速度显然是最重要的,因为网页的响应性对用户体验很重要。

当前的实现只是将所有概念以有序集合的形式加载到内存中,并在用户击键时执行简单的 log(n) 查找。然后使用尾集来提供最接近匹配之外的其他匹配。这个解决方案的问题是它不能扩展。它目前正在运行虚拟机堆空间限制(我设置了 -Xmx2g,这大约是我们可以在 32 位机器上推送的最多),这阻止了我们扩展概念表或添加更多功能。在具有更多内存的机器上切换到 64 位 VM 并不是一个直接的选择。

我一直犹豫是否要开始使用基于磁盘的解决方案,因为我担心磁盘寻道时间会影响性能。是否有可能的解决方案可以让我更好地扩展,无论是完全在内存中还是通过一些快速的磁盘支持实现?

编辑:

@Gandalf:对于我们的用例,重要的是自动完成功能是全面的,而不仅仅是对用户的额外帮助。至于我们正在完成什么,它是一个概念类型对的列表。例如,可能的条目是 [("Microsoft", "Software Company"), ("Jeff Atwood", "Programmer"), ("***.com", "Website")]。一旦用户从自动完成列表中选择了一个项目,我们就会使用 Lucene 进行完整搜索,但我还不确定 Lucene 是否能很好地用于自动完成本身。

@Glen:这里没有使用数据库。当我谈论表格时,我只是指我的数据的结构化表示。

@Jason Day:我对这个问题的最初实现是使用Trie,但由于需要大量对象引用,因此内存膨胀实际上比排序集更糟糕。我将阅读三元搜索树,看看它是否有用。

【问题讨论】:

你能告诉我们更多关于你“自动完成”的信息吗?为什么这么多术语?是否有更明显的查询可以满足 90% 的用户查询,而不是加载所有可能性? 我不能确定 Lucene 是否能满足您的需求,但在那个大小的数据集上,我非常怀疑您不会在优化的 Lucene 索引上获得亚秒级的查询时间。根据索引的设置方式,您甚至可以将其存储在内存中。 一个标准的 Trie 确实是非常占用内存的,对于更大的集合,你想使用一个压缩的 Trie,它可以大大减少内存占用。其他优化包括节点值的延迟初始化和子/值集的正确数据结构。不久前,我创建了一个 Java autocomplete library 能够处理非常大的数据集(10,000,000+)并有效地回答精确和近似搜索。 【参考方案1】:

对于这么大的集合,我会尝试使用 Lucene 索引之类的方法来查找您想要的术语,并设置一个计时器任务,该任务在每次击键后重置,延迟为 0.5 秒。这样,如果用户快速键入多个字符,它不会在每个笔划中查询索引,只有当用户暂停一秒钟时。可用性测试会让你知道暂停应该多长时间。

Timer findQuery = new Timer();
...
public void keyStrokeDetected(..) 
   findQuery.cancel();
   findQuery = new Timer();
   String text = widget.getEnteredText();
   final TimerTask task = new TimerTask() 
      public void run() 
         ...query Lucene Index for matches
      
   ;
   findQuery.schedule(task, 350); //350 ms delay

那里有一些伪代码,但就是这样。此外,如果设置了查询词,则可以预先创建和优化 Lucene 索引。

【讨论】:

我不认为他们在这里击键的东西真的很有必要,因为这听起来不像问题。但我同意您可能希望将所有内容放入 lucene 索引中。 Lucene 在这种事情上速度非常快。 现在 Lucene 已经内置了对自动完成的支持。有关示例,请参见 ***.com/questions/24968697/…。【参考方案2】:

我也有类似的要求。

我使用关系数据库和一个索引良好的合成表(避免连接和视图以加快查找速度),并使用内存缓存 (Ehcache) 来存储最常用的条目。

通过使用 MRU 缓存,您将能够对大多数查找有即时响应时间,并且在访问存储在磁盘上的大表中的索引列方面可能没有什么能比关系数据库更好了。

这是您无法存储在客户端上的大型数据集的解决方案,它的运行速度非常快(在我的情况下,非缓存查找总是在 0.5 秒内检索到)。它还具有水平可扩展性 - 您始终可以添加额外的服务器和数据库服务器。

您也可以只缓存客户端上最常用的结果,特别是如果您已经实现了它。就我而言,服务器端解决方案足够快,而客户端加载时间也足够慢,​​因此没有保证。

附:仅当用户暂停一段时间以避免重复查找时才进行客户端查询是一个很好的解决方案。在我的客户端上,我只在输入前三个字符后才查询数据库,因为少于这三个字符会在所有实例中返回太多结果。

【讨论】:

【参考方案3】:

我最终通过 Lucene 解决了这个问题;最初的性能测试对于我们的用例来说似乎已经足够了。为了使前缀查询正常工作,需要进行一些修改,因为我在扩展诸如“Jeff At*”之类的查询时遇到了 TooManyClauses 异常。我最终用 FilterIndexReader 包装了我的 IndexReader,并对前缀术语调用返回的术语数量设置了硬上限。这是我的代码:

Directory directory = FSDirectory.getDirectory(indexDir);
IndexReader reader = IndexReader.open(directory);
FilterIndexReader filteredReader = new FilterIndexReader(reader) 
  @Override public TermEnum terms(Term t) throws IOException 
    final TermEnum origEnum = super.terms(t);

    return new TermEnum() 
      protected int count = 0;
      @Override public boolean next() throws IOException 
        if (count++ < (BooleanQuery.getMaxClauseCount() - 10))
          return origEnum.next();
        else return false;
      

      @Override public Term term() 
        return origEnum.term();
      

      @Override public int docFreq() 
        return origEnum.docFreq();
      

      @Override public void close() throws IOException 
        origEnum.close();
      
    ;
  
;

IndexSearcher searcher = new IndexSearcher(filteredReader);

【讨论】:

【参考方案4】:

对于那些偶然发现这个问题的人......

我刚刚在 Google 代码上发布了 server-side autocomplete implementation。该项目包括一个可以集成到现有应用程序中的 java 库和一个独立的 HTTP AJAX 自动完成服务器。

我希望这能让人们将高效的自动完成功能整合到他们的应用程序中。踢轮胎!

【讨论】:

如何启动服务器? java -jar autocomplete-server-0.3.jar 不起作用?感谢您的信息 好问题。我向自动完成服务器主页添加了一个示例,并添加了一个新版本 (0.4)。【参考方案5】:

我已经使用Ternary search tree 为小型数据集完成了此操作。 DDJ 代码转换为 Java 并不太难,但它假定整个数据集都可以放入内存。三元搜索树有磁盘上的实现(here 是 python 中的一种),但当然它们的性能会降低。但是,由于三元搜索树在部分匹配方面表现出色,因此性能可能适合您的需求。

【讨论】:

【参考方案6】:

我使用了 hashtable 和 mmap() 并且 10,000,000+ 条记录术语列表没有问题。 在此处查看演示:http://olegh.ath.cx/autocomplete.html

【讨论】:

【参考方案7】:

这里使用 trie 数据结构是 wiki http://en.wikipedia.org/wiki/Trie

【讨论】:

【参考方案8】:

如果您无法将所有数据物理加载到 RAM 中,那么您将不得不处理磁盘上的一些问题。

您使用的是什么数据库?

例如,Oracle 有一个选项,您可以将整个表保存在内存中,并针对该表执行查询。

mysql 也声称有一些内存功能,但我对 MySQL 了解不多。

然后您可以取消基于 Java 的缓存,或者您可以将缓存用于最流行/最近的搜索。

显然,当您用完 RAM 时,一些数据会在您查询时存储在磁盘上,但根据系统的负载,这只会是第一次按键的问题,而不是后续按键的问题,因为之后该行将在内存中。

如果磁盘寻道使您放慢了速度,那么您可以研究使用 SSD 驱动器来加快读取速度。

【讨论】:

【参考方案9】:

也许我误解了您的问题,但您不能使用 JQuery 插件将信息 Ajax 到您的应用程序吗?

我以前用过这个:

Ajax Auto Suggest v2

【讨论】:

在 Web 界面方面,我使用 jQuery 进行 ajax 回调。我在这里谈论的是服务器端。【参考方案10】:

是否有可能的解决方案 让我扩大规模

是的,甲骨文。这就是构建数据库的目的。只需索引相关列。如果您在内存解决方案的墙上运行,那么磁盘寻道时间或网络延迟之间的权衡可能没有实际意义。特别是如果你在两者之间插入一个缓存层。

此外,如果您稍微调整一下客户端代码,您可能能够减少点击次数。例如在运行查询之前设置最小输入字符数,或者在用户停止输入后设置几分之一秒的延迟。如果您已经在使用它们,请将它们设置得高一点。

【讨论】:

以上是关于自动完成服务器端实现的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中构建服务器端自动完成

confd+etcd完成后端服务器的自动检测

[easyUI] autocomplete 简单自动完成以及ajax从服务器端完成

Nacos——Nacos服务端服务注册处理

使用NewLife网络库构建可靠的自动售货机Socket服务端

利用 Python 完成 App 端自动化的最优方案