You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用多进程调用boto3 S3 upload_fileobj触发SSLError问题求助

问题

运行环境:AWS Lambda(Python 3.9,boto3 1.20.32)
代码功能:从S3桶的tar文件中按字节范围提取单个文件,通过多进程上传至另一S3桶,加速大量文件的传输。

核心代码:

s3_client = boto3.client(service_name="s3")
s3_bucket = "bucket"
s3_other_bucket = "other_bucket"

def multiprocess_s3upload(tar_index: dict):

    def _upload(filename, bytes_range):
        src_key = ...

        # 按字节范围获取tar中的单个文件
        s3_obj = s3_client.get_object(
            Bucket=s3_bucket,
            Key=src_key,
            Range=f"bytes={bytes_range}"
        )

        # 上传文件(报错位置)
        s3_client.upload_fileobj(
            s3_obj["Body"],
            s3_other_bucket,
            filename
        )

    def _wait(procs):
        for p in procs:
            p.join()
    
    processes = []
    proc_limit = 256  # 限制并发数避免"打开过多文件"错误
    for filename, bytes_range in tar_index.items():
        proc = Process(
            target=_upload,
            args=(filename, bytes_range)
        )
        proc.start()
        processes.append(proc)
        
        if len(processes) == proc_limit:
            _wait(processes)
            processes = []

    _wait(processes)

报错现象:随机抛出SSLError(仅最后一个子进程报错),报错信息:

Process Process-2:
Traceback (most recent call last):
File "/var/runtime/urllib3/response.py", line 441, in _error_catcher
  yield
File "/var/runtime/urllib3/response.py", line 522, in read
  data = self._fp.read(amt) if not fp_closed else b""
File "/var/lang/lib/python3.9/http/client.py", line 463, in read
  n = self.readinto(b)
File "/var/lang/lib/python3.9/http/client.py", line 507, in readinto
  n = self.fp.readinto(b)
File "/var/lang/lib/python3.9/socket.py", line 704, in readinto
  return self._sock.recv_into(b)
File "/var/lang/lib/python3.9/ssl.py", line 1242, in recv_into
  return self.read(nbytes, buffer)
File "/var/lang/lib/python3.9/ssl.py", line 1100, in read
  return self._sslobj.read(len, buffer)
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:2633)

During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/var/lang/lib/python3.9/multiprocessing/process.py", line 315, in _bootstrap
  self._target(*self._args, **self._kwargs)
File "/var/task/main.py", line 144, in _upload
  s3_client.upload_fileobj(
File "/var/runtime/boto3/s3/inject.py", line 540, in upload_fileobj
  return future.result()
File "/var/runtime/s3transfer/futures.py", line 103, in result
  return self._coordinator.result()
File "/var/runtime/s3transfer/futures.py", line 266, in result
  raise self._exception
File "/var/runtime/s3transfer/tasks.py", line 269, in _main
  self._submit(transfer_future=transfer_future, **kwargs)
File "/var/runtime/s3transfer/upload.py", line 588, in _submit
  if not upload_input_manager.requires_multipart_upload(
File "/var/runtime/s3transfer/upload.py", line 404, in requires_multipart_upload
  self._initial_data = self._read(fileobj, threshold, False)
File "/var/runtime/s3transfer/upload.py", line 463, in _read
  return fileobj.read(amount)
File "/var/runtime/botocore/response.py", line 82, in read
  chunk = self._raw_stream.read(amt)
File "/var/runtime/urllib3/response.py", line 544, in read
  raise IncompleteRead(self._fp_bytes_read, self.length_remaining)
File "/var/lang/lib/python3.9/contextlib.py", line 137, in __exit__
  self.gen.throw(typ, value, traceback)
File "/var/runtime/urllib3/response.py", line 452, in _error_catcher
  raise SSLError(e)

urllib3.exceptions.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:2633)

已知背景:排查过旧问题(非线程安全HTTP库)但无效,需要稳定解决方案。


解决方案

1. 每个子进程创建独立的S3客户端

boto3客户端并非多进程安全,跨进程共享会导致连接池、SSL上下文冲突。修改_upload函数,在子进程内部初始化S3客户端:

def _upload(filename, bytes_range):
    # 子进程内单独创建S3客户端
    s3_client = boto3.client(service_name="s3")
    src_key = ...

    s3_obj = s3_client.get_object(
        Bucket=s3_bucket,
        Key=src_key,
        Range=f"bytes={bytes_range}"
    )

    s3_client.upload_fileobj(
        s3_obj["Body"],
        s3_other_bucket,
        filename
    )

2. 改用多线程替代多进程

Lambda环境中多进程开销更大,且boto3客户端是线程安全的(每个线程使用独立客户端或线程安全连接池即可)。用ThreadPoolExecutor替换Process:

from concurrent.futures import ThreadPoolExecutor

def multiprocess_s3upload(tar_index: dict):
    def _upload(filename, bytes_range):
        s3_client = boto3.client(service_name="s3")
        src_key = ...

        s3_obj = s3_client.get_object(
            Bucket=s3_bucket,
            Key=src_key,
            Range=f"bytes={bytes_range}"
        )

        s3_client.upload_fileobj(
            s3_obj["Body"],
            s3_other_bucket,
            filename
        )
    
    # Lambda环境建议线程数不超过100,避免资源耗尽
    with ThreadPoolExecutor(max_workers=100) as executor:
        executor.map(_upload, tar_index.keys(), tar_index.values())

3. 降低并发数

当前设置的256并发数过高,Lambda的文件句柄、网络连接资源有限,容易触发SSL异常。将proc_limit调整为64或更低,测试出适合当前Lambda配置的最优值。

4. 添加重试机制

针对SSLError这类偶发网络异常,添加指数退避重试逻辑:

from botocore.exceptions import ClientError
import time
import ssl

def _upload(filename, bytes_range):
    s3_client = boto3.client(service_name="s3")
    src_key = ...
    max_retries = 3

    for attempt in range(max_retries):
        try:
            s3_obj = s3_client.get_object(
                Bucket=s3_bucket,
                Key=src_key,
                Range=f"bytes={bytes_range}"
            )

            s3_client.upload_fileobj(
                s3_obj["Body"],
                s3_other_bucket,
                filename
            )
            break
        except (ClientError, ssl.SSLError) as e:
            if attempt == max_retries -1:
                raise
            # 指数退避重试
            time.sleep(2 ** attempt)

5. 升级boto3版本

boto3 1.20.32属于较旧版本,后续版本修复了多个多进程/线程下的连接问题。如果Lambda环境允许,升级到最新稳定版(如1.34.x)。


内容的提问来源于stack exchange,提问作者Chiamin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 20:40:34