Java Magazin 03/09

Serialisierung: Wer anderen eine Nachricht schickt…

Autor:

Sehr häufig bleiben Details, wie Daten erzeugt und übertragen werden, im Verborgenen. Ein wesentlicher Grund ist hier, dass sich der Anwendungsentwickler um diese Teile keine Gedanken machen muss, da ihm diese Aufgabe von Remoting-Frameworks abgenommen wird. Da Serialisierung aber einer der Hauptbestandteile einer verteilten Anwendung ist, sollen die Serialisierungskonzepte in diesem Artikel detailliert betrachtet werden.

Im letzten Artikel haben wir uns mit architekturellen Anti- Patterns in verteilten Systemen auseinandergesetzt. Der Fokus lag dabei auf Problemen wie dem Senden von großen Datenmengen oder ineffizienter Kommunikationsmuster. Serialisierung im Zuge von Remote-Aufrufen wurde dabei bereits als Problemquelle gestreift. Neben dem Schnittstellendesign und der Kommunikationslogik ist die Serialisierung der dritte wesentliche Bestandteil einer verteilten Anwendung, der zu Performance- und Skalierbarkeitsproblemen führen kann. Sehr oft bleibt dem Entwickler allerdings verborgen, was auf der Serialisierungsebene passiert. Serialisierung im Detail Als Erstes gilt es zu verstehen, wie sich Serialisierung auf die Performance einer Anwendung auswirkt. Dazu ist es hilfreich zu verstehen, was bei verteilter Kommunikation im Detail passiert.

Serialisierung im Detail

Als Erstes gilt es zu verstehen, wie sich Serialisierung auf die Performance einer Anwendung auswirkt. Dazu ist es hilfreich zu verstehen, was bei verteilter Kommunikation im Detail passiert.
In der nachfolgenden Grafik sehen wir den abstrahierten Ablauf eines Client-Server-Aufrufs eines Remoting-Frameworks.

Logischer Ablauf

Neben der eigentlichen Datenübertragung nimmt die Umwandlung der Objekte in das Transportformat einen wesentlichen Teil der Laufzeit in Anspruch. Aus diesem Verhalten können wir die wesentlichen Performancemetriken ableiten (Abb. 1).

Die Serialisierung selbst wirkt sich im Wesentlichen auf den CPU- und Speicherverbrauch der Anwendung aus. Je größer die Datenmenge und je komplexer die zu serialiserenden Datenstrukturen, desto höher die CPUAuslastung und je nach verwendeter Serialisierungstechnologie auch die Speicherbelastung. Durch die oft auch mehrfache Umwandlung der Daten wird eine sehr hohe Anzahl von (temporären) Objekten erzeugt, die sehr kurzfristig benötigt werden. Dieses Verhalten wird auch als „Object Churning“ bezeichnet und führt mitunter zu hoher Aktivität des Garbage Collectors, vor allem wenn bei Generationen-Heaps die Young Generation zu klein ist, um die temporären Daten der Serialisierung zu verarbeiten. Neben den temporären Objekten ist es aber auch entscheidend, ob die serialisierten Objekte direkt auf die Leitung, also z. B. direkt auf einen OutputStream geschrieben werden oder die Serialisierung im Speicher vorgehalten wird. Letzteres kann bei sehr großen Objekten oder hoher Last schnell zu Schwierigkeiten bis hin zu einem OutOfMemoryError führen. Es ist daher unbedingt notwendig, die Funktionsweise der Serialisierung bei einer verwendeten Remote-Technologie zu verstehen. Neben CPU und Speicher legt die Serialisierungstechnologie aber auch die übertragene Datenmenge fest, die ausschlaggebend für die Netzwerkbelastung ist. Je nach Bandbreite, Datenmenge und Last kann es auch hier schnell zu Engpässen kommen, die Performance und Skalierbarkeit einer Anwendung einschränken.

Formate, Formate, Formate

Alleine die Wahl einer Technologie legt aber noch nicht automatisch fest, welches Übertragungsformat gewählt wird und wie die Serialisierung erfolgt. Bei Übertragungsformaten kommt es darauf an, wie viel Overhead ein Protokoll generiert und wie die tatsächlich übertragenen Daten repräsentiert werden. Anhand eines kleinen Beispiels wollen wir uns ansehen, welche Auswirkungen die Wahl des Übertragungsformats auf die Datenmenge hat. Wir übertragen dazu einen Personendatensatz einmal mittels RMI und einmal über einen SOAP Web Service. Da SOAP XML als Übertragungsformat verwendet, werden die Daten und die Struktur der Nachricht in textueller Form übertragen. Im Vergleich dazu überträgt RMI die Daten rein binär und nimmt wesentliche Optimierungen vor, die speziell bei größeren Datenmengen ausschlaggebend für eine bessere Performance sein können.

In Abbildung 2 sind die via RMI und SOAP übertragenen Daten gegenübergestellt. Keine Angst, sollten Sie die RMI-Nutzdaten nicht lesen können – das brauchen Sie auch nicht. Es geht bei dem Beispiel nur um den Vergleich der Datenmengen.

RMI und SOAP Web Service

RMI wurde hier nur als Beispiel für ein binäres Protokoll genommen. Im Java- Umfeld gibt es weitere Alternativen, z. B. Hessian [2]. Aber selbst wenn man sich für eine Remoting-Technologie entschieden hat, steht man oft noch vor der Wahl bezüglich des Datenformats. So erlaubt RMI z. B. das Verwenden von zwei Übertragungsprotokollen. JRMP basiert auf Java-Serialisierung, GIOP – das Protokoll von CORBA – wird für RMI over IIOP verwendet. Viele Application- Server-Hersteller bieten zudem eigene optimierte Protokolle an.

XML oder binär

Eine wesentliche Entscheidung in der Entwicklung verteilter Anwendung ist die Wahl des Übertragungsformats. Als Entwickler steht man heute meistens vor der Entscheidung, ob man XML oder ein binäres Format verwenden soll. Diese Entscheidung wird sehr stark vom Einsatzszenrio beeinflusst. Wird in heterogenen Systemen kommuniziert, so muss man zwansgweise ein technologieneutrales Format wählen. Hier kommt dann meist XML zum Einsatz, wobei auch GIOP oder Hessian von unterschiedlichsten Programmiersprachen unterstützt wird – oftmals aber zu Lasten der eingesetzten Datentypen. Handelt es sich allerdings um Kommunikation zwischen zwei Java-Anwendungen, sind binäre Übertragungsprotokolle zu bevorzugen, die dem Java-Serialisierungsmechanismus folgen. Nutzt man SOAP oder natives XML als Austauschformat, so ist in der Regel die Datenmenge größer und der Serialsierungsmechanismus aufwändiger. Dafür sind die Daten aber gerade bei WS-I-konformer SOAP-Kommunikation sehr interoperabel.

Serialisierung als Performancekiller

Bei jeder eingesetzten Technologie müssen die Daten der zu übertragenden Java-Objekte auf der Senderseite in das entsprechende Protokoll der Remoting- Technologie serialisiert und durch den Empfänger deserialisiert werden. Gerade hier werden oft wertvolle Zeit und Ressourcen verbraucht. Die gängigste Form der Serialisierung von Java- Objekten ist der integrierte Serialisierungsmechanismus [3] von Java auf Basis des java.io-Pakets. Klassen, die das Interface java.io.Serializable implementieren, werden mithilfe des java. io.ObjectOutputStream serialisiert und direkt in einen OutputStream umgeleitet, der die Daten beispielsweise direkt auf das Netzwerk schreiben kann. Der ObjectOutputStream gilt trotz einiger Performanceverbesserungen in den letzten Java-Releases nicht als besonders performant. JBoss hat beispielsweise mit dem Projekt JBoss Serialization [4] eine optimierte Variante des ObjectOutputStreams entwickelt, den org.jboss.serial.io. JBossObjectOutput- Stream, der nach Angaben von JBoss mindestens zwei mal schneller ist als die Java-eigene Implementierung. Bei Klassen, die java.io.Serialization nutzen, kann zudem der Mechanismus, die Daten der Klasse zu serialisieren, nur generisch implementiert werden, was immer gewisse Performancenachteile mit sich bringt. Java bietet dafür die Möglichkeit an, die Serialisierung einer Klasse selbst zu implementieren – hierfür muss die Klasse das Interface java. io. Externalizable implementieren. Das Interface enthält zwei Methoden zum Schreiben und Lesen des Objekts – so erhält man die Möglichkeit, den Mechanismus selbst zu steuern und gegebenenfalls nur die benötigten Daten effizient zu schreiben.

Beim Serialisieren von XML-Daten wird es deutlich komplizierter und es gibt Unmengen von Möglichkeiten und Implementierungen, Java-Objekte in XML zu serialisieren. JAXB (Java Architecture for XML Binding) ist ein offizieller Standard (JSR-222) der Java- Plattform für die Serialisierung von Java-Objekten in XML, mit einer Referenzimplementierung [5] von Sun. XStream [6] und Castor [7] sind zwei weitere Open-Source-Alternativen, mit einer sehr einfachen API. XStream bietet zudem die Möglichkeit, die Daten auch als JSON [8] zu serialisieren, was die Datenmenge erheblich reduziert. Die Performance hängt dabei vor allem von der Implementierung des XML-Serialisierungsmechanismus und dem eingesetzten XML-Parsers ab. Je nach SOAP-Implementierung kann man den Mechanismus wählen oder konfigurieren, bei dokumentbasierten SOAP-Aufrufen [1] liegt die Serialisierung der Daten sowieso in der Hand des Entwicklers. Gerade bei hoher Last und großen Datenmengen bietet sich aber auch hier eine eigene Implementierung des Serialisierungsmechanismus nach XML an – dafür müssen die Objekte entsprechende Methoden zum Schreiben und Lesen des Objekts nach und von XML anbieten und implementieren. Um den Aufwand der Implementierung solcher Methoden für alle relevanten Objekte zu reduzieren (das gilt für binäre und XML-basierte Implementierungen), empfiehlt es sich, die Methoden mit einem Generator zu erzeugen – dadurch werden auch Probleme vermieden, die durch händische Implementierung der Serialisierung durch unterschiedliche Entwicker entstehen können.

 Ich schreibe dir einen langen Brief …

… denn für einen kurzen hatte ich keine Zeit. Dieser Spruch von Goethe lässt sich auch auf die Implentierung von Custom-Serialisierung anwenden. Der Vorteil moderner Remoting-Frameworks ist, dass man sich um die Datenserialisierung nicht kümmern muss, da dies schon von den diversen Implementierungen abgenommen wird. Dies ist sehr wartungsarm und – sofern ein entsprechendes Framework gewählt wurde – auch wenig fehleranfällig. Will man allerdings optimale Performance erreichen, stellt sich die Frage, ob man nicht doch zur eigenen Implementierung greift – Beispiele hierfür wurden ja bereits im vorangegangenen Kapitel gegeben. Bei einer Eigenimplementierung hat man volle Kontrolle über den gesamten Serialisierungsprozess und kann hier teilweise signifikante Performanceoptimierungen vornehmen. In Abbildung 3 wird die Datenmenge einer Standard-RMI-Serialisierung mit einer Custom-Serialisierung verglichen.

Diesen Performancegewinn erhält man allerdings nicht umsonst. Man erkauft ihn sich mit wesentlich höherem Implementierungs- und – was vielleicht noch entscheidender ist – Testaufwand. Es ist hierbei also wirklich im Einzelfall zu prüfen, wo sich dieser Aufwand tatsächlich lohnt.

RMI Aufruf

Alternative: Hardwareserialisierung

Neben den beschriebenen Technologien und der Möglichkeit, die Serialisierung angepasst an die Bedürfnisse der Anwendung zu optimieren, gibt es heute aber noch eine andere, sehr interessante Möglichkeit der Optimierung von Serialisierung: Man verlagert diesen Prozess an eine speziell dafür optimierte Hardware. Mit Layer7 [8] und IBM WebSphere DataPower [9] sind den Autoren zwei Appliances bekannt, die die Serialisierung von XML-Daten mit extremer Performance und Skalierbarkeit unterstützen. Diese Lösungen sind vor allem dann sehr leistungsfähig, wenn die XML-Daten transformiert werden müssen und die Daten sehr groß sind. In einigen Beispielen aus der Praxis konnten mit dieser Lösung Hardwarekosten gespart und die Performance und Skalierbarkeit der Anwendung signifikant verbessert werden.

Wann muss man optimieren?

Eine wesentliche Frage, die sich einem Entwickler oder Architekten zwangsweise stellt ist, wann die Serialisierung einer Anwendung optimiert werden soll oder muss. Da dies mit einem erheblichen Mehraufwand verbunden ist, sollte es nicht proaktiv gemacht werden, „um auf Nummer sicher zu gehen“. In den meisten Fällen wird man mit dem, was die Infrastruktur zu bieten hat, eine ausreichende Performance und Skalierbarkeit erreichen. Dennoch gilt es, rechtzeitig den Handlungsbedarf zu erkennen, um Performance- und Skalierungsprobleme rechtzeitig zu vermeiden. Daneben können aber auch die Kosten eine entscheidende Rolle bei der Optimierung der Serialsierung spielen. Auf Plattformen, bei denen Rechenleistung aufgrund der hohen Verfügbarkeit und Zuverlässigkeit teurer sind als bei Mainstream- Hardware, kann die Optimierung der Serialisierung zu erheblichen Kosteneinsparungen führen. Beispielsweise konnte bei einer Java-basierten Anwendung, die auf dem IBM-Mainframe-System z abläuft, nur durch die Optimierung der Remote Pattern und der Serialisierung, eine Einsparung der Hälfte der CPUs erreicht werden, was jährlich enorme Kosten erspart, sodass ein Return on Investment der Tuningmaßnahmen innerhalb eines Jahres möglich war.

Um bereits in der Entwicklung erste Abschätzungen geben zu können, ob die Datenübertragung ein potenzielles Problem darstellt, ist es notwendig, einige Metriken zu sammeln. Abbildung 4 zeigt die Analyse einzelner Transaktionen einer Anwendung in Hinsicht auf CPU-Verbrauch, Antwortzeit und übertragener Datenmenge. Wesentlich hierbei ist, bereits mit realistischen Datenmengen und -größen zu arbeiten. Zusätzlich sollte man bereits über erste – wenn auch grobe – Abschätzungen der Transaktionsvolumen verfügen. Damit lassen sich zwar nur grobe aber immerhin erste Schätzungen über das Remoting-Verhalten der Anwendung abgeben.

Diese Metriken sollten dann auch während der Lasttests gesammelt und ausgewertet werden. Moderne Performance- Management-Lösungen erlauben, diese Daten ohne Einfluss auf den Test auch in Hochlastszenarios zu sammeln.

Remoting-Metriken

Fazit

Die Serialisierung von Daten in verteilten Anwendungen kann ein entscheidender Faktor für Performance und Skalierbarkeit sein. Die Auswahl an Technologien für binäre und XMLbasierte Serialisierung in Java ist groß, sodass die Architekten und Entwickler vor der Qual der Wahl stehen. Es ist daher empfehlenswert, schon zu Beginn die Anforderungen an Interoperabilität und Performance abzuwägen und gegebenenfalls eigene Serialisierungsmechanismen zu implementieren, wenn die Anforderungen nicht durch generische Ansätze erfüllt werden können.

Links & Literatur

[1] Hessian: hessian.caucho.com

[2] Java-Serialisierung: java.sun.com/javase/6/docs/technotes/guides/serialization/ index.html

[3] JBoss-Serialisierung: www.jboss.org/serialization/

[4] JAXB-Referenzimplementierung: https://jaxb.dev.java.net

[5] XStream: xstream.codehaus.org

[6] Castor: www.castor.org

[7] JSON: json.org

[8] Layer7: www.layer7tech.com

[9] IBM WebSphere DataPower: www-01.ibm.com/software/integration/datapower/

[10] Blog-Eintrag zum Thema Serialisierung: blog.dynatrace.com/2008/07/01/ optimizing-remoting-by-optimizing-serialization/

Vollständiger Artikel