稍大些的站点都是要针对图片/视频/音频/文件做集群化的存储,我这里叙述自己对视频文件的集群化存储解决方案.图片方案较此更加简单,因为不存在视频转换服务器.
图述
再解释:
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);
}
}
/// <summary>
/// 视频转换服务器回调事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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 视频转换服务器 {
/// <summary>
/// UploadVideo 的摘要说明
/// </summary>
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);
}
}
}
/// <summary>
/// 转换视频 成功则传递视频存储服务器 否则记录视频信息 并删除源文件
/// </summary>
/// <param name="filename">原始文件路径</param>
/// <param name="saveServer">存储服务器IP</param>
/// <param name="savePath">存储服务器磁盘目录</param>
/// <param name="SecondIP">视频在存储服务器上的URL</param>
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号服务 {
/// <summary>
/// SaveFile 的摘要说明
/// </summary>
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