zoukankan      html  css  js  c++  java
  • 程序员的自我救赎---11.3:WinService服务

    《前言》

    (一) Winner2.0 框架基础分析

    (二)PLSQL报表系统

    (三)SSO单点登录

    (四) 短信中心与消息中心

    (五)钱包系统

    (六)GPU支付中心

    (七)权限系统

    (八)监控系统

    (九)会员中心

    (十) APP版本控制系统

    (十一)Winner前端框架与RPC接口规范讲解

    (十二)上层应用案例

    (十三)总结

    《WinService服务》

    说道Windows服务基本每个以.net为主要开发语言的技术团队都会用到这个,Winner2.0中对于WinServices也有一些与众不同的地方。

    正常来说,每次开发一个项目如果我们要用到Windows服务就要单独在项目下建立一个WinService。其实WinService 就是一个壳子。

    但是每次为了这个壳子都要投产到服务器还要通过cmd命令去部署。

    因为一直习惯这么做,可能不会觉得麻烦,但是项目一多就会发现要部署几十个服务那不那么顺心了。所以Jason开发了一套“WinServiceJob”工具。

    先说说有点再说是怎么实现的:

    1,无需新建WinService服务项目。

    2,无需cmd命令部署。

    3,更新无需停止服务。

    从思路上来说,Jason开发的WinServiceJob思路和《短信中心》是一样的(本身两个项目也都是Jason开发的)。

    简单讲就是 WinServiceJob 是一个类似Docker的容器,它本身不做任何业务。具体业务是读取数据库的配置然后反射程序集来执行的。

    WinServiceJob ,就是个空壳子。所有项目只需要编译一个service程序集然扔到 WinServiceJob 项目下,然后数据库一配置WinServiceJob 就会

    帮我们去执行工作,这里可以通过Cycle字段来配置执行周期,是一天一次还是60分钟一次。每次执行完一个服务之后更新NextRunTime保存下一次

    执行时间。

    这样省去了程序员们每次开发Windows 服务时的琐碎事情,只需在当前开发项目编译一个要执行的service.dll 然后交给WinServiceJob 管理人员

    就行了。看了前面《短信中心》讲解的就应该很清楚,要支持这种写法必须要求所有的执行项目要继承接口约束。

    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Winner.Job.Master.Interface
    {
        /// <summary>
        /// WinService工作单元接口
        /// </summary>
        public interface IJob
        {
            /// <summary>
            /// 执行工作单元
            /// </summary>
            /// <param name="runTime"></param>
            /// <returns>返回执行结果JobResult</returns>
            JobResult Run(DateTime runTime);
        }
    }

    两个值得一说的地方:

    1,WinServiceJob更新时服务无需重启;

    2,WinServiceJob可以在服务器上部署多个;

    这两个地方实现的方式也很特殊,先来看一段代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Remoting.Lifetime;
    using System.Text;
    using System.Threading.Tasks;
    using Winner.Job.Master.Interface;
    
    namespace Winner.Job.Master.Remoting
    {
        /// <summary>
        /// 远程处理的应用程序中跨应用程序域边界访问对象。
        /// </summary>
        public class RemoteLoader : MarshalByRefObject
        {
            private Assembly _assembly;
    
            public void LoadAssembly(string assemblyFile)
            {
                try
                {
                    _assembly = Assembly.LoadFrom(assemblyFile);
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
    
            public T GetInstance<T>(string typeName) where T : class
            {
                if (_assembly == null) return null;
                var type = _assembly.GetType(typeName);
                if (type == null) return null;
                return Activator.CreateInstance(type) as T;
            }
    
            public JobResult ExecuteMothod(string typeName, DateTime? runtime)
            {
                if (_assembly == null)
                {
                    return JobResult.FailResult("加载程序集失败");
                }
                var type = _assembly.GetType(typeName);
                var obj = Activator.CreateInstance(type);
                IJob job = obj as IJob;
                return job.Run(runtime.HasValue ? runtime.Value : DateTime.Now);
            }
    
            public override object InitializeLifetimeService()
            {
                ILease aLease = (ILease)base.InitializeLifetimeService();
                if (aLease.CurrentState == LeaseState.Initial)
                {
                    // 不过期:TimeSpan.Zero
                    aLease.InitialLeaseTime = TimeSpan.FromMinutes(1000);
                }
                return aLease;
            }
        }
    }

    这是一个远程调用的工具类,每个子服务中必须把这个程序集放到目录下,主服务拿子服务的目录下RemoteLoader来获取子服务的信息,

    准确来说是服务名:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Threading.Tasks;
    using Winner.Framework.Utils;
    using Winner.Job.Master.DataAccess;
    using Winner.Job.Master.Entites;
    using Winner.Job.Master.Entites.Map;
    using Winner.Job.Master.Interface;
    using Winner.Job.Master.Remoting;
    
    namespace Winner.Job.Master.Facade
    {
        /// <summary>
        /// 工作计划服务对象
        /// </summary>
        [Serializable]
        public class JobService
        {
            public void Execute(JobMap job)
            {
                try
                {
                    //远程执行服务
                    var result = RemoteExecute(job);
                    if (!result.Success)
                    {
                        job.Status = (int)JobStatus.失败;
                        job.ErrorInfo = result.Message;
                    }
                    else
                    {
                        job.Status = (int)JobStatus.成功;
                        job.ErrorInfo = string.Empty;
                    }
    
                    //计算状态和下次运行情况
                    ModifyModel(job);
    
                    //修改数据库时间
                    Modify(job);
    
                    //GC回收
                    GC.Collect();
                }
                catch (Exception ex)
                {
                    Log.Error(ex);
                }
    
            }
    
            /// <summary>
            /// 远程执行
            /// </summary>
            /// <param name="job"></param>
            /// <returns></returns>
            private JobResult RemoteExecute(JobMap job)
            {
                AppDomain appDomain = null;
                try
                {
                    //服务反射信息
                    string[] array = job.TypeConfig.Split(',');
                    //服务的程序集名称
                    string assemblyFile = array[0];
                    //服务的类名称
                    string className = array[1];
    
                    //设置AppDomain安装程序信息
                    AppDomainSetup setup = new AppDomainSetup();
    
                    //服务名称
                    setup.ApplicationName = job.ServiceName;
                    //安装(运行)目录(提示:在当前运行目录的子目录,而子目录则是”服务名称“)
                    setup.ApplicationBase = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, job.ServiceName);
                    setup.ShadowCopyDirectories = setup.ApplicationBase;
                    setup.ShadowCopyFiles = "true";
                    //Config配置文件路径
                    setup.ConfigurationFile = Path.Combine(setup.ApplicationBase, assemblyFile + ".dll.config");
    
                    //构造一个新的AppDomain
                    appDomain = AppDomain.CreateDomain(job.ServiceName, null, setup);
                    //获取远程调用程序 对象名称
                    string name = Assembly.LoadFile(Path.Combine(setup.ApplicationBase, "Winner.Job.Master.Remoting.dll")).GetName().FullName;
    
                    //创建远程调用程序实例
                    var remoteLoader = (RemoteLoader)appDomain.CreateInstanceAndUnwrap(name, typeof(RemoteLoader).FullName);
    
                    //加载服务程序集
                    remoteLoader.LoadAssembly(Path.Combine(setup.ApplicationBase, assemblyFile + ".dll"));
    
                    //执行服务
                    var result = remoteLoader.ExecuteMothod(className, job.NextRunTime);
                    Log.Info(string.Format("执行结果:Service={0} Success={1} Message={2}", job.ServiceName, result.Success, result.Message));
                    return result;
    
                }
                catch (Exception ex)
                {
                    Log.Error("执行服务异常", ex);
                    return JobResult.FailResult("远程执行服务出现异常:" + ex.Message); ;
                }
                finally
                {
                    if (appDomain != null)
                    {
                        Log.Info("卸载AppDomain:" + appDomain.FriendlyName);
                        AppDomain.Unload(appDomain);
                        appDomain = null;
                    }
                }
            }
    
            private void ModifyModel(JobMap job)
            {
                if (job == null)
                    return;
                if (job.Status != (int)JobStatus.暂停 && job.Status != (int)JobStatus.成功 && job.IsContinue == 2)
                {
                    job.RetryTime = DateTime.Now.AddMinutes(job.RetryInterval);
                    return;
                }
                if (job.IsContinue == 1 || job.Status == (int)JobStatus.成功)
                {
                    job.NextRunTime = job.NextRunTime.HasValue ? job.NextRunTime.Value : DateTime.Now;
                    if (job.Cycle != 0)
                    {
                        if (job.Cycle < 0)
                            job.NextRunTime = job.NextRunTime.HasValue ? job.NextRunTime.Value.AddMinutes(0 - job.Cycle) : DateTime.Now.AddMinutes(0 - job.Cycle);
                        else
                        {
                            switch ((Cycle)job.Cycle)
                            {
                                case Cycle.Daily: job.NextRunTime = job.NextRunTime.Value.AddDays(1); break;
                                case Cycle.Fortnightly: job.NextRunTime = job.NextRunTime.Value.AddDays(14); break;
                                case Cycle.Monthly: job.NextRunTime = job.NextRunTime.Value.AddMonths(1); break;
                                case Cycle.Weekly: job.NextRunTime = job.NextRunTime.Value.AddDays(7); break;
                                case Cycle.Yearly: job.NextRunTime = job.NextRunTime.Value.AddYears(1); break;
                                default:
                                    break;
                            }
                        }
                    }
                }
            }
    
            private bool Modify(JobMap job)
            {
                Tsys_Winservice daWinService = new Tsys_Winservice();
                daWinService.WinServiceId = job.WinServiceId;
                daWinService.NextRunTime = job.NextRunTime;
                daWinService.Status = job.Status;
                daWinService.RetryTime = job.RetryTime;
                daWinService.RetryInterval = job.RetryInterval;
                if (!daWinService.Update())
                {
                    return false;
                }
                return true;
            }
        }
    }

    这里采用的方式是,每次新的服务去创建一个新的APPDomain 和 线程去执行,执行完了之后线程同步,APPDomain 也卸载掉。

    包括使用影加载,这里的效果就是不会对dll进行文件占用,所以随意更新dll是不不需要去重启服务的。

    另外前面有说到 新增一个子服务 也可以不需要重启服务,这种方式是可以实现的,但是因为要不停的读取数据库,所以后期修改了一下。

    为了避免每次时时刻刻都要去扫数据库,所以采用了一次性加载到队列当中,然后如果有更新服务的话,还是要重启WinServiceJob。

    最后,要说的是如果一个WinServiceJob可能负载的子服务太多造成臃肿执行性能低的话,我们可以部署多个。由于Winservice重名的话是

    部署不了的。所以这里WinServiceJob在部署时具体的名称我们是从xml配置文件中读取的。

    <?xml version="1.0" encoding="utf-8"?>
    <Root>
      <InstallInfo>
        <ServiceName value="Winner.Job.Master.WinService" />
        <DisplayName value="Winner.Job.Master.DisplayName" />
        <Description value="Winner.Job.Master.Description 列队0号" />
      </InstallInfo>
    </Root>

    C# 这边就读取配置文件就行了:

    这里当时特地还做了测试,直接从APP.Config文件中读取是读不到的,所以要单独写xml文件去配置。

    我们来看看最终的部署结构目录:

     我们在实际的使用过程中差不多一个WinServiceJob负载了二三十个服务在跑。 总共开发的四五年里我们写的WinService不下上百个。

    WinServiceJob 帮程序员省去了开发服务,部署服务的一系列琐碎事宜。

    好了,就写到这里。最后一句话:代码不重要,还是思想。WinServiceJob主要还是模仿了类似Docker容器这种思想来做的。

    这里我把整个WinServiceJob 的代码全部开源到GitHub方便大家参考:https://github.com/demon28/WinServiceJob

    有兴趣一起探讨Winner框架的可以加我们QQ群:261083244。或者扫描左侧二维码加群。

  • 相关阅读:
    opengles 2.0 渲染Yuv视频
    wzplayer for android 版本面世
    wzplayer for android V1.0快出炉了
    wzplayer for android V1.0快出炉了
    wzplayer for android V1.0
    opengles 2.0 渲染Yuv视频
    新浪微博发布案例
    前端开发单位em
    vue 做一个简单的TodoList
    jquery对类的操作,添加,删除,点击添加,再点击删除
  • 原文地址:https://www.cnblogs.com/demon28/p/8024768.html
Copyright © 2011-2022 走看看