6

我们有一个 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))

更具体地说,该项目是从路由器中的 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")))))

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

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

1 回答 1

2

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

是的,这是一个非常合理的方法。很少有数据库系统受到传出连接数的限制。JDBC 和 Korma 都可以在 clojure 中很好地处理这个问题。当然,在构建监控和操作相关组件时,您确实需要知道哪些请求取决于哪个数据库。所以你可以知道哪个数据库导致了问题。

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

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

于 2016-07-29T20:10:18.550 回答