图片传输速度优化 阿里云OSS

2023年 8月 18日 40.5k 0

一、背景

最近接到一个需求,公司的一个演示站点上的图片加载非常慢,首次加载需要10s往上,影响用户使用体验,需要优化一下。

首先明确的是站点是部署在华东地区的阿里云服务器上,且后续会有非华东地区的用户使用,如华北或者国外用户。

其次由于这个站点只是演示性质,前期做的较为粗糙,图片直接在多端传输,影响性能。

二、方案

1、阿里云OSS+传输加速

在看到上述背景之后,首先想到的是既然站点是部署在阿里云服务器上,那是否可以借助阿里云的OSS服务来提高图片的上传下载速度。

查阅相关资料之后,常用的方案是OSS+CDN,但是阿里云OSS还直接提供传输加速服务,于是可选的方案就变成了CDN和传输加速的比较。

我们来看看两者有什么区别

  • CDN加速OSS:是建立并覆盖在承载网之上,由遍布全球的边缘节点服务器群组成的分布式网络。阿里云CDN能分担源站压力,避免网络拥塞,确保在不同区域、不同场景下加速网站内容的分发,提高资源访问速度。由CDN全球广泛分布的边缘节点缓存OSS存储的静态数据,从而实现客户端从边缘节点直接获取数据的方式来实现访问的加速。
  • OSS传输加速:利用全球分布的云机房,将全球各地用户对您存储空间(Bucket)的访问,经过智能路由解析至就近的接入点,使用优化后的网络及协议,为云存储互联网的上传、下载提供端到端的加速方案。

说白了就是阿里云CDN是利用边缘节点缓存进行加速,而传输加速则是对传输链路以及协议策略进行优化,两者还是有本质上的区别的。

最后综合考虑了一下时间以及改造的难易程度,最后还是采用了阿里云的传输加速(偷懒.jpg)

即最终方案是阿里云OSS+传输加速(需额外付费)。

2、服务端签名直传

由于站点上有上传图片的需求,之前是直接传输图片到应用服务器,网络消耗大,速度慢。

和数据直传到OSS相比,存在以下缺点:

  • 上传慢:用户数据需先上传到应用服务器,之后再上传到OSS,网络传输时间比直传到OSS多一倍。如果用户数据不通过应用服务器中转,而是直传到OSS,速度将大大提升。而且OSS采用BGP带宽,能保证各地各运营商之间的传输速度。
  • 扩展性差:如果后续用户数量逐渐增加,则应用服务器会成为瓶颈。
  • 费用高:需要准备多台应用服务器。由于OSS上行流量是免费的,如果数据直传到OSS,将节省多台应用服务器的费用。

现在采用了阿里云OSS+传输加速,需将Web端改造为直传阿里云OSS,提高上传速度,端与端之间的图片皆通过url传输。

Web端直传OSS又有以下三种方案:

  • 利用OSS Browser.js SDK将文件上传到OSS
  • 使用表单上传方式将文件上传到OSS
  • 通过小程序上传文件到OSS

我们采用表单上传方式中的服务端签名直传

服务端签名直传是指在服务端生成签名,将签名返回给客户端,然后客户端使用签名上传文件到 OSS 。由于服务端签名直传无需将访问密钥暴露在前端页面,相比客户端签名直传具有更高的安全性

三、实践

1、新建bucket,并开启传输加速

1.1 创建子用户

注意保存生成的accesskey

1.2 新建bucket

新建bucket,读写权限设置为公共读

1.3 跨域策略

1.4 生命周期设置(可忽略)

上传的的临时文件删除

1.5 开启传输加速

注意:开启之后会有传输加速域名,通过此域名才进行传输加速,并会单独付费

2、后端改造

2.1 Gradle配置SDK

implementation 'com.aliyun.oss:aliyun-sdk-oss:3.17.1'

2.2 OSS初始化

@Data
@Component
@ConfigurationProperties(prefix = "xxx.xxx")
public class AliyunOSSProperties {

    private String endpoint;

    private String accessKey;

    private String secretKey;

    private Long expire;

}
@Data
@Configuration
public class AliyunOSSConfiguration {

    @Autowired
    private AliyunOSSProperties aliyunOSSProperties;

    @Bean
    public OSS ossClient() {
        return new OSSClientBuilder().build(
                aliyunOSSProperties.getEndpoint(),
                aliyunOSSProperties.getAccessKey(),
                aliyunOSSProperties.getSecretKey());
    }

}

2.3 接口

//开启OSS客户端
long expireTime = aliyunOSSProperties.getExpire();
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;

PolicyConditions policyConditions = new PolicyConditions();
policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

//根据到期时间生成policy
Date expiration = new Date(expireEndTime);
String postPolicy = oss.generatePostPolicy(expiration, policyConditions);

//对policy进行UTF-8编码后转base64
byte[] binaryData = new byte[0];
try {
    binaryData = postPolicy.getBytes("utf-8");
} catch (UnsupportedEncodingException e) {
    throw new BusinessException("阿里云上传失败:", e);
}

String encodedPolicy = BinaryUtil.toBase64String(binaryData);

//生成signature
String postSignature = oss.calculatePostSignature(postPolicy);

AliyunOSSPolicyCO aliyunOSSPolicyCO = new AliyunOSSPolicyCO();
aliyunOSSPolicyCO.setAccessKey(aliyunOSSProperties.getAccessKey());
aliyunOSSPolicyCO.setPolicy(encodedPolicy);
aliyunOSSPolicyCO.setSignature(postSignature);
aliyunOSSPolicyCO.setExpire(expireEndTime);

return aliyunOSSPolicyCO;

3、前端改造

前端采用的是Ant Design的Upload组件

import React, {useEffect, useState} from 'react';
import {UploadOutlined} from '@ant-design/icons';
import type {UploadProps} from 'antd';
import {Button, message, Upload} from 'antd';
import type {UploadFile} from 'antd/es/upload/interface';
import {generateSign} from '@/services/xx/AliyunOSS';
import styles from "./index.less";

interface AliyunOSSUploadProps {
  value?: UploadFile[];
  onChange?: (fileList: UploadFile[]) => void;
  setOrginalPicUrl: React.Dispatch;
}

const AliyunOSSUpload : React.FC = (props) => {

  const { value, onChange, setOrginalPicUrl } = props;

  const [OSSData, setOSSData] = useState();

  const init = async () => {
    try {
      const result = await generateSign({dir: "upload"});

      if (!result || !result.success) return;
      console.log("签名请求结果: ", result.data);

      setOSSData(result.data);
    } catch (error) {
      message.error(error);
    }
  };

  useEffect(() => {
    init();
  }, []);

  const handleChange: UploadProps['onChange'] = ({ fileList , file}) => {
    onChange?.([...fileList]);

    if (file.status == 'done') {
      var url = "http://xxx" + file.originFileObj.url;
      setOrginalPicUrl(url);
    }

  };

  const onRemove = (file: UploadFile) => {
    const files = (value || []).filter((v) => v.url !== file.url);

    if (onChange) {
      onChange(files);
    }
  };

  const getExtraData: UploadProps['data'] = (file) => ({
    key: file.url,
    OSSAccessKeyId: OSSData?.accessKey,
    policy: OSSData?.policy,
    Signature: OSSData?.signature,
  });

  const beforeUpload: UploadProps['beforeUpload'] = async (file) => {
    if (!OSSData) return false;

    const expire = OSSData.expire;

    if (expire < Date.now()) {
      await init();
    }

    const suffix = file.name.slice(file.name.lastIndexOf('.'));
    const filename = Date.now() + suffix;
    // @ts-ignore
    file.url = 'upload/' + filename;

    return file;
  };

  const uploadProps: UploadProps = {
    name: 'file',
    fileList: value,
    action: "http://xxx",
    onChange: handleChange,
    onRemove,
    data: getExtraData,
    beforeUpload,
  };

  return (
    
      Upload
    
  );
};

export default AliyunOSSUpload;

四、后续改进思路

1、采用阿里云OSS+CDN

2、读写权限改为私有

五、参考文档

  • CDN加速和传输加速的区别:help.aliyun.com/zh/oss/diff…
  • 服务端签名直传:help.aliyun.com/zh/oss/use-…
  • Ant design upload组件:ant.design/components/…
  • 相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论