Clojure - 并行执行一堆HTTP请求 - pmap?

时间:2014-01-30 05:54:00

标签: asynchronous clojure

我需要发出200个左右的HTTP请求。我希望它们可以并行运行,也可以批量运行,我不知道在Clojure中可以从哪里开始。 pmap似乎具有我想要的效果,例如,使用http.async.client:

(defn get-json [url]
    (with-open [client (http/create-client)]
        (let [resp (http/GET client url)]
            (try
                (println 1)
                (http/string (http/await resp))
                (println "********DONE*********")
                nil

            (catch Exception e (println e) {})))))


music.core=> (pmap get-json [url url2])
1
1
********DONE*********
********DONE*********
(nil nil)

但我不能证明请求实际上并行执行。我是否需要调用JVM的Thread API?我正在四处寻找其他图书馆,如Netty,Lamina,Aleph - 我应该使用其中一个吗?请指出我正确的方向,以了解最佳实践/最简单的解决方案。

3 个答案:

答案 0 :(得分:13)

理想情况下,您不希望占用等待每个http请求结果的线程,因此pmap或其他基于线程的方法并不是一个好主意。

你真正想做的是:

  • 以异步方式触发所有请求
  • 只用一个帖子等待结果

我建议的方法是使用http-kit立即触发所有异步请求,产生一系列承诺。然后,您只需要在单个线程中取消引用所有这些promise,这将阻塞该线程,直到返回所有结果。

类似的东西:

(require '[org.httpkit.client :as http])

(let [urls (repeat 100 "http://google.com") ;; insert your URLs here
      promises (doall (map http/get urls))
      results (doall (map deref promises))]
  #_do_stuff_with_results 
  (first results))

答案 1 :(得分:7)

你所描述的是对pmap的完美使用,我会以类似的方式处理它。

就“证明”并行运行而言,您必须相信pmap的每次迭代都在新线程中运行该函数。但是,一个简单的方法是将线程id打印为完整性检查:

user=> (defn thread-id [_] (.getId (Thread/currentThread)))

user=> (pmap thread-id [1 2 3])

(53 11 56)

由于线程数实际上是不同的 - 意味着clojure每次都在创建一个新线程 - 您可以放心地相信JVM将并行运行您的代码。

另请参阅其他并行功能,例如pvaluespcalls。它们为您提供了不同的语义,可能是正确的答案,具体取决于手头的问题。

答案 2 :(得分:0)

看看Claypoole。示例代码:

(require '[com.climate.claypoole :as cp])
;; Run with a thread pool of 100 threads, meaning up to 100 HTTP calls
;; will run simultaneously. with-shutdown! ensures the thread pool is
;; closed afterwards.
(cp/with-shutdown! [pool (cp/threadpool 100)
  (cp/pmap pool get-json [url url2]))

在这种情况下,您希望com.climate.claypoole/pmap超过clojure.core/pmap的原因是后者根据CPU的数量设置线程数,而无法覆盖。对于非CPU限制的网络和其他I / O操作,通常需要根据所需的I / O数量设置线程数,而不是基于CPU容量。

或者使用http-kit之类的非阻止客户端,每个连接不需要一个线程,suggested by mikera