Tipps zu SOA

Beheben von Skalierbarkeitsengpässen durch verteiltes Zwischenspeichern

Iqbal Khan

Nach dem rasanten Anstieg von Webanwendungen zur Verarbeitung von hohem Datenverkehr kommt die nächste große Welle mit der serviceorientierten Architektur (SOA). Die SOA soll ein Standard für die Entwicklung stark skalierbarer Anwendungen werden, und Cloud Computing-Plattformen wie Windows Azure sind eine riesige Weiterentwicklung bezüglich der Verwendung der SOA für dieses Ziel.

Die SOA ermöglicht Benutzern, Anwendungen an mehrere Standorte, mehrere Abteilungen innerhalb einer Organisation und mehrere Unternehmen über das Internet zu verteilen. Außerdem ermöglicht sie die Wiederverwendung von vorhandenem Code innerhalb einer Organisation und, was noch wichtiger ist, die Zusammenarbeit über unterschiedliche Geschäftseinheiten hinweg.

Eine SOA-Anwendung wird in der Regel in einer Serverfarm in einer Lastenausgleichsumgebung bereitgestellt. Das Ziel besteht darin, dass die Anwendung die gesamte Last bearbeitet, die Sie an sie übergeben. Die Frage ist daher: Welche Überlegungen sollten Sie für eine Verbesserung der Leistung und der Skalierbarkeit Ihrer SOA-Anwendung anstellen?

Die SOA ist zwar für Skalierbarkeit konzipiert, es gibt aber dennoch einige Probleme, die Sie beheben müssen, bevor Sie wahre Skalierbarkeit erzielen können. Einige der Probleme beziehen sich auf die Codierung Ihrer SOA-Anwendung, die größten Engpässe stehen jedoch häufig damit im Zusammenhang, wie Sie Ihre Daten speichern und darauf zugreifen. In diesem Artikel werde ich mich mit diesen Problemen befassen und Lösungsvorschläge bereitstellen.

Aufspüren von Skalierbarkeitsengpässen

Eine echte SOA-Anwendung muss im Hinblick auf die Anwendungsarchitektur einfach skalierbar sein. Eine SOA-Anwendung besteht aus zwei Komponenten: Servicekomponenten und Clientanwendungen. Die Clientanwendung kann eine Webanwendung, ein anderer Dienst oder eine andere Anwendung sein, die auf die SOA-Dienstkomponenten zur Ausführung der entsprechenden Aufgaben angewiesen ist.

Eine der Schlüsselideen hinter der SOA ist, die Anwendung in kleine Stücke aufzuteilen, sodass diese Komponenten auf mehreren Servern als separate Dienste ausgeführt werden können.

Idealerweise sollten diesen Dienste soweit wie möglich statusfrei sein. Statusfrei bedeutet, dass die Dienste keine Daten über mehrere Aufrufe hinweg speichern, sodass Sie die Dienste auf mehreren Computern ausführen können. Es besteht keine Abhängigkeit davon, wo sich die Daten zuletzt befanden, deshalb werden keine Daten auf einem bestimmten Server über mehrere Dienstaufrufe hinweg gespeichert.

Folglich ist die Architektur von SOA-Anwendungen von Natur aus skalierbar. Sie kann problemlos auf mehrere Server und über Rechenzentren hinweg erweitert werden. Wie auch jede andere Anwendung müssen SOA-Anwendungen die Daten verarbeiten, und das kann ein Problem darstellen. Dieser Datenzugriff wird zu einem Skalierbarkeitsengpass. Engpässe gehen in der Regel mit den Anwendungsdaten einher, die in derselben Datenbank gespeichert sind, bei der es sich normalerweise um eine relationale Datenbank handelt. Wenn die SOA-Anwendung Sitzungsdaten verwendet, kann auch das Speichern dieser Daten zu einem weiteren potenziellen Skalierbarkeitsengpass führen.

Eine SOA-Anwendung, die von anderen SOA-Anwendungen abhängig ist, ist ein weiterer Bereich für Leistungs- und Skalierbarkeitsprobleme. Angenommen, Ihre Anwendung ruft einen Dienst für ihre Aufgabe aus, dieser Dienst ruft jedoch weitere Dienste auf. Diese Dienste können sich im selben Intranet oder über das WAN an einem anderen Ort befinden. Ein solcher Umweg kann sehr kostspielig sein. Sie können die Anwendung nicht effektiv skalieren, wenn Sie diese Aufrufe immer wieder ausführen; das sind die Bereiche, in denen es zu Skalierbarkeitsengpässen kommt, wie in Abbildung 1 dargestellt.

Figure 1 SOA Architecture with Potential Scalability Bottlenecks
Abbildung 1 SOA-Architektur mit möglichen Skalierbarkeitsengpässen

Leistungsstarker Code

Es gibt eine Reihe von Programmiertechniken, die bei der Verbesserung der Leistung Ihrer SOA-Anwendung behilflich sein können.

Eine Möglichkeit besteht darin, dass Sie Ihre Anwendung so entwickeln, dass diese „grobe“ Webmethodenaufrufe verwendet. Vermeiden Sie häufige Aufrufe zwischen der SOA-Clientanwendung und der SOA-Dienstebene. Zwischen diesen besteht in der Regel ein großer Abstand, da sie nicht auf demselben Computer bzw. nicht einmal im gleichen Rechenzentrum ausgeführt werden. Je weniger Aufrufe Sie von der Clientanwendung aus zu den Dienstebenen ausführen, desto besser die Leistung. Grobe Aufrufe erledigen mehr Arbeit in einem Aufruf als mehrere Aufrufe.

Eine weitere hilfreiche Technik besteht darin, die asynchronen Webmethodenaufrufe zu verwenden, die von Microsoft .NET Framework unterstützt werden. Auf diese Weise kann Ihre SOA-Clientanwendung weiterhin andere Dinge erledigen, während die Webmethode der Dienstebene aufgerufen und ausgeführt wird.

Die Kosten der Serialisierung sind ein weiterer Aspekt, der berücksichtigt werden sollte, damit Sie keine unnötigen Daten serialisieren. Sie sollten nur erforderliche Daten hin- und herschicken, sodass Sie im Hinblick auf den Typ der durchzuführenden Serialisierung freie Wahl haben.

Auswählen des richtigen Kommunikationsprotokolls

Bei SOA-Anwendungen, die in der Windows Communication Foundation (WCF) entwickelt wurden, gibt es drei unterschiedliche Protokolle, über die SOA-Clients mit SOA-Diensten kommunizieren können. Diese sind HTTP, TCP und Named Pipes.

Wenn sowohl der Client als auch der Dienst in WCF entwickelt wurden und auf demselben Computer ausgeführt werden, bieten Named Pipes die beste Lösung. Eine Named Pipe verwendet von Client- und Serverprozessen gemeinsam verwendeten Speicherplatz.

TCP bietet sich an, wenn sowohl der SOA-Client als auch der Server in WCF entwickelt wurden, jedoch auf unterschiedlichen Computern im selben Intranet ausgeführt werden. TCP ist schneller als HTTP, aber eine TCP-Verbindung bleibt über mehrere Anrufe hinweg geöffnet, deshalb können Sie die einzelnen WCF-Aufrufe nicht automatisch an einen anderen Server weiterleiten. Indem Sie die NetTcpBinding-Option verwenden, die Verbindungspools verwendet, können Sie TCP-Verbindungen häufig ablaufen lassen, um sie neu zu starten, damit diese an einen anderen Server weitergeleitet werden. Auf diese Weise erhalten Sie eine Art Lastenausgleich.

Beachten Sie, dass TCP nicht zuverlässig über das WAN arbeiten kann, da Socketverbindungen häufig unterbrochen werden. Wenn der SOA-Client und der SOA-Dienst nicht auf WCF basieren oder an unterschiedlichen Orten gehostet werden, ist HTTP die beste Option für Sie. HTTP ist zwar nicht so schnell wie TCP, bietet aber aufgrund des Lastenausgleichs eine hervorragende Skalierbarkeit.

Zwischenspeichern zur Leistungsverbesserung

Durch eine durchdachte Verwendung des Zwischenspeicherns kann die Leistung des SOA-Clients erheblich verbessert werden. Wenn ein SOA-Client einen Webmethodenaufruf an die Dienstebene ausführt, können Sie die Ergebnisse auf der Seite der Clientanwendung zwischenspeichern. Wenn dieser SOA-Client dann beim nächsten Mal denselben Webmethodenaufruf ausführen muss, erhält er die Daten stattdessen aus dem Cache.

Durch Zwischenspeichern der Daten auf der Clientseite reduziert die SOA-Clientanwendung die Anzahl von Aufrufen, die sie an die Dienstebene ausführen wird. Durch diesen Schritt wird die Leistung bedeutend gesteigert, da kein teurer SOA-Dienstaufruf ausgeführt werden musste. Dadurch wird auch der gesamte Druck auf der Dienstebene reduziert und die Skalierbarkeit verbessert. Abbildung 2 zeigt einen WCF-Client, der Zwischenspeichern verwendet.

Abbildung 2 Zwischenspeichern des WCF-Clients

using System;
using Client.EmployeeServiceReference;

using Alachisoft.NCache.Web.Caching;

namespace Client {
  class Program {
    static string _sCacheName = "mySOAClientCache";
    static Cache _sCache = NCache.InitializeCache(_sCacheName);

    static void Main(string[] args) {
      EmployeeServiceClient client = 
        new EmployeeServiceClient("WSHttpBinding_IEmployeeService");

      string employeeId = "1000";
      string key = "Employee:EmployeeId:" + employeeId;
            
      // first check the cache for this employee
      Employee emp = _sCache.Get(key);

      // if cache doesn't have it then make WCF call
      if (emp == null) {
        emp = client.Load("1000");

        // Now add it to the cache for next time
       _sCache.Insert(key, emp);
      }
    }
  }
}

In vielen Situationen wird der Client physisch aus der Dienstebene entfernt und über das WAN ausgeführt. In diesem Fall können Sie nicht wissen, ob die von Ihnen zwischengespeicherten Daten aktualisiert wurden. Sie müssen daher nur diejenigen Datenelemente zum Zwischenspeichern identifizieren, die sich, je nach Anwendung, in den nächsten Minuten bzw. Stunden nicht ändern werden. Sie können dann einen Ablaufzeitpunkt für diese Datenelemente im Cache angeben, damit dieser die Datenelemente zu diesem Zeitpunkt automatisch entfernt. Auf diese Weise können Sie sicherstellen, dass zwischengespeicherte Daten immer aktuell und korrekt sind.

Verteilte Zwischenspeicherung für Dienstskalierbarkeit

Wirkliche Skalierbarkeitssteigerungen durch Zwischenspeichern sind in der SOA-Dienstebene zu finden. Skalierbarkeitsengpässe werden trotz der zahlreichen bereits erwähnten Programmiertechniken nicht immer beseitigt, da der Großteil der Skalierbarkeitsengpässe mit der Datenspeicherung und dem Datenzugriff zu tun hat. Dienste kommen häufig in einer Serverfarm mit Lastenausgleich vor, wodurch ermöglicht wird, dass sich der Dienst selbst ganz gut skalieren kann. Nur die Datenspeicherung kann nicht auf die gleiche Weise skaliert werden. Die Datenspeicherung wird daher zu einem SOA-Engpass.

Die Dienstebene kann durch Hinzufügen weiterer Server zur Serverfarm skaliert werden, wodurch die Verarbeitungskapazität über diese zusätzlichen Anwendungsserver erhöht wird. Aber die SOA-Transaktionen müssen nach wie vor eine gewisse Datenmenge verarbeiten. Diese Daten müssen irgendwo gespeichert werden; dieser Datenspeicher kann leicht zu einem Engpass werden.

Dieses Hindernis des Datenspeichers für die Skalierbarkeit kann auf mehreren Ebenen beseitigt werden. SOA-Dienste verarbeiten zwei Typen von Daten. Dies sind auf der einen Seite Sitzungszustandsdaten und auf der anderen Seite Anwendungsdaten, die sich in der Datenbank befinden (siehe Abbildung 3). Durch beide Datentypen werden Skalierbarkeitsengpässe verursacht.

Figure 3 How Distributed Caching Reduces Pressure on a Database
Abbildung 3 Wie durch verteiltes Zwischenspeichern der Druck auf eine Datenbank reduziert werden kann

Speichern von Sitzungsdaten in einem verteilten Cache

Eine der Einschränkungen des standardmäßigen Speichers für Sitzungszustandsdaten besteht darin, dass dieser keine Webfarmen unterstützt, da es sich um eine Speicherung im Arbeitsspeicher innerhalb des WCF-Dienstprozesses handelt. Eine viel bessere Alternative ist die Verwendung des ASP.NET-Kompatibilitätsmodus und des ASP.NET-Sitzungszustands in WCF-Diensten. Auf diese Weise können Sie den OutProc-Speicher, einschließlich StateServer, SqlServer oder eines verteilten Caches, als Sitzungszustandsspeicher angeben.

Zur Aktivierung des ASP.NET-Kompatibilitätsmodus sind zwei Schritte erforderlich. Zuerst müssen Sie ASP.NET-Kompatibilität in der Klassendefinition angeben, wie in Abbildung 4 dargestellt. Dann müssen Sie dies in der Datei „app.config“ angeben, wie in Abbildung 5 dargestellt. Beachten Sie, dass in Abbildung 4 auch dargestellt ist, wie ein verteilter Cache als SessionState-Speicher in derselben web.config-Datei angegeben wird.

Abbildung 4 Angeben der ASP.NET-Kompatibilität für WCF-Dienste in Code

using System;
using System.ServiceModel;
using System.ServiceModel.Activation;

namespace MyWcfServiceLibrary {
  [ServiceContract]
  public interface IHelloWorldService {
    [OperationContract]
    string HelloWorld(string greeting);
  }

  [ServiceBehavior (InstanceContextMode = 
    InstanceContextMode.PerCall)]
  [AspNetCompatibilityRequirements (RequirementsMode = 
    AspNetCompatibilityRequirementsMode.Allowed)]

  public class HelloWorldService : IHelloWorldService {
    public string HelloWorld(string greeting) {
      return string.Format("HelloWorld: {0}", greeting);
    }
  }
}

Abbildung 5 Angeben der ASP.NET-Kompatibilität für WCF-Dienste in der Konfiguration

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <sessionState cookieless="UseCookies"
      mode="Custom" 
      customProvider="DistCacheSessionProvider" 
      timeout="20">
      <providers>
        <add name="DistCacheSessionProvider" 
          type="Vendor.DistCache.Web.SessionState.SessionStoreProvider"/>
      </providers>
    </sessionState>
    <identity impersonate="true"/>
  </system.web>

  <system.serviceModel>
    <!-- ... -->
    <serviceHostingEnvironment 
      aspNetCompatibilityEnabled="true"/>
  </system.serviceModel>
</configuration>

Die Sitzungsspeicheroptionen StateServer und SqlServer lassen sich nicht gut skalieren; StateServer ist außerdem eine einzelne Fehlerquelle. Ein verteilter Cache ist eine viel bessere Alternative, da er sich gut skalieren lässt und Sitzungen aus Gründen der Zuverlässigkeit auf mehrere Server repliziert.

Zwischenspeichern von Anwendungsdaten

Anwendungsdaten stellen bei weitem die stärkste Datennutzung in einem WCF-Dienst dar, und das Speichern bzw. Zugreifen auf diese Daten ist ein enormer Skalierbarkeitsengpass. Um diesem Skalierbarkeitsengpass zu begegnen, können Sie verteiltes Zwischenspeichern in der Implementierung der SOA-Dienstebene verwenden. Ein verteilter Cache wird zum Zwischenspeichern einer Teilmenge der Daten verwendet, die sich in der Datenbank befinden. Diese basiert darauf, welche Menge der WCF-Dienst in einem kleinen Zeitfenster von ein paar Stunden benötigt.

Zusätzlich wird durch einen verteilten Cache die Skalierbarkeit einer SOA-Anwendung bedeutend gesteigert, da dieser Cache als Ergebnis der Architektur, die er verwendet, skaliert wird. Er sorgt dafür, dass alles auf mehrere Server verteilt wird; die SOA-Anwendung weist dennoch nur eine logische Ansicht auf, sodass Sie das Gefühl haben, es handelt sich nur um einen Cache. Der Cache befindet sich hingegen auf mehreren Servern. Das ist der Grund, warum der Cache skaliert werden kann. Wenn Sie verteiltes Zwischenspeichern zwischen der Dienstebene und der Datenbank verwenden, können Sie die Leistung und Skalierbarkeit der Dienstebene enorm steigern.

Die grundlegende zu implementierende Logik ist, dass Sie, bevor Sie zur Datenbank wechseln, prüfen, ob der Cache bereits über die Daten verfügt. Wenn ja, nehmen Sie die Daten aus dem Cache. Andernfalls wechseln Sie zur Datenbank, um die Daten abzurufen, und fügen Sie diese für das nächste Mal in den Cache ein. Abbildung 6 zeigt ein Beispiel dafür.

Abbildung 6: WCF-Dienst mit Zwischenspeichern

using System.ServiceModel;
using Vendor.DistCache.Web.Caching;

namespace MyWcfServiceLibrary {
  [ServiceBehavior]
  public class EmployeeService : IEmployeeService {
    static string _sCacheName = "myServiceCache";
    static Cache _sCache = 
      DistCache.InitializeCache(_sCacheName);

    public Employee Load(string employeeId) {
      // Create a key to lookup in the cache.
      // The key for will be like "Employees:PK:1000".
      string key = "Employee:EmployeeId:" + employeeId;

      Employee employee = (Employee)_sCache[key];
      if (employee == null) {
        // item not found in the cache. 
        // Therefore, load from database.
        LoadEmployeeFromDb(employee);

        // Now, add to cache for future reference.
       _sCache.Insert(key, employee, null,
          Cache.NoAbsoluteExpiration,
          Cache.NoSlidingExpiration,
          CacheItemPriority.Default);
      }

      // Return a copy of the object since 
      // ASP.NET Cache is InProc.
      return employee;
    }
  }
}

Durch Zwischenspeichern der Anwendungsdaten spart sich der WCF-Dienst viele teure Trips zur Datenbank und sucht die häufig verwendeten transaktionalen Daten stattdessen im nahe gelegenen Cache im Arbeitsspeicher.

Ablauf von zwischengespeicherten Daten

Mithilfe von Ablaufzeitpunkten können Sie angeben, wie lange Daten im Cache verbleiben sollen, bevor Sie vom Cache automatisch entfernt werden. Es gibt zwei Arten von Ablaufzeitpunkten, die Sie angeben können: Absoluter Ablaufzeitpunkt und gleitender bzw. Leerlauf-Ablaufzeitpunkt.

Wenn die Daten in Ihrem Cache auch in der Datenbank vorhanden sind, wissen Sie, dass diese Daten in der Datenbank von anderen Benutzern oder Anwendungen geändert werden können, die möglicherweise keinen Zugriff auf den Cache haben. In diesem Fall sind die Daten im Cache veraltet, und das möchten Sie verhindern. Wenn Sie abschätzen können, wie lange die Daten sicher im Cache verbleiben können, können Sie einen absoluten Ablaufzeitpunkt angeben. Sie können beispielsweise „dieses Element soll in 10 Minuten ablaufen“ oder „dieses Element soll heute um 24 Uhr ablaufen“ angeben. Zu diesem Zeitpunkt läuft das Element im Cache ab:

using Vendor.DistCache.Web.Caching;
...
// Add an item to ASP.NET Cache with absolute expiration
_sCache.Insert(key, employee, null, 
  DateTime.Now.AddMinutes(2),
  Cache.NoSlidingExpiration, 
  CacheItemPriority.Default, null);

Sie können auch einen Leerlauf-Ablaufzeitpunkt oder einen gleitenden Ablaufzeitpunkt verwenden, wenn ein Element über einen bestimmten Zeitraum nicht verwendet wird. Sie können beispielsweise „dieses Element soll ablaufen, wenn es 10 Minuten lang von niemandem gelesen oder aktualisiert wird“ angeben. Dies ist hilfreich, wenn die Anwendung die Daten vorübergehend benötigt; wenn die Anwendung die Daten nicht mehr benötigt, sollen sie im Cache automatisch ablaufen. Der Sitzungszustand des ASP.NET-Kompatibilitätsmodus ist ein gutes Beispiel für einen Leerlauf-Ablaufzeitpunkt.

Mit dem absoluten Ablaufzeitpunkt können Sie Situationen vermeiden, in denen der Cache über eine ältere bzw. veraltete Kopie der Daten als die Masterkopie in der Datenbank verfügt. Der Leerlauf-Ablaufzeitpunkt hingegen dient einem ganz anderen Zweck. Mit ihm kann der Cache bereinigt werden, wenn die Anwendung die Daten nicht mehr benötigt. Diese Bereinigung wird vom Cache und nicht von der Anwendung übernommen.

Verwalten von Datenbeziehungen im Cache

Die meisten Daten stammen aus einer relationalen Datenbank; und auch wenn die Daten nicht aus einer relationalen Datenbank stammen, sind sie in ihrer Natur relational. Sie versuchen beispielsweise, ein Kundenobjekt und ein Bestellungsobjekt zwischenzuspeichern, und beide Objekte sind miteinander verknüpft. Ein Kunde kann über mehrere Bestellungen verfügen.

Bei solchen Beziehungen müssen Sie in der Lage sein, diese in einem Cache zu bearbeiten. Dies bedeutet, dass der Cache über die Beziehung zwischen einem Kunden und einer Bestellung informiert sein muss. Wenn Sie den Kunden aktualisieren oder aus dem Cache entfernen, sollte auch das Bestellungsobjekt automatisch aus dem Cache entfernt werden. Dies ist in vielen Situationen für die Datenintegrität von Vorteil.

Wenn ein Cache nicht in der Lage ist, diese Beziehungen nachzuverfolgen, müssen Sie es selbst tun – und dadurch wird Ihre Anwendung schwerfällig und komplex. Es ist viel einfacher, wenn Sie den Cache benachrichtigen, wenn Sie die Daten zu dieser Beziehung hinzufügen. Der Cache weiß dann, dass auch die Bestellung entfernt werden muss, wenn dieser Kunde aktualisiert oder entfernt wird.

ASP.NET umfasst eine nützliche Funktion mit dem Namen CacheDependency, mit der Sie Beziehungen zwischen unterschiedlichen zwischengespeicherten Elementen nachverfolgen können. Manche kommerzielle Caches verfügen auch über diese Funktion. Nachfolgend finden Sie ein Beispiel dafür, wie Sie mit ASP.NET Beziehungen zwischen zwischengespeicherten Elementen nachverfolgen können:

using Vendor.DistCache.Web.Caching;
...
public void CreateKeyDependency() {
  Cache["key1"] = "Value 1";

  // Make key2 dependent on key1.
  String[] dependencyKey = new String[1];
  dependencyKey[0] = "key1";
  CacheDependency dep1 = 
    new CacheDependency(null, dependencyKey);

  _sCache.Insert("key2", "Value 2", dep2);
}

Es handelt sich hierbei um eine mehrstufige Abhängigkeit. Das bedeutet, dass A von B und B von C abhängig sein kann. Wenn C also von der Anwendung aktualisiert wird, müssen sowohl A als auch B aus dem Cache entfernt werden.

Synchronisierung des Caches mit einer Datenbank

Eine Datenbanksynchronisierung ist erforderlich, da die Datenbank über mehrere Anwendungen hinweg gemeinsam genutzt wird und nicht alle diese Anwendungen Zugriff auf den Cache haben. Wenn die WCF-Dienstanwendung die einzige Anwendung ist, durch die die Datenbank aktualisiert wird, und diese auch den Cache problemlos aktualisieren kann, benötigen Sie die Funktion zur Datenbanksynchronisierung möglicherweise nicht.

In einer realen Umgebung ist dies jedoch nicht immer der Fall. Die Daten in der Datenbank werden von Drittanbieteranwendungen aktualisiert, und der Cache wird inkonsistent mit der Datenbank. Durch die Synchronisierung des Caches mit der Datenbank wird sichergestellt, dass der Cache stets über diese Datenbankänderungen auf dem Laufenden gehalten wird und sich selbst entsprechend aktualisieren kann.

Eine Synchronisierung mit der Datenbank bedeutet in der Regel, dass das verknüpfte zwischengespeicherte Element ungültig gemacht wird, damit die Anwendung das Element beim nächsten Mal aus der Datenbank abrufen muss, weil es im Cache nicht vorhanden ist.

ASP.NET umfasst die Funktion SqlCacheDependency, mit der Sie den Cache mit SQL Server 2005, SQL Server 2008 oder Oracle 10g R2 und höher – also praktisch mit jeder Datenbank, die die CLR unterstützt, synchronisieren können. Manche kommerzielle Caches verfügen auch über diese Funktion. In Abbildung 7 ist ein Beispiel für die Verwendung der SQL-Abhängigkeit zur Synchronisierung mit einer relationalen Datenbank.

Abbildung 7 Synchronisieren von Daten über die SQL-Abhängigkeit

using Vendor.DistCache.Web.Caching;
using System.Data.SqlClient;
...

public void CreateSqlDependency(
  Customers cust, SqlConnection conn) {

  // Make cust dependent on a corresponding row in the
  // Customers table in Northwind database

  string sql = "SELECT CustomerID FROM Customers WHERE ";
  sql += "CustomerID = @ID";
  SqlCommand cmd = new SqlCommand(sql, conn);
  cmd.Parameters.Add("@ID", System.Data.SqlDbType.VarChar);
  cmd.Parameters["@ID"].Value = cust.CustomerID;

  SqlCacheDependency dep = new SqlCacheDependency(cmd);
  string key = "Customers:CustomerID:" + cust.CustomerID;
_  sCache.Insert(key, cust, dep);
}

Eine Funktion, die ASP.NET nicht bereitstellt, einige kommerzielle Lösungen jedoch schon, ist die abrufbasierte Datenbanksynchronisierung. Diese ist nützlich, wenn Ihr DBMS die CLR nicht unterstützt und Sie die Funktion SqlCacheDependency nicht nutzen können. In diesem Fall wäre es schön, wenn der Cache die Datenbank in konfigurierbaren Intervallen abfragen und Änderungen in bestimmten Zeilen in einer Tabelle erfassen könnte. Wenn sich diese Zeilen geändert haben, werden die entsprechenden zwischengespeicherten Elemente vom Cache ungültig gemacht.

Unternehmensdienstbus für SOA-Skalierbarkeit

Der Unternehmensdienstbus (ESB) ist ein Branchenkonzept, bei dem viele Technologien zu dessen Erstellung verwendet werden. Bei einem ESB handelt es sich um eine Infrastruktur für Webdienste, die die Kommunikation unter Komponenten vermittelt. Kurz gesagt handelt es sich bei ESB um eine einfache und leistungsstarke Art und Weise, über die mehrere Anwendungen Daten asynchron gemeinsam verwenden. ESB ist jedoch nicht für eine Verwendung über Organisationen oder sogar über ein WAN hinweg gedacht. SOA-Anwendungen sind in der Regel mit Absicht in mehrere Stücke aufgeteilt. Wenn diese also Daten untereinander austauschen müssen, ist ESB ein leistungsstarkes Tool.

Es gibt verschiedene Wege, wie Sie einen ESB erstellen können. In Abbildung 8 sehen Sie ein Beispiel eines mit einem verteilten Cache erstellten ESB. Mehrere locker gekoppelte Anwendungen oder Dienstkomponenten können diesen verwenden, um Daten untereinander in Echtzeit und über das Netzwerk auszutauschen.


Abbildung 8 Ein mit einem verteilten Cache erstellter ESB

Ein verteilter Cache umfasst an sich mehrere Computer. Dadurch ist er stark skalierbar, was dem ersten Kriterium eines ESB entspricht. Außerdem repliziert ein guter verteilter Cache alle Daten auf intelligente Weise, um sicherzustellen, das kein Datenverlust auftritt, falls ein Cacheserver ausfällt. (Darauf werde ich später noch eingehen.) Und zu guter Letzt bietet ein guter Cache einen intelligenten Ereignisweitergabemechanismus.

Es gibt zwei Arten von Ereignissen, die ein verteilter Cache bereitstellen muss, damit er für einen ESB geeignet ist. Eine Clientanwendung des ESB muss in der Lage sein, Interesse an einem Datenelement in dem ESB zu zeigen, damit die Clientanwendung sofort benachrichtigt wird, wenn das Datenelement geändert oder gelöscht wird. Außerdem muss der Cache ermöglichen, dass Clientanwendungen benutzerdefinierte Ereignisse im ESB auslösen, damit alle anderen Anwendungen, die mit dem ESB verbunden sind und an diesem benutzerdefinierten Ereignis interessiert sind, sofort benachrichtigt werden, unabhängig davon, wo diese sich im Netzwerk befinden (natürlich innerhalb des Intranets).

Mithilfe eines ESB kann ein Großteil des Datenaustauschs, der sonst SOA-Aufrufe von einer Anwendung zu einer anderen erfordern würde, problemlos über den ESB erfolgen. Außerdem ist die asynchrone Datenfreigabe nichts, was ein einfacher WCF-Dienst problemlos ausführen kann. Der ESB kann diese Aufgabe jedoch nahtlos ausführen. Sie können ganz einfach Situationen schaffen, in denen Daten sogar mittels eines Push-Vorgangs an die Clients des ESB übertragen werden, wenn diese im Voraus Interesse daran bekundet haben.

Cacheskalierbarkeit und hohe Verfügbarkeit

Cachetopologie ist ein Begriff, der verwendet wird, um anzugeben, wie Daten tatsächlich in einem verteilten Cache gespeichert werden. Es gibt verschiedene Cachetopologien, die für unterschiedliche Umgebungen entworfen wurden. Ich werde im Folgenden drei Topologien besprechen: Partitionierter Cache, partitionierter-replizierter Cache und replizierter Cache.

Bei partitionierten Caches und partitionierten-replizierten Caches handelt es sich um zwei Cachetopologien, die eine wichtige Rolle im Skalierbarkeitsszenario spielen. In beiden Topologien wird der Cache in Partitionen unterteilt, und anschließend wird jede Partition auf unterschiedlichen Cacheservern im Cluster gespeichert. Der partitionierte-replizierte Cache weist ein Replikat jeder Partition auf, die auf einem anderen Cacheserver gespeichert ist.

Partitionierte und partitionierte-replizierte Caches sind die am stärksten skalierbaren Topologien für das Zwischenspeichern von transaktionalen Daten (bei dem Schreibvorgänge in den Cache genauso häufig wie Lesevorgänge sind), da durch das Hinzufügen weiterer Cacheserver zum Cluster nicht nur die Transaktionskapazität, sondern auch die Speicherkapazität des Caches erhöht wird, weil alle Partitionen zusammen den gesamten Cache bilden.

Bei der dritten Cachetopologie, dem replizierten Cache, wird der gesamte Cache an die einzelnen Cacheserver im Cachecluster kopiert. Dies bedeutet, dass der replizierte Cache eine höhere Verfügbarkeit bietet und für die leseintensive Verwendung geeignet ist. Er eignet sich jedoch nicht für häufige Updates, da Updates an allen Kopien synchron vorgenommen werden und diese nicht so schnell wie bei anderen Cachetopologien sind.

Wie in Abbildung 9 dargestellt, ist die partitionierte-replizierte Cachetopologie ideal für eine Kombination aus Skalierbarkeit und hoher Verfügbarkeit. Aufgrund der Replikate jeder Partition gehen keine Daten verloren.


Abbildung 9 Partitionierte-replizierte Cachetopologie für Skalierbarkeit

Die hohe Verfügbarkeit kann durch dynamisches Cacheclustering noch weiter verbessert werden. Dabei handelt es sich um die Fähigkeit, Cacheserver zur Laufzeit dem Cachecluster hinzuzufügen oder daraus zu entfernen, ohne den Cache oder die Clientanwendungen beenden zu müssen. Da ein verteilter Cache in einer Produktionsumgebung ausgeführt wird, ist hohe Verfügbarkeit eine wichtige Funktionsanforderung.

Nächste Schritte

Wie Sie bereits gesehen haben, kann eine SOA-Anwendung nicht effektiv skaliert werden, wenn die von der Anwendung verwendeten Daten in einem Speicher gespeichert sind, der nicht für häufige Transaktionen skalierbar ist. In solchen Fällen ist die verteilte Zwischenspeicherung sehr nützlich.

Die verteilte Zwischenspeicherung ist ein neues Konzept, das aber unter .NET-Entwicklern als bewährte Methode für Anwendungen mit vielen Transaktionen zunehmend akzeptiert wird. Die herkömmlichen Datenbankserver werden auch verbessert, jedoch ohne verteiltes Zwischenspeichern, da sie der rasant steigenden Nachfrage nach Skalierbarkeit in heutigen Anwendungen nicht gerecht werden können.

Anhand der beschriebenen Techniken sollten Sie in der Lage sein, Ihre SOA-Anwendungen in Sachen Skalierbarkeit auf eine neue Ebene zu bringen. Probieren Sie sie heute gleich aus. Weitere Informationen zum verteilten Zwischenspeichern finden Sie im Artikel der MSDN-Bibliothek von J.D Meier, Srinath Vasireddy, Ashish Babbar und Alex Mackman unter msdn.microsoft.com/library/ms998562.          

Iqbal Khan* ist Geschäftsführer und Technologieexperte bei Alachisoft. Alachisoft stellt NCache bereit, ein branchenführender .NET-verteilter Cache für die Steigerung der Leistung und Skalierbarkeit in Unternehmensanwendungen. Khan besitzt einen Master-Abschluss in Informatik von der Indiana University in Bloomington. Er ist zu erreichen unter iqbal@alachisoft.com.*

Unser Dank gilt den folgenden technischen Experten für das Lektorat dieses Artikels:Kirill Gavrylyuk und Stefan Schackow