Funktionale Programmierung in In-Memory-Grid Systemen

Große Datenmengen im GB und TB Bereich erlauben es nicht, die zu verarbeitenden Daten zur Verarbeitung an den Client zu übertragen. Eines der wichtigen Konzepte in modernen, verteilten In-Memory-Grid Systemen ist es daher, den auszuführenden Code zu den Daten zu bringen.

Die Ausführung von Code in solchen Systemen erscheint oft umständlich und wenig intuitiv für den Programmierer. Das Einbetten von Funktionen in Klassen, welche im Clusterverbund ausgeführt werden können, erzeugt einen zusätzlichen Boilerplate Code, da entsprechende Interfaces implementiert werden müssen. Hier bietet sich funktionale Programmierung an, um Funktionen im Cluster zur Verfügung zu stellen.
Am Beispiel von Scala als Sprache, welche funktionale Programmierung unterstützt, möchte ich zeigen, dass diese beiden Konzepte sehr gut zusammenspielen und somit die korrekte, performante Verwendung von In-Memory-Grid Systemen unterstützt wird.
Als Grid-System findet Hazelcast Verwendung.

Scala ermöglicht mit Implicit-Methoden die automatische Konvertierung von Funktionen in Objekte. Es ist also möglich, eine Funktion in einen EntryProcessor umzuwandeln, welcher verteilt im Cluster ausgeführt werden kann. Die Einbettung der entwickelten Funktion in die technisch notwendigen Klassenkonstrukte erledigt der Compiler. Die technische Anforderung der verteilten Anwendung wird für den Entwickler durch Abstraktion transparent.

Das Programmiermodel verschlankt sich hierbei auf die notwendige Businesslogik

val printSalary= (entry : Employee)  => { println( entry.salary) } 
val incrementSalaryByFivePercent= (entry : Employee)  => { entry.salary=entry.salary*(105d/100d) } 

Die Verwendung der Funktionen erfolgt nun intuitiv, wie wir im nachfolgenden Beispiel anhand eines Hazelcast EntryProcessors sehen werden.

map.executeOnEntries( printSalary ) 
map.executeOnEntries( incrementSalaryByFivePercent )
map.executeOnEntries( printSalary )

Die Funktion wird hierbei auf jeden Entry der IMap aufgerufen, kann aber auch auf einen bestimmten Schlüssel einer Map angewendet werden.

map.executeOnKey("1", printSalary)

Ein Locking der entsprechenden Entitäten erfolgt durch das Framework.

Die notwendigen Implicit Methoden, welche die Konvertierung der Funktionen in EntryProcessor Instanzen übernimmt, können in einem Scalaobject abgelegt werden und durch import Anweisung an den entsprechenden Scala Klassen zur Verfügung gestellt werden.
Die Implementierung ist nicht sonderlich aufwendig:

object MemoryGridProcessing {
  implicit def entryProcessorAnyVal[K,T]( f: ( T ) => Any ): EntryProcessor[K,T] =
    new AbstractEntryProcessor[K,T]() {   	  
      private def internProcess( e : T ) = f ( e ); 
      def process( e : Map.Entry[K,T] ) : Object = {  val value = e.getValue(); 
      val returnValue = internProcess(value); 
      e.setValue(value); 
      return returnValue.asInstanceOf[AnyRef]; 
    }; 
  }
}; 

Es wird eine Konvertierung angeboten, welche wirkt sobald ein EntryProcessor erwartet wird, aber eine Funktion übergeben wird. Der Scala Compiler fügt somit das Erzeugen des EntryProcessors ein und verwendet die Funktionsreferenz intern in der entsprechenden process-Methode.

Ich möchte diese Idee als Denkanstoß teilen. In der Praxis sind sicher noch einige Details zu überlegen. Dieses Verfahren bietet sich zum Beispiel für Funktionen an, welche keine dynamischen Parameter benötigen. Eine Parameterübergabe wäre aber sicherlich in einigen Fällen wünschenswert. Mit Closures bieten hier viele funktionale Programmiersprachen zum Beispiel Möglichkeiten, die in diese Richtung zielen. Ideen und Vorschläge für eine möglichst intuitive Parameterübergabe sind willkommen.

Short URL for this post: http://wp.me/p4nxik-27Q
This entry was posted in Other languages for the Java VM and tagged , , , , , . Bookmark the permalink.

Leave a Reply