【问题标题】:Call Solr asynchronous from Play Framework从 Play Framework 异步调用 Solr
【发布时间】:2013-06-20 15:50:12
【问题描述】:

我创建了一个 Play 2.1 Scala 应用程序。我不确定从 Play 应用程序调用 Solr 的最佳方式是什么:

  • Play 2 没有 Solr 模块。
  • AFAIK 所有 Solr-API(如 SolrJ)都处于阻塞状态。
  • 我可以将 SolrJ 调用封装到 Future 中,但这也会阻塞线程,对吗?
  • 我应该使用play.api.libs.ws.WS 库来调用 Solr 并使用 Plays JSON 支持来提取结果(如下例所示)还是有任何更简单/更快的方法?

    val solrQuery: Future[play.api.libs.ws.Response] = WS.url("http://localhost:8983/solr/collection1/select?q=id%3A123&wt=json").get()
    

【问题讨论】:

  • 您可以考虑使用 Dispatch 或 Spray,因为它们都构建在 Netty 之上,并且在发出 http 请求时完全是 NIO。
  • 谢谢,但是为什么 Dispatch 或 Spray 比 Play 的 Web 服务库 WS 更好?
  • 我不确定 play ws api 在幕后做了什么。尽管它返回了一个未来,但我不确定它在进行 http 调用时是否仍在阻塞 io。我会调查一下,如果你已经在使用 play,那么它可能是一个不错的选择。如果没有,那么您可以查看我提到的库。
  • 也许创建一个在后台使用 WS 并提供类似 solrj 的 api(用于查询和类型)的 solrj-play 库是个好主意?我觉得这也很有用:-)
  • Solr-scala-client 已经启动了一个带有异步 Solr 调用的分支(请参阅 Issue 9)。

标签: scala solr playframework playframework-2.1 solrj


【解决方案1】:

这是我在我的副项目中使用 WS 的方式:

val itselfNodeFuture = Statix.doParams( Statix.SolrSelectWSReq, 
    List(
    "wt"     -> "json", 
    "q"      -> "*:*",
    "fq"     -> "node_type:collection",
    "fq"     -> "id:%d".format( nodeId),
    "indent" -> "true",
    "rows"   -> "1",
    "fl"     -> "id,parent_id,title",
    "fl"     -> "date_created,date_about,date_modified")
).get()

//Use the first Await after the last future
val itselfJson = Await.result(
    itselfNodeFuture, Duration("2 sec")).json

val mainRow = (itselfJson \ "response" \ "docs").as[ Seq[JsValue]]
val mainNodeParent = (mainRow(0) \ "parent_id").as[Long]
val mainNodeTitle = (mainRow(0) \ "title").as[String]

这是我使用的实用程序类,doParams 特别有用。

object Statix { //Noder must extend this
    def SolrSelectWSReq = WS.url("http://127.0.0.1:8080/solr-store/collection1/select/")
    def SolrUpdateWSReq = WS.url("http://127.0.0.1:8080/solr-store/collection1/update/json/")

    def doParams(request: WS.WSRequestHolder, params: List[(String, String)]) = {
        params.foldLeft( request){
            (wsReq, tuple) => wsReq.withQueryString( tuple)}}
}

【讨论】:

  • 很有趣,谢谢。我将启动一个基于 WS 的非阻塞 Solr 客户端,就像您的示例一样。
【解决方案2】:

您想用自己的Execution context 将调用包装在 Future 中。这样调用可能会阻塞,但它会使用不同的线程池,而不是阻塞主应用程序。

事实上,这是面对阻塞或慢速任务时的标准行为,例如向数据库发送查询或执行一些繁重的任务。

【讨论】:

  • 谢谢,我不知道。
【解决方案3】:

最近遇到了这个需求,并没有找到任何有用的谷歌搜索。以下仅用于查询,但可以扩展。我假设您想继续使用 SolrJ 课程。 SolrQuery 和 QueryResponse 很容易使用。

所以去查询。您需要像往常一样构建 SolrQuery。对于“wt”,提供“javabin”。这将以 SolrJ 内部使用的压缩二进制格式为您提供响应。

val sq = new SolrQuery()
sq.set("wt", "javabin")
...

您需要将 SolrQuery 变成 WS 可以理解的东西。 (我没有添加所有的导入,因为大多数导入都很简单 [例如,通过您的 IDE]。我所包含的可能不那么明显。)

import scala.collection.JavaConverters._

def solrQueryToForm(sq: SolrQuery): Map[String, Seq[String]] = {
  sq.getParameterNames.asScala.foldLeft(Map.empty[String, Seq[String]]) {
    case (m, n) =>
      m + (n -> sq.getParams(n))
  }
}

在我的商店中,我们使用默认集合和处理程序(即“/select”),但您希望它们被 SolrQuery 覆盖

def solrEndpoint(sq: SolrQuery): String = {
  val coll = sq.get("collection", defaultCollection)
  val hand = Option(sq.getRequestHandler).getOrElse(defaultHandler)
  formSolrEndpoint(solrUrl, coll, hand)
}

def formSolrEndpoint(base: String, collection: String, handler: String): String = {
  val sb = new StringBuilder(base)
  if (sb.last != '/') sb.append('/')
  sb.append(collection)
  if (!handler.startsWith("/")) sb.append('/')
  sb.append(handler)
  sb.result()
}

您需要一些代码来将 WSResponse 映射到 QueryResponse

import com.ning.http.client.{Response => ACHResponse}

def wsResponseToQueryResponse(wsResponse: WSResponse)(implicit ctx: ExecutionContext): QueryResponse = {
  val jbcUnmarshal = {
    val rbis = wsResponse.underlying[ACHResponse].getResponseBodyAsStream

    try {
      new JavaBinCodec().unmarshal(rbis)
    }
    finally {
      if (rbis != null)
        rbis.close()
    }
  }

  // p1: SolrJ pulls the same cast
  // p2: We didn't use a SolrServer to chat with Solr so cannot provide it to QueryResponse
  new QueryResponse(jbcUnmarshal.asInstanceOf[NamedList[Object]], null)
}

这为您提供了使用 Play 的异步 WS 服务调用 Solr 的所有部分。

def query(sq: SolrQuery)(implicit ctx: ExecutionContext): Future[QueryResponse] = {
  val sqstar = sq.getCopy
  sqstar.set("wt", "javabin")

  WS.url(solrEndpoint(sqstar))
    .post(solrQueryToForm(sqstar))
    .map(wsResponseToQueryResponse)
}

由于 Play 现在将 Web 服务代码作为独立 jar 发布,这意味着几乎任何项目都应该能够异步查询 Solr。希望有用。

【讨论】:

  • 谢谢,看起来很适合我。我无法测试它,因为在我当前的项目中,我终于从 Solr 切换到 Elasticsearch(使用它的异步 API)。
猜你喜欢
  • 2012-11-08
  • 1970-01-01
  • 1970-01-01
  • 2016-12-14
  • 2017-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多