《前言》

(一) 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();
}
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服务的更多相关文章

  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. 多服务器终端交互利器--polysh和atnodes到高逼格日志中心

    最近博客更新的少了,相对而言,我在自己的个人公众号里还是挺活跃的,大家可以扫描旁边的二维码,或者微信搜索公众号:“编程一生”加关注. 在分布式的年代,一个应用需要部署到多台服务器上.那么要查看日志文件 ...

  2. JAVA 的 Date、Calendar的常用用法

    一.Date与String的互转用法,这里需要用到SimpleDateFormat Date date = new Date();        //设置格式        SimpleDateFor ...

  3. ng-options指令语法

    ng-options一般有以下用法 对于数组: label for value in array select as label for value in array label group by g ...

  4. win7旗舰版安装IIS

    1.在控制面板中:程序->打开或关闭Windows功能 2.在弹出的对话窗中,在“Internet服务信息”中作如下勾选(打钩的为全选) 点击确定后安装. 3.安装成功后回到控制面板->系 ...

  5. 用大白话扯扯那"神奇"的面向对象编程思维(一)

    前言: 每当提到面向对象的时候,初学者肯定都是一脸懵逼的状态,到底什么是面向对象?会用面向对象后有什么牛逼之处吗?不会用是不是就会死掉?答案肯定不会死掉,我们可以来简单的举一 个栗子 1.当你想到熊猫 ...

  6. selenium元素定位

    在网页自动化测试中,我们要让程序自动模拟我们的点击.输入.悬浮.拖动等操作,完成我们的测试用例组. 输入.点击.打开这样的动词,已经包含在了selenium的方法中,可以直接调用(当然你也可以自己写) ...

  7. CentOS6.8系统下,ecipse下进行编辑操作,意外退出

    错误情况:centos下打开eclipse软件,点击*.java或者*.pom软件卡死,命令行终端报错误信息,稍后eclipse自动退出. 错误信息: Java: cairo-misc.c:380: ...

  8. 继承JFrame,只是初步

    import java.awt.*; import javax.swing.*; import javax.swing.JFrame; import java.awt.event.WindowList ...

  9. 前端如何处理emoji表情

    这段时间在做移动端的开发, 有一个功能就是发表评论,其实这个功能本身是比较简单的, 但是在提测是的时候QA给哦提了一个bug,说输入手机自带的emoji表情发送失败了.我就奇怪了,emoji表情也是文 ...

  10. vim操作命令

    一,命令模式下 文件顶部: gg 文件底部: G 删除当前行:dd 删除当前行,并进入INSERT模式: cc 取消删除:u