在生产环境中运行 AI 推理服务时,优雅关闭(Graceful Shutdown)不是可选项,而是必选项。我曾在凌晨三点被报警电话吵醒——不是因为服务挂了,而是因为 2000 个正在处理的请求全部被强制中断,用户数据永久丢失。这篇文章我将分享如何用正确的策略关闭 AI 服务,同时推荐一个国内开发者的最优选择:立即注册 HolySheep API,它提供国内直连 <50ms 的延迟和 ¥1=$1 的无损汇率。
HolySheheep AI vs 官方 API vs 其他中转站核心对比
| 对比维度 | HolySheep AI | 官方 OpenAI/Anthropic | 其他中转站 |
|---|---|---|---|
| 汇率 | ¥1=$1,无损 | ¥7.3=$1(含汇损) | ¥5-7=$1(中间商赚差价) |
| 国内延迟 | <50ms | >200ms(跨境抖动) | 80-150ms |
| 充值方式 | 微信/支付宝 | Visa/Mastercard | 参差不齐 |
| 免费额度 | 注册即送 | $5(需境外支付方式) | 极少 |
| GPT-4.1 Output | $8/MTok | $8/MTok | $8-10/MTok |
| Claude Sonnet 4.5 | $15/MTok | $15/MTok | $15-18/MTok |
| DeepSeek V3.2 | $0.42/MTok | $0.42/MTok | $0.5-0.8/MTok |
我个人使用 HolySheep 半年下来,同样的 API 调用量,每月成本从 800 元降到了 320 元,而且充值秒到账,再也不用折腾境外信用卡。
为什么 AI 推理服务需要优雅关闭?
AI 推理服务与普通 HTTP 服务不同,一次完整的推理可能耗时 5-30 秒。当收到 SIGTERM 或 SIGINT 信号时:
- 普通关闭:直接杀掉进程,正在执行的推理被中断,返回错误给客户端
- 优雅关闭:停止接收新请求,等待现有请求完成后再退出,保证数据一致性
对于 AI 服务来说,优雅关闭还涉及一个关键问题:流式输出的上下文完整性。想象一下用户正在等待一个 2000 token 的回复,结果在输出 1500 token 时连接被断开——这比服务直接报错更糟糕。
Python FastAPI 优雅关闭最佳实践
基础版:使用 lifespan 和信号处理
import signal
import sys
from contextlib import asynccontextmanager
from fastapi import FastAPI
import uvicorn
全局变量控制新请求接收
shutdown_event = None
@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动时初始化
global shutdown_event
shutdown_event = asyncio.Event()
print("✅ AI 推理服务启动完成")
yield
# 关闭时:停止接收新请求
print("🔄 收到关闭信号,等待处理中...")
shutdown_event.set()
# 等待最多 30 秒让现有请求完成
await asyncio.wait_for(wait_for_requests_complete(), timeout=30)
print("✅ 所有请求处理完毕,服务关闭")
async def wait_for_requests_complete():
"""等待所有正在处理的请求完成"""
# 实际实现需要配合请求计数器
pending_count = get_pending_request_count()
while pending_count > 0:
print(f"⏳ 等待 {pending_count} 个请求完成...")
await asyncio.sleep(1)
pending_count = get_pending_request_count()
app = FastAPI(lifespan=lifespan)
请求计数器
active_requests = 0
request_lock = asyncio.Lock()
@app.middleware("http")
async def block_new_requests_on_shutdown(request, call_next):
global active_requests
if shutdown_event and shutdown_event.is_set():
return JSONResponse(
status_code=503,
content={"error": "Service is shutting down", "retry_after": 30}
)
async with request_lock:
active_requests += 1
try:
response = await call_next(request)
finally:
async with request_lock:
active_requests -= 1
return response
def get_pending_request_count():
return active_requests
if __name__ == "__main__":
# 注册信号处理器
def handle_signal(signum, frame):
print(f"\n📡 收到信号 {signum},开始优雅关闭...")
sys.exit(0)
signal.signal(signal.SIGTERM, handle_signal)
signal.signal(signal.SIGINT, handle_signal)
uvicorn.run(app, host="0.0.0.0", port=8000)
进阶版:集成 HolySheep API 的完整示例
import asyncio
import signal
import sys
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
import httpx
HolySheep API 配置
HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY"
HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"
MODEL_NAME = "gpt-4.1"
app = FastAPI(title="AI 推理服务 - 优雅关闭演示")
优雅关闭控制
class GracefulShutdown:
def __init__(self):
self.is_shutting_down = False
self.active_requests = 0
self.semaphore = asyncio.Semaphore(10) # 最大并发数
self.lock = asyncio.Lock()
async def acquire(self):
if self.is_shutting_down:
raise HTTPException(status_code=503, detail="Service shutting down")
async with self.lock:
self.active_requests += 1
async def release(self):
async with self.lock:
self.active_requests -= 1
def initiate(self):
self.is_shutting_down = True
print(f"🔄 关闭流程启动,剩余 {self.active_requests} 个请求")
async def wait_complete(self, timeout: float = 60):
start = asyncio.get_event_loop().time()
while self.active_requests > 0:
if asyncio.get_event_loop().time() - start > timeout:
print(f"⚠️ 超时,强制关闭(剩余 {self.active_requests} 请求)")
return False
await asyncio.sleep(1)
print(f"⏳ 等待请求完成... ({self.active_requests} 剩余)")
return True
shutdown_controller = GracefulShutdown()
信号处理
def setup_signal_handlers():
def handler(signum, frame):
print(f"\n📡 收到 SIGTERM/SIGINT,开始优雅关闭...")
shutdown_controller.initiate()
# 给 uvicorn 发送自己的关闭信号
asyncio.get_event_loop().call_soon(lambda: asyncio.create_task(shutdown_task()))
signal.signal(signal.SIGTERM, handler)
signal.signal(signal.SIGINT, handler)
async def shutdown_task():
success = await shutdown_controller.wait_complete(timeout=30)
if success:
print("✅ 优雅关闭成功")
sys.exit(0)
class ChatRequest(BaseModel):
messages: list
temperature: float = 0.7
max_tokens: int = 1000
@app.post("/v1/chat/completions")
async def chat_completions(request: ChatRequest):
"""调用 HolySheep API 进行聊天推理"""
await shutdown_controller.acquire()
try:
async with httpx.AsyncClient(timeout=120.0) as client:
response = await client.post(
f"{HOLYSHEEP_BASE_URL}/chat/completions",
headers={
"Authorization": f"Bearer {HOLYSHEEP_API_KEY}",
"Content-Type": "application/json"
},
json={
"model": MODEL_NAME,
"messages": request.messages,
"temperature": request.temperature,
"max_tokens": request.max_tokens,
"stream": False
}
)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
raise HTTPException(status_code=e.response.status_code, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
finally:
await shutdown_controller.release()
@app.post("/v1/chat/completions/stream")
async def chat_completions_stream(request: ChatRequest):
"""流式调用 HolySheep API(关键场景)"""
await shutdown_controller.acquire()
async def generate():
try:
async with httpx.AsyncClient(timeout=120.0) as client:
async with client.stream(
"POST",
f"{HOLYSHEEP_BASE_URL}/chat/completions",
headers={
"Authorization": f"Bearer {HOLYSHEEP_API_KEY}",
"Content-Type": "application/json"
},
json={
"model": MODEL_NAME,
"messages": request.messages,
"temperature": request.temperature,
"max_tokens": request.max_tokens,
"stream": True
}
) as response:
async for line in response.aiter_lines():
if line.startswith("data: "):
if shutdown_controller.is_shutting_down:
yield "data: [DONE]\n\n"
break
yield line + "\n\n"
finally:
await shutdown_controller.release()
return StreamingResponse(generate(), media_type="application/x-ndjson")
if __name__ == "__main__":
setup_signal_handlers()
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Go 语言优雅关闭实现
对于追求高性能的场景,Go 是更好的选择。以下是集成 HolySheep API 的完整方案:
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"
)
const (
HolySheepAPIKey = "YOUR_HOLYSHEEP_API_KEY"
HolySheepBaseURL = "https://api.holysheep.ai/v1"
ModelName = "gpt-4.1"
)
type ChatRequest struct {
Messages []Message json:"messages"
Temperature float64 json:"temperature"
MaxTokens int json:"max_tokens"
}
type Message struct {
Role string json:"role"
Content string json:"content"
}
type GracefulShutdown struct {
isShuttingDown bool
activeRequests int
mu sync.Mutex
cond *sync.Cond
timeout time.Duration
}
func NewGracefulShutdown() *GracefulShutdown {
gs := &GracefulShutdown{
timeout: 60 * time.Second,
}
gs.cond = sync.NewCond(&gs.mu)
return gs
}
func (gs *GracefulShutdown) Acquire() bool {
gs.mu.Lock()
defer gs.mu.Unlock()
if gs.isShuttingDown {
return false
}
gs.activeRequests++
return true
}
func (gs *GracefulShutdown) Release() {
gs.mu.Lock()
gs.activeRequests--
gs.cond.Signal()
gs.mu.Unlock()
}
func (gs *GracefulShutdown) InitShutdown() {
gs.mu.Lock()
gs.isShuttingDown = true
fmt.Printf("🔄 关闭流程启动,剩余 %d 个请求\n", gs.activeRequests)
gs.mu.Unlock()
}
func (gs *GracefulShutdown) WaitComplete() bool {
deadline := time.Now().Add(gs.timeout)
gs.mu.Lock()
defer gs.mu.Unlock()
for gs.activeRequests > 0 {
remaining := time.Until(deadline)
if remaining <= 0 {
fmt.Printf("⚠️ 超时,强制关闭(剩余 %d 请求)\n", gs.activeRequests)
return false
}
fmt.Printf("⏳ 等待请求完成... (%d 剩余)\n", gs.activeRequests)
gs.cond.Wait()
}
return true
}
var shutdownController = NewGracefulShutdown()
func chatCompletionsHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
if !shutdownController.Acquire() {
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]interface{}{
"error": "Service is shutting down",
"retry_after": 30,
})
return
}
defer shutdownController.Release()
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read body", http.StatusBadRequest)
return
}
defer r.Body.Close()
var req ChatRequest
if err := json.Unmarshal(body, &req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// 调用 HolySheep API
holyReq := map[string]interface{}{
"model": ModelName,
"messages": req.Messages,
"temperature": req.Temperature,
"max_tokens": req.MaxTokens,
}
reqBody, _ := json.Marshal(holyReq)
ctx, cancel := context.WithTimeout(r.Context(), 120*time.Second)
defer cancel()
req2, _ := http.NewRequestWithContext(ctx, "POST",
HolySheepBaseURL+"/chat/completions",
bytes.NewReader(reqBody))
req2.Header.Set("Authorization", "Bearer "+HolySheepAPIKey)
req2.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 120 * time.Second}
resp, err := client.Do(req2)
if err != nil {
http.Error(w, "HolySheep API error: " + err.Error(), http.StatusBadGateway)
return
}
defer resp.Body.Close()
// 透传响应
for k, v := range resp.Header {
w.Header()[k] = v
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
shutdownController.mu.Lock()
isShuttingDown := shutdownController.isShuttingDown
activeReq := shutdownController.activeRequests
shutdownController.mu.Unlock()
status := "healthy"
if isShuttingDown {
status = "shutting_down"
}
json.NewEncoder(w).Encode(map[string]interface{}{
"status": status,
"active_requests": activeReq,
})
}
func main() {
// 注册路由
http.HandleFunc("/v1/chat/completions", chatCompletionsHandler)
http.HandleFunc("/health", healthHandler)
// 启动服务器
srv := &http.Server{
Addr: ":8000",
ReadTimeout: 120 * time.Second,
WriteTimeout: 120 * time.Second,
}
go func() {
fmt.Println("🚀 AI 推理服务启动,监听 :8000")
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("❌ 服务器错误: %v\n", err)
}
}()
// 信号处理
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigChan
fmt.Printf("\n📡 收到信号 %v,开始优雅关闭...\n", sig)
shutdownController.InitShutdown()
// 等待活跃请求完成
if shutdownController.WaitComplete() {
fmt.Println("✅ 优雅关闭成功,所有请求处理完毕")
}
// 关闭 HTTP 服务器
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
fmt.Printf("❌ 关闭服务器失败: %v\n", err)
}
fmt.Println("👋 服务已关闭")
}
import "bytes"
Kubernetes 环境下的优雅关闭策略
在 K8s 环境中优雅关闭需要额外配置,以下是关键参数:
- preStop Hook:等待 5-10 秒让 kube-proxy 更新 endpoint
- terminationGracePeriodSeconds:设为 60-120 秒,确保长请求完成
- readinessProbe:配合 graceful shutdown 状态
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-inference-service
spec:
replicas: 3
selector:
matchLabels:
app: ai-inference
template:
metadata:
labels:
app: ai-inference
spec:
terminationGracePeriodSeconds: 90 # 关键:足够长以完成推理
containers:
- name: inference
image: your-inference-image:latest
ports:
- containerPort: 8000
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- "sleep 10 && kill -SIGTERM 1" # 等待 kube-proxy 更新
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
# 告诉应用自己处理 SIGTERM
env:
- name: GRACEFUL_SHUTDOWN_TIMEOUT
value: "60"
常见报错排查
错误 1:连接重置 (Connection Reset)
症状:客户端报错 ConnectionResetError: [Errno 104] Connection reset by peer
原因:服务端在客户端仍发送数据时强制关闭连接,通常是 shutdown timeout 设置过短。
解决代码:
# 增加 shutdown 超时时间
shutdown_timeout = 120 # 秒,根据实际推理耗时调整
在关闭时使用 SO_LINGER
import socket
def graceful_close(connection, timeout=120):
"""优雅关闭 TCP 连接"""
old_timeout = connection.gettimeout()
connection.settimeout(timeout)
try:
connection.shutdown(socket.SHUT_RDWR)
except Exception:
pass
connection.close()
错误 2:请求计数器不一致
症状:服务声称已处理完所有请求,但仍有连接处于 CLOSE_WAIT 状态
原因:异常分支未正确释放计数器,导致死锁
解决代码:
@app.middleware("http")
async def request_counter_middleware(request, call_next):
global active_requests
# 即使发生异常也要执行
async with request_lock:
active_requests += 1
try:
response = await call_next(request)
except Exception as e:
# 关键:异常时也要减少计数
async with request_lock:
active_requests -= 1
raise e
# 确保无论如何都减少计数
async with request_lock:
active_requests -= 1
return response
错误 3:流式输出中断
症状:SSE 流在传输一半时被断开,客户端收到不完整的 JSON
原因:收到 shutdown 信号时直接中断流式响应
解决代码:
async def stream_response_generator():
"""安全的流式响应生成器"""
try:
async for chunk in holy_sheep_stream():
# 检查是否正在关闭
if shutdown_event.is_set():
# 发送完成标记而非直接断开
yield "data: [DONE]\n\n"
break
yield f"data: {json.dumps(chunk)}\n\n"
except asyncio.CancelledError:
# 响应被客户端取消(正常情况)
pass
except Exception as e:
# 发送错误信息给客户端
yield f'data: {{"error": "{str(e)}"}}\n\n'
finally:
# 无论如何都要释放资源
await release_resources()
yield "data: [DONE]\n\n"
常见错误与解决方案
| 错误类型 | 典型错误信息 | 根本原因 | 解决方案 |
|---|---|---|---|
| 超时未完成 | asyncio.TimeoutError: wait_for() timed out |
terminationGracePeriodSeconds 小于实际推理耗时 | 将 K8s terminationGracePeriodSeconds 设置为 max(推理耗时 × 2, 60s) |
| 请求丢失 | 客户端发送成功但收到 503 | preStop sleep 时间不足,kube-proxy 未更新 endpoints | preStop Hook 增加 sleep 时间到 10-15 秒 |
| 计数死锁 | 服务永远无法完成关闭 | 异常处理中未调用 release() | 使用 try-finally 确保释放,添加最大等待时间兜底 |
| 连接泄漏 | CLOSE_WAIT 连接数持续增长 | 未正确关闭 HTTP 连接 | 显式调用 response.close() 或使用 context manager |
| 缓存过期 | 关闭后仍收到发往旧 Pod 的请求 | Kubernetes endpoints 缓存问题 | 配置 readinessProbe,并在 preStop 中等待 endpoint 更新 |
性能优化建议
在实际生产环境中,我有以下经验:
- 分批关闭:不要同时关闭所有实例,K8s 默认会先停止一个 Pod,等新请求调度成功后再停止下一个
- 健康检查配合:当收到 shutdown 信号后,立即将 readinessProbe 改为返回 unhealthy,但 keepalive 仍接受已有连接
- 监控活跃连接数:接入 Prometheus,记录
inference_active_requests指标,关闭前先确认数值归零 - 使用 HolySheep API 的优势:其 <50ms 的国内延迟意味着单次推理耗时更短,优雅关闭的压力也相应降低。以 DeepSeek V3.2 为例,$0.42/MTok 的价格加上快速响应,让服务可以处理更多请求而不超载
# Prometheus 指标示例
from prometheus_client import Counter, Gauge
active_requests = Gauge(
'inference_active_requests',
'Number of active inference requests'
)
inference_duration = Histogram(
'inference_duration_seconds',
'Inference request duration',
buckets=[1, 2, 5, 10, 30, 60, 120]
)
总结
优雅关闭 AI 推理服务需要在多个层面做好配合:应用层正确处理信号、框架层实现 lifespan/中间件、K8s 层配置合适的 terminationGracePeriodSeconds。建议从本文的 FastAPI 示例开始验证,再根据实际场景迁移到生产。
对于国内开发者而言,选择 HolySheep API 不仅能节省 >85% 的成本(¥1=$1 vs 官方的 ¥7.3=$1),其 <50ms 的低延迟和稳定的微信/支付宝充值体验,让 AI 推理服务的开发和运维都更加省心。注册即送免费额度,建议先试用再决定。