稍大些的站点都是要针对图片/视频/音频/文件做集群化的存储,我这里叙述自己对视频文件的集群化存储解决方案.图片方案较此更加简单,因为不存在视频转换服务器.
图述
再解释:
1、客户端1发送视频文件1.avi 上传请求,通过负载均衡设备,请求被转交给WebServer集群中的一台处理.
2、WebServer直接将文件流传送至视频转换专用服务器,不做其他处理
3、视频服务器接收源文件并保存在自己的临时目录文件夹下(假设E:/temp/),并将保存视频地址E:/temp/1.avi加入转换视频队列
4、后台线程调用ffmpeg.exe转换视频1.flv 保存当前目录下E:/temp/1.flv
5、视频服务器按照特定算法(机会均等的算法)从数据库获取可用视频存储服务器,将E:/temp/1.flv发送至存储服务器
6、存储服务器保存文件后,返回存储结果(true/false) 无论true/false 视频转换服务器都将视频信息写入数据库
如果为false 视频文件存储信息表Status为"已转换" 否则为出错信息
7、当客户端2浏览视频文件时 则直接从FivPath指定服务器获取视频信息
Demo结构
由于真正的环境中需要至少三台PC机做测试,恰好手头没货,所以就在一台PC上模拟,建立三个WebSite代表不同的服务器,不同的存储目录代表不同的服务器磁盘,比如: "E:/temp/" 代表视频转换专用服务器存储磁盘 "D:/video/"代表3号存储服务器存储目录
三个WebSite解决方案内容如图:
接下来依次罗列下各WebSite中较核心的代码
WebServer服务器
WebServer中UploadFile.aspx.cs使用WebClient异步上传视频至转换服务器
using System; using System.Net; namespace WebServer服务器 { public partial class UploadFile : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void btnOK_Click(object sender, EventArgs e) { if(fileuploadVideo.HasFile) { //从配置文件获取视频转换文件服务器地址 string serverURL = Common.GetServerPath() + "?filename=" + fileuploadVideo.FileName; WebClient wc = new WebClient(); //直接将用户上传文件传递给视频转换服务器 wc.UploadDataCompleted += wc_UploadDataCompleted; wc.UploadDataAsync(new Uri(serverURL), fileuploadVideo.FileBytes); } } ////// 视频转换服务器回调事件 /// /// /// void wc_UploadDataCompleted(object sender, UploadDataCompletedEventArgs e) { if(e.Result != null) { string res = System.Text.Encoding.UTF8.GetString(e.Result); Context.Response.Write(res); } } } }
在看下ShowVideo.aspx.cs接收VideoList.aspx传递过来的视频ID,加载视频操作,前台页面使用插件播放,具体代码见下载文件
using System; using System.Linq; namespace WebServer服务器.Videos { public partial class ShowVideo : System.Web.UI.Page { protected string VideoPath; protected string VideoTitle; protected void Page_Load(object sender, EventArgs e) { int id; if(int.TryParse(Request["Id"], out id)) { DistributedDemo dd = new DistributedDemo(); var video = dd.VidoFile.FirstOrDefault(item => item.Id == id); if(video != null) { VideoPath = "http://" + video.FivPath; VideoTitle = video.Title; } } } } }
这里也列出效果图:
视频转换服务器
接收文件,存储源文件,文件路径加入转换队列中
using System.IO; using System.Web; namespace 视频转换服务器 { ////// UploadVideo 的摘要说明 /// public class UploadVideo : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; string filename = context.Request["filename"]; string path = "E:/temp/" + filename;//临时文件夹 这里写死 string ffmpegPath = context.Request.MapPath("ffmpeg.exe");//获得ffmpeg.exe绝对路径 ConvertVideo类中使用 ConvertVideo.FFmpegPath = ffmpegPath; using(FileStream fs = File.OpenWrite(path)) { //保存文件 context.Request.InputStream.CopyTo(fs); //向转换队列插入转换任务 ConvertVideo.Instance.files.Enqueue(path); context.Response.Write("已上传,格式转换中..."); } } public bool IsReusable { get { return false; } } } }
视频转换+视频信息存储+转存存储服务器
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Threading; using BLL; using Model; namespace 视频转换服务器 { public class ConvertVideo { public static readonly ConvertVideo Instance = new ConvertVideo(); public Queue<string> files = new Queue<string>(); private string Msg = string.Empty; public static string FFmpegPath = string.Empty;//ffmpeg文件 private string filePath = string.Empty;//视频在存储服务器上的存储磁盘目录 private ConvertVideo() { } public void ThreadStart() { ThreadPool.QueueUserWorkItem(GetVideo); } private void GetVideo(Object obj) { while(true) { if(files.Count == 0) { Thread.Sleep(3000); } else { string filename = files.Dequeue(); BLL.ServerInfoBLL serverInfoBll = new ServerInfoBLL(); //获取可用的视频存储服务器 通过平均算法获得 这里简单的指定为3号存储服务器 var si = serverInfoBll.GetModelList(" savestate = 0").Single(s => s.ServerId == 3); TransformVideo(filename, si.ServerIP, si.SavePath, si.SecondRealmNameIP); } } } ////// 转换视频 成功则传递视频存储服务器 否则记录视频信息 并删除源文件 /// /// 原始文件路径 /// 存储服务器IP /// 存储服务器磁盘目录 /// 视频在存储服务器上的URL private void TransformVideo(string filename, string saveServer, string savePath, string SecondIP) { string srcFileName = filename;//原始文件 string destFile = "E:/temp/" + Path.GetFileNameWithoutExtension(filename) + ".flv";//全部转换为Flv格式 try { //创建并启动一个新进程 Process p = new Process(); //设置进程启动信息属性StartInfo,这是ProcessStartInfo类,包括了一些属性和方法: p.StartInfo.FileName = FFmpegPath;//程序名 p.StartInfo.UseShellExecute = false; //-y选项的意思是当输出文件存在的时候自动覆盖输出文件,不提示“y/n”这样才能自动化 p.StartInfo.Arguments = "-i " + srcFileName + " -y -ab 56 -ar 22050 -b 800 -r 29.97 -s 420x340 " + destFile; //执行参数 p.StartInfo.RedirectStandardInput = true; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true;//把外部程序错误输出写到StandardError流中 p.ErrorDataReceived += p_ErrorDataReceived; p.OutputDataReceived += p_OutputDataReceived; p.Start(); p.BeginErrorReadLine();//开始异步读取 p.WaitForExit();//阻塞等待进程结束 p.Close();//关闭进程 p.Dispose();//释放资源 } catch(Exception ex) { //logger.Error("视频转换过程出错,filename=" + filename, ex);//记录日志 Msg = "视频转换过程出错"; return; } Msg = "转换完成"; //把flv传到视频存储服务器 存储目录在这里指定 WebClient wc = new WebClient(); //根据当前时间获得存储目录 /年/月/日/文件名 string tempstr = "/" + DateTime.Now.Year + "/" + DateTime.Now.Month + "/" + DateTime.Now.Day + "/" + Path.GetFileName(destFile); filePath = savePath + tempstr;//视频在存储服务器保存绝对路径 string VideoUrlPath = SecondIP + tempstr;//视频对外公开的URL byte[] bytes = wc.UploadData(new Uri("http://" + saveServer + "/SaveFile.ashx?filePath=" + filePath), File.ReadAllBytes(destFile)); string res = System.Text.Encoding.UTF8.GetString(bytes);//视频存储服务器返回存储结果 if(res == "true") { Msg = "视频文件转换且保存成功"; } else { Msg = "存储服务器存储失败.."; VideoUrlPath = "";//转换失败设置为空 } //视频信息存储数据库 无论成功与否 Model.VidoFile vf = new VidoFile(); vf.Status = Msg; vf.Title = Path.GetFileNameWithoutExtension(filePath); vf.FileExt = Path.GetExtension(filePath); vf.FivPath = VideoUrlPath;//存储服务器地址 BLL.VidoFileBLL bll = new VidoFileBLL(); bll.AddVido(vf); //删除源文件与转换后文件 视需求 File.Delete(srcFileName); File.Delete(destFile); } static void p_OutputDataReceived(object sender, DataReceivedEventArgs e) { //logger.Debug(e.Data); } static void p_ErrorDataReceived(object sender, DataReceivedEventArgs e) { //logger.Debug(e.Data); } } }
视频集群之3号服务
using System.IO; using System.Web; namespace 视频集群之3号服务 { ////// SaveFile 的摘要说明 /// public class SaveFile : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; string filePath = context.Request["filePath"]; string path = Path.GetDirectoryName(filePath); if(!Directory.Exists(path)) { Directory.CreateDirectory(path); } using(FileStream fs = File.Open(filePath, FileMode.OpenOrCreate)) { context.Request.InputStream.CopyTo(fs); context.Response.Write("true"); } } public bool IsReusable { get { return false; } } } }
注意与补充
1.D盘目录下必须有video文件夹
2.E盘目录下必须有temp文件夹
3.D:/video添加至IIS中 指定端口为9998