无需停止即可升级的程序架构

转载我自己在 codeproject.com上的一篇文章。http://www.codeproject.com/Articles/320391/Non-stopping-upgradable-service-framework

Non-stopping upgradable service framework

A framework designed for support upgrade components in service without stop running


Download Non-Stopping-Upgadable_Service_Framework_Demo - 2.03 MB

Introduction

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.

Benefits

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.

Background

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.

Demo

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: reader1processor1dispatcher1 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.

无需停止即可升级的程序架构_第1张图片

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.

无需停止即可升级的程序架构_第2张图片

无需停止即可升级的程序架构_第3张图片

无需停止即可升级的程序架构_第4张图片

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.

Using the framework

1) Implement the class which requires inherited from IComponent. In demo program, we combined all components(ReaderProcessorDispatcher) 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 node, has following fields:

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 methodGetDownsteamComponents andGetUpstreamComponents in class Mediator to support self-downsteam mode)

You can start framework now when you finished above things.

Design of framwork

The class diagram of framework show below:

无需停止即可升级的程序架构_第5张图片

Init() method in Mediator class

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:

  • read component configurations from app.config
ComponentSectionHandler config = ComponentSectionHandler.GetConfigurationSection();
  • create each component, we implemented Observer pattern between 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);
  }
}
  • calucate downstreams and register event for each component, we have two assistant methods to get downstream and upstream components, they are GetUpstreamComponentsGetDownsteamComponents,
//------------------------------
// Register components events
//------------------------------
Console.WriteLine("------ Register components events ------");
foreach (IComponent component in this.ComponentList)
{
  List downstreamComps = 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/stop components
//------------------------------
// Start components
//------------------------------
Console.WriteLine("------ Start components ------");
this.ComponentList.ForEach(p =>
{
  p.Start();
  Console.WriteLine("\tStarted: {0}", p.Name);
});

Component assembly changed methods

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.

  • Re-create component instance from new version of assembly
// 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");
  • Replace old instance of component with new instance
// 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
List upstreamComponents = GetUpstreamComponents(oldComponent);
upstreamComponents.ForEach(p => p.DownstreamComponents.Remove(oldComponent));
// remove component from component list
this.ComponentList.Remove(oldComponent);
  • Applied new instance of component
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 Updatedmethod, we use StringBuilder to collect output in method and output at onetime.

Further thought

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.


你可能感兴趣的:(架构,.NET,C#,components,assembly,service,file,class,dependencies)