【问题标题】:Clojure : Get "OutOfMemoryError Java heap space" when parsing big log fileClojure:解析大日志文件时获取“OutOfMemoryError Java heap space”
【发布时间】:2013-11-20 03:45:52
【问题描述】:

全部。
我想使用 Clojure 解析大日志文件。
并且每行记录的结构是“UserID,Lantitude,Lontitude,Timestamp”。
我实施的步骤是:
----> 读取日志文件 & 获取 top-n 用户列表
----> 查找每个 top-n 用户的记录并存储在单独的日志文件(UserID.log)中。

实现源码:

;======================================================
(defn parse-file
  ""
  [file n]
  (with-open [rdr (io/reader file)]
    (println "001 begin with open ")
    (let [lines (line-seq rdr)
          res (parse-recur lines)
          sorted
          (into (sorted-map-by (fn [key1 key2]
                                 (compare [(get res key2) key2]
                                          [(get res key1) key1])))
                res)]
      (println "Statistic result : " res)
      (println "Top-N User List : " sorted)
      (find-write-recur lines sorted n)
      )))

(defn parse-recur
  ""
  [lines]
  (loop [ls  lines
         res {}]
    (if ls
      (recur (next ls)
               (update-res res (first ls))) 
      res)))

(defn update-res
  ""
  [res line]
  (let [params (string/split line #",")
        id     (if (> (count params) 1) (params 0) "0")]
    (if (res id)
      (update-in res [id] inc)
      (assoc res id 1))))

(defn find-write-recur
  "Get each users' records and store into separate log file"
  [lines sorted n]
  (loop [x n
         sd sorted
         id (first (keys sd))]
    (if (and (> x 0) sd)
      (do (create-write-file id
                             (find-recur lines id))
          (recur (dec x)
                 (rest sd)
                 (nth (keys sd) 1))))))

(defn find-recur
  ""
  [lines id]
  (loop [ls lines
           res []]
    (if ls
      (recur (next ls)
               (update-vec res id (first ls)))
      res)))

(defn update-vec
  ""
  [res id line]
  (let [params (string/split line #",")
        id_        (if (> (count params) 1) (params 0) "0")]
        (if (= id id_ )
          (conj res line)
          res)))

(defn create-write-file
  "Create a new file and write information into the file."
  ([file info-lines]
   (with-open [wr (io/writer (str MAIN-PATH file))]
     (doseq [line info-lines] (.write wr (str line "\n")))
     ))
  ([file info-lines append?]
   (with-open [wr (io/writer (str MAIN-PATH file) :append append?)]
     (doseq [line info-lines] (.write wr (str line "\n"))))
   ))
;======================================================

我在 REPL 中使用命令 (parse-file "./DATA/log.log" 3) 测试了这个 clj,并得到了结果:

记录-----大小-----时间----结果
1,000-------42KB----- 10,000--------420KB---- 100,000-----4.3MB----3s-----OK
1,000,000---43MB-----15s-----OK
6,000,000---258MB---->20M----"OutOfMemoryError Java 堆空间 java.lang.String.substring (String.java:1913)"

================================================ =======
这是一个问题:
1.当我尝试解析大日志文件时如何解决错误,例如 > 200MB
2.如何优化功能运行更快?
3. 有超过1G大小的日志,函数如何处理。

我还是 Clojure 的新手,任何建议或解决方案都将不胜感激~
谢谢

【问题讨论】:

    标签: clojure out-of-memory


    【解决方案1】:

    作为对您问题的直接回答;来自一些 Clojure 经验。

    1. 内存不足的快速而肮脏的修复归结为为 JVM 提供更多内存。您可以尝试将此添加到您的project.clj

      :jvm-opts ["-Xmx1G"] ;; or more
      

      这将使 Leiningen 启动具有更高内存上限的 JVM。

    2. 无论你如何工作,这种工作都会占用大量内存。 @Vidya 使用库的建议绝对值得考虑。但是,您可以进行一项优化,这应该会有所帮助。

      每当你处理你的(line-seq ...) 对象(一个惰性序列)时,你应该确保将它维护为一个惰性序列。对它执行next 会将整个事情立即拉入内存。请改用rest。看看 clojure 网站,尤其是 laziness 上的部分:

      (rest aseq) - 返回一个可能为空的 seq,从不为零

      [剪辑]

      剩余项目的(可能)延迟路径,如果有的话

      您甚至可能想要遍历日志两次——一次是从每行中提取用户名作为惰性序列,另一次是过滤掉这些用户。这将最大限度地减少您在任何时候持有的文件数量。

    3. 确保您的函数是惰性的应该可以减少将文件作为内存中的序列创建的绝对开销。这是否足以解析一个1G的文件,我想我不能说。

    【讨论】:

    • 我认为增强记忆力只会治标不治本,但您关于保持懒惰的观点非常棒。
    【解决方案2】:

    您绝对不需要 Cascalog 或 Hadoop 来解析不适合您的 Java 堆的文件。 This SO question 提供了一些如何惰性处理大文件的工作示例。要点是您需要在遍历惰性序列时保持文件打开。以下是在类似情况下对我有用的方法:

    (defn lazy-file-lines [file]
      (letfn [(helper [rdr]
                      (lazy-seq
                        (if-let [line (.readLine rdr)]
                          (cons line (helper rdr))
                          (do (.close rdr) nil))))]
             (helper (clojure.java.io/reader file))))
    

    你可以通过这个惰性序列mapreducecount等:

    (count (lazy-file-lines "/tmp/massive-file.txt"))
    ;=> <a large integer>
    

    解析是一个单独的、更简单的问题。

    【讨论】:

      【解决方案3】:

      我对 Clojure 也比较陌生,所以我看不到明显的优化。希望有经验的朋友能给点建议。但我觉得这只是数据量太大而无法使用手头工具的问题。

      出于这个原因,我建议使用Cascalog,它是对 Hadoop 的抽象或使用 Clojure 的本地计算机。我认为查询大日志文件的语法对您来说非常简单。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-07
        • 1970-01-01
        • 1970-01-01
        • 2015-05-22
        • 1970-01-01
        • 2017-03-28
        相关资源
        最近更新 更多