为什么我的 Clojure 应用程序需要_分钟_才能连接到 Postgres?

Why does my Clojure application take _minutes_ to connect to Postgres?

我的项目正在使用 Docker Compose 创建两个服务(app 和 postgres)。在本地,应用程序(使用 Compojure、JDBC、Korma、Ragtime 等的 Clojure 应用程序)立即毫无问题地连接到 postgres。但是,当我出于测试目的将我的应用程序部署到 Digital Ocean Droplet(1 GB RAM/30 GB Disk/Ubuntu 16.04.2 x64)时,应用程序似乎需要 分钟 连接到 postgres - 例如Korma 插件挂起很多分钟,然后最终开始正常工作。 Droplet 很小,但它似乎并不缺乏资源(基于 htop 的输出)。

以下是我申请的相关部分:

;; project.clj
(defproject backend "0.1.0-SNAPSHOT"
  :min-lein-version "2.0.0"
  :dependencies [[com.grammarly/perseverance "0.1.2"]
                 [commons-codec/commons-codec "1.4"]
                 [compojure "1.4.0"]
                 [environ "1.0.3"]
                 [clj-http "2.3.0"]
                 [korma "0.4.3"]
                 [lock-key "1.4.1"]
                 [me.raynes/fs "1.4.6"]
                 [midje "1.6.3"]
                 [org.clojure/clojure "1.8.0"]
                 [org.clojure/core.async "0.3.441"]
                 [org.clojure/java.jdbc "0.7.0-alpha2"]
                 [postgresql "9.3-1102.jdbc41"]
                 [ragtime "0.6.0"]
                 [ring-cors "0.1.7"]
                 [ring-mock "0.1.5"]
                 [ring/ring-defaults "0.1.5"]
                 [ring/ring-json "0.4.0"]]
  :plugins [[lein-environ "1.0.3"]
            [lein-midje "3.1.3"]
            [lein-ring "0.9.7"]]
  :aliases {"migrate"  ["run" "-m" "backend.db/ragtime-migrate"]
            "rollback" ["run" "-m" "backend.db/ragtime-rollback"]}
  :ring {:handler backend.handler/app}
  :profiles
  {:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
                        [ring/ring-mock "0.3.0"]]}})

;; db.clj
(ns backend.db
  (:use [korma.core]
        [korma.db])
  (:require [clojure.string :as string]
            [environ.core :as environ]
            [lock-key.core :refer [encrypt-as-base64 decrypt-from-base64]
                           :rename {encrypt-as-base64 encrypt
                                    decrypt-from-base64 decrypt}]
            [ragtime.jdbc :as jdbc]
            [ragtime.repl :as repl]))

(def database-host (environ/env :postgres-port-5432-tcp-addr)) ;; set by Docker
(def database-name (environ/env :database-name))
(def database-password (environ/env :database-password))
(def database-port (environ/env :postgres-port-5432-tcp-port)) ;; set by Docker
(def database-sslmode (environ/env :database-sslmode))
(def database-user (environ/env :database-user))
(def database-url (str "jdbc:postgresql://"
                       database-host
                       ":"
                       database-port
                       "/"
                       database-name
                       "?user="
                       database-user
                       "&password="
                       database-password))

(defn load-config []
    {:datastore  (jdbc/sql-database {:connection-uri database-url})
     :migrations (jdbc/load-resources "migrations")})

(defn ragtime-migrate []
  (repl/migrate (load-config)))

(defn ragtime-rollback []
  (repl/rollback (load-config)))

(defdb db (postgres {:db database-name
                     :host database-host
                     :password database-password
                     :port database-port
                     :user database-user
                     :sslmode database-sslmode}))

(defentity engagements)

(def lock (environ/env :lock))

(defn query-engagement [id]
  (let [engagement (first
                     (select
                       engagements
                       (where {:id (read-string id)})))
        decrypted-email (->
                          (:email_address engagement)
                          (decrypt lock))]
    (conj engagement {:email_address decrypted-email})))

(defn create-engagement [email-address image-path]
  (let [encrypted-email (encrypt email-address lock)]
    (insert engagements
      (values [{:email_address encrypted-email
                :image_path image-path}]))))

;; docker-compose.yml
app:
  build: .
  volumes:
    - .:/app
  ports:
    - "127.0.0.1:3000:3000"
  links:
    - postgres
postgres:
  build: .
  dockerfile: Dockerfile-postgres
  expose:
    - "5432"

我做错了什么吗?这可能是 JDBC 连接池问题?是否有调试此类问题的约定?

更新:如果我直接在 Digital Ocean Droplet 上 运行 应用程序,而不是通过 Docker。

,我可以确认问题仍然存在

我 运行 遇到了与 Hibernate 类似的问题,我设法缩小了它的范围以减慢 JDBC 驱动程序中的元数据获取速度,特别是如果数据库服务器有许多不同的数据库(没有重要的是您是否可以访问它们)。

在这种情况下可能是类似的情况,所以我建议您看看是否可以在 Korma 中禁用元数据获取。

我没有在 JDBC 驱动程序本身中找到它的设置,但是您应该能够通过将 logLevel=2 参数添加到 jdbc-url 因为它可能会向您显示问题所在的更多详细信息。

TLDR;

将以下标志添加到 project.clj 解决了我的问题::jvm-opts ["-Djava.security.egd=file:/dev/urandom"] (HT to Redditor /u/fitzoh!)

据我了解,我看到的问题是由 JVM 向 /dev/random 发出随机数阻塞请求引起的。因为 Droplet 没有做任何事情(IO、网络请求等),所以它需要很长时间(在我的例子中是几分钟)来生成足够的熵来让 /dev/random 开始生成随机数。

一种解决方法是使用 /dev/urandom,它不会等待熵累积,而是会愉快地生成(低质量)随机数。来自 this 优秀的 Digital Ocean 教程,

... however, since it's a non-blocking device, it will continue producing “random” data, even when the entropy pool runs out. This can result in lower quality random data, as repeats of previous data are much more likely. Lots of bad things can happen when the available entropy runs low on a production server, especially when this server performs cryptographic functions.

另一个看似更强大的解决方法(再次来自 the excellent DO tutorial) is to use a software solution, like haveged

Based on the HAVEGE principle, and previously based on its associated library, haveged allows generating randomness based on variations in code execution time on a processor. Since it's nearly impossible for one piece of code to take the same exact time to execute, even in the same environment on the same hardware, the timing of running a single or multiple programs should be suitable to seed a random source. The haveged implementation seeds your system's random source (usually /dev/random) using differences in your processor's time stamp counter (TSC) after executing a loop repeatedly.