写点什么

使用内容分发网络的一点小经验

作者:为自己带盐
  • 2024-12-24
    河北
  • 本文字数:4607 字

    阅读完需:约 15 分钟

使用内容分发网络的一点小经验

背景

近期公司连续承接了一些线上直播培训活动,直播资源我们是用的是腾讯云的云直播服务,直播期间,因为大部分的流量都是腾讯在抗,对我们的压力不是很大,而直播结束后的回放阶段,因为之前的经验,看回放的学员不是很多,所以为了节约成本,回放资源就是我们自己做好切片之后,放在我们自己的服务器上自己扛。


而这次由于学员人数激增,导致回放资源访问速度骤降,整个系统也濒临不可用的极限,经过讨论,决定开通 CDN 服务,缓解压力。

准备工作

开通服务

认准大厂就好,我们这边还是用的腾讯云,文档👉:https://cloud.tencent.com/product/cdn


这里要注意一下,大厂的 CDN 服务现在都有一个 Pro 版,各家叫法不一样(腾讯这边叫 EdgeOne),主要是加强了安全性,可以更好的防止被盗刷流量,但价格普遍要比常规 CDN 贵不少。


实际上,如果常规 CDN 如果配置得当,也能做到防盗刷,这个就看公司实力了,不差钱,直接上 Pro 版就行。想勤俭持家,再搭配团队有一点使用经验和开发能力,常规版足够。

解析域名

开通 CDN 服务后,要想配置生效,还需要给我们的源站域名增加一个解析,一般都是 cname 类型。


解析完成后,云服务上的控制台会实时看到解析结果👇。


相关配置

前面的配置完成后,就可以配置一些其他的配置工作了,包括访问控制,缓存配置,https 配置等;


这里我主要聊 2 个点,其他就根据各家云服务商提供的入口,自行配置即可。

访问控制

访问控制和安全相关,这里建议大家至少要把防盗链,访问频次配置上,而鉴权之类的高阶一点的安全配置,就看开发能力和实际情况了。



Http 响应头配置

这个配置是影响浏览器行为的,我们的场景,因为回放资源都是以索引文件(m3u8)和分片文件(ts)提供,所以配置上 CDN 之后会在浏览器端有一个跨域问题,这个情况,就需要在控制台配置一下了;


注意事项

需要注意的一点是,要充分了解自己的使用场景,你要加速的资源是静态还是动态?是要进行全球加速,还是只在国内?加速的资源是大文件还是小文件等等。


还有就是,要多了解下各家平台的收费政策,一般来说,预付费的流量包还是比后付费的模式更加划算一点的。可如果你要加速的资源都是哪种请求头过大的资源(mp4,flv,zip 等),开通服务的时候,最好衡量一下选择哪种付费模式,因为这种资源一般流量消耗的都会非常快,如果我们不想修改自己的网站,比如让资源请求支持 range 参数,那最好把计费模式改为按带宽计费,能最大限度避免出现流量刺客的现象。


说到这里,我个人建议我们线上网站上的视频类资源,最好是以分片的形式,或者 flv 搭配 Range 参数的方式提供,不要直接上 mp4。而笔者之前也写过批量自动处理视频的一个博客(传送门👉:)


至于具体怎么搭配,按实际情况来就好了,有时候踩踩坑才能记得住啊哈哈。

*项目代码

这点实际上也是可选的,我这里要修改一下代码,主要还是走的勤俭持家路线。我的一个资源如果开启了加速,那我不能一直让它加着,毕竟流量消耗在那摆着,所以过了需要加速的阶段,就要把它停掉。


所以我的代码主要是启停加速,支持自动启停和手动启停 2 种方式。

开启加速

 /// <summary> /// 加速视频 /// </summary> /// <param name="liveId"></param> /// <param name="minutes">加速的分钟数,默认360分钟(6小时)</param> /// <returns></returns>[HttpPost,ValidateAntiForgeryToken]public async Task<IActionResult> TurboTs(string liveId,int minutes=360){    var live = await _context.CourseLive.Where(u => u.LiveID == Guid.Parse(liveId)).FirstOrDefaultAsync();        if (string.IsNullOrEmpty(live.FileAddress))        return Json(_resp.error("加速失败,回放文件为空"));
string originAddress = live.FileAddress.ToLower(); string turboAddress = ""; if (!originAddress.EndsWith("m3u8")) return Json(_resp.error("不能加速非HLS协议的回访文件")); if (originAddress.StartsWith("http") && originAddress.Contains(Common.ConfigurationHelper.GetSectionValue("turboHost"))) { return Json(_resp.ret(1, "已加速")); } if (originAddress.StartsWith("http")) { return Json(_resp.error("不能加速外链文件")); } turboAddress = Common.ConfigurationHelper.GetSectionValue("turboHost") + originAddress; live.FileAddress = turboAddress; live.Updated_at = DateTime.Now; _context.CourseLive.Update(live); DateTime expireTime = DateTime.Now.AddMinutes(minutes); await _redisProvider.HashSetAsync("turboFiles", live.LiveID.ToString(), $"{expireTime}|{originAddress}|{turboAddress}"); await _context.SaveChangesAsync(); return Json(_resp.success(turboAddress));}
复制代码

手动关停

/// <summary>/// 取消加速/// </summary>/// <param name="liveId"></param>/// <returns></returns>[HttpPost, ValidateAntiForgeryToken]public async Task<IActionResult> CancleTurbo(string liveId){    try    {        if (!await _redisProvider.HashExistsAsync("turboFiles", liveId))        {            return Json(_resp.error("视频未加速或加速时长已过期,无需取消"));        }        var live = await _context.CourseLive.Where(u => u.LiveID == Guid.Parse(liveId)).FirstOrDefaultAsync();        if (string.IsNullOrEmpty(live.FileAddress))        {            return Json(_resp.error("无回放文件"));        }        live.FileAddress = live.FileAddress.ToLower().Replace(Common.ConfigurationHelper.GetSectionValue("turboHost"), "");        live.Updated_at = DateTime.Now;        await _redisProvider.HashDeleteAsync("turboFiles", liveId);        _context.CourseLive.Update(live);        await _context.SaveChangesAsync();        return Json(_resp.success(live.FileAddress, "操作成功"));    }    catch (Exception ex)    {        return Json(_resp.error($"取消加速失败,{ex.Message}"));    }}
复制代码

自动关停

 public async Task Invoke() {     try     {         if (!await _redisProvider.KeyExistsAsync("turboFiles"))         {             NLogUtil.fileLogger.Warn("没有配置的加速文件");             return;         }         StringBuilder stringBuilder = new StringBuilder();         var turboFiles = await RedisConfigure.db.HashGetAllAsync("turboFiles");         foreach (var item in turboFiles)         {             if (!item.Name.HasValue)             {                 continue;             }             Guid liveId = Guid.Parse(item.Name);             var currLive = await _courseliveRepo.getOneAsync(u => u.LiveID == liveId);             string[] parts = item.Value.ToString().Split('|');             DateTime expireTime = DateTime.Parse(parts[0]);             if (DateTime.Now < expireTime)             {                 continue;             }             currLive.FileAddress = parts[1];             currLive.Updated_at = DateTime.Now;             await _courseliveRepo.updateItemAsync(currLive);             string msg = $"<p>课程【{currLive.Title}】加速结束(Id:{currLive.LiveID}),原始文件地址{parts[1]},加速地址{parts[2]}---{DateTime.Now};</p>";             stringBuilder.Append(msg);             NLogUtil.fileLogger.Warn(msg);             Console.WriteLine(msg);             await RedisConfigure.db.HashDeleteAsync("turboFiles", liveId.ToString());                             }         if (stringBuilder.Length > 0 && DateTime.Now.Hour<22 && DateTime.Now.Hour>7) {             await _emailKitHelper.SendEmailKitDevAsync("{被通知人邮箱}", "课程回放视频加速结束", stringBuilder.ToString());         }     }     catch (Exception ex) {         NLogUtil.fileLogger.Error("自动清理失败" + ex.Message);     } }
复制代码


//...注入其他服务//...services.AddTransient<AutoJobs.CacheHandle.ResetTurboVideo>();
//...启动中间件//...app.Services.UseScheduler(scheduler =>{ scheduler.OnWorker(nameof(ResetTurboVideo)); scheduler .Schedule<ResetTurboVideo>() .Hourly() .PreventOverlapping(nameof(ResetTurboVideo)) ;});
复制代码


自动关停的代码是一个计划任务类的功能,我这里使用的是 Coravel,逻辑代码写完之后,要把计划任务注入到项目当中才会生效。


整个的启停加速逻辑就是,开启加速时,预估好要加速的时长,配置完成后,如果需要随时关停,那自行操作即可,如果没有自行操作,那到时间也会自己关停,来节约流量消耗。当然如果需要持续加速,那自动关停后,再次手动开启即可。


完成后的页面效果如下


加速效果

好了,接入工作完成后,可以看一下配置了加速的资源和没配置加速的资源访问速度对比

配置了加速



可以看到,配置完加速后,排除浏览器缓存的文件,访问加速后的,1M 左右的分片文件,响应速度也是在毫秒级别,而且从请求头信息也可以看出,确实是命中了缓存,看视频丝滑流畅,加载几乎无延时;

没配置加速



而没配置加速的情况,同样访问 1M 左右的分片文件,响应时间是在秒级,注意,这是还是在负载不高的情况下,如果在高负载情况下,这个响应时长会更久,看视频卡顿也会非常明显。

缓存预热

当我们的接入工作验证生效以后,要根据情况进行预热和刷新。


我这里因为加速的视频都是小的分片文件,就需要及时的进行预热,避免负载上来以后,频繁的发送回源请求,这里腾讯云也提供了方便的预热接口,只需要把索引文件提交上去,然后勾选“预热 TS 分片”选项即可。


收尾啦

好了,至此,我们的项目就对接完成了。


最后,看一下这几天站点的监控数据,真的是不上 CDN 不行啦。

数据分析

此次我们自己 ElasticSearch 集群记录的请求日志在过去一周达到了千万级的规模,比其他项目的日志加起来还要多得多。



再来看下直播流量的消耗,一周内的规模达到了 28T,带宽峰值也来到了 13G,比我们以前承担的线上活动规模都大了几个数量级。




最后,我想说,接入一个 CDN 只能是分担一些静态资源的访问负载,除了静态资源,网站的动态资源响应能力也能考验系统并发能力,所以归根结底,还是考验的系统架构是否能弹性的支撑这种高并发的场景。


这里我还是提倡服务分层,不要怕引入一些中间件就造成系统复杂性提升而踌躇不前,以我这个案例来说,Redis 中间层的引入,起了非常大的作用,如果没有缓存层承接流量洪峰,单靠几台硬件服务器,靠系统自己,靠单个的数据库实例来抗,那早崩溃了不知道多少回了。(说到这里,笔者曾经写过一篇关于 Redis 集群部署的博客,传送门👉:https://xie.infoq.cn/article/97570be6f0bff9084a6c6e938


这种高并发场景下,系统的表现能力,也能反应开发者系统架构的设计能力,我们应该不停的突破,去更加关注系统的质量属性。


好了,就这些吧,最后没头没尾的说了一堆,哈哈。


发布于: 刚刚阅读数: 4
用户头像

学着写代码 2019-04-11 加入

是一枚,热爱技术,天赋不高,又有点轴,的猿。。

评论

发布
暂无评论
使用内容分发网络的一点小经验_腾讯云_为自己带盐_InfoQ写作社区