
Heutzutage sind die meisten Informationssysteme komplexe Lösungen mit einer ziemlich komplexen Architektur und einer großen Anzahl gegenseitiger Abhängigkeiten. Während des Betriebs solcher Systeme können zum Zeitpunkt von Spitzenlasten einige der Module ausfallen oder nicht richtig funktionieren. In diesem Fall ist das System nicht mehr stabil und verarbeitet möglicherweise nicht mehr alle eingehenden Anforderungen korrekt. Um einen stabilen Betrieb des Systems zu gewährleisten, können verschiedene Strategien implementiert werden.
In einigen Fällen (sofern dies zulässig ist) erfolgt die Übersetzung des Systems in das sogenannte. "Abgesicherter Modus". In diesem Modus kann das System eine größere Anzahl von Anrufen verarbeiten, wobei die Genauigkeit der Ergebnisse der Abfrageverarbeitung geringfügig abnimmt. In anderen Fällen kann die Skalierung der Rechenressourcen und die Erhöhung der Anzahl der einzelnen Systemmodule, die für die Verarbeitung eingehender Anforderungen verantwortlich sind, hilfreich sein. In diesem Fall besteht die wichtigste Aufgabe darin
, den Zustand des Systems zu
bestimmen , je nachdem, welche Maßnahmen bereits ergriffen werden müssen, um seine Arbeit zu stabilisieren.
In meinen Projekten habe ich seit einiger Zeit ein ähnliches Problem auf verschiedene Weise gelöst. Seit ich an der Universität war, hatte ich ein kleines Projekt in .NET 4.0 geschrieben, von dem ich von Zeit zu Zeit kleine Codestücke für echte Projekte nahm. Ich habe lange geplant, dieses Projekt umzugestalten, zu bereinigen und in Form eines separaten Mini-Frameworks anzuordnen, mit dem wir das Problem der Überwachung des Systemzustands auf schöne und minimalistische Weise lösen können. Nachdem ich einige Abende und ein paar Tage frei verbracht hatte, ordnete ich diesen Code und postete ihn auf
GitHub . Des Weiteren schlage ich vor, genauer zu prüfen, was und wie in diesem Projekt umgesetzt wird.
Um festzustellen, ob der Betriebsmodus des Systems geändert werden muss, wird vorgeschlagen, einen Ansatz zu verwenden, bei dem virtuelle „Sensoren“ und „Beobachter“ verwendet werden.

▌ Entitäten und Definitionen
Zu verwendende Basisentitäten:
- Sensor - ist verantwortlich für die Überprüfung des Status einer der Anzeigen des Systems;
- Beobachter - fragt einen oder mehrere Sensoren ab. Ändert seinen Zustand in Abhängigkeit von den aktuellen Messwerten der Sensoren.
- Statusrechner - berechnet den aktuellen Status basierend auf dem Metrikprotokoll.
- Statusprotokoll - Eine Reihe von Anzeigen für jeden der Sensoren, die die Abrufzeit anzeigen.
Für jede Abstraktion gibt es eine grundlegende Implementierung sowie Mechanismen für eine einfache und bequeme Erweiterung. Betrachten wir sie genauer.
Sensor
Grundlegende
IProbe- Schnittstelle. Klassen, die IProbe implementieren, geben auf Anfrage den Wert der von ihnen beobachteten Systemparameter oder eines bestimmten Moduls / Dienstes an.
public interface IProbe { string Name { get; } Task<ProbeResult> Check(); }
Als Ergebnis der
Check- Methode gibt die IProbe-Instanz eine ProbeResult-Struktur zurück
public struct ProbeResult { public string ProbeName { get; set; } public DateTime Time { get; set; } public bool Success { get; set; } public string Data { get; set; } public Exception Exception { get; set; } public override string ToString() => $"{Time}: {Success}"; }
Dabei bestimmt das Feld Erfolg, ob der Parameter aus Sicht des Sensors erfolgreich getestet wurde, und die Felder Daten und Ausnahme können zusätzliche Informationen speichern, falls dies zum Debuggen oder Protokollieren erforderlich ist.
Beobachter
Die grundlegende Schnittstelle von
ISpectator . Es überwacht das System und generiert Ereignisse zum Zeitpunkt der Änderung des Systemstatus, um alle Module zu benachrichtigen, die diese Ereignisse abonniert haben. Es verwendet eine Instanz einer Klasse, die die IStateEvaluator-Schnittstelle implementiert, um den aktuellen Status zu berechnen.
public interface ISpectator<TState> where TState : struct, IConvertible { event EventHandler<StateEventArgs<TState>> StateChanged; event EventHandler<HealthCheckEventArgs> HealthChecked; TState State { get; } TimeSpan Uptime { get; } string Name { get; } void AddProbe(IProbe probe); void CheckHealth(); }
Während jeder der Sensorabfragen löst der Beobachter das Ereignis StateChanged aus, und ein Objekt vom Typ
StateEventArgs wird an die Abonnenten dieses Ereignisses übergeben, das Informationen über den aktuellen Status des Systems enthält.
Zustandsrechner
Die Basis-
IEvaluator- Schnittstelle. Berechnet den aktuellen Status des Systems basierend auf dem Sensorstatusprotokoll.
public interface IStateEvaluator<TState> { TState Evaluate( TState currentState, DateTime stateChangedLastTime, IReadOnlyCollection<JournalRecord> journal); }
Statusprotokoll
Eine Sammlung von Instanzen der JournalRecord-Struktur. Die JournalRecord-Instanz speichert Informationen zu allen Sensoren, die zum Zeitpunkt der Abfrage durch den Beobachter abgefragt wurden.
public struct JournalRecord { public JournalRecord(DateTime time, IEnumerable<ProbeResult> values) { Time = time; Values = values.ToImmutableList(); } public DateTime Time { get; set; } public IReadOnlyCollection<ProbeResult> Values { get; set; } public override string ToString() => $"{Time}: [{string.Join(",", Values)}]"; }
▌ Wie es funktioniert
Der Prozess der Berechnung des Systemzustands kann wie folgt beschrieben werden: Jeder der im System eingebetteten Sensoren kann einen der Parameter des beobachteten Systems / Moduls / Dienstes bestimmen. Beispielsweise kann es die Anzahl der externen aktiven Anforderungen an die API, die Menge des verwendeten RAM, die Anzahl der Einträge im Cache usw. festlegen.
Jeder Sensor kann einem oder mehreren Beobachtern zugeordnet werden. Jeder Beobachter kann mit einem oder mehreren Sensoren arbeiten. Der Beobachter muss die ISpectator-Schnittstelle implementieren und Ereignisse im Falle einer Zustandsänderung oder einer Abfrage von Sensoren generieren.
Bei der nächsten Überprüfung des Systemzustands fragt der Beobachter alle seine „Sensoren“ ab und bildet ein Array zum Schreiben in das Statusprotokoll. Wenn sich der Status des Systems geändert hat, generiert der Beobachter ein geeignetes Ereignis. Ereignisteilnehmer, die Informationen über die Änderung erhalten, können die Betriebsparameter des Systems ändern. Gleichzeitig können "Computer" verschiedener Typen verwendet werden, um den Status des Systems zu bestimmen.
Synchrone und asynchrone Betriebsarten
Stellen Sie sich zwei Hauptszenarien vor, in denen der Beobachter eine Untersuchung der „Sensoren“ initiiert.
Synchroner Modus
Eine Untersuchung der Sensoren, gefolgt von einer Neuberechnung des Systemzustands, wird durch die direkte Anziehungskraft eines der Systemmodule auf den Beobachter verursacht.
In diesem Fall arbeiten der Beobachter und die Sensoren im selben Thread. Die Fehleinschätzung des Zustands des Systems wird als Teil einer Operation innerhalb des Systems durchgeführt.
Das Projekt hat bereits eine grundlegende Implementierung eines solchen Beobachters -
SpectatorBase .
Code anzeigen public class SpectatorBase<TState> : ISpectator<TState> where TState : struct, IConvertible { private TState _state; private readonly IList<IProbe> _probes; private readonly IStateEvaluator<TState> _stateEvaluator; private readonly List<JournalRecord> _journal; private readonly ReaderWriterLockSlim _journalLock; private readonly ReaderWriterLockSlim _stateLock; private readonly Stopwatch _stopwatch; public event EventHandler<StateEventArgs<TState>> StateChanged; public event EventHandler<HealthCheckEventArgs> HealthChecked; public virtual TState State { get { _stateLock.EnterReadLock(); try { return _state; } finally { _stateLock.ExitReadLock(); } } } public TimeSpan Uptime => _stopwatch.Elapsed; public string Name { get; set; } public IReadOnlyCollection<JournalRecord> Journal { get { _journalLock.EnterReadLock(); try { return _journal; } finally { _journalLock.ExitReadLock(); } } } public DateTime StateChangedDate { get; private set; } public TimeSpan RetentionPeriod { get; private set; } public SpectatorBase(IStateEvaluator<TState> stateEvaluator, TimeSpan retentionPeriod, TState initialState) { RetentionPeriod = retentionPeriod; _state = initialState; StateChangedDate = DateTime.UtcNow; _stateEvaluator = stateEvaluator; _stopwatch = Stopwatch.StartNew(); _probes = new List<IProbe>(); _journal = new List<JournalRecord>(); _journalLock = new ReaderWriterLockSlim(); _stateLock = new ReaderWriterLockSlim(); } public void AddProbe(IProbe probe) => _probes.Add(probe); protected virtual void ChangeState(TState state, IEnumerable<string> failedProbes) { _stateLock.EnterWriteLock(); try { _state = state; } finally { _stateLock.ExitWriteLock(); } StateChangedDate = DateTime.UtcNow; StateChanged?.Invoke(this, new StateEventArgs<TState>(state, StateChangedDate, failedProbes)); } public virtual void CheckHealth() { var results = new Stack<ProbeResult>(); var tasks = _probes .Select(async o => { results.Push(await o.Check().ConfigureAwait(false)); }) .ToArray(); Task.WaitAll(tasks); var now = DateTime.UtcNow; _journalLock.EnterWriteLock(); try {
Asynchroner Modus
In diesem Fall erfolgt die Abfrage von Sensoren asynchron zu den Prozessen des Systems und kann in einem separaten Thread durchgeführt werden. Die grundlegende Implementierung eines solchen Beobachters ist ebenfalls bereits ein Projekt -
AutomatedSpectator .
Code anzeigen public class AutomatedSpectator<TState> : SpectatorBase<TState>, IAutomatedSpectator<TState> where TState : struct, IConvertible { public TimeSpan CheckHealthPeriod { get; } private readonly System.Timers.Timer _timer; public AutomatedSpectator( TimeSpan checkHealthPeriod, TimeSpan retentionPeriod, IStateEvaluator<TState> stateEvaluator, TState initialState) : base(stateEvaluator, retentionPeriod, initialState) { CheckHealthPeriod = checkHealthPeriod; _timer = new System.Timers.Timer(CheckHealthPeriod.TotalMilliseconds); _timer.Elapsed += (sender, args) => CheckHealth(); _timer.AutoReset = true; } public void Start() => _timer.Start(); }
▌ Fazit
Die Verwendung von X.Spectator hat mir persönlich bei mehreren hoch ausgelasteten Projekten geholfen, die Stabilität einer Reihe von Diensten erheblich zu verbessern. Das am besten vorgeschlagene Framework hat sich bei der Implementierung in verteilten Systemen bewährt, die auf der Microservice-Architektur basieren. Die optimalste Integrationsoption ist die Verwendung des Prinzips der Umkehrung der Steuerung, nämlich die Implementierung von Abhängigkeiten, wenn der Prozess der Implementierung von Sensoren mithilfe eines IoC-Containers implementiert wird und die Beobachter in Form von Singletones dargestellt werden, auf die eine einzelne Instanz jedes Beobachters von verschiedenen Klassen von Modulen und Diensten zugegriffen werden kann.
▌ Links und nützliche Informationen
→
Projekt-Repository→
Beispiele→
NuGet-Paket