使用20线程线程池上传图片至Amazon S3遇连接超时问题求助
先看你日志里的关键错误信息:Exception in thread "pool-1-thread-12" com.amazonaws.SdkClientException: Unable to execute HTTP request: Connect to s3.amazonaws.com:80 [s3.amazonaws.com/54.231.66.16] failed: connect timed out——你的客户端居然在连接官方AWS S3地址,但你代码里明明设置了Ceph的Endpoint,这大概率是VEEConfig.CEPH_ENDPOINT()没返回正确地址,甚至是空值,导致客户端 fallback 到默认的官方S3 endpoint,而你的网络环境无法访问它,直接触发了超时。这是第一个要紧急排查的点!
除了这个核心问题,多线程高并发场景下,默认的ClientConfiguration参数也撑不住20线程的上传需求,下面是具体的修复方案:
1. 先确认Endpoint配置的正确性
先在代码里加一行日志,验证VEEConfig.CEPH_ENDPOINT()的返回值:
System.out.println("当前使用的Ceph Endpoint: " + VEEConfig.CEPH_ENDPOINT());
确保它返回的是你的Ceph RGW网关完整地址,比如http://your-ceph-server:7480(Ceph网关默认端口是7480)。如果是空值或者错误地址,客户端就会自动用官方S3地址,这就是超时的根源。
2. 优化ClientConfiguration的并发与超时参数
默认的客户端配置连接数、超时时间都偏保守,完全不适合20线程的高并发场景,修改你的配置部分:
ClientConfiguration clientConfig = new ClientConfiguration(); clientConfig.setProtocol(Protocol.HTTP); clientConfig.setSignerOverride("S3SignerType"); // 最大连接数要大于等于线程池大小(20),比如设为30,避免连接池耗尽 clientConfig.setMaxConnections(30); // 建立TCP连接的超时时间,网络不稳定时调大,比如5000ms clientConfig.setConnectionTimeout(5000); // 读取服务器响应的超时时间,上传大文件时设长些,比如15000ms clientConfig.setSocketTimeout(15000); // 给连接超时这类临时错误加重试策略,避免单次超时直接失败 clientConfig.setRetryPolicy(new RetryPolicy( RetryPolicy.RetryCondition.NO_RETRY_CONDITION, RetryPolicy.BackoffStrategy.DEFAULT_BACKOFF_STRATEGY, 3, // 最大重试3次 true // 允许重试请求 ));
参数说明:
setMaxConnections:控制HTTP连接池的容量,线程数超过连接池大小会导致任务等待,进而触发超时,所以必须设得比线程数大。ConnectionTimeout:解决网络波动时建立连接慢的问题。SocketTimeout:避免大文件上传时因为传输慢被判定为超时。RetryPolicy:针对连接超时这类可恢复的错误自动重试,提升任务成功率。
3. 确认线程池与单例模式的合理性
你的单例模式(双重检查锁)是正确的,AmazonS3Client本身是线程安全的,复用单例比每个线程创建新客户端更节省资源。另外检查你的20线程池配置:如果任务队列过长、拒绝策略不合理,也可能间接导致资源耗尽,建议线程池的队列长度不要设置过大,避免任务堆积。
4. 最后排查网络环境
如果以上配置都没问题还是超时,要检查:
- 服务器能否ping通Ceph网关地址,用telnet测试端口是否开放(比如
telnet your-ceph-server 7480)。 - 是否有防火墙、代理限制了HTTP请求到Ceph网关。
- Ceph RGW的并发连接数是否有限制,可以查看Ceph配置文件调整。
修改后的完整CephS3Client代码示例:
import com.amazonaws.ClientConfiguration; import com.amazonaws.Protocol; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.retry.RetryPolicy; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.gaodig.stream.common.Constants; import com.gaodig.stream.config.VEEConfig; public class CephS3Client { private static volatile AmazonS3 s3Client = null; public static AmazonS3 getClient() { if (s3Client == null) { synchronized (CephS3Client.class) { if (s3Client == null) { AWSCredentials credentials = new BasicAWSCredentials(Constants.CephS3AccessKey, Constants.CephS3SecretKey); ClientConfiguration clientConfig = new ClientConfiguration(); clientConfig.setProtocol(Protocol.HTTP); clientConfig.setSignerOverride("S3SignerType"); // 优化连接与超时配置 clientConfig.setMaxConnections(30); clientConfig.setConnectionTimeout(5000); clientConfig.setSocketTimeout(15000); clientConfig.setRetryPolicy(new RetryPolicy( RetryPolicy.RetryCondition.NO_RETRY_CONDITION, RetryPolicy.BackoffStrategy.DEFAULT_BACKOFF_STRATEGY, 3, true )); s3Client = new AmazonS3Client(credentials, clientConfig); // 验证Endpoint是否正确 String endpoint = VEEConfig.CEPH_ENDPOINT(); System.out.println("Using Ceph Endpoint: " + endpoint); s3Client.setEndpoint(endpoint); } } } return s3Client; } }
内容的提问来源于stack exchange,提问作者litong

