程序员的自我救赎---11.3:WinService服务
《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();
- }
- 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[];
- //服务的类名称
- string className = array[];
- //设置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 == )
- {
- job.RetryTime = DateTime.Now.AddMinutes(job.RetryInterval);
- return;
- }
- if (job.IsContinue == || job.Status == (int)JobStatus.成功)
- {
- job.NextRunTime = job.NextRunTime.HasValue ? job.NextRunTime.Value : DateTime.Now;
- if (job.Cycle != )
- {
- if (job.Cycle < )
- job.NextRunTime = job.NextRunTime.HasValue ? job.NextRunTime.Value.AddMinutes( - job.Cycle) : DateTime.Now.AddMinutes( - job.Cycle);
- else
- {
- switch ((Cycle)job.Cycle)
- {
- case Cycle.Daily: job.NextRunTime = job.NextRunTime.Value.AddDays(); break;
- case Cycle.Fortnightly: job.NextRunTime = job.NextRunTime.Value.AddDays(); break;
- case Cycle.Monthly: job.NextRunTime = job.NextRunTime.Value.AddMonths(); break;
- case Cycle.Weekly: job.NextRunTime = job.NextRunTime.Value.AddDays(); break;
- case Cycle.Yearly: job.NextRunTime = job.NextRunTime.Value.AddYears(); 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。或者扫描左侧二维码加群。
程序员的自我救赎---11.3:WinService服务的更多相关文章
- 程序员的自我救赎---11.4:FileSystem文件服务
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
- 程序员的自我救赎---11.1:RPC接口使用规范
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
- 程序员的自我救赎---1.4.2: 核心框架讲解(BLL&Tool)
<前言> <目录> (一) Winner2.0 框架基础分析 (二) 短信中心 (三)SSO单点登录 (四)PLSQL报表系统 (五)钱包系统 (六)GPU支付中心 (七)权限 ...
- 程序员的自我救赎---3.1:理解Oauth2.0
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
- 程序员的自我救赎---1.4.1:核心框架讲解(DAL)
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
- 程序员的自我救赎---12.2.3: 虚拟币交易平台(区块链) 下 【C#与以太坊通讯】
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
- 程序员的自我救赎---3.2:SSO及应用案例
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
- 程序员的自我救赎---1.4.3: 核心框架讲解(MVC)
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
- 程序员的自我救赎---10.1:APP版本控制系统
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
随机推荐
- swift 之xib自定义view可视化到storyboard
首先直入正题:@IBInspectable & @IBDesignable 对于 @IBInspectable 和 @IBDesignable 可详见官方文档 : Creating a Cus ...
- iOS之创建一个常驻线程
// 当创建一个线程,并且希望它一直存在时,但往往我们创建的线程都是执行完成之后也就停止了,不能再次利用,那么如何创建一个线程可以让他可以再次工作呢,这个时候就需要使用到RunLoop了.下面的是我写 ...
- 写出易于调试的SQL
1.前言 相比高级语言的调试如C# , 调试SQL是件痛苦的事 . 特别是那些上千行的存储过程, 更是我等码农的噩梦. 在将上千行存储过程的SQL 分解到 C# 管理后, 也存在调试的不通畅, 如何让 ...
- 【20171106早】BeEF 工具初探
老黑今天接触BeEF工具,首先要了解这个工具能够做什么? 0x01:功能介绍 专业文档:点击这里 通俗的说就是可以控制别的浏览器,获取浏览器的信息.然后做something 专业的说就是好用的渗透测试 ...
- vs2013配置opencv环境
首先本人的opencv版本是opencv2.4.9. 步骤如下: 1. 首先下载opencv2.4.9 2. 配置环境变量: 3. 系统变量:D:\opencv\build\x86\vc12\bin ...
- DOM Exception error
INDEX_SIZE_ERR code 1 索引是负值,或者超过了索引值 DOMSTRING_SIZE_ERR code 2 ...
- ⒁bootstrap组件 工具提示框 弹出框 警告框 基础案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 查看 docker 容器使用的资源
在容器的使用过程中,如果能及时的掌握容器使用的系统资源,无论对开发还是运维工作都是非常有益的.幸运的是 docker 自己就提供了这样的命令:docker stats. 默认输出 docker sta ...
- C#Session丢失问题的解决办法
关于c# SESSION丢失问题解决办法 我们在用C#开发程序的时候经常会遇到Session很不稳定,老是数据丢失.下面就是Session数据丢失的解决办法希望对您有好处.1.在WEB.CONFI ...
- Logstash&Redis&Elasticsearch&Kibana
[搭建] 一个很好的提示,强调版本的一致性 http://www.cnblogs.com/yjf512/p/4194012.html http://michael.bouvy.net/blog/en/ ...