转载我自己在 codeproject.com上的一篇文章。http://www.codeproject.com/Articles/320391/Non-stopping-upgradable-service-framework
Download Non-Stopping-Upgadable_Service_Framework_Demo - 2.03 MB
As back-end service, we might need to upgrade it many times to meet different requirements, maybe requirements changed, maybe issue found and resolved, maybe business logic changed. All these kinds of things are required to upgrade a service. Then if we can upgrade a service without stop running, it should be a better idea. This framework is designed for upgrade a service without stop running, which support upgrade on the fly.
1. Support upgrade services without stop it. Usually, a upgrade process may contains: backup program data, stop/uninstall old version, install/start or upgrade new version. It's a long and large process compared upgrade components directly without stop services.
2. Flexible to extend components. In this framework, support from 0 to numbers of components in this framework, it's easy to define relationship between components.
3. Minimize upgrade process, easy to maintain, which also can avoid many mistake by manually.
4. Reduce outage time if services running in 24*7 mode. In some areas, services can't stop at all, even you may have backup services, stopped some services may cause others services under high pressure.
Actually, this idea came from my real project, we implemented service running back-end, which designed to collect news and distribute news to downstream. We faced many times regarding to requirements change, business logic change or dependencies changed. And every time we upgrade service, we have to following a long and hard process to do it, e.g. active some services in some location, inactive some services in another location... upgrade some services...etc, finally, we need to restore all services status back to before upgrade. It's boring job and have to following process carefully to avoid mistakes.
Steps to demo:
1) Download from above link, and unzip it to some location, e.g. X:\CodeProject.com, the following steps assume location to X:\CodeProject.com\Non-Stopping-Upgadable_Service_Framework_Demo
2) Open a CMD window and locate to
X:\CodeProject.com\Non-Stopping-Upgadable_Service_Framework_Demo\bin
, which contains demo program: DemoServiceProgram.exe and component: ClassLibrary1.dll
3) Start DemoServiceProgram.exe, and you will see the content like left windows in above image. In demo program, we have three components: reader1
, processor1
, dispatcher1
the
reader1
will create a object Created MyObject in ClassLibrary1. No.XX
, this object mocked as read object from somewhere, processor1
mocks some business, and finally the object goes to dispatcher1
, which may dispatch object to downstream or save it to local file or database.
4) Open another CMD window and locate to X:\CodeProject.com\Non-Stopping-Upgadable_Service_Framework_Demo\bin , you will see two batch files, _UpdateToClassLibrary1.bat and _UpdateToClassLibrary2.bat. Please run _UpdateToClassLibrary2.bat,and you will see the DemoServiceProgram detected components chagned, and re-created components. Then the DemoServiceProgram start do business by ClassLibrary2.dll.
5) These(2 bat files) are shortcuts for simulate a upgrade behavior, Run _UpdateToClassLibrary2.bat will use ClassLibrary2.dll file as new file to replace current using file; Run _UpdateToClassLibrary1.bat will use ClassLibrary1.dll file as new file to replace current using file; The default class library is ClassLibrary1.dll, we have a copy in v1 folder, and another version of it is ClassLibrary2.dll, also have a copy in v2 folder.
If you want replace back using ClassLibrary1, please run _UpdateToClassLibrary1.bat, you will find the program will back to using components in ClassLibrary1.dll, and started to do business.
1) Implement the class which requires inherited from IComponent
. In demo program, we combined all components(Reader
, Processor
, Dispatcher
) in one assembly, but we recommended one component one assembly in real project.
2) Configurate components in app.config file, in demo project, we have following configurations:
xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="componentSection" type="ClassContract.ComponentSectionHandler,ClassContract"/> </configSections> <componentSection> <components> <component name="reader1" assemblyName="ClassLibrary1.dll" className="ClassLibrary1.Reader" isUpgradable="true" downstreams="processor1" /> <component name="processor1" assemblyName="ClassLibrary1.dll" className="ClassLibrary1.Processor" isUpgradable="true" downstreams="dispatcher1" /> <component name="dispatcher1" assemblyName="ClassLibrary1.dll" className="ClassLibrary1.Dispatcher" isUpgradable="true" downstreams="" /> </components> </componentSection> </configuration>
At first, we added custom section, set it's type toClassContract.ComponentSectionHandler,ClassContract
, then added
node, and added all components under
name | Required, the name of component, this also used as key of component, must be a unique. |
assemblyName | Required, the assembly Name of component, this can be a related path, also can be a absolute path. e.g. ClassLibrary1.dll, which will be searched where program located. or X:\codeproject.com\ClassLibrary1.dll |
className | Required, the class fullname of component, which requried implement IComponent interface. |
isUpgradable | Optional, indicates whether support upgrade when framework running. |
downstreams | Optional, indicates which components are downstream components, support 0 or multi-components, please fill this field by component names, multi-components split by comma. e.g. component1 e.g. component1,component2,component3...componentN Notes: component itself can't be its downstream. (or you can change the method |
You can start framework now when you finished above things.
The class diagram of framework show below:
This diagram show classes from up to bottom, but I will describe classes from bottom to up, the Mediator
class is mainly business class, in main()
method, we use Mediator
like this:
static void Main(string[] args) { Mediator e = Mediator.GetInstance(); e.Init(); // keep the console alive... Console.ReadKey(); }
Firstly, we wrote Mediator class in singlton pattern, in one program, only one mediator is enough, and to protect Mediator only has one instance. We applied singltom pattern on Mediator class. Mediator is responsible for manage components:
ComponentSectionHandler config = ComponentSectionHandler.GetConfigurationSection();
Mediator
and ComponentManager
,ComponentManager
is responsible for create instance of component, and detects assembly change of component. When ComponentManager
detected assembly file change, it will inform Mediator class to determine whether to re-create component instance (we can add a property in IComponent interface, to indicate whether component is busy or not, if it's not busy, we can replace it, otherwise we can wait until it's not busy, this doesn't included in demo, just an idea). ComponentManager
class calcualtes assembly file MD5 value to determine whether the file changed or not, file changed event raised by FileSystemWatcher class.//------------------------------ // Create components //------------------------------ Console.WriteLine("------ Create components ------"); foreach (ComponentConfigurationElement componentConfig in config.Components) { ComponentManager componentMgr = new ComponentManager(); componentMgr.ComponentConfiguration = componentConfig; componentMgr.Updated += new EventHandler(ComponentManager_Updated); IComponent component = componentMgr.CreateInstance(); if (component != null) { component.Name = componentConfig.Name; component.Downstreams = componentConfig.Downstreams; componentMgr.EnableMonitor = componentConfig.IsUpgradable; this.ComponentList.Add(component); Console.WriteLine("\tCreated component: {0}", component.Name); } else { Console.WriteLine("\tError to create component: {0}", componentConfig.Name); } }
GetUpstreamComponents
, GetDownsteamComponents,
//------------------------------ // Register components events //------------------------------ Console.WriteLine("------ Register components events ------"); foreach (IComponent component in this.ComponentList) { ListdownstreamComps = GetDownsteamComponents(component); if (downstreamComps != null && downstreamComps.Count > 0) { component.DownstreamComponents = downstreamComps; component.AfterBusiness += Component_AfterBusiness; Console.WriteLine("\tRegistered component: {0} downstreams:{1}", component.Name, component.Downstreams); } }
Registered AfterBusiness
event for each component, we use event mechanism to pass data between components. In Component_AfterBusiness
method, it will inform all downstream component to do its business by call DoBusinesss
method, the data contained in ComponentDataEventArgs.Data
property, its type is object
, if we need filter data in different component, we can filter data in DoBusinesss
method in that component.
//------------------------------ // Start components //------------------------------ Console.WriteLine("------ Start components ------"); this.ComponentList.ForEach(p => { p.Start(); Console.WriteLine("\tStarted: {0}", p.Name); });
When a ComponentManager raised a Updated event, we will know one of components assembly changed, get specific component from ComponentUpdatedEventArgs.ComponentConfiguration.Name
, the name is unique, so we can easy get component by enumerate component list.
// create new component, and check its status IComponent newComponent = componentMgr.CreateInstance(); if (newComponent == null) { Console.WriteLine("\tError when re-create component {0}", e.ComponentConfiguration.Name); return; } newComponent.Name = e.ComponentConfiguration.Name; newComponent.Downstreams = e.ComponentConfiguration.Downstreams; newComponent.DownstreamComponents = GetDownsteamComponents(newComponent); sb.AppendLine("\tre-created component");
// get component which need updated. IComponent oldComponent = GetComponentByName(e.ComponentConfiguration.Name); if (oldComponent == null) return; // stop component oldComponent.Stop(); // un-register event from component oldComponent.AfterBusiness -= Component_AfterBusiness; // remove component from upstream components ListupstreamComponents = GetUpstreamComponents(oldComponent); upstreamComponents.ForEach(p => p.DownstreamComponents.Remove(oldComponent)); // remove component from component list this.ComponentList.Remove(oldComponent);
this.ComponentList.Add(newComponent); upstreamComponents.ForEach(p => p.DownstreamComponents.Add(newComponent)); newComponent.AfterBusiness += Component_AfterBusiness; sb.AppendLine("\treplaced with new component"); newComponent.Start(); sb.AppendLine("\tre-start component");
Notes:
a) To sync business bwteen components and Mediator update process, we use lock statement to protect content in multi-threads.
b) In demo program, we combined three components in one assembly, when the assembly changed, which means three components are all updated. Which will raise three times of event Updated
, for sync output in Updated
method, we use StringBuilder
to collect output in method and output at onetime.
In demo framework, we only implemented the mechanism of upgrade component when services running, for further development, I have two suggestions improve this framework:
a) To support multi-component in one assembly file, but raise only onece Updated
event.
b) For component configuration, it may have complex properties than this framework, we also can support upgrade component by changed configuration, but not assembly file change.