前言:為什么你需要一個連接池?
如果你正在使用 Node.js (尤其是像 Next.js 這樣的框架) 配合 Prisma 操作 PostgreSQL 數據庫,你很可能在某個階段會遇到那個令人頭疼的錯誤:“Error: Too many clients already
”。這通常發生在應用并發量上升,或者在 Next.js 的構建 (next build
) 階段,因為它會嘗試并行渲染多個頁面,瞬間創建大量數據庫連接。
雖然最直接的反應是增加 PostgreSQL 的 max_connections
,但這只是一個臨時的“創可貼”,它會消耗更多寶貴的服務器內存,并且無法從根本上解決連接風暴的問題。
一個更專業、更具擴展性的解決方案是在你的應用和數據庫之間引入一個中間件——連接池工具。PgBouncer 正是此領域的王者,它輕量、高效,能以極小的資源消耗管理成千上萬的連接。
本指南將詳細記錄我們如何在 Dokploy 平臺上,為一個正在運行的 PostgreSQL 數據庫和一個需要進行靜態站點生成 (SSG) 的 Next.js 應用,成功搭建并配置 PgBouncer 服務。我們將這個過程中的每一步、遇到的每一個坑、以及最終的解決方案都毫無保留地分享出來。
核心架構:我們要做什么?
我們的目標非常明確:將應用架構從“直連”模式升級為通過 PgBouncer 連接池管理的“中間人”模式。
部署前架構 (直連模式):
[Next.js App] —> [PostgreSQL Database]
- 問題: 應用的每一次數據庫操作都可能創建一個新的連接,在高并發或構建時極易耗盡數據庫資源。
部署后架構 (連接池模式):
[Next.js App] —> [PgBouncer Pooler] —> [PostgreSQL Database]
- 優勢: Next.js 應用將所有連接請求都發送給 PgBouncer。PgBouncer 維護一個與 PostgreSQL 之間的小而穩定的連接池,高效地復用這些連接來處理大量請求。數據庫本身將得到極大的保護。
Step 1: 創建 PgBouncer 服務骨架
我們的第一步是在 Dokploy 中創建一個新的應用,用于承載 PgBouncer。
- 在 Dokploy 儀表盤,點擊 “Add new” -> “Application”。
- 在 “Provider” 部分,選擇 Docker 作為來源。
- 在 Docker Image 輸入框中,填入鏡像名稱:
edoburu/pgbouncer
。 - 此時先不要部署,直接進入下一步配置。
Step 2: 暴露所需端口
為了讓服務之間可以通過網絡互相訪問,我們需要將 PostgreSQL 和 PgBouncer 的端口都映射到宿主機上。
-
暴露 PostgreSQL 端口:
- 進入你的 PostgreSQL 數據庫服務的配置頁面。
- 導航到 Advanced -> Ports。
- 添加一條規則,將數據庫的真實端口(例如
5633
)映射出來。- Published Port:
5633
- Published Port Mode:
Host
- Target Port:
5633
- Published Port:
- 保存并 Redeploy PostgreSQL 服務。
-
暴露 PgBouncer 端口:
- 回到我們剛剛創建的 PgBouncer 服務的配置頁面。
- 導航到 Advanced -> Ports。
- 添加一條規則,將 PgBouncer 的監聽端口
6432
映射出來。- Published Port:
6432
- Published Port Mode:
Host
- Target Port:
6432
- Published Port:
- 保存設置。
Step 3: 準備并掛載 PgBouncer 配置文件
現在,我們需要為 PgBouncer 提供它的“靈魂”——配置文件。
1. 在本地準備配置文件
-
pgbouncer.ini
(主配置文件): 創建一個文本文件,填入以下內容。[databases] # 定義數據庫連接。這里的 key (YOUR_DATABASE_NAME) 最好和真實的 dbname 保持一致。 # host 設置為你的服務器公網IP,port 設置為你的 PostgreSQL 真實端口。 YOUR_DATABASE_NAME = host=YOUR_SERVER_PUBLIC_IP port=YOUR_POSTGRES_PORT dbname=YOUR_DATABASE_NAME[pgbouncer] # 監聽所有網絡接口,允許來自 Docker 外部和內部的連接 listen_addr = * # PgBouncer 自身的監聽端口,給 Next.js 應用連接 listen_port = 6432# 認證方式必須是 md5,并且需要指定用戶列表文件 auth_type = md5 auth_file = /etc/pgbouncer/userlist.txt# 定義哪些用戶擁有管理和查看統計的權限 admin_users = YOUR_DATABASE_USERNAME stats_users = YOUR_DATABASE_USERNAME# 連接池模式。transaction 是對大多數應用最安全、最推薦的模式 pool_mode = transaction# 池大小等性能配置 default_pool_size = 20 max_client_conn = 1000# pgbouncer 進程的 pid 文件路徑 pidfile = /var/run/pgbouncer/pgbouncer.pid
-
userlist.txt
(認證文件): 創建第二個文本文件,定義允許連接的用戶名和密碼。# 格式: "用戶名" "密碼" # 必須使用英文雙引號將用戶名和密碼括起來,中間用一個空格隔開。 "YOUR_DATABASE_USERNAME" "YOUR_DATABASE_PASSWORD"
2. 在 Dokploy 中掛載配置文件
- 回到 PgBouncer 服務的 Advanced -> Volumes 設置區域。
- 添加兩個
File Mount
類型的掛載:- 第一個: 將
pgbouncer.ini
的全部內容粘貼進去,Mount Path 設置為/etc/pgbouncer/pgbouncer.ini
。 - 第二個: 將
userlist.txt
的全部內容粘貼進去,Mount Path 設置為/etc/pgbouncer/userlist.txt
。
- 第一個: 將
Step 4: 配置啟動命令并部署 PgBouncer
- 在 PgBouncer 服務的 Advanced -> Run Command 區域,明確告訴容器如何啟動。
- Command:
pgbouncer /etc/pgbouncer/pgbouncer.ini
- Command:
- 完成所有配置后,點擊頁面右上角的 Deploy 按鈕部署 PgBouncer 服務。
- 檢查 PgBouncer 的日志,確保它已正常啟動,并且沒有
broken auth file
或connect failed
等錯誤。
最后回到 General 中點擊 Deploy 就可以部署成功了:
Step 5: 更新 Next.js 應用配置(決勝一步)
現在 PgBouncer 服務已經通過公網 IP 和指定端口(6432
)完全就緒,我們可以直接讓 Next.js 應用連接這個公開的地址。這個方法可以同時解決構建和運行時的連接問題。
-
進入你的 Next.js 應用在 Dokploy 的配置頁面。
-
導航到 Environment 標簽頁。
-
找到
DATABASE_URL
這個環境變量(如果沒有就新建一個)。 -
將其值設置為指向 PgBouncer 的公網地址:
postgresql://YOUR_DATABASE_USERNAME:YOUR_DATABASE_PASSWORD@YOUR_SERVER_PUBLIC_IP:6432/YOUR_DATABASE_NAME?pgbouncer=true
重要: 請確保這里的
YOUR_SERVER_PUBLIC_IP
是您服務器的公網 IP,端口是 PgBouncer 的端口6432
。
[圖片占位符:Next.js 應用中配置 DATABASE_URL
環境變量的截圖]
- 確認構建命令為默認值:在 “General” 標簽頁,確保 “Build Command” 是簡單的
pnpm run build
或npm run build
,不帶任何DATABASE_URL=
的前綴。 - 保存所有修改,然后點擊 Deploy 部署你的 Next.js 應用。
- ?pgbouncer=true 是一個專門的 “兼容模式”開關,你用它來明確地告知 Prisma:“注意,你現在連接的不是一個真正的 PostgreSQL 數據庫,而是一個 PgBouncer 連接池!請調整你的行為模式。”
總結
恭喜你!如果一切順利,你的應用現在應該已經成功構建并運行起來了。
通過這次實踐,我們不僅解決了一個棘手的數據庫連接問題,還掌握了在類 PaaS 平臺(如 Dokploy)上進行復雜服務編排的關鍵技能。我們學會了如何:
- 使用 PgBouncer 管理連接池。
- 通過端口映射和服務名進行服務間通信。
- 利用文件掛載注入自定義配置。
- 通過統一使用公網地址,快速解決在 CI/CD 流程中訪問數據庫的難題。
現在,你的應用架構變得更加健壯、高效且具備彈性,能夠從容應對未來的流量挑戰。