现行的网络服务要想做大做好必须朝着分布式服务的方向去发展,比如阿里巴巴、京东这种大型的电子商务网站平台都采用了具有各自特色的分布式服务框架。分布式服务框架就是将各种服务挂载在不同的服务器上,然后进行整体的调度管理对外提供网络服务。
本博文主要讲解分布式服务的热挂载以及多版本服务共存的原理和实现,热挂载顾名思义就是在不停止框架服务进程的情况下挂载服务。
首先要实现服务的热挂载无非就是两种方式,一种是需要部署服务时候的手动操作,以及服务框架的主动监测。在这里讲解更加智能化的框架主动监测以及实现。
要实现框架的主动监测服务兵实现热挂载
(1)定时器扫描服务契约目录,监测是否有新的服务契约上传,有的话挂载上该服务。
(2)利用消息通知被动触发服务热挂载操作。这块其实底层也是封装的定时器。
所以热挂载其实就是服务框架的定时扫描契约路径,发现新的服务契约,然后进行服务挂载。
发现服务契约之后,框架要进行的操作就是为该服务提供一个独立的运行文件夹,然后把服务运行在这个文件空间里面,但是上面又提到了多版本共存,所以这里又涉及到了一个读取服务契约版本的问题。
我的实现方式是:
(1)监测到新的打包好的服务契约文件之后,生成一个临时文件夹,把契约文件解压到该临时文件夹,然后读取该服务的服务版本,把该临时文件夹重命名为服务名和版本号的组合比如服务HelloService版本号为1.0.0.1,则我就为该版本的服务建立一个HelloService_1.0.0.1的独立服务路径
(2)然后将我的服务宿主程序拷贝到该目录下,然后运行挂载程序,挂载该服务对外提供网络服务。
(3)并在服务列表添加该服务的信息
服务契约代码如下(生成DLL为SelfHost.ServiceInterface.dll):
using ServiceStack; namespace SelfHost.ServiceInterface { public class MyServices : Service { public object Any(Hello request) { return new HelloResponse { Result = string.Format("Hello, {0}!",request.Name) }; } } [Route("/hello/{Name}")] public class Hello : IReturn<HelloResponse> { public string Name { get; set; } } public class HelloResponse { public string Result { get; set; } } }
分布式服务框架的测试程序代码如下(其中涉及一些子进程):
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Reflection; using System.IO; namespace ConsoleApplication3 { class Program { public static Dictionary<string, int> Serviceinfo = new Dictionary<string, int>(); private static readonly string Dir = Environment.CurrentDirectory; private static readonly string Wpath = Environment.CurrentDirectory + @"\WorkDir"; private static readonly string Cpath = Environment.CurrentDirectory + @"\ServiceContract"; private static readonly string HostExe = Environment.CurrentDirectory + @"\HostPacket\ServiceHost.rar"; static void Main(string[] args) { GetServiceInfo(); Console.ReadLine(); } public static int SrartService(string dllasoDir, string bindadd, string servicename,string hostdir) { Process pro = new Process(); pro.StartInfo.FileName = hostdir; //pro.StartInfo.UseShellExecute = false; pro.StartInfo.Arguments = dllasoDir + " " + bindadd + " " + servicename; pro.Start(); return pro.Id; } /// <summary> /// 准备服务目录的宿主程序 /// </summary> /// <returns></returns> private static bool PrepareService(string desfolder) { try { Process pro = new Process(); pro.StartInfo.FileName = Dir + @"\OutEXE\WinRAR\WinRAR.exe"; pro.StartInfo.Arguments = " x " + HostExe + " " + desfolder + " -iback -y"; pro.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; pro.Start(); pro.WaitForExit(); pro.Close(); //TODO 把SELFHOST命名成目的服务名 FileInfo fi = new FileInfo(desfolder + "SelfHost.exe"); var data = desfolder.Split(‘\\‘); var name = data[data.Length - 2]; string hostdir = desfolder + name + ".exe"; fi.MoveTo(hostdir); //TODO 启动宿主进程 int i = SrartService(desfolder + name.Substring(0, name.IndexOf(‘_‘))+".dll", "http://*:8055/", "HelloService " ,hostdir); Serviceinfo.Add(name,i); } catch (Exception ex) { Console.WriteLine("Message:" + ex.Message + "\r\n"); Console.WriteLine("StackTrace:" + ex.StackTrace); return false; } return true; } /// <summary> /// 获取服务契约目录下的压缩文件 /// </summary> private static void GetServiceInfo() { if (!Directory.Exists(Wpath)) { Directory.CreateDirectory(Wpath); } if (Directory.Exists(Cpath)) { var files = Directory.GetFiles(Cpath, "*.zip"); foreach (var item in files) { InitServiceContract(item.ToString()); } } else { Directory.CreateDirectory(Cpath); } } /// <summary> /// 初始化服务契约文件 /// </summary> /// <param name="filename"></param> /// <returns></returns> public static bool InitServiceContract(string filename) { string strGUID = System.Guid.NewGuid().ToString(); string desdir = Wpath + @"\" + strGUID + @"\"; Process pro = new Process(); pro.StartInfo.FileName = Dir + @"\OutEXE\WinRAR\WinRAR.exe"; pro.StartInfo.Arguments = "x " + filename + " " + desdir + " -iback -y"; pro.Start(); pro.WaitForExit(); pro.Close(); string[] buff = filename.Split(‘\\‘); string singlename = buff.ElementAt(buff.Length - 1); singlename = singlename.Replace(".zip", ".dll"); string tempdir = Wpath + @"\" + strGUID + @"\" + singlename; string version = GetDllVersion(tempdir); string key = filename.Replace(".dll", "") + "_" + version; if (Serviceinfo.ContainsKey(key) || Directory.Exists(Wpath + @"\" + singlename.Replace(".dll", "") + "_" + version)) { Console.WriteLine("已挂载服务:" + singlename + "\t版本号:" + version); Directory.Delete(Wpath + @"\" + strGUID, true); return false; } else { string desfolder = Wpath + @"\" + singlename.Replace(".dll", "") + "_" + version; Directory.Move(Wpath + @"\" + strGUID, desfolder); PrepareService(desfolder + @"\"); } return true; } /// <summary> /// 调用子进程获取版本号 /// </summary> /// <param name="filename">dll路径</param> /// <returns></returns> public static string GetDllVersion(string filename) { Process pro = new Process(); pro.StartInfo.FileName = Dir + @"\OutEXE\MyTool\GetDllVersion.exe"; pro.StartInfo.Arguments = filename; pro.StartInfo.UseShellExecute = false; pro.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; pro.StartInfo.RedirectStandardOutput = true; pro.Start(); string str = pro.StandardOutput.ReadLine().ToString(); pro.WaitForExit(); pro.Close(); return str; } } }
程序的运行路径截图如下:
其中OutEXE里面其实就是一个WINRAR解压程序包,以及我的契约版本获取进程,这儿不得不提的就是在获取器乐版本并进行文件夹的重命名的时候,涉及到操作系统的资源引用的问题,所以上述操作在一个进程内无法完成,所以写了一个获取DLL版本的小程序,通过子程序的方式获DLL版本号。
该程序代码如下:
using System; using System.Reflection; namespace GetDllVersion { public class Program { static void Main(string[] args) { Assembly ass = Assembly.LoadFile(args[0]); AssemblyName assemblyName = ass.GetName(); Version version = assemblyName.Version; Console.WriteLine(version.ToString()); } } }
这儿只是实现了一个小DEMO实现了服务的热挂载以及多版本共存,真正用到生产环节还要进行进一步的优化。
示例框架使用流程:
(1)定义好服务契约如上所示,打包压缩并将压缩文件名定位DLL名
(2)然后将服务契约压缩文件拷贝到ServiceContract目录下框架将自动进行服务的挂载
OK!