Verwenden von Read-Through und Write-Through im verteilten Cache

Autor: Iqbal Khan

Mit der Explosion von Webanwendungen mit extrem hohen Transaktionsraten, SOA, Grid-Computing und anderen Serveranwendungen kann die Datenspeicherung nicht mithalten. Der Grund dafür ist, dass die Datenspeicherung im Gegensatz zu extrem skalierbaren Anwendungsarchitekturen nicht ständig weitere Server zum Aufskalieren hinzufügen kann.

In diesen Situationen bietet der verteilte In-Memory-Cache eine hervorragende Lösung für Engpässe bei der Datenspeicherung. Es umfasst mehrere Server (Cluster genannt), um deren Speicher zusammenzufassen und den gesamten Cache serverübergreifend zu synchronisieren. Und es kann diesen Cache-Cluster endlos erweitern, genau wie die Anwendungsserver. Dadurch wird der Druck auf die Datenspeicherung verringert, so dass sie kein Skalierbarkeitsengpass mehr darstellt.

Es gibt zwei Hauptmethoden, wie Benutzer einen verteilten Cache verwenden:

  • Cache-beiseite: Hier ist die Anwendung für das Lesen und Schreiben aus der Datenbank verantwortlich und der Cache interagiert überhaupt nicht mit der Datenbank. Der Cache wird als schneller und besser skalierbarer In-Memory-Datenspeicher „beiseite gehalten“. Die Anwendung überprüft den Cache, bevor sie etwas aus der Datenbank liest. Und die Anwendung aktualisiert den Cache, nachdem sie Aktualisierungen an der Datenbank vorgenommen hat. Auf diese Weise stellt die Anwendung sicher, dass die Cache wird mit der Datenbank synchronisiert gehalten.
  • Durchlesen/Durchschreiben (RT/WT): Hier behandelt die Anwendung den Cache als Hauptdatenspeicher und liest Daten daraus und schreibt Daten darauf. Der Cache ist für das Lesen und Schreiben dieser Daten in die Datenbank verantwortlich, wodurch die Anwendung von dieser Verantwortung entlastet wird.
Read-Through/Write-Through-Caching-Architektur
Abbildung 1: Read-Through/Write-Through-Caching-Architektur

Vorteile von Read-Through und Write-Through gegenüber Cache-Aside

Cache-aside ist eine sehr leistungsfähige Technik, mit der Sie komplexe Datenbankabfragen mit Verknüpfungen und verschachtelten Abfragen ausführen und Daten beliebig manipulieren können. Trotzdem, Durchlesen / Durchschreiben hat verschiedene Vorteile gegenüber Cache-Aside, wie unten erwähnt:

  • Anwendungscode vereinfachen: Beim Cache-Aside-Ansatz ist Ihr Anwendungscode weiterhin komplex und direkt von der Datenbank abhängig und sogar Code dupliziert, wenn mehrere Anwendungen mit denselben Daten arbeiten. Read-Through/Write-Through verschiebt einen Teil des Datenzugriffscodes von Ihren Anwendungen auf die Caching-Ebene. Das vereinfacht Ihre Anwendungen dramatisch und abstrahiert die Datenbasis noch deutlicher.
  • Bessere Leseskalierbarkeit mit Read-Through: Es gibt viele Situationen, in denen a Cache-Element läuft ab und mehrere parallele Benutzer-Threads treffen schließlich auf die Datenbank. Multipliziert man dies mit Millionen gecachten Items und tausenden parallelen Benutzeranfragen, wird die Belastung der Datenbank merklich höher. Read-Through behält das Cache-Element jedoch im Cache, während es die neueste Kopie davon aus der Datenbank abruft. Es aktualisiert dann das Cache-Element. Das Ergebnis ist, dass die Anwendung für diese Cache-Elemente nie zur Datenbank geht und die Datenbanklast auf ein Minimum reduziert wird.
  • Bessere Schreibleistung mit Write-behind: Neben dem Cache aktualisiert die Anwendung die Datenbank direkt synchron. Während a Hinterher schreiben lässt Ihre Anwendung den Cache schnell aktualisieren und zurückkehren. Dann lässt es den Cache die Datenbank im Hintergrund aktualisieren.
  • Bessere Skalierbarkeit der Datenbank mit Write-Behind: Mit Write-behind können Sie Drosselungsgrenzen festlegen, damit die Datenbankschreibvorgänge nicht so schnell wie die Cache-Aktualisierungen ausgeführt werden und die Datenbank daher nicht stark belastet wird. Darüber hinaus können Sie die Datenbankschreibvorgänge so planen, dass sie außerhalb der Spitzenzeiten erfolgen, um wiederum den Druck zu minimieren.
  • Cache nach Ablauf automatisch aktualisieren: Durchlesen ermöglicht dem Cache das automatische Neuladen eines Objekts aus der Datenbank, wenn es abläuft. Das bedeutet, dass Ihre Anwendung nicht zu Spitzenzeiten auf die Datenbank zugreifen muss, da sich immer die neuesten Daten im Cache befinden.
  • Cache bei Datenbankänderungen automatisch aktualisieren: Durchlesen ermöglicht dem Cache, ein Objekt automatisch aus der Datenbank neu zu laden, wenn sich die entsprechenden Daten in der Datenbank ändern. Das bedeutet, dass der Cache immer frisch ist und Ihre Anwendung nicht zu Spitzenzeiten auf die Datenbank zugreifen muss, da sich immer die neuesten Daten im Cache befinden.

Read-Through/Write-Through ist nicht für den gesamten Datenzugriff in Ihrer Anwendung vorgesehen. Es eignet sich am besten für Situationen, in denen Sie entweder einzelne Zeilen aus der Datenbank lesen oder Daten lesen, die direkt einem einzelnen Cache-Element zugeordnet werden können. Es ist auch ideal für Referenzdaten, die für häufiges Lesen im Cache gehalten werden sollen, obwohl sich diese Daten regelmäßig ändern.

Entwickeln eines Read-Through-Handlers

Ein Read-Through-Handler ist beim Cache-Server registriert und ermöglicht dem Cache, Daten direkt aus der Datenbank zu lesen. Die NCache Server stellt eine Read-Through-Handler-Schnittstelle bereit, die Sie implementieren müssen. Das ermöglicht NCache um Ihren Read-Through-Handler aufzurufen.

public class SqlReadThruProvider : IReadThruProvider
    {
        private SqlConnection _connection;
        
        // Called upon startup to initialize connection
        public void Init(IDictionary parameters, string cacheId)
        {
            _connection = new SqlConnection(parameters["connstring"]);
            _connection.Open();
        }
        
        // Called at the end to close connection
        public void Dispose()
        {
            _connection.Close();
        }
        
        // Responsible for loading object from external data source
        public ProviderCacheItem LoadFromSource(string key)
        {
            string sql = "SELECT * FROM Customers WHERE ";
            sql += "CustomerID = @ID";
            
            SqlCommand cmd = new SqlCommand(sql, _connection);
            cmd.Parameters.Add("@ID", System.Data.SqlDbType.VarChar);
            
            // Let's extract actual customerID from "key"
            int keyFormatLen = "Customers:CustomerID:".Length;
            string custId = key.Substring(keyFormatLen,
            key.Length - keyFormatLen);
            
            cmd.Parameters["@ID"].Value = custId;
            
            // fetch the row in the table
            SqlDataReader reader = cmd.ExecuteReader();
            
            Customers customer = new Customers();
            // copy data from "reader" to "cust" object
            FillCustomers(reader, customer);
            
            ProviderCacheItem cacheItem = new ProviderCacheItem(customer);
            
            // specify a SqlCacheDependency for this object
            CacheDependency dep = new SqlCacheDependency(_connection.ToString(), cmd.ToString());
            cacheItem.Dependency = dep;
            
            return cacheItem;
        }

Init() führt bestimmte Ressourcenzuweisungsaufgaben aus, z. B. das Herstellen von Verbindungen zur Hauptdatenquelle, während Dispose() soll alle diese Zuweisungen zurücksetzen. LoadFromSource() ruft der Cache auf, um die Objekte durchzulesen.

Entwicklung eines Write-Through-Handlers

Write-Through-Handler wird aufgerufen, wenn der Cache in die Datenbank schreiben muss, während der Cache aktualisiert wird. Normalerweise gibt die Anwendung eine Aktualisierung für den Cache durch Hinzufügen, Einfügen oder Entfernen aus.

public class SqlWriteThruProvider : IWriteThruProvider
    {
        private SqlConnection _connection;
        
        // Called upon startup to initialize connection
        public void Init(IDictionary parameters, string cacheId)
        {
            _connection = new SqlConnection((string)parameters["connstring"]);
            _connection.Open();
        }
        
        // Called at the end to close connection
        public void Dispose()
        {
            _connection.Close();
        }
        
    public OperationResult WriteToDataSource(WriteOperation operation)
    {
        int rowsChanged = 0;
        OperationResult result = new OperationResult(operation, OperationResult.Status.Failure);
        ProviderCacheItem cacheItem = operation.ProviderItem;
        Customers cust = cacheItem.GetValue<Customers>();
        string[] customer = {cust.Id,cust.ContactName,cust.CompanyName,
        cust.Address,cust.City, cust.Country,cust.PostalCode,
		cust.Phone,cust.Fax};
            
        SqlCommand cmd = _connection.CreateCommand();
        cmd.CommandText = String.Format(CultureInfo.InvariantCulture,
            "Update dbo.Customers " + "Set CustomerID='{0}'," +
            "ContactName='{1}',CompanyName='{2}'," +
            "Address='{3}',City='{4}'," +
            "Country='{5}',PostalCode='{6}'," +
            "Phone='{7}',Fax='{8}'" +
            "Where CustomerID = '{0}'", customer);
                
        rowsChanged = cmd.ExecuteNonQuery();
            if (rowsChanged > 0)
            {
                result.OperationStatus = OperationResult.Status.Success;
                return result;
            }
            return result;
        }
}

Init() führt Ressourcenzuweisungsaufgaben wie das Herstellen von Verbindungen zur Datenquelle durch, während Dispose() soll alle diese Zuweisungen zurücksetzen. Save ist die Methode, die der Cache für Write-Through-Objekte aufruft.

Aufrufen von Read-Through und Write-Through aus der Anwendung

Der folgende Beispielcode zeigt die Verwendung der Read-Through/Write-Through-Funktionen des Caches aus einer einfachen Windows-Anwendung

using Alachisoft.NCache.Client;
...

internal class MainForm : System.Windows.Forms.Form
{
    /// Fetches record from the cache, which internally accesses the
    /// datasource using read-thru provider
    private void OnClickFind(object sender, System.EventArgs e)
    {
        Customer customer;
        Cache cache = NCache.Caches[CacheName];
        string key = cboCustomerID.Text.Trim();
        string providerName = cboReadThruProvider.Text;
        
        customer = (Customer) cache.Get(key,
                    providerName,
                    DSReadOption.ReadThru);
                    
                    ...
    }
    
    /// Updates the record using the cache, which internally accesses
    /// the datasource using write-thru provider
    private void OnClickUpdate(object sender, System.EventArgs e)
    {
        Cache cache = NCache.Caches[CacheName];
        Customer customer = new Customer();
        
        ...
        
        string key = customer.CustomerID;
        string providerName = cboWriteThruProvider.Text;
        
        cache.Insert(key, new CacheItem(customer), DSWriteOption.WriteThru, providerName, null);
        
        ...
    }
}

Was macht man als nächstes?

© Copyright Alachisoft 2002 - Alle Rechte vorbehalten NCache ist eine eingetragene Marke der Diyatech Corp.