创建全局client

如何在服务已启动就create_async_client,并且是全局的

这个场景非常典型,而且你这个问题已经问到正确工程姿势了 👍
下面直接给你可落地、不会踩坑的做法,从最推荐到通用方案。


✅ 最推荐:应用启动时创建,全局复用

核心原则只有一句话:

服务启动时创建一次 create_async_client
服务运行期间全局复用,
服务关闭时 close()

下面按常见服务形态给你示例。


一、FastAPI / Starlette(最常见)

方式 1:lifespan(官方推荐,最新)

from fastapi import FastAPI
from contextlib import asynccontextmanager
from clickhouse_connect.driver import create_async_client

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.ch = await create_async_client(
        host="localhost",
        port=8123,
        username="default",
        password=""
    )
    yield
    await app.state.ch.close()

app = FastAPI(lifespan=lifespan)

@app.get("/ping")
async def ping():
    result = await app.state.ch.query("SELECT 1")
    return result.result_rows

✅ 服务启动就建好
✅ 全局唯一
✅ 不会被多次创建
✅ 适配 uvicorn / gunicorn / reload


方式 2:老一点但仍可用(startup / shutdown

app = FastAPI()

@app.on_event("startup")
async def startup():
    app.state.ch = await create_async_client(...)

@app.on_event("shutdown")
async def shutdown():
    await app.state.ch.close()

二、不是 FastAPI,而是“普通 async 服务”

比如你自己写的 worker / consumer / scheduler。

用一个全局变量(OK,但要有边界)

from clickhouse_connect.driver import create_async_client

ch_client = None

async def init_clickhouse():
    global ch_client
    ch_client = await create_async_client(...)

async def shutdown_clickhouse():
    await ch_client.close()
async def main():
    await init_clickhouse()
    # 服务主逻辑
    ...

⚠️ 注意:

  • 一定在 事件循环里 await
  • 不要在 import 时直接调用

三、Django / ASGI(稍微高级一点)

ASGI lifespan(和 FastAPI 本质一样)

# asgi.py
from contextlib import asynccontextmanager
from clickhouse_connect.driver import create_async_client

@asynccontextmanager
async def lifespan(scope, receive, send):
    scope["ch"] = await create_async_client(...)
    yield
    await scope["ch"].close()

四、⚠️ 千万不要踩的坑(很重要)

❌ 在模块 import 时创建

# ch.py
client = await create_async_client(...)  # ❌ 直接炸

原因:

  • import 时没有 event loop
  • 多进程下会被执行多次
  • 很容易造成连接泄漏

❌ 每个请求创建

async def handler():
    client = await create_async_client(...)  # ❌

会导致:

  • 连接池失效
  • ClickHouse 连接数爆炸

五、多进程 / 多 worker 要注意什么?

如果你用的是:

uvicorn app:app --workers 4

那意味着:

每个 worker 进程都会有一个自己的 ClickHouse client + 连接池

这是 正确且预期的行为

所以:

  • 不要试图在进程间共享 client
  • 按 worker 数量评估 CH 的 max_connections

六、一句话模板(记住这个就够)

async client = 应用生命周期资源
创建在 startup / lifespan
挂在 app.state / 全局容器
关闭在 shutdown


如果你愿意,我可以帮你:

  • worker 数 × 并发数 算 ClickHouse 连接上限
  • 给你一个 FastAPI + ClickHouse 的最佳实践模板
  • 看你现在的初始化代码有没有隐藏问题
posted @ 2026-02-06 15:24  X1OO  阅读(4)  评论(0)    收藏  举报