揭秘asyncio.gather异常处理机制:return_exceptions=True到底改变了什么?

第一章:asyncio.gather异常处理机制的核心原理

在使用 Python 的 asyncio.gather 进行并发协程调度时,其异常处理机制是确保程序健壮性的关键环节。默认情况下,gather 会在任意一个任务抛出异常时立即中断执行流程,并将异常向上抛出,但其余任务仍会继续运行,除非显式设置 return_exceptions=False

异常传播行为

当多个协程通过 gather 并发执行时,其异常处理策略取决于参数 return_exceptions 的取值:
  • 若为 False(默认),第一个引发的异常将被重新抛出,中断整体流程
  • 若为 True,所有任务的异常都会被捕获并作为结果返回,不会中断其他任务执行

代码示例与执行逻辑

import asyncio

async def task_success():
    return "成功"

async def task_fail():
    raise ValueError("模拟错误")

async def main():
    try:
        # 默认行为:遇到异常立即抛出
        results = await asyncio.gather(
            task_success(),
            task_fail()
        )
    except ValueError as e:
        print(f"捕获异常: {e}")

    # 容错模式:异常作为结果返回
    results_safe = await asyncio.gather(
        task_success(),
        task_fail(),
        return_exceptions=True
    )
    for res in results_safe:
        if isinstance(res, Exception):
            print(f"任务异常: {res}")
        else:
            print(f"任务结果: {res}")

asyncio.run(main())
上述代码中,第一次调用 gather 会因异常中断并进入 except 块;第二次调用则继续执行所有任务,并将异常实例作为结果之一返回。

异常处理策略对比

策略行为适用场景
return_exceptions=False快速失败,立即抛出首个异常强依赖所有任务成功完成
return_exceptions=True收集所有结果与异常,不中断执行容错性要求高的批量操作

第二章:return_exceptions=False的默认行为剖析

2.1 异常传播机制的理论基础

异常传播是程序在运行时处理错误的核心机制之一,它允许错误从发生点逐层向上传递,直至被适当捕获。这一过程依赖于调用栈的 unwind 操作,在异常抛出时自动回溯执行路径。
异常传播路径
当函数调用链中某一层抛出异常,运行时系统会暂停当前执行流,开始查找匹配的异常处理器(catch block)。若当前作用域无处理逻辑,则异常继续向调用者传播。
  • 异常实例携带错误类型与上下文信息
  • 每层调用栈可选择捕获、处理或重新抛出
  • 未被捕获的异常最终导致程序终止
func A() {
    panic("error occurred")
}

func B() {
    A() // 异常从此处传播出去
}

func main() {
    defer func() {
        if r := recover(); r != nil {
            log.Println("caught:", r)
        }
    }()
    B()
}
上述代码中,panic 触发异常,经由 A()B() 向上传播,最终在 main 的 defer 中通过 recover 捕获,体现了典型的传播与拦截机制。

2.2 模拟任务抛出异常的实验场景

在分布式任务调度系统中,模拟任务抛出异常是验证容错机制的重要手段。通过人为触发异常,可观察系统的重试策略、日志记录与状态回滚行为。
异常类型设计
常见的模拟异常包括空指针、超时异常和自定义业务异常。以下为Go语言实现的任务抛出示例:

func riskyTask(id int) error {
    if id == 0 {
        return fmt.Errorf("simulated task failure for ID: %d", id)
    }
    // 正常执行逻辑
    return nil
}
该函数在输入ID为0时主动返回错误,模拟任务执行失败。参数id用于控制异常触发条件,便于在批量测试中定位问题。
异常注入策略对比
  • 随机注入:模拟不可预测的运行时错误
  • 条件触发:基于输入参数或环境变量决定是否抛出
  • 阶段式引入:在特定执行阶段(如数据库提交)插入异常

2.3 第一个失败任务中断执行流的表现分析

当工作流中某个任务执行失败时,其后续任务将不会被触发,整个执行流立即中断。这种“短路”行为有助于快速暴露问题,避免无效资源消耗。
典型失败场景示例
{
  "tasks": [
    { "name": "task1", "status": "success" },
    { "name": "task2", "status": "failed" },
    { "name": "task3", "status": "skipped" }
  ]
}
上述执行记录显示,task2 失败后,task3 被标记为跳过,表明系统具备明确的中断传播机制。
中断传播机制分析
  • 任务调度器在检测到失败状态后,立即停止后续任务的调度请求
  • 执行上下文被标记为“已终止”,防止状态污染
  • 错误信息通过回调链向上抛出,便于监控系统捕获

2.4 与其他并发模式的异常处理对比

在不同并发模型中,异常处理机制存在显著差异。传统线程模型依赖 try-catch 块捕获局部异常,但无法跨线程传播。
Go 协程中的错误传递
go func() {
    defer func() {
        if r := recover(); r != nil {
            log.Println("panic recovered:", r)
        }
    }()
    panic("goroutine error")
}()
该代码通过 deferrecover 捕获协程内的 panic,避免程序崩溃。由于 Go 不支持跨 goroutine 抛出异常,必须显式使用 channel 传递错误信息。
对比总结
  • 线程模型:异常可被同步捕获,但资源开销大
  • 协程模型:需手动管理 panic,轻量但复杂度高
  • Actor 模型:通过消息传递错误,天然隔离故障

2.5 实际开发中潜在的风险与陷阱

异步编程中的竞态条件
在并发场景下,多个 goroutine 同时访问共享资源而未加同步控制,极易引发数据竞争。

var counter int
for i := 0; i < 10; i++ {
    go func() {
        counter++ // 非原子操作,存在竞态
    }()
}
上述代码中,counter++ 实际包含读取、修改、写入三个步骤,多个协程同时执行会导致结果不可预测。应使用 sync.Mutexatomic 包确保操作原子性。
常见风险汇总
  • 内存泄漏:未关闭的 goroutine 持续引用资源
  • 死锁:多个协程相互等待锁释放
  • 上下文泄漏:未设置超时的 context 导致协程无法退出

第三章:return_exceptions=True的工作机制解析

3.1 异常被捕获并作为结果返回的原理

在现代异步编程模型中,异常并非总是中断执行流,而是被封装为结果的一部分,以便调用方统一处理成功与失败情形。
错误封装机制
通过将异常捕获并转换为返回值中的错误字段,程序可在不中断控制流的前提下传递错误信息。例如在 Go 中:
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}
该函数始终返回两个值:结果和错误。调用方通过检查 error 是否为 nil 判断操作是否成功,从而实现异常的“静默传递”。
调用链中的错误传播
  • 每一层函数可选择处理错误或继续向上抛出
  • 错误被逐级包装以保留上下文(如使用 fmt.Errorf("failed to process: %w", err)
  • 最终由顶层逻辑决定重试、日志记录或用户提示
这种设计使错误成为一等公民,提升了系统的可控性与可观测性。

3.2 多任务独立完成时的异常收集实践

在并发执行多个独立任务时,各任务可能抛出不同类型的异常,若不加以统一收集与处理,将导致错误信息丢失。为确保程序的可观测性,需设计可靠的异常捕获机制。
使用通道收集错误
Go语言中可通过带缓冲的error通道集中收集各协程异常:
errors := make(chan error, 10)
for i := 0; i < 5; i++ {
    go func(id int) {
        err := processTask(id)
        if err != nil {
            errors <- fmt.Errorf("task %d failed: %w", id, err)
        }
    }(i)
}
close(errors)
上述代码创建容量为10的error通道,每个任务在出错时写入结构化错误信息,避免panic扩散。
异常汇总策略
  • 非阻塞读取:使用select配合default避免主流程卡顿
  • 上下文关联:附加任务ID、时间戳等元数据便于追踪
  • 分级上报:根据错误类型决定是否中断主流程

3.3 返回结果类型判断与后续处理策略

在接口调用或异步任务执行过程中,准确判断返回结果类型是保障系统稳定性的关键环节。根据返回值的结构和状态码,可决定重试、回调或异常处理等后续动作。
常见返回类型分类
  • 成功响应:HTTP 200 或业务码为0,携带有效数据
  • 客户端错误:如400、401,需检查请求参数或认证信息
  • 服务端异常:5xx 错误,建议启用熔断与重试机制
  • 空响应或超时:网络问题,应结合超时配置进行处理
基于类型的处理策略示例
func handleResponse(resp *http.Response, data []byte) error {
    if resp.StatusCode == 200 {
        // 解析并存储正常数据
        json.Unmarshal(data, &result)
        return nil
    } else if resp.StatusCode >= 500 {
        // 触发重试逻辑
        retry()
        return errors.New("server error")
    }
    return errors.New("client error")
}
上述代码展示了根据不同状态码执行相应流程的典型模式。200 状态码表示成功,直接解析数据;5xx 错误触发重试机制,避免因短暂故障导致整体失败。

第四章:两种模式的应用场景与最佳实践

4.1 需要快速失败的业务流程设计

在高并发系统中,快速失败(Fail-Fast)机制能有效防止资源浪费和级联故障。通过提前校验关键参数与依赖状态,系统可在异常初期即终止执行路径。
典型应用场景
  • 支付网关调用前检查账户状态
  • 订单创建时验证库存与价格一致性
  • 外部API调用前判断服务健康度
代码实现示例
func CreateOrder(order *Order) error {
    if order.UserID == 0 {
        return errors.New("invalid user id") // 快速失败:用户ID为空
    }
    if !isInventoryAvailable(order.Items) {
        return errors.New("insufficient inventory")
    }
    // 继续后续流程...
}
该函数在执行初期即对核心参数进行校验,避免进入深层逻辑后才发现问题,从而缩短错误反馈链路,提升系统响应效率。

4.2 批量请求中容忍部分失败的容错架构

在高并发系统中,批量请求常因个别条目异常导致整体失败。为提升系统韧性,需构建支持部分失败的容错架构。
响应结构设计
采用细粒度结果封装,每个子请求独立返回状态:
{
  "results": [
    { "id": "1", "status": "success", "data": { "..."} },
    { "id": "2", "status": "failed", "error": "Invalid parameter" }
  ]
}
该结构允许客户端识别成功与失败项,实现精准重试或降级处理。
重试与熔断策略
  • 对失败条目启用指数退避重试
  • 结合 Circuit Breaker 防止雪崩
  • 异步补偿任务处理持久化失败项
通过分片处理与隔离故障粒度,系统可在部分失败时仍保证整体可用性。

4.3 性能监控与错误汇总的日志记录方案

在分布式系统中,统一的日志记录机制是性能监控与错误追踪的核心。通过集中化日志采集,可实现对服务运行状态的实时洞察。
日志结构设计
采用 JSON 格式结构化输出日志,便于后续解析与分析:
{
  "timestamp": "2023-10-01T12:00:00Z",
  "level": "ERROR",
  "service": "user-service",
  "trace_id": "abc123",
  "message": "Database connection timeout",
  "duration_ms": 450
}
字段说明:`timestamp` 精确到毫秒;`level` 支持 debug/info/warn/error;`trace_id` 用于链路追踪;`duration_ms` 记录关键操作耗时。
日志采集流程
应用日志 → 日志代理(Filebeat) → 消息队列(Kafka) → 日志处理(Logstash) → 存储(Elasticsearch)
关键监控指标
  • 错误率:按服务维度统计 ERROR 日志频率
  • 响应延迟:采集 duration_ms 的 P95/P99 分位值
  • 日志吞吐量:单位时间日志条目数,反映系统活跃度

4.4 结合try-except实现精细化控制

在异常处理中,结合 try-except 可实现对程序流程的精细化控制。通过捕获特定异常类型,能够区分不同错误场景并执行相应恢复逻辑。
异常类型的分层处理
使用多个 except 块可针对不同异常做出响应:
try:
    result = 10 / int(user_input)
except ValueError:
    print("输入格式错误:请输入有效数字")
except ZeroDivisionError:
    print("数学错误:除数不能为零")
except Exception as e:
    print(f"未预期异常:{e}")
else:
    print("计算成功")
finally:
    print("执行清理操作")
上述代码中,ValueError 处理类型转换失败,ZeroDivisionError 捕获除零异常,else 仅在无异常时执行,finally 确保资源释放。
自定义异常增强控制力
通过继承 Exception 类可定义业务异常,提升代码可读性与维护性。

第五章:结论与异步编程中的健壮性思考

在构建高并发系统时,异步编程模型虽提升了吞吐能力,但也引入了复杂的状态管理与错误传播问题。健壮的异步系统必须预设任何操作都可能失败,并设计相应的恢复机制。
错误传播与上下文取消
使用带有超时控制的上下文(context)是防止资源泄漏的关键。以下 Go 示例展示了如何安全地取消长时间运行的异步任务:

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

resultCh := make(chan string, 1)
go func() {
    resultCh <- slowOperation(ctx) // 依赖 ctx 的阻塞性操作
}()

select {
case result := <-resultCh:
    log.Printf("Success: %s", result)
case <-ctx.Done():
    log.Printf("Operation cancelled: %v", ctx.Err())
}
重试策略与退避机制
网络调用应结合指数退避与最大重试次数,避免雪崩效应。常见配置如下:
  • 初始重试间隔:100ms
  • 每次退避乘数:2
  • 最大重试次数:3
  • 启用随机抖动(jitter)防止同步重试风暴
监控与可观测性
异步任务应集成结构化日志与分布式追踪。例如,在任务启动和完成时记录关键指标:
事件记录字段用途
任务开始task_id, timestamp, worker_id追踪延迟
任务失败error_code, retry_count, cause根因分析
[Task Start] id=abc123 worker=W-007 ↓ [HTTP Request] url=/api/data timeout=5s ↓ [Retry Attempt] count=2 backoff=400ms ↓ [Task Complete] status=success duration=680ms
class AsyncSocketManager: """异步Socket管理器 - 协调Reader和Writer线程""" # 分批连接参数 BATCH_SIZE = 250 # 每批处理的连接数 BATCH_INTERVAL = 20.0 # 批次间隔时间(秒) MAX_RETRIES = 5 # 增加最大重试次数 RECONNECT_INITIAL_DELAY = 20.0 # 初始重连延迟 RECONNECT_BACKOFF_FACTOR = 1.5 # 指数退避因子 MAX_RECONNECT_DELAY = 120.0 # 最大重连延迟(2分钟) HEALTH_CHECK_INTERVAL = 5.0 # 健康检查间隔 def __init__(self): self.running = True self._shutdown_event = asyncio.Event() self._cleanup_lock = asyncio.Lock() self.shutdown_requested = asyncio.Event() self.original_sigint_handler = None self.shutdown_requested = asyncio.Event() self.server_config = {'ip': 'charge.b-conn.com', 'port': 5455} self.total = 0 self.writer = None self.reader = None self._active_tasks = set() self.connections: Dict[str, Dict] = {} self.batch_tasks: List[asyncio.Task] = [] # 批次任务列表 self._read_lock = asyncio.Lock() # 读取操作专用锁 self._write_lock = asyncio.Lock() # 写入操作专用锁 self._state_lock = asyncio.Lock() # 状态变更锁 self.logger = logging.getLogger(__name__) self.setup_logging() self.connection_pool = {} # 连接池 # 连接状态统计 self.stats = { "success": 0, "failed": 0, "pending": 0, "active": 0 } # 每个连接一个专用的状态锁 self.connection_locks = defaultdict(asyncio.Lock) def install_signal_handlers(self): """安装信号处理器""" try: loop = asyncio.get_running_loop() # 保存原始信号处理器 self.original_sigint_handler = loop.add_signal_handler( signal.SIGINT, self._initiate_shutdown ) self.logger.info("✅ 信号处理器安装完成") except (NotImplementedError, RuntimeError): # 在某些平台上可能不支持 self.logger.warning("⚠️ 当前平台不支持异步信号处理") def _initiate_shutdown(self): """初始化关闭过程""" self.logger.info("🔄 接收到关闭信号,开始优雅关闭...") self.shutdown_requested.set() def restore_signal_handlers(self): """恢复原始信号处理器""" try: loop = asyncio.get_running_loop() if self.original_sigint_handler is not None: loop.remove_signal_handler(signal.SIGINT) # 重新安装默认处理器 loop.add_signal_handler(signal.SIGINT, signal.default_int_handler) except Exception as e: self.logger.debug(f"恢复信号处理器时发生异常: {e}") async def close_all_connections(self): """异步关闭所有Socket连接""" self.logger.info("正在关闭所有Socket连接...") self.running = False self._shutdown_event.set() # 取消所有活跃任务 for task in self._active_tasks: if not task.done(): task.cancel() # 关闭所有连接 close_tasks = [] for pileCode in list(self.connections.keys()): conn = self.connections[pileCode] if 'writer' in conn and conn['writer'] is not None: writer = conn['writer'] close_tasks.append(self.close_writer(writer, pileCode)) if close_tasks: await asyncio.gather(*close_tasks, return_exceptions=True) self.logger.info("所有连接已关闭") async def close_writer(self, writer, pileCode: str): """安全关闭写入器""" try: if not writer.is_closing(): writer.close() await writer.wait_closed() self.logger.debug(f"关闭连接: {pileCode}") except Exception as e: self.logger.error(f"关闭连接失败: {pileCode} 错误: {e}") async def _close_single_connection(self, conn_key: str): """关闭单个Socket连接""" try: if conn_key in self.connections: conn_info = self.connections[conn_key] # 实际的Socket关闭逻辑 if 'writer' in conn_info: writer = conn_info['writer'] writer.close() await writer.wait_closed() self.logger.debug(f"🔌 关闭连接 {conn_key}") del self.connections[conn_key] except Exception as e: self.logger.error(f"❌ 关闭连接 {conn_key} 时发生异常: {e}") async def batch_create_connections(self, pile_codes: List[str]): """添加健康监控器""" # 启动健康监控器 health_task = asyncio.create_task( self.connection_health_monitor(), name="connection_health_monitor" ) self.batch_tasks.append(health_task) """批量创建Socket连接(分批处理)""" total = len(pile_codes) batches = math.ceil(total / self.BATCH_SIZE) self.total_connections = total self.logger.info(f"开始分批创建连接,共{total}个桩号,分{batches}批处理,每批{self.BATCH_SIZE}个") for batch_num in range(batches): if self.shutdown_requested.is_set(): self.logger.warning("关闭信号已触发,停止创建新批次") break start_idx = batch_num * self.BATCH_SIZE end_idx = min(start_idx + self.BATCH_SIZE, total) batch = pile_codes[start_idx:end_idx] batch_id = batch_num + 1 self.logger.info(f"处理批次 {batch_id}/{batches} ({len(batch)}个桩号)") # 创建批量连接任务 task = asyncio.create_task( self.process_batch(batch, batch_id), name=f"batch_{batch_id}" ) self.batch_tasks.append(task) # 添加批次间隔 if batch_num < batches - 1: await asyncio.sleep(self.BATCH_INTERVAL) # 等待所有批次完成 if self.batch_tasks: await asyncio.gather(*self.batch_tasks) self.logger.info(f"所有批次处理完成! 成功:{self.stats['success']} 失败:{self.stats['failed']}") async def process_batch(self, pile_codes: List[str], batch_id: int): """处理单个批次的连接""" tasks = [] for i, pileCode in enumerate(pile_codes): message_id = f"batch{batch_id}-conn{i + 1}" task = asyncio.create_task( self.socket_worker(message_id, pileCode), name=f"batch{batch_id}-{pileCode}" ) tasks.append(task) self.connection_pool[pileCode] = {"status": "pending"} self.stats["pending"] += 1 # 等待批次内所有连接完成初始化 results = await asyncio.gather(*tasks, return_exceptions=True) # 统计批次结果 for result in results: if isinstance(result, Exception): self.stats["failed"] += 1 else: self.stats["success"] += 1 self.stats["pending"] -= len(pile_codes) self.stats["active"] = len(self.connections) async def socket_worker(self, message_id: str, pileCode: str): global logger """异步Socket工作协程""" retry_count = 0 last_reconnect_time = 0 # health_check_last = time.time() while self.running and not self._shutdown_event.is_set(): try: # 检查是否需要等待重连延迟 current_time = time.time() if last_reconnect_time > 0: delay = self.calculate_reconnect_delay(retry_count) if current_time - last_reconnect_time < delay: await asyncio.sleep(delay - (current_time - last_reconnect_time)) # 尝试创建连接 success = await self.create_socket_connection_with_retry(message_id, pileCode, retry_count) if not success: retry_count = min(retry_count + 1, self.MAX_RETRIES) last_reconnect_time = time.time() continue # 重置重试计数(连接成功) retry_count = 0 last_reconnect_time = 0 # 启动读写任务 await self.start_reader_writer(message_id, pileCode) # 健康检查和状态报告 # current_time = time.time() # if current_time - health_check_last > self.HEALTH_CHECK_INTERVAL: # pending_count = self.stats["pending"] # self.logger.info( # f"等待中={pending_count} 重试={retry_count}" # ) # health_check_last = current_time except asyncio.CancelledError: self.logger.info(f"⚠️ Socket工作协程被取消: {message_id}") if pileCode in self.connections: conn = self.connections[pileCode] if 'writer' in conn and conn['writer'] is not None: writer = conn['writer'] try: if not writer.is_closing(): writer.close() await writer.wait_closed() except Exception: pass if pileCode in self.connections: del self.connections[pileCode] break except Exception as e: self.logger.error(f"❌ Socket工作异常: {message_id} 桩号: {pileCode} 错误: {e}") retry_count = min(retry_count + 1, self.MAX_RETRIES) last_reconnect_time = time.time() # 清理当前连接 if pileCode in self.connections: conn = self.connections[pileCode] if 'writer' in conn and conn['writer'] is not None: writer = conn['writer'] try: if not writer.is_closing(): writer.close() await writer.wait_closed() except Exception: pass if pileCode in self.connections: del self.connections[pileCode] await asyncio.sleep(1) # 短暂暂停 finally: # 更新连接池状态 if pileCode in self.connection_pool: self.connection_pool[pileCode] = { "status": "retrying" if retry_count > 0 else "active", "retry_count": retry_count, "last_retry": last_reconnect_time } def calculate_reconnect_delay(self, retry_count: int) -> float: """计算指数退避的重连延迟""" delay = self.RECONNECT_INITIAL_DELAY * (self.RECONNECT_BACKOFF_FACTOR ** retry_count) return min(delay, self.MAX_RECONNECT_DELAY) def handle_task_completion(self, task: asyncio.Task): """任务完成回调处理""" try: # 首先检查任务是否被取消 if task.cancelled(): self.logger.debug(f"任务 {task.get_name()} 被取消") return if task.done() and task.exception(): self.logger.error(f"任务失败: {task.get_name()} {task.exception()}") # 标记连接需要重启 pile_code = task.get_name().split("_")[1] if pile_code in self.connections: writer = self.connections[pile_code]["writer"] if writer is not None: writer.close() if pile_code in self.connections: del self.connections[pile_code] except Exception as e: self.logger.error(f"任务回调处理错误: {e}") async def connection_health_monitor(self): """连接健康监控器""" while self.running and not self._shutdown_event.is_set(): try: real_active_count = 0 current_time = time.time() connections_to_restart = [] for pile_code, conn in list(self.connections.items()): # 检查是否超过最大重试次数 if conn.get("retry_count", 0) >= self.MAX_RETRIES: self.logger.warning(f"❌ 连接超过最大重试次数: {pile_code}") await self._close_single_connection(pile_code) continue if conn.get("isLogin") is True: real_active_count += 1 # 检查最后活动时间 last_activity = conn.get("last_time_stamp", 0) if current_time - last_activity > 60: # 1分钟无活动 self.logger.warning(f"桩号: {pile_code} 上次报文时间: {last_activity} 当前时间: {current_time} 时间间隔: {current_time - last_activity}") self.logger.warning(f"🕒 连接无活动超时: {pile_code}") connections_to_restart.append(pile_code) # 检查是否需要重启 if conn.get("needs_restart", False): connections_to_restart.append(pile_code) # 重启需要重置的连接 for pile_code in connections_to_restart: if pile_code in self.connections: conn = self.connections[pile_code] if conn['isLogin'] and conn['isLogin'] is not None: connections_to_restart.remove(pile_code) continue else: await self._close_single_connection(pile_code) # 重新启动工作协程 self.logger.info(f"🔄 重启连接: {pile_code}") task = asyncio.create_task( self.socket_worker(f"restart-{pile_code}", pile_code), name=f"restart_{pile_code}" ) self.batch_tasks.append(task) await asyncio.sleep(1) # 定期报告 restart_count = len(connections_to_restart) self.logger.info( f"🔍 健康检查: 活跃连接={real_active_count} 待重启={restart_count}" ) await asyncio.sleep(5) except Exception as e: self.logger.error(f"健康监控器异常: {e}") await asyncio.sleep(10) async def create_socket_connection_with_retry(self, message_id: str, pileCode: str, attempt: int) -> bool: """带重试机制的连接创建""" try: reader, writer = await asyncio.wait_for( asyncio.open_connection(self.server_config['ip'], self.server_config['port']), timeout=30.0 + min(attempt * 5, 30) # 随重试次数增加超时 ) async with self.connection_locks[pileCode]: if pileCode not in self.connections: self.connections[pileCode] = { 'reader': reader, 'writer': writer, 'pileCode': pileCode, 'message_id': message_id, 'retry_count': 0, 'last_heartbeat1': 0, 'last_heartbeat2': 0, 'last_time_stamp': 0, 'isLogin': False, 'timeout_count_login': 0, 'timeout_count': 0, 'heart_serialNum': "00 00", 'status': 'Offline', 'charging': False, 'priceModelList': [] } self.logger.info(f"连接成功: {message_id} 桩号: {pileCode}") return True except asyncio.TimeoutError: self.logger.warning(f"⛔ 连接超时: {message_id} 桩号: {pileCode} (尝试 #{attempt + 1})") if pileCode in self.connections: conn = self.connections[pileCode] if 'writer' in conn and conn['writer'] is not None: writer = conn['writer'] try: if not writer.is_closing(): writer.close() await writer.wait_closed() except Exception: pass if pileCode in self.connections: del self.connections[pileCode] await asyncio.sleep(1) # 短暂暂停 except ConnectionRefusedError: self.logger.warning(f"🚫 连接拒绝: {message_id} 桩号: {pileCode} (尝试 #{attempt + 1})") if pileCode in self.connections: conn = self.connections[pileCode] if 'writer' in conn and conn['writer'] is not None: writer = conn['writer'] try: if not writer.is_closing(): writer.close() await writer.wait_closed() except Exception: pass if pileCode in self.connections: del self.connections[pileCode] await asyncio.sleep(1) # 短暂暂停 except Exception as e: self.logger.error( f"❌连接错误: {message_id} 桩号: {pileCode} 错误: {e} (尝试 #{attempt + 1})") if pileCode in self.connections: conn = self.connections[pileCode] if 'writer' in conn and conn['writer'] is not None: writer = conn['writer'] try: if not writer.is_closing(): writer.close() await writer.wait_closed() except Exception: pass if pileCode in self.connections: del self.connections[pileCode] await asyncio.sleep(1) # 短暂暂停 return False async def start_reader_writer(self, message_id: str, pileCode: str): """启动读写任务""" if pileCode not in self.connections: return conn = self.connections[pileCode] reader = conn['reader'] writer = conn['writer'] self.writer, self.reader = AsyncSocketWriter(writer, pileCode, message_id, self.total, self.connections, self.connection_locks, self.logger), AsyncSocketReader( reader, pileCode, message_id, self.connections, self.connection_locks, self.logger) # 创建读写任务 read_task = asyncio.create_task( self.reader.start_reading(), name=f"reader_{pileCode}_{message_id}" ) write_task = asyncio.create_task( self.writer.start_write(), name=f"writer_{pileCode}_{message_id}" ) # 添加完成回调 read_task.add_done_callback(self.handle_task_completion) write_task.add_done_callback(self.handle_task_completion) # 添加到活跃任务集合 self._active_tasks.add(read_task) self._active_tasks.add(write_task) # 并行运行读写任务 try: await asyncio.gather( read_task, write_task, return_exceptions=True ) except Exception as e: self.logger.error(f"读写任务异常: {message_id} {pileCode}: {e}") 有无问题?
11-08
class AsyncSocketManager: """异步Socket管理器 - 协调Reader和Writer线程""" # 分批连接参数 BATCH_SIZE = 250 # 每批处理的连接数 BATCH_INTERVAL = 2.0 # 批次间隔时间(秒) MAX_RETRIES = 3 # 增加最大重试次数 RECONNECT_INITIAL_DELAY = 3.0 # 初始重连延迟 RECONNECT_BACKOFF_FACTOR = 1.5 # 指数退避因子 MAX_RECONNECT_DELAY = 300.0 # 最大重连延迟(5分钟) HEALTH_CHECK_INTERVAL = 30.0 # 健康检查间隔 def __init__(self): self.running = True self._shutdown_event = asyncio.Event() self._cleanup_lock = asyncio.Lock() self.shutdown_requested = asyncio.Event() self.original_sigint_handler = None self.shutdown_requested = asyncio.Event() self.server_config = {'ip': 'conn.com', 'port': 5455} self.total = 0 self.writer = None self.reader = None self._active_tasks = set() self.connections: Dict[str, Dict] = {} self.batch_tasks: List[asyncio.Task] = [] # 批次任务列表 self._read_lock = asyncio.Lock() # 读取操作专用锁 self._write_lock = asyncio.Lock() # 写入操作专用锁 self._state_lock = asyncio.Lock() # 状态变更锁 self.logger = logging.getLogger(__name__) self.setup_logging() self.connection_pool = {} # 连接池 # 连接状态统计 self.stats = { "success": 0, "failed": 0, "pending": 0, "active": 0 } self.semaphore = asyncio.Semaphore(10000) def setup_logging(self): """配置日志系统""" logging.config.dictConfig({ 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': '%(asctime)s [%(levelname)s] %(name)s - %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S' }, 'detailed': { 'format': '%(asctime)s [%(levelname)s] %(name)s::%(funcName)s - %(message)s' } }, 'handlers': { 'console': { 'level': 'INFO', 'class': 'logging.StreamHandler', 'formatter': 'standard' }, 'file_handler': { 'level': 'DEBUG', 'class': 'logging.FileHandler', 'filename': '/mnt/test/socket_client.log', 'encoding': 'utf-8', 'formatter': 'detailed' }, 'error_file': { 'level': 'WARNING', 'class': 'logging.FileHandler', 'filename': '/mnt/test/socket_errors.log', 'encoding': 'utf-8', 'formatter': 'detailed' } }, 'loggers': { '': { # root logger 'handlers': ['console', 'file_handler', 'error_file'], 'level': 'DEBUG', 'propagate': True } } }) def install_signal_handlers(self): """安装信号处理器""" try: loop = asyncio.get_running_loop() # 保存原始信号处理器 self.original_sigint_handler = loop.add_signal_handler( signal.SIGINT, self._initiate_shutdown ) self.logger.info("✅ 信号处理器安装完成") except (NotImplementedError, RuntimeError): # 在某些平台上可能不支持 self.logger.warning("⚠️ 当前平台不支持异步信号处理") def _initiate_shutdown(self): """初始化关闭过程""" self.logger.info("🔄 接收到关闭信号,开始优雅关闭...") self.shutdown_requested.set() def restore_signal_handlers(self): """恢复原始信号处理器""" try: loop = asyncio.get_running_loop() if self.original_sigint_handler is not None: loop.remove_signal_handler(signal.SIGINT) # 重新安装默认处理器 loop.add_signal_handler(signal.SIGINT, signal.default_int_handler) except Exception as e: self.logger.debug(f"恢复信号处理器时发生异常: {e}") async def close_all_connections(self): """异步关闭所有Socket连接""" self.logger.info("正在关闭所有Socket连接...") self.running = False self._shutdown_event.set() # 取消所有活跃任务 for task in self._active_tasks: if not task.done(): task.cancel() # 关闭所有连接 close_tasks = [] for pileCode in list(self.connections.keys()): conn = self.connections[pileCode] if 'writer' in conn: writer = conn['writer'] close_tasks.append(self.close_writer(writer, pileCode)) if close_tasks: await asyncio.gather(*close_tasks, return_exceptions=True) self.logger.info("所有连接已关闭") async def close_writer(self, writer, pileCode: str): """安全关闭写入器""" try: if not writer.is_closing(): writer.close() await writer.wait_closed() self.logger.debug(f"关闭连接: {pileCode}") except Exception as e: self.logger.error(f"关闭连接失败: {pileCode} 错误: {e}") async def _close_single_connection(self, conn_key: str): """关闭单个Socket连接""" try: if conn_key in self.connections: conn_info = self.connections[conn_key] # 实际的Socket关闭逻辑 if 'writer' in conn_info: writer = conn_info['writer'] writer.close() await writer.wait_closed() self.logger.debug(f"🔌 关闭连接 {conn_key}") del self.connections[conn_key] except Exception as e: self.logger.error(f"❌ 关闭连接 {conn_key} 时发生异常: {e}") async def batch_create_connections(self, pile_codes: List[str]): """添加健康监控器""" # 启动健康监控器 health_task = asyncio.create_task( self.connection_health_monitor(), name="connection_health_monitor" ) self.batch_tasks.append(health_task) """批量创建Socket连接(分批处理)""" total = len(pile_codes) batches = math.ceil(total / self.BATCH_SIZE) self.total_connections = total self.logger.info(f"开始分批创建连接,共{total}个桩号,分{batches}批处理,每批{self.BATCH_SIZE}个") for batch_num in range(batches): if self.shutdown_requested.is_set(): self.logger.warning("关闭信号已触发,停止创建新批次") break start_idx = batch_num * self.BATCH_SIZE end_idx = min(start_idx + self.BATCH_SIZE, total) batch = pile_codes[start_idx:end_idx] batch_id = batch_num + 1 self.logger.info(f"处理批次 {batch_id}/{batches} ({len(batch)}个桩号)") # 创建批量连接任务 task = asyncio.create_task( self.process_batch(batch, batch_id), name=f"batch_{batch_id}" ) self.batch_tasks.append(task) # 添加批次间隔 if batch_num < batches - 1: await asyncio.sleep(self.BATCH_INTERVAL) # 等待所有批次完成 if self.batch_tasks: await asyncio.gather(*self.batch_tasks) self.logger.info(f"所有批次处理完成! 成功:{self.stats['success']} 失败:{self.stats['failed']}") async def process_batch(self, pile_codes: List[str], batch_id: int): """处理单个批次的连接""" tasks = [] for i, pileCode in enumerate(pile_codes): message_id = f"batch{batch_id}-conn{i + 1}" task = asyncio.create_task( self.socket_worker(message_id, pileCode), name=f"batch{batch_id}-{pileCode}" ) tasks.append(task) self.connection_pool[pileCode] = {"status": "pending"} self.stats["pending"] += 1 # 等待批次内所有连接完成初始化 results = await asyncio.gather(*tasks, return_exceptions=True) # 统计批次结果 for result in results: if isinstance(result, Exception): self.stats["failed"] += 1 else: self.stats["success"] += 1 self.stats["pending"] -= len(pile_codes) self.stats["active"] = len(self.connections) async def socket_worker(self, message_id: str, pileCode: str): global logger """异步Socket工作协程""" retry_count = 0 last_reconnect_time = 0 health_check_last = time.time() while self.running and not self._shutdown_event.is_set(): try: # 检查是否需要等待重连延迟 current_time = time.time() if last_reconnect_time > 0: delay = self.calculate_reconnect_delay(retry_count) if current_time - last_reconnect_time < delay: await asyncio.sleep(delay - (current_time - last_reconnect_time)) # 尝试创建连接 success = await self.create_socket_connection_with_retry(message_id, pileCode, retry_count) if not success: retry_count = min(retry_count + 1, self.MAX_RETRIES) last_reconnect_time = time.time() continue # 重置重试计数(连接成功) retry_count = 0 last_reconnect_time = 0 # 启动读写任务 await self.start_reader_writer(message_id, pileCode) # 健康检查和状态报告 current_time = time.time() list_count = 0 if current_time - health_check_last > self.HEALTH_CHECK_INTERVAL: pending_count = self.stats["pending"] self.logger.info( f"等待中={pending_count} 重试={retry_count}" ) health_check_last = current_time except asyncio.CancelledError: self.logger.info(f"⚠️ Socket工作协程被取消: {message_id}") break except Exception as e: self.logger.error(f"❌ Socket工作异常: {message_id} 桩号: {pileCode} 错误: {e}") retry_count = min(retry_count + 1, self.MAX_RETRIES) last_reconnect_time = time.time() # 清理当前连接 if pileCode in self.connections: conn = self.connections[pileCode] if 'writer' in conn: writer = conn['writer'] try: if not writer.is_closing(): writer.close() await writer.wait_closed() except Exception: pass del self.connections[pileCode] await asyncio.sleep(1) # 短暂暂停 finally: # 更新连接池状态 if pileCode in self.connection_pool: self.connection_pool[pileCode] = { "status": "retrying" if retry_count > 0 else "active", "retry_count": retry_count, "last_retry": last_reconnect_time } def calculate_reconnect_delay(self, retry_count: int) -> float: """计算指数退避的重连延迟""" delay = self.RECONNECT_INITIAL_DELAY * (self.RECONNECT_BACKOFF_FACTOR ** retry_count) return min(delay, self.MAX_RECONNECT_DELAY) def handle_task_completion(self, task: asyncio.Task): """任务完成回调处理""" try: # 首先检查任务是否被取消 if task.cancelled(): self.logger.debug(f"任务 {task.get_name()} 被取消") return if task.done() and task.exception(): self.logger.error(f"任务失败: {task.get_name()} {task.exception()}") # 标记连接需要重启 pile_code = task.get_name().split("_")[1] if pile_code in self.connections: self.connections[pile_code]["needs_restart"] = True except Exception as e: self.logger.error(f"任务回调处理错误: {e}") async def connection_health_monitor(self): """连接健康监控器""" while self.running and not self._shutdown_event.is_set(): try: current_time = time.time() connections_to_restart = [] for pile_code, conn in list(self.connections.items()): # 检查是否超过最大重试次数 if conn.get("retry_count", 0) >= self.MAX_RETRIES: self.logger.warning(f"❌ 连接超过最大重试次数: {pile_code}") await self._close_single_connection(pile_code) continue # 检查最后活动时间 last_activity = conn.get("last_time_stamp", 0) if current_time - last_activity > 60: # 1分钟无活动 self.logger.warning(f"🕒 连接无活动超时: {pile_code}") connections_to_restart.append(pile_code) # 检查是否需要重启 if conn.get("needs_restart", False): connections_to_restart.append(pile_code) # 重启需要重置的连接 for pile_code in connections_to_restart: if pile_code in self.connections: await self._close_single_connection(pile_code) # 重新启动工作协程 self.logger.info(f"🔄 重启连接: {pile_code}") task = asyncio.create_task( self.socket_worker(f"restart-{pile_code}", pile_code), name=f"restart_{pile_code}" ) self.batch_tasks.append(task) # 定期报告 active_count = len(self.connections) restart_count = len(connections_to_restart) self.logger.info( f"🔍 健康检查: 活跃连接={active_count} 待重启={restart_count}" ) await asyncio.sleep(self.HEALTH_CHECK_INTERVAL) except Exception as e: self.logger.error(f"健康监控器异常: {e}") await asyncio.sleep(10) async def create_socket_connection_with_retry(self, message_id: str, pileCode: str, attempt: int) -> bool: """带重试机制的连接创建""" try: reader, writer = await asyncio.wait_for( asyncio.open_connection(self.server_config['ip'], self.server_config['port']), timeout=30.0 + min(attempt * 5, 30) # 随重试次数增加超时 ) self.connections[pileCode] = { 'reader': reader, 'writer': writer, 'pileCode': pileCode, 'message_id': message_id, 'retry_count': 0, 'last_heartbeat1': 0, 'last_heartbeat2': 0, 'last_time_stamp': 0, 'isLogin': False, 'timeout_count_login': 0, 'timeout_count': 0, 'heart_serialNum': "00 00", 'status': 'Offline', 'charging': False, 'priceModelList': [] } self.logger.info(f"连接成功: {message_id} 桩号: {pileCode}") return True except asyncio.TimeoutError: self.logger.warning(f"⛔ 连接超时: {message_id} 桩号: {pileCode} (尝试 #{attempt + 1})") if pileCode in self.connections: conn = self.connections[pileCode] if 'writer' in conn: writer = conn['writer'] try: if not writer.is_closing(): writer.close() await writer.wait_closed() except Exception: pass del self.connections[pileCode] await asyncio.sleep(1) # 短暂暂停 except ConnectionRefusedError: self.logger.warning(f"🚫 连接拒绝: {message_id} 桩号: {pileCode} (尝试 #{attempt + 1})") if pileCode in self.connections: conn = self.connections[pileCode] if 'writer' in conn: writer = conn['writer'] try: if not writer.is_closing(): writer.close() await writer.wait_closed() except Exception: pass del self.connections[pileCode] await asyncio.sleep(1) # 短暂暂停 except Exception as e: self.logger.error( f"❌连接错误: {message_id} 桩号: {pileCode} 错误: {e} (尝试 #{attempt + 1})") if pileCode in self.connections: conn = self.connections[pileCode] if 'writer' in conn: writer = conn['writer'] try: if not writer.is_closing(): writer.close() await writer.wait_closed() except Exception: pass del self.connections[pileCode] await asyncio.sleep(1) # 短暂暂停 return False async def start_reader_writer(self, message_id: str, pileCode: str): """启动读写任务""" if pileCode not in self.connections: return conn = self.connections[pileCode] reader = conn['reader'] writer = conn['writer'] self.writer, self.reader = AsyncSocketWriter(writer, pileCode, message_id, self.total, self.connections, self.logger), AsyncSocketReader( reader, pileCode, message_id, self.connections, self.logger) # 创建读写任务 read_task = asyncio.create_task( self.reader.start_reading(), name=f"reader_{pileCode}_{message_id}" ) write_task = asyncio.create_task( self.writer.start_write(), name=f"writer_{pileCode}_{message_id}" ) # 添加完成回调 read_task.add_done_callback(self.handle_task_completion) write_task.add_done_callback(self.handle_task_completion) # 添加到活跃任务集合 self._active_tasks.add(read_task) self._active_tasks.add(write_task) # 并行运行读写任务 try: await asyncio.gather( read_task, write_task, return_exceptions=True ) except Exception as e: self.logger.error(f"读写任务异常: {message_id} {pileCode}: {e}") def read_to_array(file_path, remove_empty=True, encoding='utf-8'): """ 读取txt文件到数组 参数: file_path: 文件路径 remove_empty: 是否移除空行 encoding: 文件编码 """ try: with open(file_path, 'r', encoding=encoding) as file: if remove_empty: lines = [line.strip() for line in file if line.strip()] else: lines = [line.strip() for line in file] # 对每行数据进行处理 processed_lines = [] for line in lines: if len(line) == 14 and line.isdigit(): # 将14位数字分割为每2位一组 parts = [line[i:i + 2] for i in range(0, 14, 2)] processed_line = ' '.join(parts) processed_lines.append(processed_line) else: processed_lines.append(line) return processed_lines except FileNotFoundError: print(f"❌错误: 文件 '{file_path}' 不存在") return [] except UnicodeDecodeError: print(f"❌错误: 使用 {encoding} 编码读取文件失败") return [] except Exception as e: print(f"❌未知错误: {e}") return [] async def async_main(): # 读取桩号列表 piles_list_config = read_to_array('pileList.txt') if not piles_list_config: print("桩号配置文件为空") return # 创建管理器实例 manager = AsyncSocketManager() manager.install_signal_handlers() try: print("开始分批建立Socket连接...") await manager.batch_create_connections(piles_list_config) # 等待关闭信号 await manager.shutdown_requested.wait() print("接收到关闭信号...") except asyncio.CancelledError: print("异步任务被取消") except Exception as e: print(f"异步Socket客户端异常: {e}") finally: # 确保资源清理 print("开始最终资源清理...") await manager.close_all_connections() manager.restore_signal_handlers() print("异步Socket客户端已完全关闭") def main(): """主入口函数""" try: # 提升资源限制(Unix系统) try: import resource soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard)) print(f"文件描述符限制提升至: {hard}") except (ImportError, ValueError): print("无法提升文件描述符限制") asyncio.run(async_main()) except KeyboardInterrupt: print("程序被用户中断") except Exception as e: print(f"程序异常退出: {e}") return 1 return 0 if __name__ == "__main__": exit(main()) 2025-11-06 23:34:56,728 [ERROR] __main__::create_socket_connection_with_retry - ❌连接错误: restart-31 01 17 00 05 65 92 桩号: 31 01 17 00 05 65 92 错误: [Errno 99] Cannot assign requested address (尝试 #1)
11-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值