创建全局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 的最佳实践模板
- 看你现在的初始化代码有没有隐藏问题

浙公网安备 33010602011771号