博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
实现可热插拔的服务框架
阅读量:6585 次
发布时间:2019-06-24

本文共 11286 字,大约阅读时间需要 37 分钟。

需求

由于业务要求,我们实现了很多windows服务,但是制作服务并不是一件简单的事情:

1.需要创建独立安装包

2.升级时也需要制作安装包

3.服务间无法相互通信

我们需要一个容器将这些服务包装在一起,将每个服务视作一个子服务,提供方便安装,升级,卸载的功能,监控每个服务的状态,并实现消息总线以方便子服务间通信

概念

我们可以将服务加载到相互独立的应用程序域中, 可以将windows服务管理这部分功能通过操作应用程序域来完成。这样实际上就是要实现管理这些windows服务的容器,这个容器我们称之为HostService。

HostService本身就是一个windows 服务,可以动态加载,升级,卸载子服务

HostService包括以下模块

ServiceManager-加载,卸载子服务,并保存子服务实例

autoupdater-基于squirrel.windows的自动升级模块,子服务从repository中自动下载(支持文件或HTTP协议),根据版本自动升级,支持patch升级

控制面板-用来配置服务运行的参数,比如子服务repository路径,监控子服务运行状态。控制面板和hostservice之间通过thrif通信

热插拔子服务

每个子服务就是.NET的assembly,脱离hostservice可以直接运行。如果运行在hostservice,实现主逻辑的类需要派生于HostServicebase的抽象类并实现以下接口

// base class each plugin must inherit     public abstract class HostServiceBase    {        protected abstract void OnStart(String assemblyLocation); // trigger plugin logic to start        protected abstract void OnStop(); // trigger plugin logic to stop        protected abstract String GetServiceName();// plugin friendly name        protected abstract String GetVersion(); // can be used to store verison of assembly}

  HostService加载派生于HostServicebase的类时会自动分配一个messagequeue用于和servicemanager通信,这个messagequeue是hostservice创建的,并通过)子服务程序域传到子服务实例AppDomain.CurrentDomain.SetData

HostMessageQueue

public class HostServiceMessageQueue : MarshalByRefObject    {         BlockingCollection
mMessageQueue = new BlockingCollection
(); public void AddMessage(HostServiceMessage sMsg) { mMessageQueue.Add(sMsg); } public HostServiceMessage TakeMessage() { return mMessageQueue.Take(); } public override object InitializeLifetimeService() { return null; } }

ServiceManager加载子服务的serviceRunner

public class ServiceRunner : MarshalByRefObject    {        public static int EXECUTION_TIMEOUT = 15;        protected HostServiceMessageQueue mMessageQueue = new HostServiceMessageQueue();        protected HostServiceMessageQueue mHostMessageQueue = null;          public ServiceRunner()        {                  }        public Assembly loadAssembly(String assemblyPath)        {            return AppDomain.CurrentDomain.Load(assemblyPath);        }        public override object InitializeLifetimeService()        {            return null;        }        public void SetMessageQueue( HostServiceMessageQueue hostMsgQueue)        {            mHostMessageQueue = hostMsgQueue;            AppDomain.CurrentDomain.SetData(ServiceDomainTags.TAG_PLUGINMESSAGEQUEUE, mMessageQueue);            AppDomain.CurrentDomain.SetData(ServiceDomainTags.TAG_HOSTMESSAGEQUEUE, hostMsgQueue);        }        String assemblyFolder;        public bool LoadAssembly(string assemblyFile)        {            assemblyFolder = Path.GetDirectoryName(assemblyFile);            AppDomain.CurrentDomain.SetData(ServiceDomainTags.TAG_ASSEMBLYLOCATION, assemblyFile);            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;            byte[] bytes = File.ReadAllBytes(assemblyFile);            Assembly assembly = Assembly.Load(bytes);         //   Assembly assembly = Assembly.LoadFrom(assemblyFile);            MethodInfo main = assembly.EntryPoint;            Task.Run(() => {                //  main.Invoke(null, new object[] { null});                string[] str = { "runasplugin" };/// Fill str                 object[] obj = new object[1];                obj[0] = str;                 string[] myArray = { "runasplugin" };                try                {                 //   AppDomain.CurrentDomain.ExecuteAssembly(assemblyFile, str);                    main.Invoke(null, obj);                }                catch(Exception ex)                {                    //ServiceManager.getInstance().UnLoadModule(moduleName);                    //  arg = true;                    mHostMessageQueue.AddMessage(new HostServiceMessage { Type="plugin_crash", Source=assemblyFolder ,Content = ex.Message });                }            });            int nCount = 0;            while(true)            {                Boolean? isReady = (Boolean?)AppDomain.CurrentDomain.GetData(ServiceDomainTags.TAG_READY);                if(isReady != null&& isReady.Value)                {                    break;                }                Thread.Sleep(1000);                if (++nCount >= EXECUTION_TIMEOUT)                    return false;            }                        return true;        }         private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)        {            Console.WriteLine("CurrentDomain_AssemblyResolve:" + args.RequestingAssembly.CodeBase);            string[] Parts = args.Name.Split(',');            string strFilePath = Path.Combine(assemblyFolder, Parts[0].Trim() + ".dll");            if(File.Exists(strFilePath))            {                return System.Reflection.Assembly.LoadFrom(strFilePath);            }            return null;                 }        public void Start()        {            mMessageQueue.AddMessage(new HostServiceMessage { Type = HostMessageType.STARTSERVICE });        }        public void Stop()        {                   mMessageQueue.AddMessage(new HostServiceMessage { Type = HostMessageType.STOPSERVICE });            int nCount = 0;            while (true)            {                Boolean? isExit = (Boolean?)AppDomain.CurrentDomain.GetData(ServiceDomainTags.TAG_EXIT);                if (isExit != null && isExit.Value)                {                    break;                }                Thread.Sleep(1000);                if (++nCount >= EXECUTION_TIMEOUT)                    return;            }        }         public String GetName()        {            return (String)AppDomain.CurrentDomain.GetData(ServiceDomainTags.TAG_SERVICENANME);        }        public String GetVersion()        {            return (String)AppDomain.CurrentDomain.GetData(ServiceDomainTags.TAG_SERVICEVERSION);        }        public String GetId()        {            return (String)AppDomain.CurrentDomain.GetData(ServiceDomainTags.TAG_SERVICEID);        }    }

前面提过,为了保证数据隔离,servicerunner的实例需要在一个新建的程序域创建

HostServiceMessageQueue hostMessageQueue = new HostServiceMessageQueue();AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_ReflectionOnlyAssemblyResolve;            AppDomainSetup setup = new AppDomainSetup();            setup = AppDomain.CurrentDomain.SetupInformation;            setup.ApplicationName = "ApplicationLoader";            setup.ShadowCopyFiles = "false";            setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;            setup.ConfigurationFile = entryLocation + ".config"; this.appDomain = AppDomain.CreateDomain("ApplicationLoaderDomain", null, setup); this.remoteLoader = (ServiceRunner)this.appDomain.CreateInstanceAndUnwrap(                typeof(ServiceRunner).Assembly.FullName,                typeof(ServiceRunner).FullName); this.remoteLoader.SetMessageQueue(hostMessageQueue);

通过messagequeue我们可以实现对子服务的操作:升级,日志,启动,停止

public class HostMessageType    {        public static readonly String LOG = "log";        public static readonly String UPDATE = "update";        public static readonly string STARTSERVICE = "start";        public static readonly string STOPSERVICE = "stop";    }

以下是子服务需要派生的完整的Hosrservicebase类的实现

// base class each plugin must inherit     public abstract class HostServiceBase    {        String mAssemblyPath; // plugin assembly location path                          HostServiceMessageQueue mMessagequeue = null; // messsqge queue contains message from hostService        HostServiceMessageQueue mHostMessagequeue = null; // messagque in hostservice, plugin can put message to hostservice in this queue        protected abstract void OnStart(String assemblyLocation); // trigger plugin logic to start        protected abstract void OnStop(); // trigger plugin logic to stop        protected abstract String GetServiceName();// plugin friendly name        protected abstract String GetVersion(); // can be used to store verison of assembly        protected String GetServiceID() { return "E85DE0B7-524F-46C9-9889-9243FB53258A"; }  // this should be a unique GUID for the plugin - a different one may be used for each version of the plugin.         bool _stop = false;        protected void DoStart(String assemblyLocation)        {            Log("do start");            _stop = false;           OnStart(assemblyLocation);        }         protected void DoStop()        {            Log("do stop");            _stop = true;            OnStop();        }        // log to hostservice domain        protected void Log(String strLog)        {            Console.WriteLine(strLog);            if (mHostMessagequeue != null)            {                HostServiceMessage evt = new HostServiceMessage();                evt.Source = GetServiceID();                evt.Type = HostMessageType.LOG;                evt.Content = @"(" + GetServiceName()+@") "+strLog;                mHostMessagequeue.AddMessage(evt);            }         }        public String GetAssemblyFolderPath() { return mAssemblyPath; }        // plugin working thread,need to executed in plugin main entry point        public void Execute(bool bUseServiceBus)         {            Log("Start to execute");            if (bUseServiceBus)            {                AppDomain.CurrentDomain.SetData(ServiceDomainTags.TAG_SERVICENANME,GetServiceName());                AppDomain.CurrentDomain.SetData(ServiceDomainTags.TAG_SERVICEID, GetServiceID());                AppDomain.CurrentDomain.SetData(ServiceDomainTags.TAG_SERVICEVERSION, GetVersion());                AppDomain.CurrentDomain.SetData(ServiceDomainTags.TAG_READY,true);                mMessagequeue = (HostServiceMessageQueue) AppDomain.CurrentDomain.GetData(ServiceDomainTags.TAG_PLUGINMESSAGEQUEUE);                mHostMessagequeue = (HostServiceMessageQueue)AppDomain.CurrentDomain.GetData(ServiceDomainTags.TAG_HOSTMESSAGEQUEUE);                mAssemblyPath =(String) AppDomain.CurrentDomain.GetData(ServiceDomainTags.TAG_ASSEMBLYLOCATION);                if (mMessagequeue == null || mHostMessagequeue == null) return;                while(true)                {                    HostServiceMessage message = mMessagequeue.TakeMessage();                        //  if(message.Target.CompareTo(GetServiceID()) == 0)                    {                        if(message.Type.CompareTo(HostMessageType.STOPSERVICE) == 0)                         {                            DoStop();                            break;                        }                        if (message.Type.CompareTo(HostMessageType.STARTSERVICE) == 0)                        {                            DoStart(mAssemblyPath);                                                   }                    }                }            }            else            {                DoStart(Assembly.GetExecutingAssembly().Location);                Console.WriteLine("Press any key to stop");                Console.ReadLine();                DoStop();            }             AppDomain.CurrentDomain.SetData(ServiceDomainTags.TAG_EXIT, true);        }     }

 

转载于:https://www.cnblogs.com/myhw/p/8330243.html

你可能感兴趣的文章
Java环境变量配置
查看>>
BZOJ4032 [HEOI2015]最短不公共子串
查看>>
div中的相对定位与绝对定位(转)
查看>>
PHP 根据IP地址获取所在城市
查看>>
阅读演出信息
查看>>
BZOJ 1008: [HNOI2008]越狱
查看>>
远程连接powershell
查看>>
集成Struts2+Spring+Hibernate_两种方案
查看>>
CentOS 6.5下的lamp环境rsyslog+MySQL+loganalyzer实现日志集中分析管理
查看>>
使用fiddler模拟重复请求接口
查看>>
第八周
查看>>
Python 9 Redis
查看>>
Linux Shell编程
查看>>
福大软工1816 · 第六次作业 - 团队选题报告
查看>>
【node.js】mongodb<二>
查看>>
Spring定时器Quartz的用法
查看>>
ubuntu下打开终端插件
查看>>
(转) 报文格式【定长报文】
查看>>
Gradle 构建工具
查看>>
LeetCode OJ - construct Binary Tree from Inorder and Postorder/Preorder Traversal
查看>>