需求

由于业务要求,我们实现了很多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<HostServiceMessage> mMessageQueue = new BlockingCollection<HostServiceMessage>();
         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);
        }
 
    }