【问题标题】:FastAPI - (psycopg2.OperationalError) server closed the connection unexpectedlyFastAPI - (psycopg2.OperationalError) 服务器意外关闭连接
【发布时间】:2021-08-24 14:46:21
【问题描述】:

我有一个使用 FastAPI 和 SQLAlchemy 构建的 Web 应用程序,它在 Docker 本地运行良好,但在 DigitalOcean 上使用托管 Postgres DB 时数据库查询失败并出现错误:

(psycopg2.OperationalError) 服务器意外关闭连接\n\t这可能意味着服务器异常终止\n\t在处理请求之前或期间。\n\n(此错误的背景:http://sqlalche.me/e/14/e3q8)"}

我之前在使用 Flask 时遇到过这个错误,问题是我必须设置引擎选项 pool_pre_ping=True 并将我的集群/droplet IP 添加到数据库的可信来源。但看起来使用 FastAPI 是不够的。我还能做些什么来成功执行查询?

背景

  • Python 3.9
  • DigitalOcean 托管 Postgres 13
  • psycopg==2.8.6 但也尝试了 2.8.5(对我来说 Flask 在类似情况下 100% 有效)和 2.7.4 以防万一
  • 我设置了pool_pre_ping=True
    • 我在使用session.get_bind().pool._pre_ping 的请求之前检查了它是否真的设置为True,它实际上是True
  • 我检查了我的集群节点的 IP 是否在数据库可信来源中
  • 我使用一个 uvicorn.workers.UvicornH11Worker worker 运行带有 gunicorn 的应用程序
  • 我使用中间件在 FastAPI 端点中访问我的数据库会话,如下所示:
class DBMiddleware:
    def __init__(self, app, sqlalchemy_uri):
        self.app = app
        self.sqlalchemy_uri = sqlalchemy_uri
        self.engine = None

    async def __call__(self, scope: Scope, receive: Receive, send: Send):
        if scope['type'] not in ['http', 'websocket']:
            await self.app(scope, receive, send)
            return

        if not self.engine:
            self.engine = create_engine(self.sqlalchemy_uri, pool_pre_ping=True, pool_recycle=3600)

        session = Session(autoflush=False, autocommit=False, bind=self.engine)
        scope['db'] = session
        await self.app(scope, receive, send)
        session.close()


def get_db(request: Request):
    return request.scope.get('db')

...

@app.on_event('startup')
async def startup():
    ...
    app.add_middleware(DBMiddleware, sqlalchemy_uri=config.SQLALCHEMY_DATABASE_URI)

@router.post('/endpoint')
async def endpoint(db: Session = Depends(get_db)):
    ...
  • 此外,我尝试将全局定义的引擎与会话上下文一起使用(只是为了检查),但仍然具有相同的行为,因此看起来中间件不是问题
  • Postgres 端没有有用的日志
  • 我还尝试将我的应用查询更改为 db.execute('SELECT 1'),以防出现一些奇怪的超时或其他问题 - 还是一样
  • 我阅读了很多关于 psycopg2 的类似问题,而关于我能找到的 FastAPI 的问题很少,例如thisthat 当然还有官方文档。

经过所有尝试,问题仍然存在。我不太了解async Python,所以我怀疑问题可能在于连接的共享方式或其他问题(但我目前只使用一名工作人员)。

更新

我尝试切换到asyncpg(文档:https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html)。也可以在本地工作,但在 DigitalOcean 查询挂起时,我收到以下错误:

[Errno 104] Connection reset by peer

看起来原因相同,但 asyncpg 的错误看起来不同。

还尝试在 DigitalOcean 上创建连接池并连接到它 - 仍然是同样的错误。

【问题讨论】:

  • 这里使用异步的原因是什么?如果不异步调用,代码会起作用吗?鉴于异步函数理论上可以相对于彼此以任何顺序启动,我认为这是最可能的原因。
  • 是的,根据 fastapi 文档,同步示例没有意义。我试图删除async,但还是一样。此外,它在两种情况下都可以在本地正常工作,并且使用 asyncpg 应该有 async 并且它也可以按预期在本地工作。只有 DO 托管的数据库才会出现问题。看起来数据库设置不同(但我无法获得托管数据库配置)。同样在 99% 的类似问题中,只需设置 pool_pre_ping 即可解决问题,并且在 DO 托管数据库的相同设置中也为我解决了问题,但当它是 Flask 应用程序时。

标签: python postgresql sqlalchemy digital-ocean fastapi


【解决方案1】:

您是否尝试将任何connect_args 添加到您的sqlalchemy create_engine?这些参数应该允许您保持与数据库的连接。

这里列出了可能有用的各种libpq connection parameters

create_engine(self.sqlalchemy_uri, 
              pool_pre_ping=True, 
              pool_recycle=3600, # this line might not be needed
              connect_args={
                  "keepalives": 1,
                  "keepalives_idle": 30,
                  "keepalives_interval": 10,
                  "keepalives_count": 5,
              }
            )

值得注意的是,任何使用psycopg 的人也可以使用这些libpq 连接参数

【讨论】:

    【解决方案2】:

    这可能是一个有点笼统的答案,但这些步骤将有助于定位问题:

    1. 尝试通过 SSH 进行连接,这样您就可以确保您的请求有效并且原因不在数据库中。
    2. 检查连接的端口号,看看它们是否与 uvicorn、Digital Ocean 设置和 python 脚本匹配。
    3. 尝试暂时允许来自任何 IP 的访问 - 也许某些奇怪的路由不允许您访问

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-20
      • 2021-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-12
      • 1970-01-01
      相关资源
      最近更新 更多