Die Verwendung der Caching-Infrastruktur in ASP.NET wurde fortgesetzt

    In einem früheren Beitrag habe ich darüber gesprochen, wie die Caching-Infrastruktur in ASP.NET verwendet werden kann, um die Leistung der Site zu steigern. Durch Hinzufügen einiger Codezeilen konnte ich die Leistung der Homepage um das Fünffache steigern. Dieses Mal können Sie noch mehr Leistung erzielen, ohne auf verschiedene Hacks zurückgreifen zu müssen.

    Zum Beispiel verwende ich immer noch das Mvc Music Store-Projekt .
    Wenn Sie die vorherigen Beiträge nicht gelesen haben, ist es Zeit zu sehen, wie die Homepage beschleunigt wurde .

    Alle Optimierungen betrafen die Homepage, jetzt gehe ich zur internen.

    Belastungstest


    Zur Verifizierung habe ich in Visual Studio einen Belastungstest für 25 „virtuelle Benutzer“ mit dem folgenden Szenario durchgeführt:
    1) Anforderung der Hauptseite
    2) Anforderung der Seite des Genres (Katalog)
    3) Anforderung der Seite des Albums (Produkt)
    Aus Genauigkeitsgründen habe ich sichergestellt, dass mehrere vorhanden sind Dieselbe Seite des Katalogs \ Produkt und zufällig auf verschiedene Seiten verteilt.
    Und auch der Prozentsatz der neuen Benutzer auf 80% erhöht, was wahr ist.

    Das Ergebnis sind 42 Skripte pro Sekunde.

    Caching hinzufügen - Ein einfacher Ansatz


    In ASP.NET können Sie Attribute festlegen, die mit Datenbankabhängigkeiten zwischengespeichert werden.
    Dazu müssen Sie einige einfache Schritte ausführen:
    1. Geben Sie die Caching-Optionen in die Datei web.config ein


    Das Element sqlCacheDependencybestimmt die Cache-Abhängigkeit von der Datenbank. Die Datenbankabhängigkeit sucht in Intervallen nach Änderungen pollTime, in diesem Fall 1000 Millisekunden (1 Sekunde).
    Das outputCacheProfiles-Element legt Profile fest, um nicht die gleichen Einstellungen für verschiedene Aktionen zu wiederholen. Darüber hinaus können Sie das Caching verwalten, ohne das Projekt neu erstellen zu müssen.

    2. Nehmen Sie Änderungen am Datenbankschema vor, damit Abhängigkeiten funktionieren

    Rufen Sie dazu beim Start der Anwendung die folgenden Codezeilen auf
    String connStr = System.Configuration.ConfigurationManager.ConnectionStrings["MusicStoreEntities"].ConnectionString;
    System.Web.Caching.SqlCacheDependencyAdmin.EnableNotifications(connStr);
    System.Web.Caching.SqlCacheDependencyAdmin.EnableTableForNotifications(connStr, "Genres");
    System.Web.Caching.SqlCacheDependencyAdmin.EnableTableForNotifications(connStr, "Albums");
    


    3. Attribute hinzufügen

    [OutputCache(CacheProfile = "Catalog")]
    public ActionResult Browse(string genre)
    {
     //...
    }
    [OutputCache(CacheProfile = "Catalog")]
    public ActionResult Details(int id)
    {
     //...
    }
    


    Führen Sie den Test erneut aus - 60 Skripte pro Sekunde. Das heißt, die Geschwindigkeit konnte in diesem Fall um fast 50% erhöht werden.

    Abhängigkeiten aus Code installieren


    Wenn Sie WebAPI verwenden, können Sie die Caching-Attribute nicht verwenden. Aber in diesem Fall hilft Ihnen die Klasse SqlCacheDependency. Die Verwendung ist sehr einfach: Geben Sie im Konstruktor den Datenbanknamen aus web.config und den Tabellennamen an. Mit der SqlCacheDependency-Instanz können Sie die Abhängigkeiten der lokalen Cache-Elemente angeben.

    Sie können also Navigations-Caching durchführen, wenn das Caching aller Seiten nicht rentabel ist.
    
    [ChildActionOnly]
    public ActionResult GenreMenu()
    {
        var cacheKey = "Nav";
        var genres = this.HttpContext.Cache.Get(cacheKey);
        if (genres == null)
        {
            genres = storeDB.Genres.ToList();
            this.HttpContext.Cache.Insert(cacheKey, genres, new SqlCacheDependency("MusicStore","Genres"));
        }
        return PartialView(genres);
    }
    


    Es gibt einen anderen Konstruktor SqlCacheDependency, der akzeptiert SqlCommand. Hierbei handelt es sich um ein völlig anderes Datenbank-Änderungsverfolgungsmodul, das auf Warnungen von SQL Server Service Broker basiert. Ich habe versucht, diese Warnungen zu verwenden, aber sie funktionieren nicht für alle Anforderungen. Wenn die Anforderung außerdem "falsch" ist, treten keine Fehler auf, und die Benachrichtigung kommt unmittelbar nach der Erstellung an. Außerdem sind Warnungen sehr langsam. Nach meinen Messungen wird das Schreiben in Tabellen 8-mal verlangsamt.

    Rückseite der Münze


    Datenbankabhängigkeiten sind überhaupt nicht frei. Für ihre Arbeit werden Trigger erstellt, die das Erstellen, Ändern und Löschen von Datensätzen auslösen. Diese Trigger aktualisieren die Informationen in der Servicetabelle darüber, welche Tabellen wann geändert wurden. Der Thread auf der Anwendungsseite liest regelmäßig die Tabelle und benachrichtigt die Abhängigkeiten.

    Wenn der Änderungsbetrag nicht häufig auftritt, ist der Aufwand für Trigger gering. Wenn häufig Änderungen vorgenommen werden, sinkt die Effizienz des Caches. Im Beispiel mit dem Mvc Music Store wird durch jede Änderung an einem Album der gesamte Cache für den gesamten Katalog zurückgesetzt.

    Was zu tun


    Wenn Sie sich auf demselben Server befinden, wird derselbe Ansatz verwendet, der zum Zwischenspeichern des Warenkorbs im Mvc Music Store verwendet wird: Speichern einzelner Elemente oder Datenbeispiele im Zwischenspeicher und Zurücksetzen des Zwischenspeichers beim Aufzeichnen (mehr in einem früheren Beitrag ). Mit der richtigen Auswahl von Caching-Granularität und Flushing können Sie eine hohe Cache-Effizienz erzielen.

    Bei der Skalierung auf mehrere Server funktioniert dieser Ansatz jedoch fast immer nicht. Im Falle des Warenkorbs funktioniert dies nur bei Client-Affinität, wenn derselbe Client zum selben Server kommt. Moderne NLBs bieten dies, aber im Fall von beispielsweise der Zwischenspeicherung von Waren hilft die Kundenaffinität nicht mehr weiter.

    Der verteilte Cache hilft uns dabei.

    Wenn Sie bereits mehr als einen Webserver für die Bearbeitung von Anforderungen installiert haben, sollten Sie sich einen verteilten Cache überlegen.
    Eine der besten Optionen für heute ist Redis. Es ist sowohl lokal als auch in der Microsoft Azure-Cloud verfügbar.

    Öffnen Sie zum Hinzufügen von Redis zu einem ASP.NET-Projekt die Package Manager-Konsole und führen Sie einige Befehle aus
    Install-Package Redis-64
    Install-Package StackExchange.Redis
    


    Redis unterstützt eine hervorragende Funktion - die sogenannten Keyspace Notifications (http://redis.io/topics/notifications). Auf diese Weise können Sie nachverfolgen, wann ein Element geändert wurde, auch wenn Änderungen auf einem anderen Server vorgenommen wurden.

    Um diese Funktion in ASP.NET zu integrieren, habe ich eine kleine Klasse geschrieben:
    class RedisCacheDependency: CacheDependency
    {
        public RedisCacheDependency(string key):base()
        {
           Redis.Client.GetSubscriber().Subscribe("__keyspace@0__:" + key, (c, v) =>
           {
               this.NotifyDependencyChanged(new object(), EventArgs.Empty );                                        
           });
       }
    }
    

    Diese Klasse implementiert CacheDependency in Redis.

    Und jetzt der Kunde selbst:
    public static class Redis
    {
        public static readonly ConnectionMultiplexer Client = ConnectionMultiplexer.Connect("localhost");
        public static CacheDependency CreateDependency(string key)
        {
            return new RedisCacheDependency(key);
        }
        public static T GetCached(string key, Func getter) where T:class 
        {
            var localCache = HttpRuntime.Cache;
            var result = (T) localCache.Get(key);
            if (result != null) return result;
            var redisDb = Client.GetDatabase();
            var value = redisDb.StringGet(key);
            if (!value.IsNullOrEmpty)
            {
                result = Json.Decode(value);
                localCache.Insert(key, result, CreateDependency(key));
                return result;
            }
            result = getter();
            redisDb.StringSet(key, Json.Encode(result));
            localCache.Insert(key, result, CreateDependency(key));
            return result;
        }
        public static void DeleteKey(string key)
        {
            HttpRuntime.Cache.Remove(key);
            var redisDb = Client.GetDatabase();
            redisDb.KeyDelete(key);
        }
    }
    

    Die GetCached-Methode speichert das Ergebnis im lokalen ASP.NET-Cache. Der lokale Cache ist sehr schnell. Die Überprüfung eines Elements im Cache dauert Nanosekunden. Dies ist viel schneller als eine Remote-Anfrage an Redis + Serialization-Deserialization.

    Jetzt kann ich das Element im Redis-Cache an den Seiten-Cache binden:
    public ActionResult Browse(string genre)
    {
        var cacheKey = "catalog-" + genre;
        var genreModel = Redis.GetCached(cacheKey, () =>
            (from g in storeDB.Genres
                where g.Name == genre
                select new GenreBrowse
                {
                    Name =  g.Name,
                    Albums = from a in g.Albums
                            select new AlbumSummary
                            {
                                Title =  a.Title,
                                AlbumId =  a.AlbumId,
                                AlbumArtUrl = a.AlbumArtUrl
                            }
                }
                    ).Single()
            );
        this.Response.AddCacheItemDependency(cacheKey);
        this.Response.Cache.SetLastModifiedFromFileDependencies();
        this.Response.Cache.AppendCacheExtension("max-age=0");
        this.Response.Cache.VaryByParams["genre"] = true;
        this.Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate);
        return View(genreModel);
    }

    Das Standard-OutputCache-Attribut muss entfernt werden, da es sonst nicht auf Ihre Abhängigkeiten reagiert. Falls gewünscht, können Sie Ihren ActionFilter für das Caching schreiben, um den Code nicht zu kopieren und einzufügen.

    Um den Cache zurückzusetzen, müssen Sie Redis.DeleteKey in den Methoden aufrufen, die die Daten ändern.

    Beim zweiten Auslastungstest wurden 52 Skripte pro Sekunde erstellt. Dies ist weniger als ohne Redis, aber die Leistung nimmt nicht merklich ab, wenn die Anzahl der Datensätze in der Tabelle zunimmt.

    Was kann man sonst noch mit Redis machen?

    Zusätzlich zum manuellen Platzieren von Daten im Cache können Sie die NuGet-Pakete Microsoft.Web.RedisSessionStateProvider und Microsoft.Web.RedisOutputCacheProvider verwenden , um den Sitzungsstatus und den Seitencache in Redis zu platzieren. Leider schränkt der benutzerdefinierte OutputCacheProvider die Verwendung von CacheDependency zum Leeren des Ausgabecaches ein.

    Fazit


    ASP.NET verfügt über zahlreiche Caching-Optionen. Zusätzlich zu den in dieser Reihe von Beiträgen behandelten Optionen gibt es auch Rückrufe für die Cache-Überprüfung sowie Datei- und Verzeichnisbindungen. Aber es gibt Fallstricke, über die ich noch nicht gesprochen habe. Wenn Sie sich für alles rund um die Optimierung von Webanwendungen in ASP.NET interessieren, besuchen Sie mein Seminar - gandjustas.timepad.ru/event/150915

    Alle Beiträge in der Serie


    Der Quellcode sowie Tests sind auf GitHub-github.com/gandjustas/McvMusicStoreCache verfügbar

    Jetzt auch beliebt: