Mit Spring skalieren - JAXenter

Mit Spring skalieren

Skalierbarkeit im Framework

Die Skalierbarkeit kann man aber nicht nur der Plattform übertragen: Wenn man bei der Entwicklung der Software nicht aufpasst, kann man die Skalierbarkeit torpedieren, die eine Plattform eigentlich anbietet.

Spring nutzt ohne weitere Änderungen Singletons – es gibt also von jedem Service in der Anwendung nur jeweils eine Instanz. Man könnte nun denken, dass es irgendwann zu einem Engpass kommt, zumal andere Plattformen standardmäßig Pooling verwenden. Ganz so einfach ist es allerdings nicht – Spring verwendet Singletons für die Objekte wie Services oder DAOs, die Teil der Geschäftslogik sind. Ressourcen wie Datenbankverbindungen, Threads oder JMS-Sessions werden typischerweise mit Spring genauso gepoolt, sogar wenn die Plattform selber solche Features nicht bietet. Spring bietet eine Abstraktion über typische Technologien, und außerdem sind einige gängige Implementierungen für Datenbank-Pools und für Thread-Pools in der Spring-Distribution enthalten. Für die Objekte, die Geschäftslogik enthalten, wird jedoch kein Pooling verwendet. Dafür gibt es verschiedene Gründe:

  • Pooling ist nur sinnvoll, wenn eine Ressource zu einem Zeitpunkt nur von einem Thread genutzt werden kann. In den typischen Architekturen kommt man aber praktisch nie in eine solche Situation. Services und DAOs übernehmen Parameter und geben Objekte zurück – diese gehören sowieso zum aktuellen Thread und stellen daher kein Risiko für parallele Nutzung dar. Gleiches gilt für Ressourcen wie Datenbankverbindungen oder Hibernate-Sessions. Sie werden durch Spring an den jeweiligen Thread „geheftet“ und dann an die Singletons bei den entsprechenden Anfragen weitergegeben. Auch hier können mehrere Threads problemlos mit einem Singleton arbeiten.
  • Selbst wenn die Objekte nicht Thread-sicher wären, ist Pooling nicht die einzige Lösung. Man kann auch vor jedem Aufruf einfach ein neues Objekt für den Aufruf erzeugen. Dies mag sich nach einer sehr performance-intensiven Lösung anhören, aber die Hotspot-FAQ warnt sogar ausdrücklich vor Object-Pooling. Grund dafür ist, dass die Laufzeit des Garbage-Collection-Algorithmus für kurzlebige Objekte nur von der Anzahl der überlebenden Objekte abhängt. Also ist die Beseitigung eines kurzlebigen Objekts kostenlos – es kann höchstens sein, dass man die Garbage Collection häufiger auslöst. Dadurch kann das Erzeugen und anschließende Beseitigen eines Objekts schneller sein als das Pooling.

Pooling solcher Objekte ist also ein Relikt aus der Zeit, in der noch keine effizienten Garbage-Collection-Algorithmen verwendet wurden, und ihm liegt die mittlerweile sinnlose Annahme zu Grunde, dass Objekte typischerweise nicht Thread-safe sind. Der typische Spring-Singleton-Ansatz stellt also kein Problem für die Skalierbarkeit einer Anwendung dar.

Natürlich kann man mithilfe der CommonsPoolTargetSource auch ein Pooling für Spring Beans durchführen – interessanterweise wird dieses Feature in der Praxis so gut wie nie genutzt, weil sich der Bedarf einfach nicht ergibt. Und es gibt es auch die PrototypeTargetSource, die vor jedem Aufruf eine neue Instanz des Objekts erzeugt.

Ansonsten kann man mit Spring für Threading das normale Java-Programmiermodell verwenden. Neben den bekannten Elementen zum Synchronisieren von Threads bietet Spring auch einen ConcurrencyThrottleInterceptor, der die Anzahl der parallel ausgeführten Threads bei einer Bean limitieren kann.
Eine andere Herausforderung bei der Skalierbarkeit ist die Verwaltung des Zustands. Bei den Patterns of Enterprise Application Architecture [Martin Fowler: Pattern of Enterprise Application Architecture, Addison-Wesley, 2002] werden drei Möglichkeiten unterschieden:

  • Der Zustand kann auf dem Client gehalten werden. Dadurch entlastet man den Server und verbessert daher die Skalierbarkeit, gleichzeitig muss man die jeweils für eine Funktionalität relevanten Daten vom Client zum Server übertragen.
  • Die Daten werden auf dem Server gehalten. Dadurch wird das Clustering und damit die Skalierbarkeit schwieriger, weil man den Zustand im Cluster replizieren muss. Dafür steht er unmittelbar zur Verfügung und muss nicht erst vom Client übertragen werden.
  • Die Daten werden in der Datenbank gehalten. Dadurch überleben sie Abstürze der Server und auf sie kann von allen Servern aus zugegriffen werden. Clustering und damit Skalierbarkeit werden recht einfach, aber der Zustand muss jeweils aus der Datenbank geladen werden und die Datenbank muss mit der Last umgehen können.

Plattformen haben meistens eine Unterstützung für das Halten von Zuständen auf dem Server. Web-Server unterstützten dies, während beispielsweise Java Spaces dies sogar zu der wichtigsten unterstützten Technik macht. Andere Konzepte zur Verwaltung von Zuständen muss man oft selbst entwickeln. Spring hilft hier, indem es ein Konzept für Scopes hat, in das man Beans legen kann. Dieses Konzept ist erweiterbar, sodass jede Plattform ihre eigenen Scopes anbieten kann, und es ist sogar möglich, für ein Projekt eigene zu definieren. Von Haus aus bietet Spring Scopes für die HTTP-Session und den HTTP-Request. Somit ist es auch möglich, Spring Beans in einem Scope abzulegen, der in einem Cluster unterstützt wird. Web-Server-Cluster bieten nämlich ein Replizieren der Session auf verschiedene Knoten im Cluster an.

Fazit

Zusammenfassend lässt sich festhalten, dass Spring selber keine Plattform ist und daher auch keine eigene Unterstützung für Skalierbarkeit bieten kann. Aber man kann mit Spring eine Anwendung von der Plattform unabhängig halten, sodass man ohne weiteres die jeweils passende Plattform auswählen kann. Dadurch kann man die Skalierbarkeit einer Anwendung verbessern, indem man die richtige Plattform auswählt.

Fälschlicherweise wird bei Skalierung häufig angenommen, dass man alles poolen müsste. Dies ist aber nur notwendig bei teuren Objekten, die auch pro Thread exklusiv zur Verfügung stehen müssen, wie die Threads selber oder Datenbankverbindungen. Typische Bestandteile der Anwendung selber müssen hingegen nicht gepoolt werden, weil sie von mehreren Threads parallel genutzt werden können. Selbst wenn dies nicht möglich ist, ist das Erzeugen dieser Objekte sehr billig, sodass statt eines Poolings ein Neuerzeugen pro Aufruf sinnvoll sein kann. Hier stellt Spring also der Skalierbarkeit keine Hindernisse in den Weg. Gleiches gilt für das Management des Zustands: Spring unterstützt einige Verfahren von sich aus und kann um eigene Verfahren ergänzt werden. Gerade die Verwaltung des Zustands im Cluster ist ein wichtiger Punkt bei der Skalierbarkeit von Anwendungen.

Somit bietet Spring zwar selber keine direkte Unterstützung für die Skalierbarkeit, weil es eben nicht die Domäne eines solchen Frameworks ist. Aber man kann sich dank Spring die Plattform aussuchen, welche die beste Skalierbarkeit für die jeweilige Aufgabe verspricht, ohne dass man den Code ändern muss. Springs Programmiermodell legt einem dabei nicht nur keine Hürden in den Weg, sondern es wählt einfache, skalierbare Ansätze und ist an den richtigen Stellen erweiterbar.

Eberhard Wolff ist Regional Director bei SpringSource, der Firma hinter dem Spring-Framework. Er ist Java Champion und Sprecher auf zahlreichen, auch internationalen Konferenzen. Außerdem ist er Autor vieler Fachartikel und Bücher – unter anderem auch das erste deutschsprachige Spring-Buch.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.