http_pool.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. """
  2. HTTP连接池管理器 - 类似SQLAlchemy的连接池模式
  3. 统一管理DashScope API的HTTP客户端连接,避免重复创建连接池
  4. """
  5. import httpx
  6. import logging
  7. import threading
  8. import asyncio
  9. import time
  10. from typing import Optional
  11. from config import settings, get_dashscope_http_base_url
  12. logger = logging.getLogger(__name__)
  13. class HTTPConnectionPool:
  14. """
  15. HTTP连接池管理器(单例模式)
  16. 类似SQLAlchemy的连接池,统一管理所有HTTP客户端连接
  17. """
  18. _instance: Optional['HTTPConnectionPool'] = None
  19. _lock = threading.Lock()
  20. def __new__(cls):
  21. """单例模式实现"""
  22. if cls._instance is None:
  23. with cls._lock:
  24. if cls._instance is None:
  25. cls._instance = super().__new__(cls)
  26. cls._instance._initialized = False
  27. return cls._instance
  28. def __init__(self):
  29. """初始化连接池(只执行一次)"""
  30. if self._initialized:
  31. return
  32. self._client: Optional[httpx.Client] = None
  33. self._keepalive_task: Optional[asyncio.Task] = None
  34. self._keepalive_interval = 240 # 4分钟发送一次心跳(小于keepalive_expiry)
  35. self._last_keepalive_time = 0.0
  36. self._initialized = True
  37. logger.info("🔧 HTTP连接池管理器初始化")
  38. def get_client(self) -> httpx.Client:
  39. """
  40. 获取HTTP客户端(连接池复用)
  41. 类似SQLAlchemy的session.get_bind(),返回共享的连接池客户端
  42. Returns:
  43. httpx.Client: 配置好的HTTP客户端实例
  44. """
  45. if self._client is None:
  46. with self._lock:
  47. if self._client is None:
  48. self._client = self._create_client()
  49. logger.info("✅ HTTP连接池已创建")
  50. return self._client
  51. def _create_client(self) -> httpx.Client:
  52. """
  53. 创建HTTP客户端(带连接池配置)
  54. 类似SQLAlchemy的Engine创建,配置连接池参数
  55. """
  56. import os
  57. # 临时清除代理环境变量(只在这个进程中)
  58. env_backup = {}
  59. proxy_vars = ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy', 'ALL_PROXY', 'all_proxy']
  60. for var in proxy_vars:
  61. if var in os.environ:
  62. env_backup[var] = os.environ[var]
  63. del os.environ[var]
  64. try:
  65. # 🔥 创建高性能 HTTP 客户端 - 连接池配置
  66. # 类似SQLAlchemy的pool_size、max_overflow等参数
  67. http_client = httpx.Client(
  68. proxy=None, # 禁用代理
  69. timeout=60.0,
  70. trust_env=False, # 不信任环境变量中的代理设置
  71. # 连接池优化:复用 TCP 连接
  72. limits=httpx.Limits(
  73. max_keepalive_connections=settings.HTTPX_MAX_KEEPALIVE_CONNECTIONS,
  74. max_connections=settings.HTTPX_MAX_CONNECTIONS,
  75. keepalive_expiry=settings.HTTPX_KEEPALIVE_EXPIRY,
  76. ),
  77. # HTTP/2支持 - 多路复用,减少连接开销
  78. http2=True,
  79. )
  80. logger.info(
  81. f"📊 HTTP连接池配置: "
  82. f"keepalive={settings.HTTPX_MAX_KEEPALIVE_CONNECTIONS}, "
  83. f"max_connections={settings.HTTPX_MAX_CONNECTIONS}, "
  84. f"keepalive_expiry={settings.HTTPX_KEEPALIVE_EXPIRY}s"
  85. )
  86. return http_client
  87. finally:
  88. # 恢复环境变量(避免影响其他模块)
  89. for var, value in env_backup.items():
  90. os.environ[var] = value
  91. def recreate_client(self):
  92. """
  93. 重建HTTP客户端(连接失效时)
  94. 类似SQLAlchemy的Engine.dispose(),关闭旧连接并创建新连接
  95. """
  96. with self._lock:
  97. if self._client is not None:
  98. try:
  99. self._client.close()
  100. logger.info("🔄 关闭旧HTTP连接池")
  101. except Exception as e:
  102. logger.warning(f"⚠️ 关闭HTTP连接池时出错: {e}")
  103. self._client = self._create_client()
  104. logger.info("✅ HTTP连接池已重建")
  105. def start_keepalive(self, ai_service=None):
  106. """
  107. 启动连接保活任务(后台定期发送心跳)
  108. 在应用启动时调用,保持连接活跃
  109. Args:
  110. ai_service: AIService实例,用于发送保活请求(可选)
  111. """
  112. if self._keepalive_task is not None:
  113. return # 已经启动
  114. if ai_service is None:
  115. logger.warning("⚠️ 未提供AIService实例,连接保活功能将使用轻量级方式")
  116. async def keepalive_loop():
  117. """连接保活循环"""
  118. while True:
  119. try:
  120. await asyncio.sleep(self._keepalive_interval)
  121. # 检查连接是否存在
  122. if self._client is None:
  123. continue
  124. # 发送心跳请求(使用极小的测试请求)
  125. try:
  126. # 方法1:如果有AIService,发送一个极小的LLM请求
  127. if ai_service is not None:
  128. def send_heartbeat():
  129. # 发送一个极小的LLM请求来保持连接活跃
  130. from openai import OpenAI
  131. from config import settings, get_dashscope_http_base_url
  132. client = OpenAI(
  133. api_key=settings.active_dashscope_api_key,
  134. base_url=get_dashscope_http_base_url(),
  135. http_client=self._client, # 使用连接池
  136. )
  137. # 发送一个极小的请求(只请求1个token,快速返回)
  138. try:
  139. client.chat.completions.create(
  140. model=settings.llm_model,
  141. messages=[{"role": "user", "content": "Hi"}],
  142. max_tokens=1,
  143. timeout=5.0, # 5秒超时
  144. )
  145. except Exception:
  146. pass # 忽略错误,只为了保持连接活跃
  147. # 在后台线程中执行(避免阻塞事件循环)
  148. await asyncio.to_thread(send_heartbeat)
  149. # 记录保活时间
  150. self._last_keepalive_time = time.time()
  151. logger.debug("💓 HTTP连接保活心跳")
  152. except Exception as e:
  153. logger.warning(f"⚠️ 连接保活心跳失败: {e}")
  154. except asyncio.CancelledError:
  155. logger.info("🛑 连接保活任务已停止")
  156. break
  157. except Exception as e:
  158. logger.error(f"❌ 连接保活任务异常: {e}")
  159. await asyncio.sleep(60) # 出错后等待1分钟再继续
  160. # 启动保活任务
  161. try:
  162. loop = asyncio.get_event_loop()
  163. self._keepalive_task = loop.create_task(keepalive_loop())
  164. logger.info(f"✅ HTTP连接保活任务已启动(间隔{self._keepalive_interval}秒)")
  165. except RuntimeError:
  166. # 如果没有事件循环,在后台线程中创建
  167. def start_in_thread():
  168. new_loop = asyncio.new_event_loop()
  169. asyncio.set_event_loop(new_loop)
  170. self._keepalive_task = new_loop.create_task(keepalive_loop())
  171. new_loop.run_forever()
  172. thread = threading.Thread(target=start_in_thread, daemon=True)
  173. thread.start()
  174. logger.info(f"✅ HTTP连接保活任务已启动(后台线程,间隔{self._keepalive_interval}秒)")
  175. def stop_keepalive(self):
  176. """停止连接保活任务"""
  177. if self._keepalive_task is not None:
  178. self._keepalive_task.cancel()
  179. self._keepalive_task = None
  180. logger.info("🛑 HTTP连接保活任务已停止")
  181. def close(self):
  182. """
  183. 关闭连接池(应用关闭时)
  184. 类似SQLAlchemy的Engine.dispose(),清理所有连接
  185. """
  186. # 先停止保活任务
  187. self.stop_keepalive()
  188. with self._lock:
  189. if self._client is not None:
  190. try:
  191. self._client.close()
  192. logger.info("🔒 HTTP连接池已关闭")
  193. except Exception as e:
  194. logger.warning(f"⚠️ 关闭HTTP连接池时出错: {e}")
  195. finally:
  196. self._client = None
  197. # 全局连接池实例(单例)
  198. http_pool = HTTPConnectionPool()