【问题标题】:Clojure and HBase: Iterate Lazily over a ScanClojure 和 HBase:懒惰地遍历扫描
【发布时间】:2013-05-02 00:30:01
【问题描述】:

假设我想在 clojure 中打印 hbase 表扫描的输出。

(defmulti scan (fn [table & args] (map class args)))

(defmethod scan [java.lang.String java.lang.String] [table start-key end-key]
    (let [scan (Scan. (Bytes/toBytes start-key) (Bytes/toBytes end-key))]
        (let [scanner (.getScanner table scan)]
            (doseq [result scanner]
                (prn
                    (Bytes/toString (.getRow result))
                    (get-to-map result))))))

get-to-map 将结果转换为地图。可以这样运行:

(hbase.table/scan table "key000001" "key999999")

但是如果我想让用户对扫描结果做一些事情呢?我可以允许他们将一个函数作为回调传递给每个结果。但我的问题是:如果我希望用户能够懒惰地迭代每个结果,我应该返回什么

(Bytes/toString (.getRow result))
(get-to-map result)

不保留以前的结果,这可能发生在使用惰性序列的简单实现中。

【问题讨论】:

    标签: java clojure iterator hbase sequence


    【解决方案1】:

    如果你接受一个回调参数,你可以在doseq中调用它:

    (defmulti scan [f table & args] (mapv class args)) ; mapv returns vector
    
    (defmethod scan [String String] [f table start-key end-key]
                   ; ^- java.lang classes are imported implicitly
      (let [scan ...
            scanner ...] ; no need for two separate lets
        (doseq [result scanner]
          ; call f here, e.g.
          (f result))))
    

    这里f 将在每个结果中调用一次。它的返回值以及结果本身将立即被丢弃。您当然可以使用result 的一些预处理版本调用f,例如(f (foo result) (bar result)).

    您还可以将结果的序列/向量返回给客户端,并让它自己处理。如果序列是惰性的,您需要确保支持它的任何资源在处理期间保持打开状态(并且可能稍后关闭它们 - 参见with-open;处理代码需要在@987654330 内执行@ 并在它返回时完成处理)。

    例如,要将预处理结果的向量返回给客户端,您可以这样做

    (defmethod scan ...
      (let [...]
        (mapv (fn preprocess-result [result]
                (result->map result))
              scanner)))
    

    然后客户可以对他们做任何想做的事。使用 map 代替返回惰性序列。如果客户端需要打开/关闭一个资源,你可以接受它作为参数进行扫描,这样客户端就可以说

    (with-open [r (some-resource)]
      ; or mapv, dorun+map, doall+for, ...
      (doseq [result (scan r ...)]
        (do-stuff-with result)))
    

    【讨论】:

    • 知道了,谢谢。我现在所处的位置是返回一个函数,该函数在结果扫描器上调用 next 直到其耗尽#(let [result (.next result-scanner)] (if (not= result nil) [(Bytes/toString (.getRow result)) (get-to-map result)] nil)) 接下来我正在努力理解的是如何简洁地调用该函数直到它返回 nil,然后爆发。 (let [cursor (hbase.table/scan table "k1" "r2")] ... )
    • 当然。我编辑了答案以添加两个示例,说明如何将预处理结果发送给客户端。在 Clojure 中,将集合(通常是惰性序列)返回给客户端比使用 Java 迭代器或任何类似工作的方法更常见。另一种解决方案是返回一个减速器;为此,您需要使用 Clojure 1.5,但是您可以使用 r/map 进行预处理,让客户端自己进行 r/map(或其他),并且无需担心中间分配。代码的总体感觉非常相似。
    • 查看 Rich 的博客文章了解有关 reducer 的信息:introductionfollow-up。请注意,如果您要减少惰性序列,则通常需要注意关闭底层资源。
    • 明白了,我认为我主要担心的是这些列表是否会变大,例如 hbase 扫描可能会很大,但我想将其用作流。到目前为止我做的最好的是:(let [cursor (hbase.table/scan table "k1" "r2")] (loop [result nil] (let [result (cursor)] (prn result) (if (not= nil result) (recur nil))))) 问题是它打印了一个最终的 nil,这很烦人,但重复出现必须在尾部位置......
    • 如果 HBase 为您提供了一个迭代器,那么您可以在 doseq 中对其进行迭代,并且您将其用作流(之前的结果不会坚持)。您还可以将迭代器包装在惰性序列中——(map f iter)——并将其传递给客户端;如果客户端随后使用doseq 处理它,他们仍然会将其用作流(一旦处理经过它们,惰性序列的初始片段将符合 GC 条件)。您只需要确保在正确的时刻关闭资源,无论如何这对于任何方法都是正确的; with-open 对此有所帮助。
    猜你喜欢
    • 2012-06-07
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多