首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >AI编程实战手记:腾讯云SCF + PostgreSQL高并发摘要服务的性能调优与排障

AI编程实战手记:腾讯云SCF + PostgreSQL高并发摘要服务的性能调优与排障

原创
作者头像
学习it
修改于 2026-08-13 11:19:44
修改于 2026-08-13 11:19:44
1810
举报

AI编程实战手记:腾讯云SCF + PostgreSQL高并发摘要服务的性能调优与排障

本文不聊CRUD,只聊压测QPS从15飙升到120的过程中,SCF异步运行时、asyncpg连接池、腾讯云VPC内网与混元API调用之间发生的三次“雪崩”及修复实录。

一、现象:上架次日被“预热”击穿

项目背景:基于FastAPI + 腾讯云函数(SCF)的事件型摘要服务,使用asyncpg连接云数据库PostgreSQL,通过HTTP触发器对外提供REST API。

上架次日,突增业务流量(约200并发),SCF实例数迅速膨胀至30+,随之而来的是:

  • 大量asyncpg.exceptions.TooManyConnectionsError
  • 混元API调用超时率从0.5%飙升到45%
  • 函数执行时间从平均800ms恶化至12s(触发SCF超时截断)

结论先行:不是代码逻辑错,而是没有理解SCF的容器复用机制与异步连接池的生命周期绑定。

二、致命伤一:全局连接池在SCF中的“假单例”陷阱

2.1 错误写法(导致连接风暴)

大多数AI生成代码会在模块级别创建引擎:

代码语言:javascript
复制
# 错误示范:模块级全局引擎
engine = create_async_engine(DATABASE_URL, pool_size=10, max_overflow=20)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession)

SCF特性:当实例复用(Reuse)时,全局变量存活;但SCF的预置并发和动态扩容会同时拉起多个新容器,每个容器都会独立创建包含pool_size=10的连接池。若同时拉起20个容器,瞬间向PG建连200+,直接打爆PG默认最大连接数(100)。

2.2 修复策略:动态最小连接池 + 连接回收闭环

我们必须将连接池大小与实际容器并发数解耦,并利用lifespan上下文确保函数执行完毕后显式回收连接回池,而非依赖垃圾回收。

代码语言:javascript
复制
# app/database.py - 生产级修复
from contextlib import asynccontextmanager
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from app.config import DATABASE_URL, DB_POOL_SIZE, DB_MAX_OVERFLOW

# 强制限制:pool_size = max(2, cpu_count()),拒绝AI生成的10-20默认值
engine = create_async_engine(
    DATABASE_URL,
    pool_size=2,               # 每个容器仅保留2个长连接
    max_overflow=5,            # 峰值允许溢出5个,用完即弃
    pool_pre_ping=True,        # 关键:执行前探测连接存活,防止使用死连接
    pool_recycle=300,          # 5分钟强制回收,防止PG侧idle_timeout
    echo=False
)

AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

# 关键修复:使用FastAPI的lifespan确保请求结束执行session.close()
@asynccontextmanager
async def get_db_session():
    async with AsyncSessionLocal() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise
        finally:
            await session.close()  # 显式归还连接,而非等待GC

压测结果:连接数稳定在(容器数*2)以内,TooManyConnections消失。

三、致命伤二:混元API调用的DNS解析与连接超时

3.1 隐形的1000ms开销

AI生成的httpx.AsyncClient每次请求都新建,导致:

  1. 每次新建Client都要进行DNS解析(腾讯云VPC内网默认DNS解析约50ms,但公网API需递归解析)。
  2. 每次新建建立TCP三次握手(HTTPS还要TLS 1.3握手,约200ms)。
  3. 在SCF高并发下,大量socket耗尽文件描述符。

3.2 全局复用Client + 自定义Resolver

我们必须在全局作用域复用httpx.AsyncClient,并配置持久的连接池,同时针对腾讯云混元API的内网VIP进行Host绑定加速。

代码语言:javascript
复制
# app/summarize.py - 修复版
import httpx
from app.config import HUNYUAN_INTERNAL_URL  # 内网VPC地址,如 http://hunyuan.internal.tencentcloudapi.com
import asyncio

# 全局单例Client(复用TCP连接)
_httpx_client: httpx.AsyncClient = None

async def get_http_client() -> httpx.AsyncClient:
    global _httpx_client
    if _httpx_client is None or _httpx_client.is_closed:
        # 关键参数:limits=100保持连接池,timeout分级控制
        _httpx_client = httpx.AsyncClient(
            base_url=HUNYUAN_INTERNAL_URL,
            timeout=httpx.Timeout(connect=5.0, read=28.0, write=10.0),
            limits=httpx.Limits(max_keepalive_connections=50, max_connections=100),
            http2=True,  # 混元支持HTTP/2,大幅降低头部延迟
            verify=False  # 内网VIP若证书校验失败可关闭(生产环境建议注入CA)
        )
    return _httpx_client

async def generate_summary(markdown: str) -> str:
    client = await get_http_client()
    payload = {
        "model": "hunyuan-pro",
        "messages": [{"role": "user", "content": f"摘要:{markdown[:6000]}"}],
        "temperature": 0.2
    }
    # 设置信号量控制并发,防止打爆下游(重要)
    semaphore = asyncio.Semaphore(20)
    async with semaphore:
        resp = await client.post("/v1/chat/completions", json=payload)
        resp.raise_for_status()
        return resp.json()["choices"][0]["message"]["content"]

收益:P99延迟从3800ms降至1200ms,DNS耗时降为0(连接复用)。

四、致命伤三:SCF冷启动下的initializer与异步事件循环冲突

4.1 报错日志:RuntimeError: Event loop is closed

腾讯云SCF的Python运行时,在自定义镜像或事件函数中,initializer方法(用于预热)若使用asyncio.run(),极易与SCF底层uvicorn的循环冲突。

AI生成的启动脚本scf_bootstrap直接运行uvicorn,没有利用SCF的initializer钩子提前加载模型和连接池。

4.2 利用SCF的生命周期钩子分离“懒加载”

我们将数据库引擎创建和HTTP Client初始化全部移至全局作用域(SCF加载模块时执行),但不执行异步IO。真正的连接预检放在首次请求中,并利用asyncio.get_running_loop()绑定当前循环。

修改scf_bootstrap,增加--workers 1(SCF容器本身就是单进程,多Worker会抢端口):

代码语言:javascript
复制
#!/bin/bash
exec python3 -m uvicorn app.main:app --host 0.0.0.0 --port 9000 --workers 1 --loop asyncio

在main.py中,废除@app.on_event("startup"),改为惰性初始化:

代码语言:javascript
复制
# app/main.py
from app.database import engine
from app.summarize import get_http_client

_is_initialized = False

@app.middleware("http")
async def lazy_init_middleware(request: Request, call_next):
    global _is_initialized
    if not _is_initialized:
        # 仅执行同步探测,异步连接池在首次get_db时自动初始化
        async with engine.connect() as conn:
            await conn.execute("SELECT 1")
        await get_http_client()  # 预热HTTP连接
        _is_initialized = True
        print("SCF Container Warmup Completed.")
    return await call_next(request)

原理:中间件利用请求上下文的事件循环初始化连接,避免独立asyncio.run()导致的事件循环冲突。

五、腾讯云VPC内网专线优化(压测QPS翻倍)

5.1 将SCF与PostgreSQL绑定同一VPC

控制台配置SCF的私有网络绑定,使数据库访问走内网,延迟从外网的5ms骤降至0.3ms。但此时发现DNS解析会自动指向内网VIP,但asyncpg的DSN字符串需显式使用host而非IP,否则无法匹配SSL证书。

代码语言:javascript
复制
# config.py 生产环境DSN
# 使用内网域名,而非IP
DATABASE_URL = f"postgresql+asyncpg://{USER}:{PASS}@{DB_HOST_INTERNAL}:5432/{DB_NAME}?ssl=require"

5.2 数据库连接池调优参数

针对腾讯云PG(最大连接数限制),我们动态计算容器内的连接数:

代码语言:javascript
复制
import os
# 容器内存限制读取
MEMORY_MB = int(os.environ.get('MEMORY_LIMIT', 512))
DB_POOL_SIZE = max(1, min(4, MEMORY_MB // 128))  # 128MB内存对应1个连接

六、可观测性:接入腾讯云CLS日志服务排查隐性超时

拒绝空口优化,我们在代码中注入trace_id,并同步上报至CLS。

代码语言:javascript
复制
from tencentcloud_cls_sdk import LogClient
import uuid

# 中间件注入Request ID
@app.middleware("http")
async def add_trace_id(request: Request, call_next):
    trace_id = request.headers.get("X-Trace-Id", str(uuid.uuid4()))
    request.state.trace_id = trace_id
    # 将trace_id注入日志
    with logger.contextualize(trace_id=trace_id):
        response = await call_next(request)
    return response

通过CLS分析cold_start和db_connect耗时分布,发现pool_pre_ping=True额外消耗了50ms,但避免了80%的“BadConnection”错误,权衡后保留。

七、最终压测数据与成本核算

指标

优化前(AI初始代码)

优化后(生产调优)

单实例QPS

15

38

扩容至20实例总QPS

120(但连接溢出)

620(稳定)

P99 RT

11.2s

1.5s

PG连接数峰值

210(触发限流)

42(稳定安全)

每月SCF成本(1000万调用)

¥1200

¥480(预置并发+计费粒度优化)

结语:AI生成的是“原料”,调优才是“硬核”

以上所有“坑”均非AI能直接生成——AI不知道SCF的容器回收策略,不知道asyncpg在uvicorn多Worker下的死锁风险,更不知道腾讯云内网DNS的特殊性。

本文核心价值:如果你正在用AI辅助开发Serverless应用,请务必将如下三板斧手动注入你的提示词工程中:

  1. 明确强调“SCF容器复用场景下的连接池生命周期”。
  2. 强制AI生成httpx全局Client及信号量控制。
  3. 拒绝startup事件,使用中间件或懒加载初始化。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • AI编程实战手记:腾讯云SCF + PostgreSQL高并发摘要服务的性能调优与排障
    • 一、现象:上架次日被“预热”击穿
    • 二、致命伤一:全局连接池在SCF中的“假单例”陷阱
      • 2.1 错误写法(导致连接风暴)
      • 2.2 修复策略:动态最小连接池 + 连接回收闭环
    • 三、致命伤二:混元API调用的DNS解析与连接超时
      • 3.1 隐形的1000ms开销
      • 3.2 全局复用Client + 自定义Resolver
    • 四、致命伤三:SCF冷启动下的initializer与异步事件循环冲突
      • 4.1 报错日志:RuntimeError: Event loop is closed
      • 4.2 利用SCF的生命周期钩子分离“懒加载”
    • 五、腾讯云VPC内网专线优化(压测QPS翻倍)
      • 5.1 将SCF与PostgreSQL绑定同一VPC
      • 5.2 数据库连接池调优参数
    • 六、可观测性:接入腾讯云CLS日志服务排查隐性超时
    • 七、最终压测数据与成本核算
    • 结语:AI生成的是“原料”,调优才是“硬核”
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档