前言:为什么你需要一个连接池?
如果你正在使用 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 流程中访问数据库的难题。
现在,你的应用架构变得更加健壮、高效且具备弹性,能够从容应对未来的流量挑战。