《前言》

(一) 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 管理人员

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

  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using System.Text;
  4. using System.Threading.Tasks;
  5.  
  6. namespace Winner.Job.Master.Interface
  7. {
  8. /// <summary>
  9. /// WinService工作单元接口
  10. /// </summary>
  11. public interface IJob
  12. {
  13. /// <summary>
  14. /// 执行工作单元
  15. /// </summary>
  16. /// <param name="runTime"></param>
  17. /// <returns>返回执行结果JobResult</returns>
  18. JobResult Run(DateTime runTime);
  19. }
  20. }

两个值得一说的地方:

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

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

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using System.Runtime.Remoting.Lifetime;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using Winner.Job.Master.Interface;
  9.  
  10. namespace Winner.Job.Master.Remoting
  11. {
  12. /// <summary>
  13. /// 远程处理的应用程序中跨应用程序域边界访问对象。
  14. /// </summary>
  15. public class RemoteLoader : MarshalByRefObject
  16. {
  17. private Assembly _assembly;
  18.  
  19. public void LoadAssembly(string assemblyFile)
  20. {
  21. try
  22. {
  23. _assembly = Assembly.LoadFrom(assemblyFile);
  24. }
  25. catch (Exception ex)
  26. {
  27. throw ex;
  28. }
  29. }
  30.  
  31. public T GetInstance<T>(string typeName) where T : class
  32. {
  33. if (_assembly == null) return null;
  34. var type = _assembly.GetType(typeName);
  35. if (type == null) return null;
  36. return Activator.CreateInstance(type) as T;
  37. }
  38.  
  39. public JobResult ExecuteMothod(string typeName, DateTime? runtime)
  40. {
  41. if (_assembly == null)
  42. {
  43. return JobResult.FailResult("加载程序集失败");
  44. }
  45. var type = _assembly.GetType(typeName);
  46. var obj = Activator.CreateInstance(type);
  47. IJob job = obj as IJob;
  48. return job.Run(runtime.HasValue ? runtime.Value : DateTime.Now);
  49. }
  50.  
  51. public override object InitializeLifetimeService()
  52. {
  53. ILease aLease = (ILease)base.InitializeLifetimeService();
  54. if (aLease.CurrentState == LeaseState.Initial)
  55. {
  56. // 不过期:TimeSpan.Zero
  57. aLease.InitialLeaseTime = TimeSpan.FromMinutes();
  58. }
  59. return aLease;
  60. }
  61. }
  62. }

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

准确来说是服务名:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using Winner.Framework.Utils;
  9. using Winner.Job.Master.DataAccess;
  10. using Winner.Job.Master.Entites;
  11. using Winner.Job.Master.Entites.Map;
  12. using Winner.Job.Master.Interface;
  13. using Winner.Job.Master.Remoting;
  14.  
  15. namespace Winner.Job.Master.Facade
  16. {
  17. /// <summary>
  18. /// 工作计划服务对象
  19. /// </summary>
  20. [Serializable]
  21. public class JobService
  22. {
  23. public void Execute(JobMap job)
  24. {
  25. try
  26. {
  27. //远程执行服务
  28. var result = RemoteExecute(job);
  29. if (!result.Success)
  30. {
  31. job.Status = (int)JobStatus.失败;
  32. job.ErrorInfo = result.Message;
  33. }
  34. else
  35. {
  36. job.Status = (int)JobStatus.成功;
  37. job.ErrorInfo = string.Empty;
  38. }
  39.  
  40. //计算状态和下次运行情况
  41. ModifyModel(job);
  42.  
  43. //修改数据库时间
  44. Modify(job);
  45.  
  46. //GC回收
  47. GC.Collect();
  48. }
  49. catch (Exception ex)
  50. {
  51. Log.Error(ex);
  52. }
  53.  
  54. }
  55.  
  56. /// <summary>
  57. /// 远程执行
  58. /// </summary>
  59. /// <param name="job"></param>
  60. /// <returns></returns>
  61. private JobResult RemoteExecute(JobMap job)
  62. {
  63. AppDomain appDomain = null;
  64. try
  65. {
  66. //服务反射信息
  67. string[] array = job.TypeConfig.Split(',');
  68. //服务的程序集名称
  69. string assemblyFile = array[];
  70. //服务的类名称
  71. string className = array[];
  72.  
  73. //设置AppDomain安装程序信息
  74. AppDomainSetup setup = new AppDomainSetup();
  75.  
  76. //服务名称
  77. setup.ApplicationName = job.ServiceName;
  78. //安装(运行)目录(提示:在当前运行目录的子目录,而子目录则是”服务名称“)
  79. setup.ApplicationBase = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, job.ServiceName);
  80. setup.ShadowCopyDirectories = setup.ApplicationBase;
  81. setup.ShadowCopyFiles = "true";
  82. //Config配置文件路径
  83. setup.ConfigurationFile = Path.Combine(setup.ApplicationBase, assemblyFile + ".dll.config");
  84.  
  85. //构造一个新的AppDomain
  86. appDomain = AppDomain.CreateDomain(job.ServiceName, null, setup);
  87. //获取远程调用程序 对象名称
  88. string name = Assembly.LoadFile(Path.Combine(setup.ApplicationBase, "Winner.Job.Master.Remoting.dll")).GetName().FullName;
  89.  
  90. //创建远程调用程序实例
  91. var remoteLoader = (RemoteLoader)appDomain.CreateInstanceAndUnwrap(name, typeof(RemoteLoader).FullName);
  92.  
  93. //加载服务程序集
  94. remoteLoader.LoadAssembly(Path.Combine(setup.ApplicationBase, assemblyFile + ".dll"));
  95.  
  96. //执行服务
  97. var result = remoteLoader.ExecuteMothod(className, job.NextRunTime);
  98. Log.Info(string.Format("执行结果:Service={0} Success={1} Message={2}", job.ServiceName, result.Success, result.Message));
  99. return result;
  100.  
  101. }
  102. catch (Exception ex)
  103. {
  104. Log.Error("执行服务异常", ex);
  105. return JobResult.FailResult("远程执行服务出现异常:" + ex.Message); ;
  106. }
  107. finally
  108. {
  109. if (appDomain != null)
  110. {
  111. Log.Info("卸载AppDomain:" + appDomain.FriendlyName);
  112. AppDomain.Unload(appDomain);
  113. appDomain = null;
  114. }
  115. }
  116. }
  117.  
  118. private void ModifyModel(JobMap job)
  119. {
  120. if (job == null)
  121. return;
  122. if (job.Status != (int)JobStatus.暂停 && job.Status != (int)JobStatus.成功 && job.IsContinue == )
  123. {
  124. job.RetryTime = DateTime.Now.AddMinutes(job.RetryInterval);
  125. return;
  126. }
  127. if (job.IsContinue == || job.Status == (int)JobStatus.成功)
  128. {
  129. job.NextRunTime = job.NextRunTime.HasValue ? job.NextRunTime.Value : DateTime.Now;
  130. if (job.Cycle != )
  131. {
  132. if (job.Cycle < )
  133. job.NextRunTime = job.NextRunTime.HasValue ? job.NextRunTime.Value.AddMinutes( - job.Cycle) : DateTime.Now.AddMinutes( - job.Cycle);
  134. else
  135. {
  136. switch ((Cycle)job.Cycle)
  137. {
  138. case Cycle.Daily: job.NextRunTime = job.NextRunTime.Value.AddDays(); break;
  139. case Cycle.Fortnightly: job.NextRunTime = job.NextRunTime.Value.AddDays(); break;
  140. case Cycle.Monthly: job.NextRunTime = job.NextRunTime.Value.AddMonths(); break;
  141. case Cycle.Weekly: job.NextRunTime = job.NextRunTime.Value.AddDays(); break;
  142. case Cycle.Yearly: job.NextRunTime = job.NextRunTime.Value.AddYears(); break;
  143. default:
  144. break;
  145. }
  146. }
  147. }
  148. }
  149. }
  150.  
  151. private bool Modify(JobMap job)
  152. {
  153. Tsys_Winservice daWinService = new Tsys_Winservice();
  154. daWinService.WinServiceId = job.WinServiceId;
  155. daWinService.NextRunTime = job.NextRunTime;
  156. daWinService.Status = job.Status;
  157. daWinService.RetryTime = job.RetryTime;
  158. daWinService.RetryInterval = job.RetryInterval;
  159. if (!daWinService.Update())
  160. {
  161. return false;
  162. }
  163. return true;
  164. }
  165. }
  166. }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

程序员的自我救赎---11.3:WinService服务的更多相关文章

  1. 程序员的自我救赎---11.4:FileSystem文件服务

    <前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...

  2. 程序员的自我救赎---11.1:RPC接口使用规范

    <前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...

  3. 程序员的自我救赎---1.4.2: 核心框架讲解(BLL&Tool)

    <前言> <目录> (一) Winner2.0 框架基础分析 (二) 短信中心 (三)SSO单点登录 (四)PLSQL报表系统 (五)钱包系统 (六)GPU支付中心 (七)权限 ...

  4. 程序员的自我救赎---3.1:理解Oauth2.0

    <前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...

  5. 程序员的自我救赎---1.4.1:核心框架讲解(DAL)

    <前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...

  6. 程序员的自我救赎---12.2.3: 虚拟币交易平台(区块链) 下 【C#与以太坊通讯】

    <前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...

  7. 程序员的自我救赎---3.2:SSO及应用案例

    <前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...

  8. 程序员的自我救赎---1.4.3: 核心框架讲解(MVC)

    <前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...

  9. 程序员的自我救赎---10.1:APP版本控制系统

    <前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...

随机推荐

  1. swift 之xib自定义view可视化到storyboard

    首先直入正题:@IBInspectable & @IBDesignable 对于 @IBInspectable 和 @IBDesignable 可详见官方文档 : Creating a Cus ...

  2. iOS之创建一个常驻线程

    // 当创建一个线程,并且希望它一直存在时,但往往我们创建的线程都是执行完成之后也就停止了,不能再次利用,那么如何创建一个线程可以让他可以再次工作呢,这个时候就需要使用到RunLoop了.下面的是我写 ...

  3. 写出易于调试的SQL

    1.前言 相比高级语言的调试如C# , 调试SQL是件痛苦的事 . 特别是那些上千行的存储过程, 更是我等码农的噩梦. 在将上千行存储过程的SQL 分解到 C# 管理后, 也存在调试的不通畅, 如何让 ...

  4. 【20171106早】BeEF 工具初探

    老黑今天接触BeEF工具,首先要了解这个工具能够做什么? 0x01:功能介绍 专业文档:点击这里 通俗的说就是可以控制别的浏览器,获取浏览器的信息.然后做something 专业的说就是好用的渗透测试 ...

  5. vs2013配置opencv环境

    首先本人的opencv版本是opencv2.4.9. 步骤如下: 1. 首先下载opencv2.4.9 2. 配置环境变量: 3. 系统变量:D:\opencv\build\x86\vc12\bin ...

  6. DOM Exception error

    INDEX_SIZE_ERR  code 1                              索引是负值,或者超过了索引值 DOMSTRING_SIZE_ERR  code 2       ...

  7. ⒁bootstrap组件 工具提示框 弹出框 警告框 基础案例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. 查看 docker 容器使用的资源

    在容器的使用过程中,如果能及时的掌握容器使用的系统资源,无论对开发还是运维工作都是非常有益的.幸运的是 docker 自己就提供了这样的命令:docker stats. 默认输出 docker sta ...

  9. C#Session丢失问题的解决办法

    关于c# SESSION丢失问题解决办法   我们在用C#开发程序的时候经常会遇到Session很不稳定,老是数据丢失.下面就是Session数据丢失的解决办法希望对您有好处.1.在WEB.CONFI ...

  10. Logstash&Redis&Elasticsearch&Kibana

    [搭建] 一个很好的提示,强调版本的一致性 http://www.cnblogs.com/yjf512/p/4194012.html http://michael.bouvy.net/blog/en/ ...