OSS(Object Storage Service)是阿里云提供的海量、安全、低成本的云存储服务,用于存储和管理任意类型的文件(如图片、视频、文档等)

特点:

  • 无限容量:按需扩展,无需提前规划存储空间
  • 高可靠性:数据多副本存储,保障持久性
  • 低成本:按实际使用量付费,无闲置费用
  • 高并发访问:支持CDN加速,适合大流量场景

能解决什么问题?

  • 文件存储与管理:替代自建文件服务器,避免运维成本
  • 静态资源托管:存储网站图片、视频等静态资源,提升加载速度
  • 备份与归档:长期保存日志、数据库备份等冷数据
  • 跨地域访问:结合CDN实现全球加速

OSS的组成

OSS的存储结构相当于windows的盘符中,不创建文件夹,而是把所有文件都堆到一块

Bucket (存储桶)

在初始化oss容器时就需要指定,在OSS全局命名空间中必须有唯一名称,支持私有(只有Bucket拥有者可以读写,其他用户无权限)、公共读(Bucket拥有者可读写,匿名用户可读,知道对象URL即可下载)、公共读写(Bucket拥有者可读写,匿名用户可读写,可上传/删除/覆盖对象)三种ACL权限

  • 相当于文件系统中的”顶级目录”
  • 是存储对象的容器
  • 创建时可以指定区域(Region),数据会物理存储在该区域
  • 可以设置自动转换存储类型(标准→低频访问→归档)
  • 可以设置自动过期删除规则

Object (对象)

虽然OSS控制台显示类似目录的结构,但实际上OSS内部是扁平存储结构,目录是通过Object Key中的”/“字符模拟的,即列出目录内容时,OSS服务端会做前缀匹配

  • 相当于文件系统中的”文件”
  • 包含三个部分:
    • Key (对象键/文件名)
    • Data (数据内容)
    • Metadata (元数据)

与Windows文件系统对比

特性 阿里云 OSS Windows 文件系统
组织结构 扁平结构(只有Bucket和Object两级) 树状结构(多级目录)
目录概念 通过Key中的”/“模拟目录(实际不存在真实目录) 真实存在的目录结构
访问方式 RESTful API、SDK、控制台 文件系统API、资源管理器
元数据 每个Object有独立的元数据 文件属性与文件内容分离
性能 高并发访问性能好 单机文件系统性能有限
扩展性 理论上无限扩展 受单机存储容量限制

数据写入流程

客户端调用oss的sdk,发送http请求到OSS网关中,前端服务器接收HTTP请求,对数据进行流式处理,然后路由到对应的bucket,写入Object

持久化过程中,oss会对写入的数据进行数据分块(默认4MB/块),并行写入多个存储节点,还会有分层存储的逻辑,可以在oss控制台配置冷热数据逻辑:

  • 热数据:保留在SSD缓存层
  • 温数据:存储在HDD层
  • 冷数据:下沉到归档存储

上传速度优化

OSS在单连接上传时,通常可达50-200Mbps,多线程并发上传时,最高可达5Gbps

对速度要求不是非常高时,可以使用单连接上传,配合线程池进行优化,在我的技术选型中,我使用了单连接+OkHttpClient进行吞吐优化

OkHttpClient

OkHttpClient是一个高性能、支持HTTP/HTTPS的Java网络请求客户端库,常用于:

  • 调用REST接口
  • 发送文件或表单
  • 下载文件/数据流
  • 和OSS等第三方云服务通信

OkHttpClient本身是线程安全的,是一个连接复用+异步调度的客户端,它内部包含:

  • 连接池(ConnectionPool):复用TCP连接,减少握手开销
  • 调度器(Dispatcher):管理请求队列和异步执行
  • 线程池(ExecutorService):用于调度异步任务,允许发起多个异步请求而不会阻塞主线程,并提高网络IO的吞吐量
  • 失败重试:提升弱网稳定性

示例

引入sdk依赖:

<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>

oss配合OkHttpClient使用

private final static OkHttpClient client = new OkHttpClient().newBuilder()
.dispatcher(new Dispatcher(new ThreadPoolExecutor(2,10,60L,TimeUnit.SECONDS,new SynchronousQueue(),Util.threadFactory("OkHttp Dispatcher", false))))
.writeTimeout(30, TimeUnit.MINUTES)
.readTimeout(30, TimeUnit.MINUTES)
.build();

public static String uploadInnerFile(String url, String filePath, String bucketName, String objectName, String apiToken) throws Exception {
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", UUID.randomUUID().toString(),
RequestBody.create(MediaType.parse("multipart/form-data"), new File(filePath)))
.addFormDataPart("objectName",objectName)
.build();

Request request = new Request.Builder()
.header("accessToken",apiToken)
.header("bucketName", bucketName)
.header("objectName", objectName)
.url(url)
.post(requestBody)
.build();

Response response = client.newCall(request).execute(); // 同步请求
return response.body().string();
}