FastAPI - (psycopg2.OperationalError) 服务器意外关闭了连接

14

我有一个使用FastAPI和SQLAlchemy构建的Web应用程序,它在本地使用Docker正常工作,但在DigitalOcean托管的Postgres数据库上查询时出现错误:

(psycopg2.OperationalError) server closed the connection unexpectedly\n\tThis probably means the server terminated abnormally\n\tbefore or while processing the request.\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是否在受信任的DB来源中
  • 我使用一个中间件在FastAPI端点内访问我的db会话,就像这样:
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的问题很少,例如这个那个以及官方文档。

经过所有尝试,问题仍然存在。我对async Python并不太熟悉,因此可以怀疑问题可能在连接共享方式或其他方面(但目前只使用一个worker)。

更新

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

[Errno 104] Connection reset by peer

看起来原因是一样的,但是对于asyncpg错误看起来不同。

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


在这里使用async的原因是什么?如果不异步调用代码,它还能正常运行吗?鉴于理论上异步函数可以任意顺序启动,我认为这可能是最有可能的原因。 - match
根据fastapi文档,对于同步示例来说去掉async是没有意义的。我尝试了去掉async但结果还是一样。同时,在本地环境下两种情况都可以正常工作,并且使用asyncpg时应该使用async,在本地环境下也能按预期工作。问题只出现在DO托管的数据库中。看起来数据库设置不同(但我无法获取托管数据库配置)。此类似问题中99%的情况只需设置pool_pre_ping即可解决问题,并且在Flask应用程序中使用相同的设置已经解决了DO托管数据库的问题。 - Max
2个回答

10

您是否尝试过向您的sqlalchemycreate_engine添加任何连接参数?这些参数应该允许您维护与数据库的连接。

以下是一些可能有用的libpq连接参数列表。

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连接参数

1
这可能是一个比较笼统的答案,但是以下步骤可以帮助定位问题:
  1. 尝试通过SSH连接,以确保您的请求正常工作,问题不在于数据库。
  2. 检查连接的端口号,看看它们是否与uvicorn、Digital Ocean设置和python脚本匹配。
  3. 尝试暂时允许来自任何IP的访问 - 或许一些奇怪的路由不会让你访问。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接