前段时间,公司的一个前端项目中需要展示3D模型和材质,其中模型是固定的,但材质所用到的贴图是渲染段动态生成的。实测前端加载模型+材质,总耗时将近20s,产品当然不满意。然后需要程序这边给出优化方案,首先想到的是方案一:减少资源大小,提高加载效率。另外一方面,由于图片由渲染端上传到OSS上,并通过了CDN加速,那有没有可能因为前端第一次下载时,资源没有被加速的问题呢?所以提出了方案二:图片上传后,主动调用CDN资源预热API,然后再通知前端加载被加速过的资源。
通过两个方案的实施,发现方案一已经解决了问题,前端基本上可以秒加载,但为了以后考虑,还是决定将方案二的实施过程在此记录一下。
在中间一栏的 [ ObjectPath ] 处填写需要预热的文件URL,如果有多个,使用 [ \r ] 或 [ \r\n ] 间隔。切换到C#,点击运行示例,等待片刻。
当底部控制台出现以下提示时,敲下回车即可继续
Type 'dotnet run' to make an api request with Aliyun C# SDK
shell@Alicloud:~/alibabacloud_sdk_demo/cdn/GfTCX95Fu/csharp/core$ dotnet run
若出现如下提示,即说明调试成功
ASP.NET Core
------------
Successfully installed the ASP.NET Core HTTPS Development Certificate.
To trust the certificate run 'dotnet dev-certs https --trust' (Windows and macOS only). For establishing trust on other platforms refer to the platform specific documentation.
For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
Unhandled Exception: Tea.TeaException: code: 404, The domain [hologarment-update.oss-cn-shanghai.aliyuncs.com] does not belong to you. request id: 28D96D69-4C39-5412-9CBB-37A5214928AB
at AlibabaCloud.OpenApiClient.Client.DoRPCRequest(String action, String version, String protocol, String method, String authType, String bodyType, OpenApiRequest request, RuntimeOptions runtime)
at AlibabaCloud.SDK.Cdn20180510.Client.PushObjectCacheWithOptions(PushObjectCacheRequest request, RuntimeOptions runtime)
at AlibabaCloud.SDK.Sample.Sample.Main(String[] args) in /home/shell/alibabacloud_sdk_demo/cdn/GfTCX95Fu/csharp/core/Sample.cs:line 46
细心的你可能已经发现,刚才调试时并没有填写AccessKeyId、AccessKeySecret(即AK),而且示例工程的代码也发生了变化(刚才填写的ObjectPath已经在代码中赋值),这都要归功于Aliyun,已经帮我们自动获取了当前登录账户的AK。
1. 打开压缩包,使用记事本打开 Sample.csproj ,将 netcoreapp2.1 修改为 net45 ,保存后,使用 vs201x 打开,
2. 这里要填写自己的账户AK
AlibabaCloud.SDK.Cdn20180510.Client client = CreateClient("accessKeyId", "accessKeySecret");
3. 修改ObjectPath中URL的域名,为CDN加速域名
例如:https://hologarment-update.oss-cn-shanghai.aliyuncs.com/1111111111111.jpg
改为:https://update.hologarment.tech/1111111111111.jpg
ps. 请提前在CDN-域名管理配置好需要加速的域名。然后在域名列表里将刚才配置好的加速域名绑定到二级域名,像这样
参考API:
【 获取缓存刷新预热信息 DescribeRefreshQuota 】主要用于获取当前缓存刷新预热的余量
【 预热源站内容到缓存节点 PushObjectCache 】将URL预热到缓存节点
【 通过任务编号查询刷新预热任务信息 DescribeRefreshTaskById 】根据任务id查询预热状态
为了调试方便,我将URL放入到了一个txt文档中,这样就可以一键预热多个URL了。
完整代码如下:
// This file is auto-generated, don't edit it. Thanks.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AlibabaCloud.SDK.Cdn20180510.Models;
using Tea;
using Newtonsoft.Json;
using TaskInfo = AlibabaCloud.SDK.Cdn20180510.Models.DescribeRefreshTaskByIdResponseBody.DescribeRefreshTaskByIdResponseBodyTasks;
using System.IO;
using System.Text;
namespace AlibabaCloud.SDK.Sample
{
public class Sample
{
public class Config
{
//此处填写为自己的账号AK
public const string AccessKeyId = "AccessKeyId";
public const string AccessKeySecret = "AccessKeySecret";
}
public static void Main(string[] args)
{
Cdn20180510.Client client = CreateClient(Config.AccessKeyId, Config.AccessKeySecret);
string objectPath = GetObjectPathFromAsset();
int objectPathCount = GetSubStrCountInStr(objectPath, "http", 0);
//获取缓存预热余量
int refreshRemain = GetRefreshRemain(client);
//余量不足,不再发送预热请求
if (refreshRemain < objectPathCount)
{
return;
}
PushObjectCacheRequest request = new PushObjectCacheRequest
{
// 预热区域。取值: domestic、overseas。 如果不传该参数,默认的预热区域为您的域名所配置的CDN加速区域
//Area = "";
// 需要预热的URL,多个URL之间需要用换行符\n或\r\n分隔。
//
// 注意:需要将OSS返回路径的前缀域名地址 hologarment-virtualloom.oss-cn-shanghai.aliyuncs.com
// 设置为CDN加速域名地址 hologarment-virtualloom.hologarment.tech
//
ObjectPath = objectPath,
// 安全验证
//SecurityToken="", // 默认不需要填写。除非为App用户设置了临时Token
};
string pushTaskId = PushObjectCache(client, request);
System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)); // 当地时区
long start = (long)(DateTime.Now - startTime).TotalMilliseconds; // 相差毫秒数
if (!pushTaskId.Equals("-1"))
{
TeaConsole.Client.Log("--------------------检查预热请求状态--------------------");
CheckCacheStatusById(client, pushTaskId, objectPathCount);//此处为 objectPathCount 是因为上边 ObjectPath 总共有objectPathCount个url
}
long end = (long)(DateTime.Now - startTime).TotalMilliseconds; // 相差毫秒数
TeaConsole.Client.Log($"预热 {objectPathCount} 个URL,共用时:{end - start}");
}
/**
* 使用AK&SK初始化账号Client
* @param accessKeyId
* @param accessKeySecret
* @return Client
* @throws Exception
*/
public static Cdn20180510.Client CreateClient(string accessKeyId, string accessKeySecret)
{
OpenApiClient.Models.Config config = new OpenApiClient.Models.Config
{
// 您的AccessKey ID
AccessKeyId = accessKeyId,
// 您的AccessKey Secret
AccessKeySecret = accessKeySecret,
};
// 访问的域名
config.Endpoint = "cdn.aliyuncs.com";
return new Cdn20180510.Client(config);
}
///
/// PushObjectCache 预热源站内容到缓存节点
/// https://next.api.aliyun.com/document/Cdn/2018-05-10/PushObjectCache
///
///
///
public static string PushObjectCache(Cdn20180510.Client client, PushObjectCacheRequest request)
{
try
{
PushObjectCacheResponse resp = client.PushObjectCache(request);
TeaConsole.Client.Log("--------------------预热源站内容成功--------------------");
TeaConsole.Client.Log(TeaUtil.Common.ToJSONString(resp.Body.ToMap()));
return resp.Body.PushTaskId;
}
catch (TeaException error)
{
TeaConsole.Client.Log(error.Message);
}
catch (Exception _error)
{
TeaException error = new TeaException(new Dictionary<string, object>
{
{ "message", _error.Message }
});
TeaConsole.Client.Log(error.Message);
}
return "-1";
}
/**
* PushObjectCache 预热源站内容
*/
public static async Task PushObjectCacheAsync(Cdn20180510.Client client, PushObjectCacheRequest request)
{
try
{
PushObjectCacheResponse resp = await client.PushObjectCacheAsync(request);
TeaConsole.Client.Log("--------------------预热源站内容成功--------------------");
TeaConsole.Client.Log(TeaUtil.Common.ToJSONString(resp.Body.ToMap()));
}
catch (TeaException error)
{
TeaConsole.Client.Log(error.Message);
}
catch (Exception _error)
{
TeaException error = new TeaException(new Dictionary<string, object>
{
{ "message", _error.Message }
});
TeaConsole.Client.Log(error.Message);
}
}
///
/// 通过任务编号查询刷新预热任务信息
/// https://next.api.aliyun.com/document/Cdn/2018-05-10/DescribeRefreshTaskById
///
///
///
public static void CheckCacheStatusById(Cdn20180510.Client client, string pushTaskId, int pushObjCount)
{
DescribeRefreshTaskByIdRequest request = new DescribeRefreshTaskByIdRequest()
{
TaskId = pushTaskId
};
while (true)
{
try
{
DescribeRefreshTaskByIdResponse response = client.DescribeRefreshTaskById(request);
DescribeRefreshTaskByIdResponseBody responseBody = response.Body;
Dictionary<string, object> keyValuePairs = responseBody.ToMap();
TeaConsole.Client.Log(TeaUtil.Common.ToJSONString(keyValuePairs));
if (Convert.ToInt32(keyValuePairs["TotalCount"]) == pushObjCount)
{
if (isAllCompleted(keyValuePairs["Tasks"]))
{
TeaConsole.Client.Log("---------------------预热请求已完成---------------------");
TeaConsole.Client.Log("--------------------------------------------------------");
break;
}
}
}
catch (TeaException error)
{
TeaConsole.Client.Log(error.Message);
}
catch (Exception _error)
{
TeaException error = new TeaException(new Dictionary<string, object>
{
{ "message", _error.Message }
});
TeaConsole.Client.Log(error.Message);
}
Thread.Sleep(100);
}
}
///
/// 缓存预热是否全部完成
///
///
///
public static bool isAllCompleted(object tasks)
{
List<object> taskInfos = (List<object>)tasks;
for (int i = 0; i < taskInfos.Count; i++)
{
string json = TeaUtil.Common.ToJSONString(taskInfos[i]);
TaskInfo taskInfo = JsonConvert.DeserializeObject<TaskInfo>(json);
if (!taskInfo.Status.Equals("Complete"))
{
return false;
}
}
return true;
}
///
/// 获取缓存刷新预热信息
/// https://next.api.aliyun.com/document/Cdn/2018-05-10/DescribeRefreshQuota
///
///
/// 缓存预热余量
public static int GetRefreshRemain(Cdn20180510.Client client)
{
try
{
DescribeRefreshQuotaRequest request = new DescribeRefreshQuotaRequest();
DescribeRefreshQuotaResponse response = client.DescribeRefreshQuota(request);
TeaConsole.Client.Log("--------------------缓存刷新预热余量--------------------");
Dictionary<string, object> keyValuePairs = response.Body.ToMap();
TeaConsole.Client.Log(TeaUtil.Common.ToJSONString(keyValuePairs));
TeaConsole.Client.Log($"当前剩余预热余量:{keyValuePairs["PreloadRemain"]}");
return Convert.ToInt32(keyValuePairs["PreloadRemain"]);
}
catch (TeaException error)
{
TeaConsole.Client.Log(error.Message);
}
catch (Exception _error)
{
TeaException error = new TeaException(new Dictionary<string, object>
{
{ "message", _error.Message }
});
TeaConsole.Client.Log(error.Message);
}
return 0;
}
///
/// 读取需要预热的URL地址列表
///
static string GetObjectPathFromAsset()
{
string objectPath = "";
using (FileStream fs = new FileStream("ObjectPath.txt", FileMode.Open, FileAccess.Read))
{
StreamReader reader = new StreamReader(fs, Encoding.Default);
objectPath = reader.ReadToEnd();
reader.Close();
fs.Close();
}
return objectPath;
}
public static int GetSubStrCountInStr(string str, string substr, int StartPos)
{
int foundPos = -1;
int count = 0;
do
{
foundPos = str.IndexOf(substr, StartPos);
if (foundPos > -1)
{
StartPos = foundPos + 1;
count++;
}
} while (foundPos > -1 && StartPos < str.Length);
return (count);
}
}
}