Leistung von System.Messaging
Ingo Rammer ermittelt in diesem Artikel die Leistungscharakteristiken von .NET System.Messaging im Vergleich zur systemeigenen MSMQ COM-API für unterschiedliche Nachrichtenmuster und Anforderungen. Zugleich zeigt er Richtlinien zum Erstellen einer leistungsstarken Messaging-Anwendung auf. Dieser Artikel enthält auch Links zu englischsprachigen Seiten. (28 gedruckte Seiten)
Laden Sie das zugehörige Codebeispiel SystemMessagingPerformanceTests.msi (in englischer Sprache) herunter.
Auf dieser Seite
Einführung
Ansatz
Software- und Hardware-Setup
Verwaltete und nicht verwaltete Leistung
Optimierungen für Messaging-Anwendungen in verwaltetem Code
Die Testanwendung
Anhang A - Testergebnisse
Vergleich der .NET-Formatierungsprogramme (Einreihen)
Einführung
Mit MSMQ (Microsoft Message Queuing) und der .NET-API System.Messaging verfügen Sie über die notwendige Infrastruktur, um zuverlässige, skalierbare und leistungsstarke Messaging-Anwendungen zu erstellen. Obwohl solche Anwendungen meist mit vollständig asynchronen Aufrufs- und Kommunikationsmustern entwickelt werden, spielt dennoch die Leistungsfähigkeit in den meisten Produktionsumgebungen eine wichtige Rolle.
Wenn Sie Message Queuing bereits in einer nicht verwalteten Umgebung verwendet haben, werden Sie von den Auswirkungen der .NET-Umgebung auf die Leistung Ihrer Anwendungen möglicherweise überrascht sein. Außerdem möchten Sie sicher auch wissen, wie Sie in der .NET-Umgebung benutzerdefinierte Daten am effizientesten zwischen verschiedenen Warteschlangen übermitteln können.
In diesem Whitepaper wird die Leistung der nicht verwalteten MSMQ COM-Schnittstellen mit der .NET-Implementierung in System.Messaging verglichen. Sie erhalten außerdem Anleitungen zur Steigerung der Leistung Ihrer Messaging-Anwendung. Durch die Tests werden die unterschiedlichen Nachrichten-Transportgeschwindigkeiten in einer verwalteten und einer nicht verwalteten Umgebung sowie die Unterschiede zwischen den in den beiden Umgebungen bereitgestellten Serialisierungstechniken verdeutlicht. Der zweite Punkt ist besonders wichtig, da in den meisten asynchronen Messaging-Anwendungen die Zeit, die zum Entfernen von Nachrichten aus Warteschlangen benötigt wird, von besonderer Bedeutung ist. Die Dauer für die vollständige Verarbeitung ist dabei von geringerem Interesse.
Ansatz
Wie schon in Leistung von ASP.NET Web Services, Enterprise Services und .NET Remoting haben wir auch hier lange überlegt, ob dieses Whitepaper die Form eines ausführlichen Vergleichstests haben soll, in dem das Erreichen der bestmöglichen Leistung dargelegt wird, oder ob in diesem Whitepaper gute, vernünftige und in den meisten Unternehmensszenarios reproduzierbare Vergleiche der "relativen Leistung" bereitgestellt werden sollen. Wir haben uns für den zweiten Ansatz entschieden, da er die Anwendungsbereiche eines möglichst großen Leserkreises abdeckt und sehr genau die realen Leistungscharakteristiken von .NET System.Messaging darstellt.
Die Ziele dieses Whitepapers sind:
-
Ermittlung der relativen Leistungsunterschiede zwischen System.Messaging und MSMQ COM-APIs
-
Verdeutlichung der realen Leistungscharakteristiken von .NET und MSMQ COM
-
Unterstützung für Ihre Entscheidungen, wann, wo und wie diese Technologien am sinnvollsten eingesetzt werden
-
Bereitstellung einer Testanwendung, damit Sie diese Tests auf Ihren Computern in Ihren eigenen Umgebungen ausführen können. Es wird dringend empfohlen, diese Testumgebung zu erstellen und auszuführen sowie die Leistungsmerkmale dieser Technologien zu untersuchen und zu analysieren. Nur auf diese Weise verstehen Sie die vielen Faktoren, durch die die Leistung von Message Queuing-Systemen beeinflusst wird.
Hinweis In diesem Whitepaper werden die Leistungscharakteristiken der systemeigenen MSMQ Win32/"C"-APIs nicht behandelt. Hierbei handelt es sich um die zu Grunde liegende MSMQ-API, auf die beide Implementierungen von .NET und MSMQ COM aufbauen. Wenn Leistung Ihr primäres Ziel ist, sollten Sie die wesentlichen Leistungssteigerungen untersuchen, die durch eine Codierung der MSMQ Win32/"C"-APIs möglich sind. Diese Leistungssteigerung hat allerdings ihren Preis - Sie benötigen erheblich mehr Zeit und Aufwand zum Schreiben, Testen, Sichern und Bereitstellen dieses Codes.
Software- und Hardware-Setup
Die folgenden Tests wurden mit zwei Computern durchgeführt: einem Sender (Client) und einem Empfänger (Server).
Hardware/Software-Spezifikationen
Beide Computer, Sender und Empfänger, wurden folgendermaßen konfiguriert:
-
CPU: 2,8 GHz Intel P4 Prescott, HT, 800 MHz FSB
-
Festplatte: 40 GB UltraDMA 100, ExcelStor Technology J340
-
Chipset: Intel 865G/ICH 5
-
Arbeitsspeicher: 1.024 MB RAM (PC3200)
-
Betriebssystem: Windows Server 2003 Standard Edition mit MSMQ 3.0
Testanwendung
Die Testanwendungen wurden als Single-Thread-Anwendungen in C++ und .NET geschrieben und in Visual Studio 2003 im Freigabemodus erstellt und kompiliert. Statt das System auf seine maximale Leistung zu testen, haben wir uns für Single-Thread-Testanwendungen entschieden, da diese zu vergleichbaren Untersuchungen der relativen Leistungsstufen der verwalteten und der systemeigenen Implementierung führen.
Alle Tests wurden mit dem Maximum an verfügbarem physischen Arbeitsspeicher durchgeführt - Speicherfehler und Auslagerungsvorgänge traten daher selten auf. Beachten Sie außerdem, dass MSMQ in diesen Tests Nachrichtendaten von schnellen (Express-)Nachrichten nicht auslagert.
Der Testcode wurde in der Art geschrieben, die Sie typischerweise in Unternehmensanwendungen antreffen. Die Leistung der COM-APIs könnte verbessert werden, z. B. durch manuelles Marshalling von char*-Zeichenfolgen in ein SafeArray (VT_I1), um die Leistungsbeeinträchtigung durch die systemeigene VARIANT-Konvertierung von Zeichenfolgen in ein Double-Byte-Unicodeformat zu verhindern. Solche Implementierungen sind jedoch in typischen Unternehmensanwendungen nur selten anzutreffen. Daher werden in den Testanwendungen solche Optimierungen nicht verwendet. Wenn Sie die Testprogramme weiter verwenden möchten, ist die Beschäftigung mit dieser Vorgehensweise jedoch zu empfehlen!
Verwaltete und nicht verwaltete Leistung
Vor einer Leistungsüberprüfung von Messaging-Anwendungen sollten Sie verstehen, dass unterschiedliche Anwendungen auch unterschiedliche Anforderungen an die Infrastruktur voraussetzen. Mit MSMQ können Sie für jede Nachricht entscheiden, ob diese ausschließlich im Arbeitsspeicher (schnelle Nachrichten) übermittelt oder vor der Weiterleitung an den nächsten Knoten dauerhaft auf einem Datenträger (wieder herstellbare Nachrichten) gespeichert wird. Auf diese Weise können die Anwendungen Systemausfälle ausgleichen. Sie müssen außerdem festlegen, ob eine Warteschlange Transaktionsnachrichten unterstützt. All diese Entscheidungen beeinflussen unmittelbar die Anzahl von Nachrichten, die in einer bestimmten Zeitspanne übertragen werden können.
Um die meisten Szenarios abzudecken, haben wir die Tests in eine Kombination von Nachrichtennutzlasten und Nachrichtenoptionen unterteilt.
Nutzlasten
Wir erstellen vier Nutzlasten mit verschiedenen Größen:
-
Reine Infrastruktur: Nachrichten ohne Text
-
Transport: Vorformatierte Zeichenfolgen mit unterschiedlichen Längen. Auf diese Weise können wir die Infrastruktur und die Übermittlung mit minimalem Aufwand an Marshalling und Serialisierung testen. Die Zeichenfolgen haben die folgenden Längen: 500, 1.000, 10.000, 100.000, 1.000.000 und 2.000.000 Zeichen.
-
XML-Nachrichten: Hier vergleichen wir das Senden und Empfangen von Daten, die durch .NET XML Serializer und durch die COM-basierte MSXML-Komponente codiert wurden.
-
Serialisierte Objekte: In diesem Fall werden die .NET-Objektserialisierung und die COM IPersistStream-Mechanismen verglichen.
Warteschlangen- und Nachrichtentypen
Alle oben angesprochenen Nutzlasttypen werden auf Basis der folgenden Optionen für Nachrichten und Warteschlangen verglichen, jeweils für ein lokales und entferntes Einreihen in und Entfernen aus einer Warteschlange:
-
Schnell
-
Wiederherstellbar
-
Transaktionsorientiert (hier werden lediglich lokale Warteschlangenvorgänge unterstützt)
Timing, oder: Was genau haben wir gemessen?
In herkömmlichen verteilten Anwendungen, die auf synchronen Aufrufen über RPC, DCOM, .NET Remoting oder ASP.NET Web Services basieren, besteht der für die Leistung wichtigste Messpunkt in der vollständigen Antwortzeit für einen einzelnen Anfrage/Antwort-Aufruf. In asynchronen Messaging-Umgebungen ist dieses Anfrage/Antwort-Modell jedoch oft von weniger oder geringem Wert. Stattdessen werden die meisten asynchronen Messaging-Anwendungen auf diese drei Timings untersucht:
-
Fire-and-Forget: Senden einer Reihe von Nachrichten an eine lokale oder entfernte Warteschlange
-
Nur empfangen: Abrufen einer Reihe von in einer Warteschlange vorhandenen Nachrichten
-
End-to-End: Senden von Nachrichten, Abrufen von der empfangenden Seite und vollständiges Deserialisieren jeder Nachricht in eine äquivalente Darstellung (z. B. einer Zeichenfolge in eine Zeichenfolge oder ein COM-Objekt, das IPersistStream implementiert, in eine deserialisierte Kopie des Objekts).
Nutzlast-Testergebnisse
Im folgenden Abschnitt finden Sie die Ergebnisse unserer Leistungstests.
Test 1 - Leere Nachrichten
Im ersten Test haben wir die Anzahl von leeren Nachrichten pro Sekunde gemessen, deren Bereitstellung in einer lokalen oder entfernten Warteschlange bestätigt wurde. Die Ergebnisse dieses Tests werden in Abbildung 1 dargestellt.
Abbildung 1: Einreihen von leeren Nachrichten in eine Warteschlange
Aus der Abbildung wird deutlich, dass die .NET Framework System.Messaging-API beim Senden von schnellen Nachrichten geringfügig besser als die COM-API arbeitet, es jedoch praktisch keinen Unterschied bei wiederherstellbaren oder Transaktionsnachrichten gibt. Dies zeigt die Gleichwertigkeit der .NET- und MSMQ COM-Infrastruktur beim Senden von schnellen Nachrichten.
Abbildung 2: Entfernen von leeren Nachrichten aus einer Warteschlange
In Abbildung 2 können Sie erkennen, dass die Messergebnisse für das Entfernen von Nachrichten vergleichbar sind, mit Ausnahme der lokalen schnellen Nachrichten, bei denen der Unterschied zwischen den beiden APIs erheblich größer ist.
Im letzten Teil dieses Tests haben wir eine Serveranwendung verwendet, um eingehende Nachrichten zu verarbeiten, sobald diese gesendet wurden. Der Server hat dann eine Bestätigungsnachricht an den Client gesandt, sobald er alle Aufgaben eines Testlaufs erfolgreich abgearbeitet hat. Aus diesem Grund lag keine direkte Anforderung/Antwort-Semantik für jede Nachricht vor, da dies den grundsätzlichen Designrichtlinien von asynchronen Messaging-Anwendungen widersprechen würde. Die Ergebnisse werden in Abbildung 3 dargestellt.
Abbildung 3: Vollständiges Verarbeiten von leeren Nachrichten
Diese Ergebnisse machen deutlich, dass die Funktionen des .NET Framework ein schnelleres Entfernen von Nachrichten aus einer Warteschlange und gleichzeitig eine kürzere Zeitspanne für eine vollständige Verarbeitung im Vergleich zur COM-API ermöglichen.
Test 2 - Senden von Zeichenfolgen mit unterschiedlichen Längen
In der folgenden umfangreichen Testreihe werden die Unterschiede in der Nachrichtenübertragungsleistung in Abhängigkeit von der Nutzlastgröße hervorgehoben. Zur Durchführung dieses Tests müssen Sie einfach Zeichenfolgen mit den erforderlichen Längen (500, 1.000, 2.000, 10.000, 100.000, 1.000.000 und 2.000.000 Zeichen) erstellen und diese jeweils einem Nachrichtentext zuordnen. Für diesen Test werden die internen Formatierungsfunktionen des .NET Framework (durch den BinaryMessageFormatter) und der COM-API verwendet.
Der C#-Quellcode zum Senden einer Nicht-Transaktionsnachricht, die eine Zeichenfolge mit 1.000 Zeichen enthält, sieht folgendermaßen aus:
MessageQueue que = new MessageQueue( ... );
// open queue, etc.
String bodyString = new String('x', 1000);
m.Formatter = new BinaryMessageFormatter();
m.Label = "Test";
m.Body = bodyString;
m.Recoverable = false; // Depending on configuration
que.Send(m);
In C++ können Sie einen Code schreiben, der dem folgenden Beispiel entspricht. Beachten Sie, dass für diesen Test CoGetClassObject() statt CoCreateInstance() verwendet wurde, um eine schnellere Verarbeitung von mehreren Nachrichten zu ermöglichen:
MSMQ::IMSMQQueue3* pQueue;
// ... open the queue ...
HRESULT hr;
MSMQ::IMSMQMessage3* pMsg;
IClassFactory* pFact;
CString bodyString ('x',1000);
hr = CoGetClassObject(CLSID_MSMQMessage, CLSCTX_ALL, NULL,
IID_IClassFactory, reinterpret_cast<void**>(&pFact));
if (FAILED(hr)) exit(1);
hr = pFact->CreateInstance(NULL, IID_IMSMQMessage3,
reinterpret_cast<void**>(&pMsg));
if(FAILED(hr)) exit(1);
pMsg->Label = L"Test";
_variant_t var(bodyString);
pMsg->Body = var;
pMsg->Delivery = MSMQ::MQMSG_DELIVERY_EXPRESS;
hr = pMsg->Send(pQueue);
if (FAILED(hr)) exit(1);
pMsg->Release();
pQueue->Release();
Bei der Durchführung von Tests mit unterschiedlichen Nachrichtengrößen erzielen Sie Ergebnisse, die denen in Abbildung 4 entsprechen oder ähneln.
Abbildung 4: Einreihen von vordefinierten Zeichenfolgen im Schnellmodus
Die COM-API arbeitet im Schnellmodus beim Senden von Nachrichten etwas besser, und das unabhängig von der Länge der Zeichenfolge.
Im Gegensatz zur Leistung beim Senden arbeitet .NET beim Empfangen dieser Nachrichtentypen aus Warteschlangen geringfügig besser als COM. Dies zeigt Abbildung 5.
Abbildung 5: Entfernen von vordefinierten Zeichenfolgen im Schnellmodus
Diese Abbildung verdeutlicht, dass kein wesentlicher Unterschied zwischen beiden Plattformen besteht, insbesondere dann, wenn Testabweichungen und -toleranzen in die Auswertung des Tests miteinbezogen werden.
Bei entfernten Warteschlangen entsprechen die relativen Unterschiede zwischen COM und .NET den Unterschieden bei lokalen Warteschlangen.
Alle bisher durchgeführten Tests haben lediglich Teile einer Messaging-Lösung betrachtet: Senden und Empfangen. Für ein besseres Verständnis der Leistungsunterschiede zwischen realen Anwendungen müssen wir die Dauer für eine vollständige Verarbeitung für einen Nachrichtenstapel auf einem Remotesystem aufzeichnen. In diesen Testläufen sendet der Client eine Reihe von Nachrichten an den Server, auf dem sie so schnell wie möglich aus der Warteschlange entfernt werden. Damit eine Leistungsmessung ermöglicht werden kann, sendet der Server eine Bestätigung zurück an den Server, sobald er in der Lage ist, alle Nachrichten zu verarbeiten. Die Ergebnisse dieses Tests werden in Abbildung 6 dargestellt.
Abbildung 6: Zeitspannen für eine vollständige Verarbeitung für Zeichenfolgen mit unterschiedlichen Längen
Diese Ergebnisse machen deutlich, dass .NET im Vergleich zum systemeigenen MSMQ COM eine erhebliche Gesamtdurchsatzsteigerung beim Übergeben von Zeichenfolgen als Nachrichtentext bietet. Dies liegt in erster Linie daran, dass MSMQ COM in der Standardeinstellung alle Zeichenfolgen als Double-Byte-Unicode verarbeitet, .NET jedoch Zeichenfolgen im wesentlich effizienteren UTF-8 codiert und so die Längen der Zeichenfolgen erheblich reduziert.
Test 3 - Serialisierte Objekte
Zum Austausch von Daten über Nachrichtenwarteschlangen werden überwiegend die im .NET Framework integrierten Serialisierungsfunktionen verwendet. .NET Framework ermöglicht die Auswahl eines Standardformatierungsprogramms, das ohne benutzerdefinierten Serialisierungscode einsatzfähig ist. Für COM ist dafür üblicherweise eine benutzerdefinierte Implementierung von IPersistStream erforderlich.
Zur Durchführung der nachfolgenden Tests werden Nachrichten übermittelt, die auf dem folgenden Order-Objekt basieren, einschließlich 50 LineItem-Bestandteilen. Parallel haben wir eine vergleichbare COM-Version von diesen Klassen erstellt, die IPersistStream implementieren.
[Serializable]
public class Order
{
public DateTime Date;
public int CustomerID;
public Address ShippingAddress;
public Address BillingAddress;
public double Total;
[XmlArrayItem(typeof(LineItem))]
public ArrayList LineItems;
}
[Serializable]
public class Address
{
public String Firstname;
public String Lastname;
public string Company;
public string City;
public string Street;
public string ZIPCode;
public string Country;
public string State;
}
[Serializable]
public class LineItem
{
public int ArticleID;
public String Name;
public double Price;
public double Quantity;
public double LineTotal;
}
Die automatische Serialisierung solcher Objekte verdeutlicht einen der großen Vorteile beim Einsatz des .NET Framework, da die integrierten Serialisierungsfunktionen dem Benutzer eine Menge Arbeit abnehmen.
In der .NET-Version haben wir zum Senden von den Nachrichten, die eine serialisierte Darstellung eines Order-Objekts enthalten, Code verwendet, der dem folgenden Beispiel ähnelt:
Order o = new Order( ... ); // populate the order; MessageQueue que = new MessageQueue( ... ); // open queue, etc. m.Formatter = new BinaryMessageFormatter(); m.Label = "Test"; m.Body = ord; m.Recoverable = false; // Depending on configuration que.Send(m);
Bei der Verwendung der COM-API können Sie ein COM-Objekt, das IPersistStream implementiert, einsetzen, und dessen IUnknown-Schnittstellenzeiger als VARIANT wrappen. Die MSQM COM-API übernimmt dann das Erstellen eines Streams zum Serialisieren und Deserialisieren:
IOrder* pOrd;
// create and populate the order
MSMQ::IMSMQQueue3* pQueue;
// ... open the queue ...
HRESULT hr;
MSMQ::IMSMQMessage3* pMsg;
IClassFactory* pFact;
CString bodyString ('x',1000);
hr = CoGetClassObject(CLSID_MSMQMessage, CLSCTX_ALL, NULL,
IID_IClassFactory, reinterpret_cast<void**>(&pFact));
if (FAILED(hr)) exit(1);
hr = pFact->CreateInstance(NULL, IID_IMSMQMessage3,
reinterpret_cast<void**>(&pMsg));
if(FAILED(hr)) exit(1);
pMsg->Label = L"Test";
hr=pOrd->QueryInterface(IID_IUnknown, reinterpret_cast<void**>(&pUnk));
if (FAILED(hr)) exit(1);
CComVariant var;
var.punkVal = pUnk;
var.vt = VT_UNKNOWN;
pMsg->Body = var;
pMsg->Delivery = MSMQ::MQMSG_DELIVERY_EXPRESS;
hr = pMsg->Send(pQueue);
if (FAILED(hr)) exit(1);
pMsg->Release();
pQueue->Release();
Aus den Abbildungen 7 und 8 ist zu erkennen, dass der große Nutzen der in .NET Framework integrierten Serialisierungsfunktionen in Bezug auf die Entwicklungszeit Nachteile bei der Laufzeit zur Folge hat. Beim Vergleich mit einer optimierten IPersistStream-Implementierung können Sie feststellen, dass die COM-Version beim Einreihen oder Entfernen von Nachrichten schneller als die .NET-Version arbeitet.
Abbildung 7: Vergleich von Serialisierungstechniken beim Einreihen von Nachrichten
Abbildung 8: Vergleich von Serialisierungstechniken beim Entfernen von Nachrichten
Es ist wichtig, dass Sie die Ursachen für diese beachtlichen Unterschiede verstehen. Das .NET Framework stellt Serialisierungsfunktionen zur Laufzeit zur Verfügung, die jeden kompatiblen Typ (z. B. mit [Serializable] markierte Klassen) entgegennehmen und die integrierte Reflection-API zur dynamischen Serialisierung und Deserialisierung verwenden. Diese Serialisierer können beispielsweise automatisch öffentliche bzw. private Felder und sogar vollständige Objektgrafiken speichern. Auf der anderen Seite bedingt die Verwendung von IPersistStream in der COM-Welt eine sehr strikte Serialisierungsimplementierung: Sie müssen genau angeben, wie Sie die Daten speichern möchten. Der zweite Ansatz ist natürlich im Allgemeinen schneller.
Die gute Nachricht für Ihre leistungsstarke und wichtige Anwendung ist, dass es keinen Grund gibt, auf die Implementierung einer Serialisierungstechnik in .NET zu verzichten, die sich wie COM-IPersistStream verhält.
In den nun folgenden Testläufen haben wir eben dies realisiert, indem wir Methoden zum Speichern und Laden eines Objektstatus unter Verwendung einer BinaryWriter/BinaryReader-Kombination hinzugefügt haben. Im folgenden Codeausschnitt ist eine dieser Implementierungen dargestellt:
[Serializable]
public class LineItem
{
public int ArticleID;
public String Name;
public double Price;
public double Quantity;
public double LineTotal;
public void Load(BinaryReader rdr)
{
ArticleID = rdr.ReadInt32();
Name = rdr.ReadString();
Price = rdr.ReadDouble();
Quantity = rdr.ReadDouble();
LineTotal = rdr.ReadDouble();
}
public void Save(BinaryWriter wrt)
{
wrt.Write(ArticleID);
wrt.Write(Name);
wrt.Write(Price);
wrt.Write(Quantity);
wrt.Write(LineTotal);
}
}
Beim Vergleich mit entscheidenden Bestandteilen Ihrer IPersistStream-Implementierung in COM fällt jetzt eine bemerkenswerte Ähnlichkeit auf:
bool CLineItem::InternalLoad( IStream *pStm )
{
m_bstrName.ReadFromStream(pStm);
pStm->Read(&m_nArticleID, sizeof(m_nArticleID),0);
pStm->Read(&m_nPrice, sizeof(m_nPrice),0);
pStm->Read(&m_nQuantity, sizeof(m_nQuantity),0);
pStm->Read(&m_nLineTotal, sizeof(m_nLineTotal),0);
return false;
}
bool CLineItem::InternalSave( IStream *pStm )
{
m_bstrName.WriteToStream(pStm);
pStm->Write(&m_nArticleID, sizeof(m_nArticleID),0);
pStm->Write(&m_nPrice, sizeof(m_nPrice),0);
pStm->Write(&m_nQuantity, sizeof(m_nQuantity),0);
pStm->Write(&m_nLineTotal, sizeof(m_nLineTotal),0);
pStm->Commit(STGC_DEFAULT);
return true;
}
Sie können dann folgenden oder ähnlichen Code verwenden, um eine Nachricht mithilfe dieser manuell optimierten Serialisierungstechnik in .NET zu senden:
MessageQueue que = new MessageQueue ( ... );
using (Message m = new Message())
{
MemoryStream ms = new MemoryStream();
BinaryWriter wrt = new BinaryWriter(ms);
ord.Save(wrt);
wrt.Flush();
ms.Flush();
m.BodyStream= ms;
m.Label = "Test";
m.Recoverable = false;
que.Send(m);
}
Beim Empfang einer Nachricht muss dann folgender oder ähnlicher Code verwendet werden:
MessageQueue que = new MessageQueue ( ... );
using (Message m = que.Receive())
{
Order o = new Order();
BinaryReader rdr = new BinaryReader(m.BodyStream);
o.Load(rdr);
}
Vor dem nächsten Vergleich wurden die Methoden Load() und Save() zu allen Geschäftsentitätsklassen (Order, Address und LineItem) hinzugefügt, um die Leistungsunterschiede zwischen der ursprünglichen COM-Version und der neuen optimierten BinaryWriter/BinaryReader-Kombination hervorzuheben.
In den Abbildungen 9 und 10 sind die deutlichen Leistungssteigerungen zu erkennen, die aus dieser Änderung resultieren.
Abbildung 9: Verwenden der manuell optimierten .NET-Serialisierung zum Einreihen von Nachrichten
Abbildung 10: Verwenden der manuell optimierten .NET-Serialisierung zum Entfernen von Nachrichten
Durch die oben dargestellten Ergebnisse wird deutlich, wie statt der Verwendung des .NET-Standardserialisierungsprogramms mit einem geringen Mehraufwand (praktisch der gleiche Arbeitsaufwand wie für eine IPersistStream-Implementierung in COM) eine enorme Leistungssteigerung bei .NET-basierten Queuinganwendungen erreicht werden kann.
Optimierungen für Messaging-Anwendungen in verwaltetem Code
Die Testergebnisse auf den vorherigen Seiten haben gezeigt, dass man mit dem .NET Framework Messaging-Anwendungen erstellen kann, die in einer Reihe von Anwendungsfällen ebenso gut oder sogar besser als COM-Anwendungen arbeiten. Zusätzlich zu den oben dargestellten Leistungssteigerungen möchten wir Ihnen einige weitere Tipps und Richtlinien zur Verbesserung der Antwortzeiten Ihrer Anwendung präsentieren.
Auswählen des richtigen Nachrichtentyps
Die für dieses Whitepaper durchgeführten Testläufe belegen, dass die größten Leistungsunterschiede durch die Auswahl des Nachrichtentyps verursacht werden. MSMQ ermöglicht den Zugriff auf die folgenden Typen:
Schnell: Diese Nachrichten werden ausschließlich im Arbeitsspeicher gespeichert. Wenn ein Computer, der eine Nachrichten-Warteschlange verwaltet, neu gestartet oder durch einen Stromausfall lahm gelegt wird, gehen diese Nachrichten verloren. Schnelle Nachrichten sind jedoch durch Netzwerkausfälle nicht betroffen, solange die einzelnen Computer arbeiten. Sie können z. B. die Netzwerkverkabelung trennen und eine Reihe von Nachrichten in die Warteschlange einreihen, die dann unmittelbar nach Wiederherstellung der Netzwerkverbindung übertragen werden. Obwohl diese Nachrichten im Arbeitsspeicher gespeichert werden, können Leistungsprobleme auftreten, wenn die Grenzen des vorhandenen physischen Speichers erreicht werden. In diesem Fall kann sich der Paging/Swapping-Mechanismus der virtuellen Speicherverwaltung möglicherweise negativ auf die Leistung auswirken.
Wiederherstellbar: Wiederherstellbare Nachrichten werden immer auf Festplatten abgespeichert. Wird ein Computer vor der erfolgreichen Übermittlung von Nachrichten an die Zielempfänger neu gestartet, wird der Zustand wiederhergestellt, sobald der MSMQ-Dienst neu gestartet wird. Wiederherstellbare Nachrichten werden normalerweise langsamer als schnelle Nachrichten verarbeitet.
Transaktional: Transaktions-Warteschlangen garantieren eine einmalige und geordnete Übermittlung von Nachrichten. Weiterhin ermöglichen sie datenbankähnliche Transaktionen zum Senden und Empfangen von Nachrichten. Beispielsweise können Sie eine Reihe von Nachrichten innerhalb einer Transaktion senden und dabei sicherstellen, dass entweder keine einzige oder aber alle Nachrichten in der Warteschlange verarbeitet werden und diese in der gleichen Abfolge ankommen. Transaktionsinteraktionen lassen sich auch in MSDTC- oder COM+-verteilte Transaktionen integrieren, um Datenbankzugriffe mit Nachrichten-Warteschlangenoperationen zu koordinieren. Transaktions-Warteschlangen verwenden den langsamsten Modus.
Die Leistung einer Message Queuing-Anwendung ist von mehreren Faktoren abhängig. Einer der wichtigsten Faktoren ist die Auswahl des richtigen Nachrichtentyps. Vor einer voreiligen Auswahl von schnellen Nachrichten sollten Sie jedoch die Infrastrukturanforderungen Ihrer Anwendung etwas genauer prüfen. Bei dieser Wahl müssen Sie die Wiederherstellungslogik berücksichtigen, welche die Komplexität Ihrer Anwendung wesentlich erhöhen kann. Ebenso müssen Sie alle Nachrichten-Fehlerszenarios in den Tests abdecken, wenn Sie sich zum Arbeiten ohne wiederherstellbare Nachrichten entscheiden.
Auswählen des besten Nachrichtenformatierungsprogramms in .NET
Ein wesentlicher Vorteil von .NET im Vergleich zu COM-basierten Messaging-Anwendungen ist das einfach zu verwendende und flexible Objektserialisierungs-Framework. Wenn Sie nicht unbedingt die maximal mögliche Leistung benötigen, müssen Sie keinerlei Persistenzfunktionalitäten wie IPersistStream implementieren. Sie können sich stattdessen auf die integrierten Serialisierungs- und Formatierungsfunktionen verlassen.
.NET Framework unterstützt drei Nachrichtenformatierungs-Programme für eine transparente Serialisierung und Deserialisierung von .NET-Objekten. Ein drittes Formatierungsprogramm dient in erster Linie zur Interaktion mit COM-Objekten, die IPersistStream implementieren. Dazu erhalten Sie später mehr Informationen. Die Auswahl eines Formatierungsprogramms ist besonders wichtig für die Kommunikation von .NET zu .NET. Dadurch haben Sie die zusätzliche Option, einen schnelleren binären Serialisierungsmechanismus zu wählen.
Um die Unterschiede zwischen XML- und binärer Serialisierung zu überprüfen, haben wir uns dazu entschlossen, die oben erwähnte Order-Klasse zu verwenden und lediglich das Formatierungsprogramm zu wechseln. In Abbildung 11 beeinflusst die Auswahl des Formatierungsprogramms die Leistung beim Einreihen von Nachrichten, wenn Sie benutzerdefinierte Geschäftsobjekte übertragen. Dies gilt für schnelle, wiederherstellbare und Transaktionsnachrichten.
Abbildung 11: Leistung beim Einreihen von Nachrichten mit verschiedenen Formatierungsprogrammen
In Abbildung 12 wird eine Reihe weiterer entscheidender Leistungsunterschiede bei der vollständigen Verarbeitung einer Nachricht ersichtlich. Durch die Verwendung des binären Nachrichtenformatierungs-Programms können Sie im Vergleich zum XML-Formatierungsprogramm bei schnellen Nachrichten 77 % mehr Nachrichten pro Sekunde verarbeiten.
Abbildung 12: Leistungsunterschiede bei der vollständigen Verarbeitung
Optimierung von COM-kompatiblen Typen
Wenn der Nachrichtentext aus einem COM-kompatiblen Typ besteht (z. B. aus einem Integer, Double, einer Zeichenfolge oder einem Objekt, das IPersistStream implementiert), sollten Sie .NET-ActiveXMessageFormatter verwenden, da dieses Formatierungsprogramm am schnellsten arbeitet. Dieses Formatierungsprogramm ermöglicht Ihnen außerdem den Austausch von serialisierten COM-Objekten zwischen einem klassischen COM-Client in Visual Basic oder C++ und einer .NET-basierten Anwendung.
Die Leistungsunterschiede zwischen BinaryMessageFormatter, XmlMessageFormatter und ActiveXMessageFormatter bei der Übertragung einer Nachricht mit dem Texttyp System.Int32 werden in den Abbildungen 13 und 14 dargestellt.
Abbildung 13: Einreihen von COM-kompatiblen Nachrichten
Abbildung 14: Entfernen von COM-kompatiblen Nachrichten
1:n-Messaging
Durch das mit Windows Server 2003 und Windows XP erstmalig verfügbare MSMQ 3.0 stehen zwei neue Möglichkeiten zum Senden einer Nachricht an mehrere Empfänger zur Verfügung. Sie können auf IP-Multicasting vertrauen, wenn Ihre Netzwerkinfrastruktur dieses Protokoll unterstützt und Sie keine bestimmten Übermittlungs- oder Transaktionsgarantien übernehmen müssen. Bei der Verwendung von IP-Multicasting zur Kommunikation mit mehreren Empfängern wird jedes Paket nur einmal im Netzwerk übertragen (unabhängig von der Anzahl der Empfänger) und auf Empfängerseite in mehrere Warteschlangen auf mehreren Computern eingereiht. Es besteht jedoch keine Garantie für eine erfolgreiche Übermittlung oder Transaktion - MSMQ IP-Multicast bietet keine Möglichkeit der Überprüfung, ob eine Nachricht von dem oder den vorgesehenen Empfängern empfangen wurde.
Bei System.Messaging oder der COM-API von MSMQ 3.0 können Sie eine neue Syntax verwenden, um mehrere Empfänger anzugeben. Dies wird jedoch intern durch herkömmliche Punkt-zu-Punkt-Verbindungen realisiert. Auf diese Weise erhalten Sie die erforderlichen Übermittlungs- oder Transaktionsgarantien. Diese Technik reduziert den Serialisierungsaufwand, der ansonsten beim Senden der gleichen Nachricht an mehrere Empfänger entsteht.
Um eine Nachricht an mehrere Warteschlangen zu senden, können Sie die folgende Syntax verwenden und die Formatnamen der Zielwarteschlangen mit einem Komma im Formatnamen trennen:
String queues = "DIRECT=OS:localhost\\private$\\Q1," +
"DIRECT=OS:localhost\\private$\\Q2," +
"DIRECT=OS:localhost\\private$\\Q3"
MessageQueue que = new MessageQueue("FORMATNAME:" + queues);
Message msg = new Message();
msg.Formatter = fmt;
msg.Label = "MULTI";
que.Send(msg);
Stets Remote Senden/Lokal Lesen
Bei der Übertragung von ausgehenden Nachrichten von Ihrem System zu einem Remote-Zielcomputer durch MSMQ geschieht Folgendes:
-
Sobald Sie eine Nachricht zu einer Remote-Warteschlange senden, indem Sie z. B. einen Formatnamen wie "DIRECT=OS:remotehostname/private$/queuename" angeben, erstellt MSMQ eine so genannte "ausgehende Warteschlange" auf dem Computer des Senders.
-
Alle von Ihrer Clientanwendung an die Remote-Warteschlange gesendeten Nachrichten werden zunächst in der ausgehenden Warteschlange gespeichert. Ihre Clientanwendung kommuniziert niemals direkt mit dem Remote-Server; sie kommuniziert lediglich mit der lokalen MSMQ-Instanz, die auf dem Client-Computer ausgeführt wird. Dadurch wird eine vollständige asynchrone Entkopplung erreicht, einer der großen Vorteile bei der Verwendung von MSMQ: Ihr Client kann unabhängig von der Verfügbarkeit des Servers Nachrichten senden.
-
MSMQ kontaktiert anschließend den Remote-Server und überträgt die Nachrichten unter Verwendung eines internen, optimierten Protokolls.
Beim Empfangen von Nachrichten liegen die Dinge jedoch etwas anders:
-
Wenn Sie eine Warteschlange für den Empfangszugriff öffnen, kommunizieren Sie immer mit der "echten" Warteschlange.
-
MSMQ verwendet in diesem Fall Standard-RPC anstelle des optimierten internen MSMQ-Protokolls, das für den Sendezugriff zur Kommunikation mit dem Remotesystem verwendet wird. Daher ist die Verfügbarkeit des Remotesystems erforderlich und, noch wichtiger, Sie sind zur Verwendung eines langsameren und "geschwätzigeren" Protokolls mit vielen Einzelaufrufen gezwungen.
-
Wenn der Remote-Server nicht antwortet, ist Ihr Client möglicherweise so lange blockiert, bis die Verbindung wiederhergestellt werden kann. Darüber hinaus sind Remote-Lesevorgänge lediglich mit Nicht-Transaktionswarteschlangen möglich.
Diese Überlegungen führen zu einer der wichtigsten Grundregeln für zuverlässige Messaging-basierte Systeme: Stets remote senden, lokal lesen. Wenn ein anderer Computer Rückmeldungen an Ihren Client sendet, sollte dies über eine Weiterleitung an "DIRECT=OS:yourclient/private$/somequeue" geschehen und nicht über eine Verbindungsherstellung des Clients zum Ursprungssystem. Dies ist eines der grundlegenden Entwurfsprinzipien für MSMQ-basierte Messaging-Anwendungen. Es sollte nur dann vernachlässigt werden, wenn ganz besondere Anforderungen dies nötig machen und Sie mit den Auswirkungen auf die Zuverlässigkeit Ihrer Anwendung leben können.
Weitere Leistungstipps
In diesem Whitepaper finden Sie einen Vergleich der .NET-API mit der COM-API, da erfahrungsgemäß überwiegend das COM-API als nicht verwaltete Schnittstelle zu MSMQ verwendet wird. Zusätzlich zu dieser Schnittstelle bietet MSMQ außerdem eine Low-Level-Win32-API.
Die Win32-MSMQ-API arbeitet bedeutend schneller als COM und .NET, da diese beiden APIs auf höherer Ebene im Wesentlichen die zu Grunde liegende systemeigene Win32-API wrappen. Wenn Ihre systemeigene Windows-Anwendung (in einer nicht verwalteten Sprache geschrieben) die bestmögliche Leistung erfordert, empfiehlt sich eine Verwendung der Low-Level-Win32-API statt der COM-API.
Um das Optimum aus der MSMQ-Infrastruktur zu holen (unabhängig von der gewählten API), sollten Sie die folgenden Whitepaper lesen, die auf der MSDN-Website in englischer Sprache zur Verfügung stehen:
MSMQ Frequently Asked Questions (March 02, 2004)
MSMQ Best Practices (May 27, 2003)
Optimizing Message Queuing Performance (March 28, 2003)
Auf diese und weitere Dokumente verweist auch die MSMQ-Produktwebsite.
Die Testanwendung
Alle .NET- und C#-Testanwendungen können in verschiedenen Modi zum Testen unterschiedlicher Szenarios ausgeführt werden. Das Verhalten kann durch die Übergabe verschiedener Befehlszeilenargumente konfiguriert werden:
MsmqPerfTest.exe <TransactionMode> <Operation> <Queue> [<ResponseQueue>] Example: MsmqPerfTest NOTX SENDANDPURGE DIRECT=OS:localhost\private$\myQueue
Parameter
TransactionMode
NOTX: Es werden keine Transaktionen verwendet.
TX: In MSMQ integrierte Transaktionen für jeden Sende- und Empfangsvorgang. Hinweis: In diesem Fall müssen die angegebenen Warteschlangen Transaktionswarteschlangen sein!
Betrieb
SENDANDPURGE: Senden von Nachrichten an eine Zielwarteschlange, Messen der Zeitspanne zum Senden aller Nachrichten und Leeren der Warteschlange nach jedem Testlauf.
SENDANDRECEIVE: Senden von Nachrichten an eine Zielwarteschlange, anschließendes Empfangen aller Nachrichten und Messen der Empfangszeit.
SERVER: Starten als Server, der Nachrichten unmittelbar nach deren Eingang verarbeitet. Der Server sendet nach jedem Testlauf (der aus einigen tausend Nachrichten bestehen kann) jeweils eine einzelne Nachricht an den Client zurück. In einer Client/Server-Umgebung muss zunächst der Server gestartet werden, da er vor dem Warten auf Testnachrichten alle Warteschlangen leert.
CLIENT: Senden von Nachrichten an eine angegebene Warteschlange und Warten auf eine Antwort, nachdem alle Nachrichten eines Testlaufs verarbeitet wurden.
Queue/ResponseQueue
Ein gültiger Formatname für eine vorhandene Warteschlange (z. B. "DIRECT=OS:localhost\private$\myQueue"). Wenn Sie TX als TransactionMode-Parameter angeben, muss es sich um eine Transaktionswarteschlange handeln. Geben Sie NOTX an, muss es sich um eine Nicht-Transaktionswarteschlange handeln.
Hinweis In den Betriebsarten CLIENT oder SERVER bezieht sich der erste Warteschlangenname in der Befehlszeile auf die Warteschlange des Servers. Der zweite Name bezieht sich auf die Antwortwarteschlange des Clients. Diese Parametersequenz ist identisch für Client und Server.
Die beiden folgenden Befehlszeilen starten ein passendes Paar von Empfänger und Sender.
MsmqPerfTest NOTX SERVER DIRECT=OS:localhost\private$\server DIRECT=OS:localhost\private$\client
MsmqPerfTest NOTX CLIENT DIRECT=OS:localhost\private$\server DIRECT=OS:localhost\private$\client
Anhang A - Testergebnisse
Senden von leeren Nachrichten
| Nachrichten/Sek. | Nachrichten/Sek. | StdAbw | StdAbw | ||
|---|---|---|---|---|---|
| Modus | Lokal/Remote | .NET | COM | .NET | COM |
| Schnell | Lokal | 49.311 | 48.674 | 761,23 | 430,25 |
| | Remote | 25.257 | 24.864 | 199,42 | 93,04 |
| Wiederherstellbar | Lokal | 2.455 | 2.457 | 11,21 | 11,76 |
| | Remote | 2.506 | 2.500 | 10,89 | 7,38 |
| Transaktional | Lokal | 1.683 | 1.724 | 37,65 | 36,59 |
| | Remote | 1.662 | 1.739 | 19,84 | 23,91 |
Empfangen von leeren Nachrichten
| Nachrichten/Sek. | Nachrichten/Sek. | StdAbw | StdAbw | ||
|---|---|---|---|---|---|
| Modus | Lokal/Remote | .NET | COM | .NET | COM |
| Schnell | Lokal | 37.351 | 25.708 | 75,14 | 478,76 |
| | Remote | 2.095 | 2.074 | 18,09 | 13,54 |
| Wiederherstellbar | Lokal | 2.955 | 2.796 | 36,59 | 38,49 |
| | Remote | 2.104 | 2.092 | 14,42 | 28,18 |
| Transaktional | Lokal | 2.006 | 1.942 | 11,16 | 26,04 |
Vollständiges Verarbeiten von leeren Nachrichten
| Nachrichten/Sek. | Nachrichten/Sek. | StdAbw | StdAbw | ||
|---|---|---|---|---|---|
| Modus | Lokal/Remote | .NET | COM | .NET | COM |
| Schnell | Lokal | 24.209 | 18.890 | 28,57 | 15,88 |
| | Remote | 11.184 | 11.026 | 94,85 | 33,82 |
| Wiederherstellbar | Lokal | 1.953 | 2.039 | 272,5 | 354,8 |
| | Remote | 1.645 | 1.629 | 12,62 | 6,83 |
| Transaktional | Lokal | 1.414 | 1.522 | 18,37 | 34,87 |
| | Remote | 1.228 | 1.242 | 9,91 | 15,32 |
Schnelles Senden (Lokal)
| Nachrichten/Sek. | Nachrichten/Sek. | StdAbw | StdAbw | |
|---|---|---|---|---|
| Größe | .NET | COM | .NET | COM |
| 500 | 19.814 | 28.572 | 196,96 | 1311,04 |
| 1.000 | 15.117 | 23.476 | 294,62 | 1098,6 |
| 2.000 | 10.722 | 15.345 | 229,27 | 1167,3 |
| 10.000 | 3.083 | 4.500 | 79,62 | 236,12 |
| 100.000 | 276 | 341 | 4,36 | 30,18 |
| 1.000.000 | 28,83 | 30,33 | 0,75 | 2,94 |
| 2.000.000 | 14,17 | 14,50 | 0,41 | 1,64 |
Schnelles Empfangen (Lokal)
| Nachrichten/Sek. | Nachrichten/Sek. | StdAbw | StdAbw | |
|---|---|---|---|---|
| Größe | .NET | COM | .NET | COM |
| 500 | 24.137 | 20.237 | 65,85 | 72,03 |
| 1.000 | 14.595 | 18.318 | 41,36 | 81,24 |
| 2.000 | 12.930 | 11.221 | 50,31 | 57,77 |
| 10.000 | 6.611 | 6.750 | 34,21 | 28,17 |
| 100.000 | 696 | 643 | 6,63 | 2,35 |
| 1.000.000 | 86,33 | 49,17 | 0,52 | 0,41 |
| 2.000.000 | 42,50 | 24,00 | 0,55 | 0 |
Vollständiges Verarbeiten von schnellen Nachrichten (Remote)
| Nachrichten/Sek. | Nachrichten/Sek. | StdAbw | StdAbw | |
|---|---|---|---|---|
| Größe | .NET | COM | .NET | COM |
| 500 | 8.190 | 7.769 | 189,1 | 130,69 |
| 1.000 | 6.853 | 4.702 | 61,34 | 53,41 |
| 2.000 | 4.549 | 2.569 | 71,86 | 10,01 |
| 10.000 | 1.045 | 529 | 13,75 | 40,49 |
| 100.000 | 105 | 53 | 1,83 | 0,75 |
| 1.000.000 | 9,83 | 5,00 | 0,41 | 0 |
| 2.000.000 | 4,67 | 2,00 | 0,52 | 0 |
IPersistStream im Vergleich zur .NET-Serialisierung (Senden)
| Nachrichten/Sek. | Nachrichten/Sek. | StdAbw | StdAbw | ||||
|---|---|---|---|---|---|---|---|
| Modus | Lokal/Remote | .NET | COM | .NET Optimiert | .NET | COM | .NET Optimiert |
| Schnell | Lokal | 1.718 | 6.188 | 13.305 | 5,34 | 331,93 | 685,93 |
| | Remote | 1.441 | 4.523 | 9.129 | 3,29 | 44,34 | 247,91 |
| Wiederherstellbar | Lokal | 910 | 1.490 | 2.236 | 26,65 | 57,45 | 43,44 |
| | Remote | 948 | 1.333 | 1.973 | 3,89 | 46,42 | 16,59 |
| Transaktional | Lokal | 791 | 1.261 | 1.660 | 18,1 | 13,26 | 81,88 |
| | Remote | 603 | 1.114 | 954 | 15,81 | 295,88 | 29,2 |
IPersistStream im Vergleich zur .NET-Serialisierung (Empfangen)
| Nachrichten/Sek. | Nachrichten/Sek. | StdAbw | StdAbw | ||||
|---|---|---|---|---|---|---|---|
| Modus | Lokal/Remote | .NET | COM | .NET Optimiert | .NET | COM | .NET Optimiert |
| Schnell | Lokal | 1.870 | 3.049 | 10.441 | 1,9 | 10,59 | 16,65 |
| | Remote | 509 | 511 | 672 | 2,34 | 2,34 | 1,64 |
| Wiederherstellbar | Lokal | 1.096 | 1.373 | 2.042 | 38,58 | 27,65 | 118,32 |
| | Remote | 505 | 528 | 690 | 4,18 | 2,88 | 1,83 |
| Transaktional | Lokal | 922 | 1.203 | 1.604 | 19,77 | 5,68 | 71,65 |
Vergleich der .NET-Formatierungsprogramme (Einreihen)
| Nachrichten/Sek. | Nachrichten/Sek. | StdAbw | StdAbw | ||
|---|---|---|---|---|---|
| Modus | Lokal/Remote | .NET Binär | .NET XML | .NET Binär | .NET XML |
| Schnell | Lokal | 1.718 | 1.344 | 5,34 | 15,38 |
| | Remote | 1.441 | 987 | 3,29 | 4,22 |
| Wiederherstellbar | Lokal | 910 | 573 | 26,65 | 7,33 |
| | Remote | 948 | 682 | 3,89 | 5,59 |
| Transaktional | Lokal | 791 | 493 | 18,1 | 5,68 |
| | Remote | 603 | 495 | 15,81 | 13,99 |
Vergleich der .NET-Formatierungsprogramme (Entfernen)
| Nachrichten/Sek. | Nachrichten/Sek. | StdAbw | StdAbw | ||
|---|---|---|---|---|---|
| Modus | Lokal/Remote | .NET Binär | .NET XML | .NET Binär | .NET XML |
| Schnell | Lokal | 1.870 | 791 | 1,9 | 1,48 |
| | Remote | 509 | 282 | 2,34 | 1,14 |
| Wiederherstellbar | Lokal | 1.096 | 594 | 38,58 | 7,19 |
| | Remote | 505 | 284 | 4,18 | 0,71 |
| Transaktional | Lokal | 922 | 533 | 19,77 | 6.44 |
