Clojure建立多个数据库连接

Dr *_*son 5 database-connection clojure connman luminus

我们有一个Clojure Web应用程序,供多个项目(> 20)使用,这些项目有多个用户同时登录。所有项目都有自己的MySQL数据库。我们试图找到一种方法来使用一个应用程序实例来满足从用户的项目数据库传递来的用户请求。

以下脚本显示了我们多个连接的原理,应在REPL中执行(具有正确的数据库设置)。

(ns testmultiple.core
  (:require
    [clojure.java.jdbc :as jdbc]
    [compojure.core :refer [defroutes GET ANY routes context]]
    [conman.core :as conman]
    [mount.core :refer [defstate]]))

(def database-urls {:DB1 "jdbc:mysql://localhost:3306/DB1?user=DB1_user&password=DB1_password"
                    :DB2 "jdbc:mysql://localhost:3306/DB2?user=DB2_user&password=DB2_password"})

;; Connects to all databases in pool-specs
(defn connect!
  [pool-specs]
  (reduce merge (map (fn [pool-spec]
                       {(keyword (key pool-spec)) (conman/connect! {:jdbc-url (val pool-spec)})}) pool-specs)))

;; Disconnect from all databases in db-connections
(defn disconnect!
  [db-connections]
  (map (fn [db] (conman/disconnect! (val db))) db-connections))

;; Establish connections to all databases
;; and store connections in *dbs*
(defstate ^:dynamic *dbs*
          :start (connect!
                   database-urls)
          :stop (disconnect! *dbs*))

;; Bind queries to *db* dynamic variable which is bound
;; to each clients database before executing queries
;; The queries file defines the query get-user which
;; returns user by user id
(def ^:dynamic *db* nil)
(conman/bind-connection *db* "sql/queries.sql")

(mount.core/start)

; Define function that executes in current *db* binding
(defn getuser [id] (get-user {:id id}))

; Works, the user with Id 670 is returned from DB1
(with-bindings {#'*db* (:DB1 *dbs*)} (getuser 670))

; Works, the user with Id 670 is returned from DB2
(with-bindings {#'*db* (:DB2 *dbs*)} (getuser 670))
Run Code Online (Sandbox Code Playgroud)

更具体地说,从路由器中的URL请求推断项目。以下代码显示了路由器的原理。访问www.example.com/DB1/page1和www.example.com/DB2/page2将分别显示具有DB1数据的page1和具有DB2数据的page2。

(defn serve-page1 [] (str "page1" (getuser 670)))
(defn serve-page2 [] (str "page2" (getuser 670)))

(def home-routes
  (context "/:project" [project]
    (if (contains? *dbs* (keyword project))
      (routes
        (GET "/page1" []
          (with-bindings {#'*db* ((keyword project) *dbs*)}
            (serve-page1)))
        (GET "/page2" []
          (with-bindings {#'*db* ((keyword project) *dbs*)}
            (serve-page2))))
      (ANY "*" [] (str "Project not found")))))
Run Code Online (Sandbox Code Playgroud)

这将是一个流量很大的应用程序。值得注意的是,我们仍处于开发阶段,因此无法在本地主机上运行多个数据库来测试此解决方案。我们的问题是

  • 是否建立像这样的合理,稳定和可扩展的多个连接?
  • 还有其他更好的方法来路由和动态绑定项目的数据库吗?

Art*_*ldt 2

这样建立多个连接是否合理、稳定、可扩展?

是的,这是一个非常合理的做法。很少有数据库系统受到传出连接数量的限制。JDBC 和 Korma 都可以在 clojure 中很好地处理这个问题。当然,在构建监控和操作相关组件时,您确实需要了解哪些请求依赖于哪个数据库。这样您就可以判断哪个数据库引起了问题。

对于项目数据库的路由和动态绑定还有其他更好的方法吗?

我唯一的建议是将数据库显式传递给每个函数,而不是使用绑定,尽管这是个人风格的意见,并且您的方法显然会起作用。