项目中多处使用阿里云 OSS 服务,但测试环境最近频频出现异常 [Client]Unable to execute HTTP request: SocketException。起初还以为会不会是上传方式有问题,从普通上传改为了分片上传,本想通过官方文档和浏览器检索看看有没有相关问题,但都没有什么结论。
后来在阿里云控制台提交工单,描述了一下异常情况后也没有得到很好的解决,但获得了有用的线索——OSS 日志,紧接着去开通了日志管理服务。
但这个搜索字段也是蛮多,由于异常的原因获取不到 RequestID,只能通过 http_method 字段检索 PUT 请求:

在查询结果中找到对应时间节点的记录,发现 Http 状态码为 408,请求超时:

既然请求超时了,为什么最终还能上传成功呢?通过查看源码发现添加了重试策略,默认最大重试次数 3 次,下图正是打印错误日志的代码:

所以最终得到的结论:
- 异常原因为请求超时。
- 发生异常后仍然成功是因为底层请求添加了重试策略,最多重试 3 次;如果最终结果上传失败,那就是达到重试次数上限后依然请求超时。
- 我这边只在一台云服务器上出现该情况,换了其它机器都没问题,暂时没找到原因。。。
解决方案:暂无
最后贴上分片上传的代码:
static final ExecutorService MULTIPART_UPLOAD_EXECUTOR = Executors.newFixedThreadPool(5);
/**
* 分片上传
*
* @param bucket bucket
* @param key 文件名
* @param file 要上传的文件
*
*/
public static void multipartUpload(OssProperties.BucketDetail bucket, String key, final File file) {
OSSClient ossClient = getOSSClient(bucket.getEndpoint());
try {
// 创建 InitiateMultipartUploadRequest 对象
InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucket.getName(), key);
// 初始化分片
InitiateMultipartUploadResult uploadResult = ossClient.initiateMultipartUpload(request);
// 每个分片的大小,用于计算文件有多少个分片.单位为字节,默认 1M
long partSize = 1024L * 1024;
long fileLength = file.length();
// 分片号取值范围 1~10000,如果超出此范围 OSS 将返回 InvalidArgument 错误码
int partCount = (int) (fileLength / partSize);
if (fileLength % partSize != 0) {
partCount++;
}
if (partCount > MAX_SHARD_COUNT) {
throw new RuntimeException("[OSS Multipart Upload] 文件过大,超出分片范围:" + MAX_SHARD_COUNT);
}
final String uploadId = uploadResult.getUploadId();
log.info("[OSS Multipart Upload] UploadId={},PartCount={}", uploadId, partCount);
List<PartETag> partETags = new ArrayList<>(partCount);
CountDownLatch countDownLatch = new CountDownLatch(partCount);
for (int i = 0; i < partCount; i++) {
long startPos = i * partSize;
long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
OssMultipartUploader multipartUploader = new OssMultipartUploader();
multipartUploader.setOssClient(ossClient);
multipartUploader.setBucketName(bucket.getName());
multipartUploader.setObjectName(key);
multipartUploader.setFile(file);
multipartUploader.setUploadId(uploadId);
multipartUploader.setStartPos(startPos);
multipartUploader.setPartSize(curPartSize);
multipartUploader.setPartNumber(i + 1);
multipartUploader.setPartETags(partETags);
multipartUploader.setCountDownLatch(countDownLatch);
MULTIPART_UPLOAD_EXECUTOR.execute(multipartUploader);
}
// 等待子线程执行..
countDownLatch.await();
if (partCount != partETags.size()) {
// 分片数与已上传的分片数不一致
throw new RuntimeException("[OSS Multipart Upload] 分片上传结果不一致");
}
// 按照分片号排序
partETags.sort(Comparator.comparingInt(PartETag::getPartNumber));
// 创建 CompleteMultipartUploadRequest 对象.
// 在执行完成分片上传操作时,需要提供所有有效的 partETags,OSS 收到提交的 partETags 后,会逐一验证每个分片的有效性,当所有的数据分片验证通过后,OSS 将把这些分片组合成一个完整的文件.
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(bucket.getName(), key, uploadId, partETags);
CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
log.debug("[OSS Multipart Upload] 上传完成,UploadId={},ETag={}", uploadId, completeMultipartUploadResult.getETag());
} finally {
ossClient.shutdown();
}
}
/**
* Oss 分片上传
* <p>
* 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS 会按照分片号排序组成完整的文件
*/
@Setter
@Slf4j
public class OssMultipartUploader implements Runnable {
private OSSClient ossClient;
private String bucketName;
private String objectName;
/**
* 上传的文件
*/
private File file;
/**
* 分片上传事件的唯一标识,通过 {@link InitiateMultipartUploadResult#getUploadId()} 获取
*/
private String uploadId;
private long startPos;
private long partSize;
private int partNumber;
private List<PartETag> partETags;
/**
* 上传后执行 {@link CountDownLatch#countDown}
*/
private CountDownLatch countDownLatch;
public OssMultipartUploader() {
}
@Override
public void run() {
try (InputStream inputStream = new FileInputStream(file)) {
// 跳过已经上传的分片
inputStream.skip(startPos);
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setBucketName(bucketName);
uploadPartRequest.setKey(objectName);
uploadPartRequest.setUploadId(uploadId);
uploadPartRequest.setInputStream(inputStream);
// 设置分片大小.除了最后一个分片没有大小限制,其他的分片最小为 100 KB
uploadPartRequest.setPartSize(partSize);
uploadPartRequest.setPartNumber(partNumber);
UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
// 保存分片的 PartETag
partETags.add(uploadPartResult.getPartETag());
} catch (Exception e) {
log.error("[OSS Multipart Upload] 分片上传失败,UploadId={},errMsg:", uploadId, e);
} finally {
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
}
}
本文记录了一次在使用阿里云OSS服务时遇到的[Client]Unable to execute HTTP request: SocketException异常排查过程。通过分析OSS日志,发现异常原因为请求超时,但由于设置了重试策略,导致最终请求成功。虽然问题出现在一台特定的云服务器上,但更换其他服务器则正常。目前尚未找到根本解决方案。
723

被折叠的 条评论
为什么被折叠?



